@tma.js/sdk 0.11.3

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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/lib/browser.js +2 -0
  4. package/dist/lib/browser.js.map +1 -0
  5. package/dist/lib/index.cjs +2 -0
  6. package/dist/lib/index.cjs.map +1 -0
  7. package/dist/lib/index.mjs +2 -0
  8. package/dist/lib/index.mjs.map +1 -0
  9. package/dist/types/components/BackButton/BackButton.d.ts +44 -0
  10. package/dist/types/components/BackButton/index.d.ts +2 -0
  11. package/dist/types/components/BackButton/types.d.ts +9 -0
  12. package/dist/types/components/ClosingBehaviour/ClosingBehaviour.d.ts +35 -0
  13. package/dist/types/components/ClosingBehaviour/index.d.ts +2 -0
  14. package/dist/types/components/ClosingBehaviour/types.d.ts +7 -0
  15. package/dist/types/components/CloudStorage/CloudStorage.d.ts +47 -0
  16. package/dist/types/components/CloudStorage/index.d.ts +1 -0
  17. package/dist/types/components/HapticFeedback/HapticFeedback.d.ts +37 -0
  18. package/dist/types/components/HapticFeedback/index.d.ts +1 -0
  19. package/dist/types/components/InitData/InitData.d.ts +52 -0
  20. package/dist/types/components/InitData/index.d.ts +1 -0
  21. package/dist/types/components/MainButton/MainButton.d.ts +114 -0
  22. package/dist/types/components/MainButton/index.d.ts +2 -0
  23. package/dist/types/components/MainButton/types.d.ts +15 -0
  24. package/dist/types/components/Popup/Popup.d.ts +44 -0
  25. package/dist/types/components/Popup/index.d.ts +2 -0
  26. package/dist/types/components/Popup/types.d.ts +60 -0
  27. package/dist/types/components/Popup/utils.d.ts +7 -0
  28. package/dist/types/components/QRScanner/QRScanner.d.ts +40 -0
  29. package/dist/types/components/QRScanner/index.d.ts +2 -0
  30. package/dist/types/components/QRScanner/types.d.ts +7 -0
  31. package/dist/types/components/ThemeParams/ThemeParams.d.ts +73 -0
  32. package/dist/types/components/ThemeParams/index.d.ts +2 -0
  33. package/dist/types/components/ThemeParams/types.d.ts +9 -0
  34. package/dist/types/components/Viewport/Viewport.d.ts +108 -0
  35. package/dist/types/components/Viewport/index.d.ts +2 -0
  36. package/dist/types/components/Viewport/types.d.ts +10 -0
  37. package/dist/types/components/WebApp/WebApp.d.ts +145 -0
  38. package/dist/types/components/WebApp/index.d.ts +2 -0
  39. package/dist/types/components/WebApp/types.d.ts +11 -0
  40. package/dist/types/components/index.d.ts +11 -0
  41. package/dist/types/env.d.ts +8 -0
  42. package/dist/types/errors/MethodNotSupportedError.d.ts +6 -0
  43. package/dist/types/errors/ParameterNotSupportedError.d.ts +6 -0
  44. package/dist/types/errors/index.d.ts +2 -0
  45. package/dist/types/index.d.ts +8 -0
  46. package/dist/types/init/creators/createBackButton.d.ts +9 -0
  47. package/dist/types/init/creators/createClosingBehavior.d.ts +8 -0
  48. package/dist/types/init/creators/createMainButton.d.ts +11 -0
  49. package/dist/types/init/creators/createPostEvent.d.ts +7 -0
  50. package/dist/types/init/creators/createRequestIdGenerator.d.ts +5 -0
  51. package/dist/types/init/creators/createSyncedThemeParams.d.ts +7 -0
  52. package/dist/types/init/creators/createViewport.d.ts +10 -0
  53. package/dist/types/init/creators/createWebApp.d.ts +14 -0
  54. package/dist/types/init/creators/index.d.ts +8 -0
  55. package/dist/types/init/css.d.ts +57 -0
  56. package/dist/types/init/index.d.ts +2 -0
  57. package/dist/types/init/init.d.ts +6 -0
  58. package/dist/types/init/types.d.ts +107 -0
  59. package/dist/types/launch-params.d.ts +20 -0
  60. package/dist/types/state/State.d.ts +15 -0
  61. package/dist/types/state/index.d.ts +2 -0
  62. package/dist/types/state/types.d.ts +30 -0
  63. package/dist/types/storage.d.ts +48 -0
  64. package/dist/types/supports.d.ts +22 -0
  65. package/dist/types/theme-params.d.ts +18 -0
  66. package/dist/types/types.d.ts +14 -0
  67. package/dist/types/url.d.ts +7 -0
  68. package/package.json +67 -0
  69. package/src/components/BackButton/BackButton.ts +90 -0
  70. package/src/components/BackButton/index.ts +2 -0
  71. package/src/components/BackButton/types.ts +13 -0
  72. package/src/components/ClosingBehaviour/ClosingBehaviour.ts +62 -0
  73. package/src/components/ClosingBehaviour/index.ts +6 -0
  74. package/src/components/ClosingBehaviour/types.ts +12 -0
  75. package/src/components/CloudStorage/CloudStorage.ts +135 -0
  76. package/src/components/CloudStorage/index.ts +1 -0
  77. package/src/components/HapticFeedback/HapticFeedback.ts +62 -0
  78. package/src/components/HapticFeedback/index.ts +1 -0
  79. package/src/components/InitData/InitData.ts +116 -0
  80. package/src/components/InitData/index.ts +1 -0
  81. package/src/components/MainButton/MainButton.ts +243 -0
  82. package/src/components/MainButton/index.ts +2 -0
  83. package/src/components/MainButton/types.ts +20 -0
  84. package/src/components/Popup/Popup.ts +81 -0
  85. package/src/components/Popup/index.ts +8 -0
  86. package/src/components/Popup/types.ts +69 -0
  87. package/src/components/Popup/utils.ts +59 -0
  88. package/src/components/QRScanner/QRScanner.ts +87 -0
  89. package/src/components/QRScanner/index.ts +2 -0
  90. package/src/components/QRScanner/types.ts +11 -0
  91. package/src/components/ThemeParams/ThemeParams.ts +154 -0
  92. package/src/components/ThemeParams/index.ts +2 -0
  93. package/src/components/ThemeParams/types.ts +18 -0
  94. package/src/components/Viewport/Viewport.ts +197 -0
  95. package/src/components/Viewport/index.ts +2 -0
  96. package/src/components/Viewport/types.ts +14 -0
  97. package/src/components/WebApp/WebApp.ts +310 -0
  98. package/src/components/WebApp/index.ts +2 -0
  99. package/src/components/WebApp/types.ts +17 -0
  100. package/src/components/index.ts +11 -0
  101. package/src/env.ts +17 -0
  102. package/src/errors/MethodNotSupportedError.ts +9 -0
  103. package/src/errors/ParameterNotSupportedError.ts +9 -0
  104. package/src/errors/index.ts +2 -0
  105. package/src/index.ts +8 -0
  106. package/src/init/creators/createBackButton.ts +22 -0
  107. package/src/init/creators/createClosingBehavior.ts +22 -0
  108. package/src/init/creators/createMainButton.ts +56 -0
  109. package/src/init/creators/createPostEvent.ts +36 -0
  110. package/src/init/creators/createRequestIdGenerator.ts +13 -0
  111. package/src/init/creators/createSyncedThemeParams.ts +14 -0
  112. package/src/init/creators/createViewport.ts +50 -0
  113. package/src/init/creators/createWebApp.ts +49 -0
  114. package/src/init/creators/index.ts +8 -0
  115. package/src/init/css.ts +166 -0
  116. package/src/init/index.ts +2 -0
  117. package/src/init/init.ts +160 -0
  118. package/src/init/types.ts +133 -0
  119. package/src/launch-params.ts +70 -0
  120. package/src/state/State.ts +53 -0
  121. package/src/state/index.ts +2 -0
  122. package/src/state/types.ts +34 -0
  123. package/src/storage.ts +65 -0
  124. package/src/supports.ts +44 -0
  125. package/src/theme-params.ts +34 -0
  126. package/src/types.ts +28 -0
  127. package/src/url.ts +24 -0
