@tma.js/bridge 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/lib/browser.js +2 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/lib/index.cjs +2 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/env.d.ts +29 -0
- package/dist/types/events/emitter.d.ts +11 -0
- package/dist/types/events/events.d.ts +113 -0
- package/dist/types/events/index.d.ts +7 -0
- package/dist/types/events/off.d.ts +7 -0
- package/dist/types/events/on.d.ts +10 -0
- package/dist/types/events/onTelegramEvent.d.ts +7 -0
- package/dist/types/events/once.d.ts +9 -0
- package/dist/types/events/parsing.d.ts +38 -0
- package/dist/types/events/payloads.d.ts +47 -0
- package/dist/types/events/subscribe.d.ts +9 -0
- package/dist/types/events/unsubscribe.d.ts +6 -0
- package/dist/types/globals.d.ts +27 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/methods/haptic.d.ts +40 -0
- package/dist/types/methods/index.d.ts +5 -0
- package/dist/types/methods/invoke-custom-method.d.ts +24 -0
- package/dist/types/methods/params.d.ts +227 -0
- package/dist/types/methods/popup.d.ts +50 -0
- package/dist/types/methods/postEvent.d.ts +31 -0
- package/dist/types/request.d.ts +66 -0
- package/dist/types/shared.d.ts +5 -0
- package/dist/types/supports.d.ts +22 -0
- package/package.json +67 -0
- package/src/env.ts +49 -0
- package/src/events/emitter.ts +125 -0
- package/src/events/events.ts +152 -0
- package/src/events/index.ts +7 -0
- package/src/events/off.ts +12 -0
- package/src/events/on.ts +17 -0
- package/src/events/onTelegramEvent.ts +83 -0
- package/src/events/once.ts +16 -0
- package/src/events/parsing.ts +94 -0
- package/src/events/payloads.ts +65 -0
- package/src/events/subscribe.ts +16 -0
- package/src/events/unsubscribe.ts +11 -0
- package/src/globals.ts +44 -0
- package/src/index.ts +6 -0
- package/src/methods/haptic.ts +52 -0
- package/src/methods/index.ts +5 -0
- package/src/methods/invoke-custom-method.ts +25 -0
- package/src/methods/params.ts +245 -0
- package/src/methods/popup.ts +55 -0
- package/src/methods/postEvent.ts +103 -0
- package/src/request.ts +171 -0
- package/src/shared.ts +5 -0
- package/src/supports.ts +99 -0
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tma.js/bridge",
|
|
3
|
+
"version": "1.3.2",
|
|
4
|
+
"description": "Communication layer between Telegram and frontend applications.",
|
|
5
|
+
"author": "Vladislav Kibenko <wolfram.deus@gmail.com>",
|
|
6
|
+
"homepage": "https://github.com/Telegram-Mini-Apps/tma.js#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Telegram-Mini-Apps/tma.js.git",
|
|
10
|
+
"directory": "packages/bridge"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Telegram-Mini-Apps/tma.js/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"telegram-mini-apps",
|
|
17
|
+
"typescript",
|
|
18
|
+
"bridge",
|
|
19
|
+
"webview"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"main": "dist/lib/index.cjs",
|
|
29
|
+
"browser": "dist/lib/browser.js",
|
|
30
|
+
"module": "dist/lib/index.mjs",
|
|
31
|
+
"types": "dist/types/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/types/index.d.ts",
|
|
35
|
+
"import": "./dist/lib/index.mjs",
|
|
36
|
+
"require": "./dist/lib/index.cjs",
|
|
37
|
+
"default": "./dist/lib/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./*": {
|
|
40
|
+
"types": "./dist/types/*.d.ts",
|
|
41
|
+
"import": "./dist/lib/*.mjs",
|
|
42
|
+
"require": "./dist/lib/*.cjs",
|
|
43
|
+
"default": "./dist/lib/*.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@tma.js/utils": "0.5.2",
|
|
48
|
+
"@tma.js/logger": "0.0.1",
|
|
49
|
+
"@tma.js/event-emitter": "0.0.2",
|
|
50
|
+
"@tma.js/colors": "0.0.2",
|
|
51
|
+
"@tma.js/parsing": "0.0.2",
|
|
52
|
+
"@tma.js/util-types": "0.0.2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"tsconfig": "0.0.2",
|
|
56
|
+
"eslint-config-custom": "0.1.0",
|
|
57
|
+
"jest-config-custom": "0.1.0"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"test": "jest",
|
|
64
|
+
"lint": "eslint -c .eslintrc.cjs src/**/* __tests__/**/*",
|
|
65
|
+
"build": "rimraf dist && rollup --config rollup.config.js"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isRecord } from '@tma.js/utils';
|
|
2
|
+
|
|
3
|
+
type AnyFunc = (...args: unknown[]) => unknown;
|
|
4
|
+
|
|
5
|
+
type WithExternalNotify<T> = T & {
|
|
6
|
+
external: { notify: AnyFunc }
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type WithWebviewProxy<T> = T & {
|
|
10
|
+
TelegramWebviewProxy: {
|
|
11
|
+
postEvent: AnyFunc;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns true in case, passed value contains path `external.notify` property and `notify` is a
|
|
17
|
+
* function.
|
|
18
|
+
* @param value - value to check.
|
|
19
|
+
*/
|
|
20
|
+
export function hasExternalNotify<T extends {}>(value: T): value is WithExternalNotify<T> {
|
|
21
|
+
return 'external' in value
|
|
22
|
+
&& isRecord(value.external)
|
|
23
|
+
&& 'notify' in value.external
|
|
24
|
+
&& typeof value.external.notify === 'function';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns true in case, passed value contains path `TelegramWebviewProxy.postEvent` property and
|
|
29
|
+
* `postEvent` is a function.
|
|
30
|
+
* @param value - value to check.
|
|
31
|
+
*/
|
|
32
|
+
export function hasWebviewProxy<T extends {}>(value: T): value is WithWebviewProxy<T> {
|
|
33
|
+
return 'TelegramWebviewProxy' in value
|
|
34
|
+
&& isRecord(value.TelegramWebviewProxy)
|
|
35
|
+
&& 'postEvent' in value.TelegramWebviewProxy
|
|
36
|
+
&& typeof value.TelegramWebviewProxy.postEvent === 'function';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns true in case, current environment is iframe.
|
|
41
|
+
* @see https://stackoverflow.com/a/326076
|
|
42
|
+
*/
|
|
43
|
+
export function isIframe(): boolean {
|
|
44
|
+
try {
|
|
45
|
+
return window.self !== window.top;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { EventEmitter as UtilEventEmitter } from '@tma.js/event-emitter';
|
|
2
|
+
import { string } from '@tma.js/parsing';
|
|
3
|
+
|
|
4
|
+
import { log } from '../globals.js';
|
|
5
|
+
import {
|
|
6
|
+
clipboardTextReceivedPayload,
|
|
7
|
+
customMethodInvokedPayload,
|
|
8
|
+
invoiceClosedPayload,
|
|
9
|
+
phoneRequestedPayload,
|
|
10
|
+
popupClosedPayload,
|
|
11
|
+
qrTextReceivedPayload,
|
|
12
|
+
themeChangedPayload,
|
|
13
|
+
viewportChangedPayload,
|
|
14
|
+
writeAccessRequestedPayload,
|
|
15
|
+
} from './parsing.js';
|
|
16
|
+
import { onTelegramEvent } from './onTelegramEvent.js';
|
|
17
|
+
|
|
18
|
+
import type { EventEmitter, EventName } from './events.js';
|
|
19
|
+
|
|
20
|
+
const CACHED_EMITTER = '__telegram-cached-emitter__';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns event emitter which could be safely used, to process events from
|
|
24
|
+
* Telegram native application.
|
|
25
|
+
*/
|
|
26
|
+
export function createEmitter(): EventEmitter {
|
|
27
|
+
const emitter: EventEmitter = new UtilEventEmitter();
|
|
28
|
+
const emit: EventEmitter['emit'] = (event: any, ...data: any[]) => {
|
|
29
|
+
log('log', 'Emitting processed event:', event, ...data);
|
|
30
|
+
emitter.emit(event, ...data);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Desktop version of Telegram is sometimes not sending the viewport_changed
|
|
34
|
+
// event. For example, when main button is shown. That's why we should
|
|
35
|
+
// add our own listener to make sure, viewport information is always fresh.
|
|
36
|
+
// Issue: https://github.com/Telegram-Web-Apps/tma.js/issues/10
|
|
37
|
+
window.addEventListener('resize', () => {
|
|
38
|
+
emitter.emit('viewport_changed', {
|
|
39
|
+
width: window.innerWidth,
|
|
40
|
+
height: window.innerHeight,
|
|
41
|
+
is_state_stable: true,
|
|
42
|
+
is_expanded: true,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// In case, any Telegram event was received, we should prepare data before
|
|
47
|
+
// passing it to emitter.
|
|
48
|
+
onTelegramEvent((eventType: EventName | string, eventData): void => {
|
|
49
|
+
log('log', 'Received raw event:', eventType, eventData);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
switch (eventType) {
|
|
53
|
+
case 'viewport_changed':
|
|
54
|
+
return emit(eventType, viewportChangedPayload.parse(eventData));
|
|
55
|
+
|
|
56
|
+
case 'theme_changed':
|
|
57
|
+
return emit(eventType, themeChangedPayload.parse(eventData));
|
|
58
|
+
|
|
59
|
+
case 'popup_closed':
|
|
60
|
+
// FIXME: Payloads are different on different platforms.
|
|
61
|
+
// Issue: https://github.com/Telegram-Web-Apps/tma.js/issues/2
|
|
62
|
+
if (
|
|
63
|
+
// Sent on desktop.
|
|
64
|
+
eventData === undefined
|
|
65
|
+
// Sent on iOS.
|
|
66
|
+
|| eventData === null
|
|
67
|
+
) {
|
|
68
|
+
return emit(eventType, {});
|
|
69
|
+
}
|
|
70
|
+
return emit(eventType, popupClosedPayload.parse(eventData));
|
|
71
|
+
|
|
72
|
+
case 'set_custom_style':
|
|
73
|
+
return emit(eventType, string().parse(eventData));
|
|
74
|
+
|
|
75
|
+
case 'qr_text_received':
|
|
76
|
+
return emit(eventType, qrTextReceivedPayload.parse(eventData));
|
|
77
|
+
|
|
78
|
+
case 'clipboard_text_received':
|
|
79
|
+
return emit(eventType, clipboardTextReceivedPayload.parse(eventData));
|
|
80
|
+
|
|
81
|
+
case 'invoice_closed':
|
|
82
|
+
return emit(eventType, invoiceClosedPayload.parse(eventData));
|
|
83
|
+
|
|
84
|
+
case 'phone_requested':
|
|
85
|
+
return emit('phone_requested', phoneRequestedPayload.parse(eventData));
|
|
86
|
+
|
|
87
|
+
case 'custom_method_invoked':
|
|
88
|
+
return emit('custom_method_invoked', customMethodInvokedPayload.parse(eventData));
|
|
89
|
+
|
|
90
|
+
case 'write_access_requested':
|
|
91
|
+
return emit('write_access_requested', writeAccessRequestedPayload.parse(eventData));
|
|
92
|
+
|
|
93
|
+
// Events which have no parameters.
|
|
94
|
+
case 'main_button_pressed':
|
|
95
|
+
case 'back_button_pressed':
|
|
96
|
+
case 'settings_button_pressed':
|
|
97
|
+
case 'scan_qr_popup_closed':
|
|
98
|
+
return emit(eventType);
|
|
99
|
+
|
|
100
|
+
// All other event listeners will receive unknown type of data.
|
|
101
|
+
default:
|
|
102
|
+
return emit(eventType as any, eventData);
|
|
103
|
+
}
|
|
104
|
+
} catch (cause) {
|
|
105
|
+
log('error', 'Error processing event:', cause);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return emitter;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Returns singleton instance of bridge EventEmitter. Also, defines
|
|
114
|
+
* Telegram event handlers.
|
|
115
|
+
*/
|
|
116
|
+
export function singletonEmitter(): EventEmitter {
|
|
117
|
+
const wnd: any = window;
|
|
118
|
+
const cachedEmitter = wnd[CACHED_EMITTER];
|
|
119
|
+
|
|
120
|
+
if (cachedEmitter === undefined) {
|
|
121
|
+
wnd[CACHED_EMITTER] = createEmitter();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return wnd[CACHED_EMITTER];
|
|
125
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventEmitter as UtilEventEmitter,
|
|
3
|
+
EventName as UtilEventName,
|
|
4
|
+
EventListener as UtilEventListener,
|
|
5
|
+
EventParams as UtilEventParams, AnySubscribeListener,
|
|
6
|
+
} from '@tma.js/event-emitter';
|
|
7
|
+
import type { IsNever, Not } from '@tma.js/util-types';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ClipboardTextReceivedPayload,
|
|
11
|
+
CustomMethodInvokedPayload,
|
|
12
|
+
InvoiceClosedPayload,
|
|
13
|
+
PhoneRequestedPayload,
|
|
14
|
+
PopupClosedPayload,
|
|
15
|
+
QrTextReceivedPayload,
|
|
16
|
+
ThemeChangedPayload,
|
|
17
|
+
ViewportChangedPayload,
|
|
18
|
+
WriteAccessRequestedPayload,
|
|
19
|
+
} from './payloads.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map where key is known event name, and value is its listener.
|
|
23
|
+
* @see Documentation https://docs.telegram-mini-apps.com/docs/apps-communication/events
|
|
24
|
+
*/
|
|
25
|
+
export interface Events {
|
|
26
|
+
/**
|
|
27
|
+
* User clicked back button.
|
|
28
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#back_button_pressed
|
|
29
|
+
*/
|
|
30
|
+
back_button_pressed: () => void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Text was extracted from clipboard.
|
|
34
|
+
* @param payload - event information.
|
|
35
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#clipboard_text_received
|
|
36
|
+
*/
|
|
37
|
+
clipboard_text_received: (payload: ClipboardTextReceivedPayload) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Being called whenever custom method was invoked.
|
|
41
|
+
* @param payload - event payload.
|
|
42
|
+
* @since 6.9
|
|
43
|
+
*/
|
|
44
|
+
custom_method_invoked: (payload: CustomMethodInvokedPayload) => void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Invoice was closed.
|
|
48
|
+
* @param payload - invoice close information.
|
|
49
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#invoice_closed
|
|
50
|
+
*/
|
|
51
|
+
invoice_closed: (payload: InvoiceClosedPayload) => void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* User clicked main button.
|
|
55
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#main_button_pressed
|
|
56
|
+
*/
|
|
57
|
+
main_button_pressed: () => void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Application received phone access request status.
|
|
61
|
+
* @param payload - event payload. - event payload.
|
|
62
|
+
* @since 6.9
|
|
63
|
+
*/
|
|
64
|
+
phone_requested: (payload: PhoneRequestedPayload) => void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Popup was closed.
|
|
68
|
+
* @param payload - popup close information.
|
|
69
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#main_button_pressed
|
|
70
|
+
*/
|
|
71
|
+
popup_closed: (payload: PopupClosedPayload) => void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Data from QR was extracted.
|
|
75
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#qr_text_received
|
|
76
|
+
*/
|
|
77
|
+
qr_text_received: (payload: QrTextReceivedPayload) => void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* QR scanner was closed.
|
|
81
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#scan_qr_popup_closed
|
|
82
|
+
*/
|
|
83
|
+
scan_qr_popup_closed: () => void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Telegram requested to update current application style.
|
|
87
|
+
* @param html - `style` tag inner HTML.
|
|
88
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#set_custom_style
|
|
89
|
+
*/
|
|
90
|
+
set_custom_style: (html: string) => void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Occurs when the Settings item in context menu is pressed.
|
|
94
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#settings_button_pressed
|
|
95
|
+
*/
|
|
96
|
+
settings_button_pressed: () => void;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Occurs whenever theme settings are changed in the user's Telegram app
|
|
100
|
+
* (including switching to night mode).
|
|
101
|
+
* @param payload - theme information.
|
|
102
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#theme_changed
|
|
103
|
+
*/
|
|
104
|
+
theme_changed: (payload: ThemeChangedPayload) => void;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Viewport was changed.
|
|
108
|
+
* @param payload - viewport information.
|
|
109
|
+
* @see https://docs.telegram-mini-apps.com/docs/apps-communication/events#viewport_changed
|
|
110
|
+
*/
|
|
111
|
+
viewport_changed: (payload: ViewportChangedPayload) => void;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Application received write access requests status.
|
|
115
|
+
* @param payload - event payload.
|
|
116
|
+
* @since 6.9
|
|
117
|
+
*/
|
|
118
|
+
write_access_requested: (payload: WriteAccessRequestedPayload) => void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Any known event name.
|
|
123
|
+
*/
|
|
124
|
+
export type EventName = UtilEventName<Events>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parameters of specified event.
|
|
128
|
+
*/
|
|
129
|
+
export type EventParams<E extends EventName> = UtilEventParams<Events[E]>[0];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns event listener for specified event name.
|
|
133
|
+
*/
|
|
134
|
+
export type EventListener<E extends EventName> =
|
|
135
|
+
UtilEventListener<Events[E]>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Event emitter, based describe events map.
|
|
139
|
+
*/
|
|
140
|
+
export type EventEmitter = UtilEventEmitter<Events>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns true in case, event has parameters.
|
|
144
|
+
*/
|
|
145
|
+
export type EventHasParams<E extends EventName> = Not<IsNever<EventParams<E>>>;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Event listener used in `subscribe` and `unsubscribe` functions.
|
|
149
|
+
*/
|
|
150
|
+
export type GlobalEventListener =
|
|
151
|
+
| AnySubscribeListener<Events>
|
|
152
|
+
| ((event: string, data: unknown) => void);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { singletonEmitter } from './emitter.js';
|
|
2
|
+
|
|
3
|
+
import type { EventName, EventListener } from './events.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Removes listener from specified event.
|
|
7
|
+
* @param event - event to listen.
|
|
8
|
+
* @param listener - event listener.
|
|
9
|
+
*/
|
|
10
|
+
export function off<E extends EventName>(event: E, listener: EventListener<E>): void {
|
|
11
|
+
singletonEmitter().off(event, listener);
|
|
12
|
+
}
|
package/src/events/on.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { singletonEmitter } from './emitter.js';
|
|
2
|
+
import { off } from './off.js';
|
|
3
|
+
|
|
4
|
+
import type { EventName, EventListener } from './events.js';
|
|
5
|
+
|
|
6
|
+
type StopListening = () => void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adds new listener to the specified event. Returns handler
|
|
10
|
+
* which allows to stop listening to event.
|
|
11
|
+
* @param event - event name.
|
|
12
|
+
* @param listener - event listener.
|
|
13
|
+
*/
|
|
14
|
+
export function on<E extends EventName>(event: E, listener: EventListener<E>): StopListening {
|
|
15
|
+
singletonEmitter().on(event, listener);
|
|
16
|
+
return () => off(event, listener);
|
|
17
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { json, string } from '@tma.js/parsing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts event data from native application event.
|
|
5
|
+
*/
|
|
6
|
+
const eventDataJson = json<{ eventType: string; eventData?: unknown }>({
|
|
7
|
+
eventType: string(),
|
|
8
|
+
eventData: (value) => value,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Emits event sent from Telegram native application like it was sent in
|
|
13
|
+
* default web environment between 2 iframes. It dispatches new MessageEvent
|
|
14
|
+
* and expects it to be handled via `window.addEventListener('message', ...)`
|
|
15
|
+
* as developer would do it to handle messages sent from parent iframe.
|
|
16
|
+
* @param eventType - event name.
|
|
17
|
+
* @param eventData - event payload.
|
|
18
|
+
*/
|
|
19
|
+
function emitEvent(eventType: string, eventData: unknown): void {
|
|
20
|
+
window.dispatchEvent(new MessageEvent('message', {
|
|
21
|
+
data: JSON.stringify({ eventType, eventData }),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Defines special handlers by known paths, which are recognized by
|
|
27
|
+
* Telegram as ports to receive events. This function also sets special
|
|
28
|
+
* function in global window object to prevent duplicate declaration.
|
|
29
|
+
*/
|
|
30
|
+
function defineEventHandlers(): void {
|
|
31
|
+
const wnd: any = window;
|
|
32
|
+
|
|
33
|
+
// Prevent from duplicate event handlers definition.
|
|
34
|
+
if ('TelegramGameProxy_receiveEvent' in wnd) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Iterate over each path, where "receiveEvent" function should be
|
|
39
|
+
// defined. This function is called by external environment in case,
|
|
40
|
+
// it wants to emit some event.
|
|
41
|
+
[
|
|
42
|
+
['TelegramGameProxy_receiveEvent'], // Windows Phone.
|
|
43
|
+
['TelegramGameProxy', 'receiveEvent'], // Desktop.
|
|
44
|
+
['Telegram', 'WebView', 'receiveEvent'], // Android and iOS.
|
|
45
|
+
].forEach((path) => {
|
|
46
|
+
// Path starts from "window" object.
|
|
47
|
+
let pointer = wnd;
|
|
48
|
+
|
|
49
|
+
path.forEach((item, idx, arr) => {
|
|
50
|
+
// We are on the last iteration, where function property name is passed.
|
|
51
|
+
if (idx === arr.length - 1) {
|
|
52
|
+
pointer[item] = emitEvent;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!(item in pointer)) {
|
|
57
|
+
pointer[item] = {};
|
|
58
|
+
}
|
|
59
|
+
pointer = pointer[item];
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Adds listener to window "message" event assuming, that this event could
|
|
66
|
+
* be sent by Telegram native application. Calls passed callback with event
|
|
67
|
+
* type and data.
|
|
68
|
+
* @param cb - callback to call.
|
|
69
|
+
*/
|
|
70
|
+
export function onTelegramEvent(cb: (eventType: string, eventData: unknown) => void): void {
|
|
71
|
+
// Define event handlers to make sure, message handler will work correctly.
|
|
72
|
+
defineEventHandlers();
|
|
73
|
+
|
|
74
|
+
// We expect Telegram to send us new event through "message" event.
|
|
75
|
+
window.addEventListener('message', (event) => {
|
|
76
|
+
try {
|
|
77
|
+
const { eventType, eventData } = eventDataJson.parse(event.data);
|
|
78
|
+
cb(eventType, eventData);
|
|
79
|
+
} catch {
|
|
80
|
+
// We ignore incorrect messages as they could be generated by any other code.
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { singletonEmitter } from './emitter.js';
|
|
2
|
+
import { off } from './off.js';
|
|
3
|
+
|
|
4
|
+
import type { EventName, EventListener } from './events.js';
|
|
5
|
+
|
|
6
|
+
type StopListening = () => void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Works the same as "on" method, but after catching the event, will remove event listener.
|
|
10
|
+
* @param event - event name.
|
|
11
|
+
* @param listener - event listener.
|
|
12
|
+
*/
|
|
13
|
+
export function once<E extends EventName>(event: E, listener: EventListener<E>): StopListening {
|
|
14
|
+
singletonEmitter().once(event, listener);
|
|
15
|
+
return () => off(event, listener);
|
|
16
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
number,
|
|
3
|
+
string,
|
|
4
|
+
boolean,
|
|
5
|
+
json,
|
|
6
|
+
rgb,
|
|
7
|
+
} from '@tma.js/parsing';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ClipboardTextReceivedPayload, CustomMethodInvokedPayload,
|
|
11
|
+
InvoiceClosedPayload, PhoneRequestedPayload,
|
|
12
|
+
PopupClosedPayload, QrTextReceivedPayload,
|
|
13
|
+
ThemeChangedPayload,
|
|
14
|
+
ViewportChangedPayload, WriteAccessRequestedPayload,
|
|
15
|
+
} from './payloads.js';
|
|
16
|
+
|
|
17
|
+
function isNullOrUndefined(value: unknown): boolean {
|
|
18
|
+
return value === null || value === undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses incoming value as ThemeChangedPayload.
|
|
23
|
+
*/
|
|
24
|
+
export const themeChangedPayload = json<ThemeChangedPayload>({
|
|
25
|
+
theme_params: json<ThemeChangedPayload['theme_params']>({
|
|
26
|
+
bg_color: rgb().optional(),
|
|
27
|
+
text_color: rgb().optional(),
|
|
28
|
+
hint_color: rgb().optional(),
|
|
29
|
+
link_color: rgb().optional(),
|
|
30
|
+
button_color: rgb().optional(),
|
|
31
|
+
button_text_color: rgb().optional(),
|
|
32
|
+
secondary_bg_color: rgb().optional(),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parses incoming value as ViewportChangedPayload.
|
|
38
|
+
* @param value - value to parse.
|
|
39
|
+
*/
|
|
40
|
+
export const viewportChangedPayload = json<ViewportChangedPayload>({
|
|
41
|
+
height: number(),
|
|
42
|
+
width: number().optional(isNullOrUndefined).default(() => window.innerWidth),
|
|
43
|
+
is_state_stable: boolean(),
|
|
44
|
+
is_expanded: boolean(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parses incoming value as PopupClosedPayload.
|
|
49
|
+
*/
|
|
50
|
+
export const popupClosedPayload = json<PopupClosedPayload>({
|
|
51
|
+
button_id: string().optional(isNullOrUndefined),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parses incoming value as QrTextReceivedPayload.
|
|
56
|
+
*/
|
|
57
|
+
export const qrTextReceivedPayload = json<QrTextReceivedPayload>({
|
|
58
|
+
data: string().optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parses incoming value as InvoiceClosedPayload.
|
|
63
|
+
*/
|
|
64
|
+
export const invoiceClosedPayload = json<InvoiceClosedPayload>({
|
|
65
|
+
slug: string(),
|
|
66
|
+
status: string(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses incoming value as clipboard text received payload.
|
|
71
|
+
*/
|
|
72
|
+
export const clipboardTextReceivedPayload = json<ClipboardTextReceivedPayload>({
|
|
73
|
+
req_id: string(),
|
|
74
|
+
data: (value) => (value === null ? value : string().optional().parse(value)),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parses incoming value as WriteAccessRequestedPayload.
|
|
79
|
+
*/
|
|
80
|
+
export const writeAccessRequestedPayload = json<WriteAccessRequestedPayload>({ status: string() });
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses incoming value as PhoneRequestedPayload.
|
|
84
|
+
*/
|
|
85
|
+
export const phoneRequestedPayload = json<PhoneRequestedPayload>({ status: string() });
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parses incoming value as CustomMethodInvokedPayload.
|
|
89
|
+
*/
|
|
90
|
+
export const customMethodInvokedPayload = json<CustomMethodInvokedPayload>({
|
|
91
|
+
req_id: string(),
|
|
92
|
+
result: (value) => value,
|
|
93
|
+
error: string().optional(),
|
|
94
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RGB } from '@tma.js/colors';
|
|
2
|
+
|
|
3
|
+
import type { RequestId } from '../shared.js';
|
|
4
|
+
|
|
5
|
+
export interface ClipboardTextReceivedPayload {
|
|
6
|
+
req_id: RequestId;
|
|
7
|
+
data?: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CustomMethodInvokedPayload<R = unknown> {
|
|
11
|
+
req_id: RequestId;
|
|
12
|
+
result?: R;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type InvoiceStatus =
|
|
17
|
+
| 'paid'
|
|
18
|
+
| 'failed'
|
|
19
|
+
| 'pending'
|
|
20
|
+
| 'cancelled'
|
|
21
|
+
| string;
|
|
22
|
+
|
|
23
|
+
export interface InvoiceClosedPayload {
|
|
24
|
+
slug: string;
|
|
25
|
+
status: InvoiceStatus;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface QrTextReceivedPayload {
|
|
29
|
+
data?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ThemeChangedPayload {
|
|
33
|
+
theme_params: {
|
|
34
|
+
bg_color?: RGB;
|
|
35
|
+
text_color?: RGB;
|
|
36
|
+
hint_color?: RGB;
|
|
37
|
+
link_color?: RGB;
|
|
38
|
+
button_color?: RGB;
|
|
39
|
+
button_text_color?: RGB;
|
|
40
|
+
secondary_bg_color?: RGB;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ViewportChangedPayload {
|
|
45
|
+
height: number;
|
|
46
|
+
width: number;
|
|
47
|
+
is_expanded: boolean;
|
|
48
|
+
is_state_stable: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PopupClosedPayload {
|
|
52
|
+
button_id?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type PhoneRequestedStatus = 'sent' | string;
|
|
56
|
+
|
|
57
|
+
export interface PhoneRequestedPayload {
|
|
58
|
+
status: PhoneRequestedStatus;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type WriteAccessRequestedStatus = 'allowed' | string;
|
|
62
|
+
|
|
63
|
+
export interface WriteAccessRequestedPayload {
|
|
64
|
+
status: WriteAccessRequestedStatus;
|
|
65
|
+
}
|