Skip to content

Commit

Permalink
Merge pull request #176 from Telegram-Mini-Apps/feature/new-theme-par…
Browse files Browse the repository at this point in the history
…ameters

New theme parameters, new TMA methods and events, safer `postEvent`
  • Loading branch information
heyqbnk authored Nov 12, 2023
2 parents 8dd8783 + 79ef697 commit 54d3a49
Show file tree
Hide file tree
Showing 39 changed files with 735 additions and 607 deletions.
8 changes: 8 additions & 0 deletions .changeset/perfect-jeans-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@tma.js/bridge": minor
---

- Support `reload_iframe` event, `iframe_will_reload` method and `iframe_ready` `reload_supported` parameter
- Implement `createPostEvent` method
- Add error classes
- Add new theme parameters
10 changes: 7 additions & 3 deletions apps/docs/apps-communication/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ Available since: **v6.4**

Telegram application attempted to extract text from clipboard.

| Field | Type | Description |
|--------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| req_id | `string` | Passed during the [web_app_read_text_from_clipboard](methods.md#web-app-read-text-from-clipboard) method invocation `req_id` value. |
| Field | Type | Description |
|--------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| req_id | `string` | Passed during the [web_app_read_text_from_clipboard](methods.md#web-app-read-text-from-clipboard) method invocation `req_id` value. |
| data | `string` or `null` | _Optional_. Data extracted from the clipboard. The returned value will have the type `string` only in the case, application has access to the clipboard. |

### `custom_method_invoked`
Expand Down Expand Up @@ -184,6 +184,10 @@ Application received phone access request status.
|-----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------|
| button_id | `string` | _Optional_. Identifier of the clicked button. In case, the popup was closed without clicking any button, this property will be omitted. |

### `reload_iframe`

Parent iframe requested current iframe reload.

### `qr_text_received`

Available since: **v6.4**
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/apps-communication/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ Notifies parent iframe about the current frame is ready. This method is only use
of Telegram. As a result, Mini App will receive [set_custom_style](events.md#set-custom-style)
event.

| Field | Type | Description |
|------------------|-----------|------------------------------------------------------------------|
| reload_supported | `boolean` | _Optional_. True, if current Mini App supports native reloading. |

### `iframe_will_reload`

Notifies parent iframe about the current iframe is going to reload.

### `web_app_close`

Closes Mini App.
Expand Down
35 changes: 34 additions & 1 deletion apps/docs/packages/typescript/tma-js-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ utmost level of control over cross-application communication.
## Installation

::: code-group

```bash [pnpm]
pnpm i @tma.js/bridge
```
Expand All @@ -27,6 +28,7 @@ npm i @tma.js/bridge
```bash [yarn]
yarn add @tma.js/bridge
```

:::

## Calling methods
Expand Down Expand Up @@ -111,11 +113,42 @@ supports('web_app_trigger_haptic_feedback', '6.0'); // false
supports('web_app_trigger_haptic_feedback', '6.1'); // true
```

The `supports` function also allows checking if specified parameter in method parameters is
supported:

```typescript
import { supports } from '@tma.js/bridge';

supports('web_app_open_link', 'try_instant_view', '6.0'); // false
supports('web_app_open_link', 'try_instant_view', '6.7'); // true
```

::: tip
It is recommended to use this function before calling Telegram Mini Apps methods to prevent
It is recommended to use this function before calling Telegram Mini Apps methods to prevent
applications from stalling and other unexpected behavior.
:::

### Creating safer `postEvent`

This package includes a function named `createPostEvent` that takes the current Telegram Mini Apps
version as input. It returns the `postEvent` function, which internally checks if the specified
method and parameters are supported. If they are not, the function will throw an error.

```typescript
import { createPostEvent } from '@tma.js/bridge';

const postEvent = createPostEvent('6.5');

// Will work fine.
postEvent('web_app_read_text_from_clipboard');

// Will throw an error.
postEvent('web_app_request_phone');
```

It is highly recommended to use this `postEvent` generator to ensure that method calls work as
expected.

## Debugging

Package supports enabling the debug mode, which leads to logging
Expand Down
13 changes: 13 additions & 0 deletions packages/bridge/src/errors/MethodUnsupportedError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Version } from '@tma.js/utils';

import type { MethodName } from '../methods/index.js';

/**
* Error thrown in case, unsupported method was called.
*/
export class MethodUnsupportedError extends Error {
constructor(method: MethodName, version: Version) {
super(`Method "${method}" is unsupported in the Mini Apps version ${version}.`);
Object.setPrototypeOf(this, MethodUnsupportedError.prototype);
}
}
13 changes: 13 additions & 0 deletions packages/bridge/src/errors/ParameterUnsupportedError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Version } from '@tma.js/utils';

import type { MethodName } from '../methods/index.js';

/**
* Error thrown in case, unsupported parameter was used.
*/
export class ParameterUnsupportedError extends Error {
constructor(method: MethodName, param: string, version: Version) {
super(`Parameter "${param}" in method "${method}" is unsupported in the Mini Apps version ${version}.`);
Object.setPrototypeOf(this, ParameterUnsupportedError.prototype);
}
}
2 changes: 2 additions & 0 deletions packages/bridge/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './MethodUnsupportedError.js';
export * from './ParameterUnsupportedError.js';
1 change: 1 addition & 0 deletions packages/bridge/src/events/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function createEmitter(): EventEmitter {
case 'back_button_pressed':
case 'settings_button_pressed':
case 'scan_qr_popup_closed':
case 'reload_iframe':
return emit(eventType);

// All other event listeners will receive unknown type of data.
Expand Down
15 changes: 10 additions & 5 deletions packages/bridge/src/events/events.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {
EventEmitter as UtilEventEmitter,
EventName as UtilEventName,
EventListener as UtilEventListener,
EventParams as UtilEventParams, AnySubscribeListener,
EventParams as UtilEventParams,
AnySubscribeListener,
} from '@tma.js/event-emitter';
import type { IsNever, Not } from '@tma.js/util-types';

Expand Down Expand Up @@ -74,6 +74,12 @@ export interface Events {
*/
popup_closed: (payload: PopupClosedPayload) => void;

/**
* Parent iframe requested current iframe reload.
* @see https://docs.telegram-mini-apps.com/apps-communication/events#reload-iframe
*/
reload_iframe: () => void;

/**
* The QR scanner scanned some QR and extracted its content.
* @param payload - event payload.
Expand Down Expand Up @@ -133,7 +139,7 @@ export interface Events {
/**
* Any known event name.
*/
export type EventName = UtilEventName<Events>;
export type EventName = keyof Events;

/**
* Parameters of specified event.
Expand All @@ -143,8 +149,7 @@ export type EventParams<E extends EventName> = UtilEventParams<Events[E]>[0];
/**
* Returns event listener for specified event name.
*/
export type EventListener<E extends EventName> =
UtilEventListener<Events[E]>;
export type EventListener<E extends EventName> = UtilEventListener<Events[E]>;

/**
* Event emitter, based describe events map.
Expand Down
12 changes: 9 additions & 3 deletions packages/bridge/src/events/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ const windowWidthParser = createValueParserGenerator(
*/
export const themeChangedPayload = json<ThemeChangedPayload>({
theme_params: json({
accent_text_color: rgbOptional,
bg_color: rgbOptional,
text_color: rgbOptional,
hint_color: rgbOptional,
link_color: rgbOptional,
button_color: rgbOptional,
button_text_color: rgbOptional,
destructive_text_color: rgbOptional,
header_bg_color: rgbOptional,
hint_color: rgbOptional,
link_color: rgbOptional,
secondary_bg_color: rgbOptional,
section_bg_color: rgbOptional,
section_header_text_color: rgbOptional,
subtitle_text_color: rgbOptional,
text_color: rgbOptional,
}),
});

Expand Down
23 changes: 16 additions & 7 deletions packages/bridge/src/events/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,28 @@ export interface QrTextReceivedPayload {
data?: string;
}

export type ThemeParamsKey =
| 'accent_text_color'
| 'bg_color'
| 'button_color'
| 'button_text_color'
| 'destructive_text_color'
| 'header_bg_color'
| 'hint_color'
| 'link_color'
| 'secondary_bg_color'
| 'section_header_text_color'
| 'section_bg_color'
| 'subtitle_text_color'
| 'text_color';

export interface ThemeChangedPayload {
/**
* Map where the key is a theme stylesheet key and value is the corresponding color in
* `#RRGGBB` format.
*/
theme_params: {
bg_color?: RGB;
text_color?: RGB;
hint_color?: RGB;
link_color?: RGB;
button_color?: RGB;
button_text_color?: RGB;
secondary_bg_color?: RGB;
[Key in ThemeParamsKey]?: RGB;
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/bridge/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './errors/index.js';
export * from './events/index.js';
export * from './methods/index.js';
export * from './env.js';
Expand Down
39 changes: 39 additions & 0 deletions packages/bridge/src/methods/createPostEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isRecord, type Version } from '@tma.js/utils';

import { supports } from '../supports.js';
import { MethodUnsupportedError, ParameterUnsupportedError } from '../errors/index.js';
import { postEvent, type PostEvent } from './index.js';

/**
* Creates function which checks if specified method and parameters are supported. In case,
* method or parameters are unsupported, an error will be thrown.
* @param version - Telegram Mini Apps version.
* @throws {MethodUnsupportedError} Method is unsupported.
* @throws {ParameterUnsupportedError} Method parameter is unsupported.
*/
export function createPostEvent(version: Version): PostEvent {
return (method: any, params: any) => {
// Firstly, check if method itself is supported.
if (!supports(method, version)) {
throw new MethodUnsupportedError(method, version);
}

// Method could use parameters, which are supported only in specific versions of Telegram
// Mini Apps.
if (isRecord(params)) {
let validateParam: string | undefined;

if (method === 'web_app_open_link' && 'try_instant_view' in params) {
validateParam = 'try_instant_view';
} else if (method === 'web_app_set_header_color' && 'color' in params) {
validateParam = 'color';
}

if (validateParam && !supports(method, validateParam, version)) {
throw new ParameterUnsupportedError(method, validateParam, version);
}
}

return postEvent(method, params);
};
}
9 changes: 5 additions & 4 deletions packages/bridge/src/methods/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type * from './haptic.js';
export type * from './invoke-custom-method.js';
export type * from './params.js';
export type * from './popup.js';
export * from './createPostEvent.js';
export * from './haptic.js';
export * from './invoke-custom-method.js';
export * from './methods.js';
export * from './popup.js';
export * from './postEvent.js';
Loading

0 comments on commit 54d3a49

Please sign in to comment.