@@ -0,0 +1,116 @@
1
+ import type { HasUndefined, If } from '@tma.js/util-types';
2
+ import type {
3
+ Chat,
4
+ InitData as InitDataType,
5
+ User,
6
+ } from '@tma.js/init-data';
7
+
8
+ import { State } from '../../state/index.js';
9
+
10
+ type InitDataState = {
11
+ [K in keyof InitDataType]-?: If<
12
+ HasUndefined<InitDataType[K]>,
13
+ Exclude<InitDataType[K], undefined> | null,
14
+ InitDataType[K]
15
+ >;
16
+ };
17
+
18
+ /**
19
+ * Class which is responsible for displaying Web Apps init data.
20
+ */
21
+ export class InitData {
22
+ private readonly state: State<InitDataState>;
23
+
24
+ constructor(
25
+ authDate: Date,
26
+ hash: string,
27
+ options: Omit<InitDataType, 'authDate' | 'hash'> = {},
28
+ ) {
29
+ const {
30
+ chat = null,
31
+ canSendAfter = null,
32
+ user = null,
33
+ queryId = null,
34
+ receiver = null,
35
+ startParam = null,
36
+ } = options;
37
+ this.state = new State({
38
+ authDate,
39
+ canSendAfter,
40
+ chat,
41
+ user,
42
+ queryId,
43
+ receiver,
44
+ startParam,
45
+ hash,
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Init data generation date.
51
+ */
52
+ get authDate(): Date {
53
+ return this.state.get('authDate');
54
+ }
55
+
56
+ /**
57
+ * Date after which a message can be sent via the answerWebAppQuery
58
+ * method.
59
+ * @see https://core.telegram.org/bots/api#answerwebappquery
60
+ */
61
+ get canSendAfter(): Date | null {
62
+ return this.state.get('canSendAfter');
63
+ }
64
+
65
+ /**
66
+ * An object containing data about the chat where the bot was
67
+ * launched via the attachment menu. Returned for supergroups, channels and
68
+ * group chats – only for Web Apps launched via the attachment menu.
69
+ */
70
+ get chat(): Chat | null {
71
+ return this.state.get('chat');
72
+ }
73
+
74
+ /**
75
+ * A hash of all passed parameters, which the bot server can use to
76
+ * check their validity.
77
+ * @see https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
78
+ */
79
+ get hash(): string {
80
+ return this.state.get('hash');
81
+ }
82
+
83
+ /**
84
+ * A unique identifier for the Web App session, required for sending
85
+ * messages via the `answerWebAppQuery` method.
86
+ * @see https://core.telegram.org/bots/api#answerwebappquery
87
+ */
88
+ get queryId(): string | null {
89
+ return this.state.get('queryId');
90
+ }
91
+
92
+ /**
93
+ * An object containing data about the chat partner of the current
94
+ * user in the chat where the bot was launched via the attachment menu.
95
+ * Returned only for private chats and only for Web Apps launched
96
+ * via the attachment menu.
97
+ */
98
+ get receiver(): User | null {
99
+ return this.state.get('receiver');
100
+ }
101
+
102
+ /**
103
+ * The value of the `startattach` parameter, passed via link. Only
104
+ * returned for Web Apps when launched from the attachment menu via link.
105
+ */
106
+ get startParam(): string | null {
107
+ return this.state.get('startParam');
108
+ }
109
+
110
+ /**
111
+ * An object containing data about the current user.
112
+ */
113
+ get user(): User | null {
114
+ return this.state.get('user');
115
+ }
116
+ }
@@ -0,0 +1 @@
1
+ export * from './InitData.js';
@@ -0,0 +1,243 @@
1
+ import { EventEmitter } from '@tma.js/event-emitter';
2
+ import { on, off, postEvent as defaultPostEvent, type PostEvent } from '@tma.js/bridge';
3
+
4
+ import type { RGB } from '@tma.js/colors';
5
+
6
+ import { State } from '../../state/index.js';
7
+
8
+ import type { MainButtonEventListener, MainButtonEvents, MainButtonState } from './types.js';
9
+
10
+ /**
11
+ * Controls the main button, which is displayed at the bottom
12
+ * of the Web App in the Telegram interface.
13
+ */
14
+ export class MainButton {
15
+ private readonly ee = new EventEmitter<MainButtonEvents>();
16
+
17
+ private readonly state: State<MainButtonState>;
18
+
19
+ constructor(
20
+ backgroundColor: RGB,
21
+ isEnabled: boolean,
22
+ isVisible: boolean,
23
+ isProgressVisible: boolean,
24
+ text: string,
25
+ textColor: RGB,
26
+ private readonly postEvent: PostEvent = defaultPostEvent,
27
+ ) {
28
+ this.state = new State({
29
+ backgroundColor,
30
+ isEnabled,
31
+ isVisible,
32
+ isProgressVisible,
33
+ text,
34
+ textColor,
35
+ }, this.ee);
36
+ }
37
+
38
+ private set isEnabled(value: boolean) {
39
+ this.state.set('isEnabled', value);
40
+ this.commit();
41
+ }
42
+
43
+ /**
44
+ * Returns true in case, MainButton is currently enabled.
45
+ */
46
+ get isEnabled(): boolean {
47
+ return this.state.get('isEnabled');
48
+ }
49
+
50
+ private set isProgressVisible(value: boolean) {
51
+ this.state.set('isProgressVisible', value);
52
+ this.commit();
53
+ }
54
+
55
+ /**
56
+ * Returns true in case, MainButton loading progress is currently visible.
57
+ */
58
+ get isProgressVisible(): boolean {
59
+ return this.state.get('isProgressVisible');
60
+ }
61
+
62
+ private set isVisible(value: boolean) {
63
+ this.state.set('isVisible', value);
64
+ this.commit();
65
+ }
66
+
67
+ /**
68
+ * Returns true in case, MainButton is currently visible.
69
+ */
70
+ get isVisible(): boolean {
71
+ return this.state.get('isVisible');
72
+ }
73
+
74
+ /**
75
+ * Sends current local button state to Telegram application.
76
+ */
77
+ private commit(): void {
78
+ // We should not commit changes until payload is correct. We could
79
+ // have some invalid values in case, button instance was created
80
+ // with empty values. Otherwise, an unexpected behaviour could be received.
81
+ if (this.text === '') {
82
+ return;
83
+ }
84
+
85
+ this.postEvent('web_app_setup_main_button', {
86
+ is_visible: this.isVisible,
87
+ is_active: this.isEnabled,
88
+ is_progress_visible: this.isProgressVisible,
89
+ text: this.text,
90
+ color: this.backgroundColor,
91
+ text_color: this.textColor,
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Returns current main button background color.
97
+ */
98
+ get backgroundColor(): RGB {
99
+ return this.state.get('backgroundColor');
100
+ }
101
+
102
+ /**
103
+ * Returns current main button text.
104
+ */
105
+ get text(): string {
106
+ return this.state.get('text');
107
+ }
108
+
109
+ /**
110
+ * Returns current main button text color.
111
+ */
112
+ get textColor(): RGB {
113
+ return this.state.get('textColor');
114
+ }
115
+
116
+ /**
117
+ * Disables button. Returns current button instance for chaining.
118
+ */
119
+ disable(): this {
120
+ // FIXME: This method does not work on Android. Event "main_button_pressed"
121
+ // keeps getting received even in case, button is disabled.
122
+ // Issue: https://github.com/Telegram-Web-Apps/documentation/issues/1
123
+ this.isEnabled = false;
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * Enables button. Returns current button instance for chaining.
129
+ */
130
+ enable(): this {
131
+ this.isEnabled = true;
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Hides button. Returns current button instance for chaining.
137
+ */
138
+ hide(): this {
139
+ this.isVisible = false;
140
+ return this;
141
+ }
142
+
143
+ /**
144
+ * Hides button progress. Returns current button instance for chaining.
145
+ */
146
+ hideProgress(): this {
147
+ this.isProgressVisible = false;
148
+ return this;
149
+ }
150
+
151
+ /**
152
+ * Adds new event listener.
153
+ * FIXME: Event 'main_button_pressed' is still being received on Android
154
+ * even if the main button is disabled.
155
+ * Issue: https://github.com/Telegram-Mini-Apps/tma.js/issues/3
156
+ * @param event - event name.
157
+ * @param listener - event listener.
158
+ */
159
+ on: typeof this.ee.on = (event, listener) => {
160
+ if (event === 'click') {
161
+ return on('main_button_pressed', listener as MainButtonEventListener<'click'>);
162
+ }
163
+ this.ee.on(event, listener);
164
+ };
165
+
166
+ /**
167
+ * Removes event listener.
168
+ * @param event - event name.
169
+ * @param listener - event listener.
170
+ */
171
+ off: typeof this.ee.off = (event, listener) => {
172
+ if (event === 'click') {
173
+ return off('main_button_pressed', listener as MainButtonEventListener<'click'>);
174
+ }
175
+ this.ee.off(event, listener);
176
+ };
177
+
178
+ /**
179
+ * Shows the button. Note that opening the Web App from the attachment
180
+ * menu hides the main button until the user interacts with the Web App
181
+ * interface.
182
+ *
183
+ * Returns current button instance for chaining.
184
+ */
185
+ show(): this {
186
+ this.isVisible = true;
187
+ return this;
188
+ }
189
+
190
+ /**
191
+ * A method to show a loading indicator on the button.
192
+ * It is recommended to display loading progress if the action tied to the
193
+ * button may take a long time.
194
+ *
195
+ * Returns current button instance for chaining.
196
+ */
197
+ showProgress(): this {
198
+ this.isProgressVisible = true;
199
+ return this;
200
+ }
201
+
202
+ /**
203
+ * Sets new main button text. Returns current button instance for chaining.
204
+ * Minimal length for text is 1 symbol, and maximum is 64 symbols.
205
+ *
206
+ * Returns current button instance for chaining.
207
+ * @param value - new text.
208
+ */
209
+ setText(value: string): this {
210
+ this.state.set('text', value);
211
+ this.commit();
212
+
213
+ return this;
214
+ }
215
+
216
+ /**
217
+ * Sets new main button text color. Returns current button instance for
218
+ * chaining.
219
+ *
220
+ * Returns current button instance for chaining.
221
+ * @param value - new text color.
222
+ */
223
+ setTextColor(value: RGB): this {
224
+ this.state.set('textColor', value);
225
+ this.commit();
226
+
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * Updates current button color. Returns current button instance for
232
+ * chaining.
233
+ *
234
+ * Returns current button instance for chaining.
235
+ * @param value - color to set.
236
+ */
237
+ setBackgroundColor(value: RGB): this {
238
+ this.state.set('backgroundColor', value);
239
+ this.commit();
240
+
241
+ return this;
242
+ }
243
+ }
@@ -0,0 +1,2 @@
1
+ export * from './MainButton.js';
2
+ export type { MainButtonEvents, MainButtonEventListener, MainButtonEventName } from './types.js';
@@ -0,0 +1,20 @@
1
+ import type { RGB } from '@tma.js/colors';
2
+
3
+ import type { StateEvents } from '../../state/index.js';
4
+
5
+ export interface MainButtonState {
6
+ backgroundColor: RGB;
7
+ isEnabled: boolean;
8
+ isVisible: boolean;
9
+ isProgressVisible: boolean;
10
+ text: string;
11
+ textColor: RGB;
12
+ }
13
+
14
+ export interface MainButtonEvents extends StateEvents<MainButtonState> {
15
+ click: () => void;
16
+ }
17
+
18
+ export type MainButtonEventName = keyof MainButtonEvents;
19
+
20
+ export type MainButtonEventListener<E extends MainButtonEventName> = MainButtonEvents[E];
@@ -0,0 +1,81 @@
1
+ import { EventEmitter } from '@tma.js/event-emitter';
2
+ import { postEvent as defaultPostEvent, request, type PostEvent } from '@tma.js/bridge';
3
+
4
+ import type { Version } from '@tma.js/utils';
5
+
6
+ import { preparePopupParams } from './utils.js';
7
+ import { createSupportsFunc, type SupportsFunc } from '../../supports.js';
8
+ import { State } from '../../state/index.js';
9
+
10
+ import type { PopupParams, PopupEvents, PopupState } from './types.js';
11
+
12
+ /**
13
+ * Controls currently displayed application popup. It allows developers to
14
+ * open new custom popups and detect popup-connected events.
15
+ */
16
+ export class Popup {
17
+ private readonly ee = new EventEmitter<PopupEvents>();
18
+
19
+ private readonly state: State<PopupState>;
20
+
21
+ constructor(version: Version, private readonly postEvent: PostEvent = defaultPostEvent) {
22
+ this.state = new State({ isOpened: false }, this.ee);
23
+ this.supports = createSupportsFunc(version, { open: 'web_app_open_popup' });
24
+ }
25
+
26
+ /**
27
+ * Shows whether popup is currently opened.
28
+ */
29
+ get isOpened(): boolean {
30
+ return this.state.get('isOpened');
31
+ }
32
+
33
+ /**
34
+ * Adds new event listener.
35
+ */
36
+ on: typeof this.ee.on = this.ee.on.bind(this.ee);
37
+
38
+ /**
39
+ * Removes event listener.
40
+ */
41
+ off: typeof this.ee.off = this.ee.off.bind(this.ee);
42
+
43
+ /**
44
+ * A method that shows a native popup described by the `params` argument.
45
+ * Promise will be resolved when popup is closed. Resolved value will have
46
+ * an identifier of pressed button.
47
+ *
48
+ * In case, user clicked outside the popup or clicked top right popup close
49
+ * button, null will be returned.
50
+ *
51
+ * FIXME: In desktop, this function may work incorrectly.
52
+ * Issue: https://github.com/Telegram-Mini-Apps/tma.js/issues/7
53
+ * @param params - popup parameters.
54
+ * @throws {Error} Popup is already opened.
55
+ */
56
+ async open(params: PopupParams): Promise<string | null> {
57
+ if (this.isOpened) {
58
+ throw new Error('Popup is already opened.');
59
+ }
60
+
61
+ this.state.set('isOpened', true);
62
+
63
+ try {
64
+ const { button_id: buttonId = null } = await request(
65
+ 'web_app_open_popup',
66
+ preparePopupParams(params),
67
+ 'popup_closed',
68
+ { postEvent: this.postEvent },
69
+ );
70
+
71
+ return buttonId;
72
+ } finally {
73
+ this.state.set('isOpened', false);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Checks if specified method is supported by current component.
79
+ */
80
+ supports: SupportsFunc<'open'>;
81
+ }
@@ -0,0 +1,8 @@
1
+ export * from './Popup.js';
2
+ export type {
3
+ PopupParams,
4
+ PopupEvents,
5
+ PopupEventListener,
6
+ PopupEventName,
7
+ PopupButton,
8
+ } from './types.js';
@@ -0,0 +1,69 @@
1
+ import type { StateEvents } from '../../state/index.js';
2
+
3
+ export interface PopupState {
4
+ isOpened: boolean;
5
+ }
6
+
7
+ export type PopupEvents = StateEvents<PopupState>;
8
+
9
+ export type PopupEventName = keyof PopupEvents;
10
+
11
+ export type PopupEventListener<E extends PopupEventName> = PopupEvents[E];
12
+
13
+ /**
14
+ * This object describes the native popup.
15
+ * @see https://core.telegram.org/bots/webapps#popupparams
16
+ */
17
+ export interface PopupParams {
18
+ /**
19
+ * The text to be displayed in the popup title, 0-64 characters.
20
+ * @default ""
21
+ */
22
+ title?: string;
23
+
24
+ /**
25
+ * The message to be displayed in the body of the popup, 1-256 characters.
26
+ */
27
+ message: string;
28
+
29
+ /**
30
+ * List of buttons to be displayed in the popup, 1-3 buttons.
31
+ * @default [{type: 'close'}]
32
+ */
33
+ buttons?: PopupButton[]
34
+ }
35
+
36
+ /**
37
+ * This object describes the native popup button.
38
+ * @see https://core.telegram.org/bots/webapps#popupbutton
39
+ */
40
+ export type PopupButton = {
41
+ /**
42
+ * Identifier of the button, 0-64 characters.
43
+ * @default ""
44
+ */
45
+ id?: string;
46
+ } & (
47
+ {
48
+ /**
49
+ * Type of the button:
50
+ * - `default`, a button with the default style;
51
+ * - `destructive`, a button with a style that indicates a destructive
52
+ * action (e.g. "Remove", "Delete", etc.).
53
+ *
54
+ * @default "default"
55
+ */
56
+ type?: 'default' | 'destructive';
57
+ /**
58
+ * The text to be displayed on the button, 0-64 characters.
59
+ */
60
+ text: string;
61
+ } | {
62
+ /**
63
+ * Type of the button:
64
+ * - `ok`, a button with the localized text "OK";
65
+ * - `close`, a button with the localized text "Close";
66
+ * - `cancel`, a button with the localized text "Cancel".
67
+ */
68
+ type: 'ok' | 'close' | 'cancel';
69
+ });
@@ -0,0 +1,59 @@
1
+ import type { PopupButton, PopupParams as BridgePopupParams } from '@tma.js/bridge';
2
+
3
+ import type { PopupParams } from './types.js';
4
+
5
+ /**
6
+ * Prepares popup parameters before sending them to native app.
7
+ * @param params - popup parameters.
8
+ */
9
+ export function preparePopupParams(params: PopupParams): BridgePopupParams {
10
+ const message = params.message.trim();
11
+ const title = (params.title || '').trim();
12
+ const buttons = params.buttons || [];
13
+ let preparedButtons: PopupButton[];
14
+
15
+ // Check title.
16
+ if (title.length > 64) {
17
+ throw new Error(`Title has incorrect size: ${title.length}`);
18
+ }
19
+
20
+ // Check message.
21
+ if (message.length === 0 || message.length > 256) {
22
+ throw new Error(`Message has incorrect size: ${message.length}`);
23
+ }
24
+
25
+ // Check buttons.
26
+ if (buttons.length > 3) {
27
+ throw new Error(`Buttons have incorrect size: ${buttons.length}`);
28
+ }
29
+
30
+ // Append button in case, there are no buttons passed.
31
+ if (buttons.length === 0) {
32
+ preparedButtons = [{ type: 'close', id: '' }];
33
+ } else {
34
+ // Otherwise, check all the buttons.
35
+ preparedButtons = buttons.map((b) => {
36
+ const { id = '' } = b;
37
+
38
+ // Check button ID.
39
+ if (id.length > 64) {
40
+ throw new Error(`Button ID has incorrect size: ${id}`);
41
+ }
42
+
43
+ if (b.type === undefined || b.type === 'default' || b.type === 'destructive') {
44
+ const text = b.text.trim();
45
+
46
+ if (text.length === 0 || text.length > 64) {
47
+ const type = b.type || 'default';
48
+
49
+ throw new Error(`Button text with type "${type}" has incorrect size: ${b.text.length}`);
50
+ }
51
+
52
+ return { ...b, text, id };
53
+ }
54
+
55
+ return { ...b, id };
56
+ });
57
+ }
58
+ return { title, message, buttons: preparedButtons };
59
+ }
@@ -0,0 +1,87 @@
1
+ import { EventEmitter } from '@tma.js/event-emitter';
2
+ import { postEvent as defaultPostEvent, request, type PostEvent } from '@tma.js/bridge';
3
+
4
+ import type { Version } from '@tma.js/utils';
5
+
6
+ import { createSupportsFunc, type SupportsFunc } from '../../supports.js';
7
+ import { State } from '../../state/index.js';
8
+
9
+ import type { QRScannerEvents, QRScannerState } from './types.js';
10
+
11
+ /**
12
+ * Provides QR scanner functionality.
13
+ */
14
+ export class QRScanner {
15
+ private readonly ee = new EventEmitter<QRScannerEvents>();
16
+
17
+ private readonly state: State<QRScannerState>;
18
+
19
+ constructor(version: Version, private readonly postEvent: PostEvent = defaultPostEvent) {
20
+ this.state = new State({ isOpened: false }, this.ee);
21
+ this.supports = createSupportsFunc(version, {
22
+ close: 'web_app_close_scan_qr_popup',
23
+ open: 'web_app_open_scan_qr_popup',
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Closes scanner.
29
+ */
30
+ close(): void {
31
+ this.postEvent('web_app_close_scan_qr_popup');
32
+ this.isOpened = false;
33
+ }
34
+
35
+ private set isOpened(value) {
36
+ this.state.set('isOpened', value);
37
+ }
38
+
39
+ /**
40
+ * Returns true in case, QR scanner is currently opened.
41
+ */
42
+ get isOpened(): boolean {
43
+ return this.state.get('isOpened');
44
+ }
45
+
46
+ /**
47
+ * Opens scanner with specified title shown to user. Method returns promise
48
+ * with scanned QR content in case, it was scanned. It will contain null in
49
+ * case, scanner was closed.
50
+ * @param text - title to display.
51
+ */
52
+ async open(text?: string): Promise<string | null> {
53
+ if (this.isOpened) {
54
+ throw new Error('QR scanner is already opened.');
55
+ }
56
+
57
+ this.isOpened = true;
58
+
59
+ try {
60
+ const result = await request(
61
+ 'web_app_open_scan_qr_popup',
62
+ { text },
63
+ ['qr_text_received', 'scan_qr_popup_closed'],
64
+ { postEvent: this.postEvent },
65
+ );
66
+
67
+ return typeof result === 'object' && typeof result.data === 'string' ? result.data : null;
68
+ } finally {
69
+ this.isOpened = false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Adds new event listener.
75
+ */
76
+ on: typeof this.ee.on = this.ee.on.bind(this.ee);
77
+
78
+ /**
79
+ * Removes event listener.
80
+ */
81
+ off: typeof this.ee.off = this.ee.off.bind(this.ee);
82
+
83
+ /**
84
+ * Checks if specified method is supported by current component.
85
+ */
86
+ supports: SupportsFunc<'open' | 'close'>;
87
+ }
@@ -0,0 +1,2 @@
1
+ export * from './QRScanner.js';
2
+ export type { QRScannerEvents, QRScannerEventName, QRScannerEventListener } from './types.js';