Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New theme parameters, new TMA methods and events, safer postEvent #176

Merged
merged 13 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading