@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,166 @@
1
+ import type { RGB } from '@tma.js/colors';
2
+
3
+ import type { ThemeParams, WebApp, Viewport } from '../components/index.js';
4
+ import type { InitCSSVarsOption, InitCSSVarsSpecificOption } from './types.js';
5
+
6
+ /**
7
+ * Sets CSS variable.
8
+ * @param name - variable name.
9
+ * @param value - variable value.
10
+ */
11
+ function setVariable(name: string, value: string): void {
12
+ document.documentElement.style.setProperty(name, value);
13
+ }
14
+
15
+ /**
16
+ * Sets new CSS color variable in case, its value is not null.
17
+ * @param name - variable name.
18
+ * @param color - variable value.
19
+ */
20
+ function setColorVariable(name: string, color: RGB | null): void {
21
+ if (color === null) {
22
+ return;
23
+ }
24
+ setVariable(name, color);
25
+ }
26
+
27
+ /**
28
+ * Sets new CSS variable which value is amount of pixels.
29
+ * @param name - variable name.
30
+ * @param size - variable value.
31
+ */
32
+ function setSizeVariable(name: string, size: number) {
33
+ setVariable(name, `${size}px`);
34
+ }
35
+
36
+ /**
37
+ * Creates CSS variables based on theme parameters.
38
+ * @param themeParams - ThemeParams instance.
39
+ */
40
+ function createThemeVariables(themeParams: ThemeParams): void {
41
+ const {
42
+ backgroundColor,
43
+ buttonTextColor,
44
+ secondaryBackgroundColor,
45
+ hintColor,
46
+ buttonColor,
47
+ linkColor,
48
+ textColor,
49
+ } = themeParams;
50
+
51
+ setColorVariable('--tg-theme-bg-color', backgroundColor);
52
+ setColorVariable('--tg-theme-button-color', buttonColor);
53
+ setColorVariable('--tg-theme-button-text-color', buttonTextColor);
54
+ setColorVariable('--tg-theme-hint-color', hintColor);
55
+ setColorVariable('--tg-theme-link-color', linkColor);
56
+ setColorVariable('--tg-theme-secondary-bg-color', secondaryBackgroundColor);
57
+ setColorVariable('--tg-theme-text-color', textColor);
58
+ }
59
+
60
+ /**
61
+ * Creates CSS variables based on Web App background and header colors with
62
+ * theme parameters.
63
+ * @param webApp - WebApp instance.
64
+ * @param themeParams - theme parameters.
65
+ */
66
+ function createWebAppVariables(webApp: WebApp, themeParams: ThemeParams): void {
67
+ const { backgroundColor, secondaryBackgroundColor } = themeParams;
68
+ const { backgroundColor: webAppBackgroundColor, headerColor } = webApp;
69
+
70
+ setColorVariable('--tg-bg-color', webAppBackgroundColor);
71
+ setColorVariable('--tg-header-color', headerColor === 'bg_color' ? backgroundColor : secondaryBackgroundColor);
72
+ }
73
+
74
+ /**
75
+ * Creates CSS variables connected with theme parameters.
76
+ *
77
+ * Created variables:
78
+ * - `--tg-theme-bg-color`
79
+ * - `--tg-theme-button-color`
80
+ * - `--tg-theme-button-text-color`
81
+ * - `--tg-theme-hint-color`
82
+ * - `--tg-theme-link-color`
83
+ * - `--tg-theme-secondary-bg-color`
84
+ * - `--tg-theme-text-color`
85
+ *
86
+ * Variables are being automatically updated in case, corresponding properties
87
+ * updated in passed ThemeParams instance.
88
+ *
89
+ * @param themeParams - ThemeParams instance.
90
+ */
91
+ export function bindThemeCSSVariables(themeParams: ThemeParams): void {
92
+ const actualize = () => createThemeVariables(themeParams);
93
+
94
+ themeParams.on('changed', actualize);
95
+
96
+ actualize();
97
+ }
98
+
99
+ /**
100
+ * Creates CSS variables connected with WebApp background and header colors based on
101
+ * passed WebApp and ThemeParams instances.
102
+ *
103
+ * Created variables:
104
+ * - `--tg-bg-color`
105
+ * - `--tg-header-color`
106
+ *
107
+ * Variables are being automatically updated in case, corresponding properties are updating.
108
+ *
109
+ * @param webApp - WebApp instance.
110
+ * @param themeParams - ThemeParams instance.
111
+ */
112
+ export function bindWebAppVariables(webApp: WebApp, themeParams: ThemeParams): void {
113
+ const actualize = () => createWebAppVariables(webApp, themeParams);
114
+
115
+ themeParams.on('changed', actualize);
116
+ webApp.on('backgroundColorChanged', actualize);
117
+ webApp.on('headerColorChanged', actualize);
118
+
119
+ actualize();
120
+ }
121
+
122
+ /**
123
+ * Accepts Viewport instance and sets CSS variables connected with viewport
124
+ * sizes.
125
+ *
126
+ * Be careful using this function as long as it can impact application
127
+ * performance. Viewport size is changing rather often, this makes CSS
128
+ * variables update, which leads to possible layout redraw.
129
+ *
130
+ * Variables:
131
+ * - `--tg-viewport-height`
132
+ * - `--tg-viewport-stable-height`
133
+ *
134
+ * Variables are being automatically updated in case, corresponding properties
135
+ * updated in passed Viewport instance.
136
+ *
137
+ * @param viewport - Viewport instance.
138
+ */
139
+ export function bindViewportCSSVariables(viewport: Viewport): void {
140
+ const setHeight = () => {
141
+ setSizeVariable('--tg-viewport-height', viewport.height);
142
+ };
143
+
144
+ const setStableHeight = () => {
145
+ setSizeVariable('--tg-viewport-stable-height', viewport.stableHeight);
146
+ };
147
+
148
+ viewport.on('heightChanged', setHeight);
149
+ viewport.on('stableHeightChanged', setStableHeight);
150
+
151
+ setHeight();
152
+ setStableHeight();
153
+ }
154
+
155
+ /**
156
+ * Converts init cssVars option to more narrow type.
157
+ * @param option - option value.
158
+ */
159
+ export function parseCSSVarsOptions(option: InitCSSVarsOption): InitCSSVarsSpecificOption {
160
+ if (typeof option === 'boolean') {
161
+ return option
162
+ ? { themeParams: true, viewport: true, webApp: true }
163
+ : {};
164
+ }
165
+ return option;
166
+ }
@@ -0,0 +1,2 @@
1
+ export * from './init.js';
2
+ export * from './types.js';
@@ -0,0 +1,160 @@
1
+ import {
2
+ isIframe,
3
+ setDebug,
4
+ setTargetOrigin,
5
+ on,
6
+ } from '@tma.js/bridge';
7
+ import { withTimeout } from '@tma.js/utils';
8
+
9
+ import {
10
+ CloudStorage,
11
+ HapticFeedback,
12
+ InitData,
13
+ Popup,
14
+ QRScanner,
15
+ } from '../components/index.js';
16
+ import { parseLaunchParams, retrieveLaunchParams, type LaunchParams } from '../launch-params.js';
17
+ import {
18
+ bindThemeCSSVariables,
19
+ bindViewportCSSVariables,
20
+ bindWebAppVariables,
21
+ parseCSSVarsOptions,
22
+ } from './css.js';
23
+ import {
24
+ createPostEvent,
25
+ createSyncedThemeParams,
26
+ createBackButton,
27
+ createMainButton,
28
+ createViewport,
29
+ createWebApp, createRequestIdGenerator, createClosingBehavior,
30
+ } from './creators/index.js';
31
+
32
+ import type { InitOptions, InitResult } from './types.js';
33
+
34
+ /**
35
+ * Represents actual init function.
36
+ * @param options - init options.
37
+ */
38
+ async function actualInit(options: InitOptions = {}): Promise<InitResult> {
39
+ const {
40
+ checkCompat = true,
41
+ cssVars = false,
42
+ acceptScrollbarStyle = true,
43
+ acceptCustomStyles = acceptScrollbarStyle,
44
+ targetOrigin,
45
+ debug = false,
46
+ launchParams: launchParamsRaw,
47
+ } = options;
48
+
49
+ // Set global debug mode.
50
+ if (debug) {
51
+ setDebug(debug);
52
+ }
53
+
54
+ // Set global target origin.
55
+ if (typeof targetOrigin === 'string') {
56
+ setTargetOrigin(targetOrigin);
57
+ }
58
+
59
+ // Get Web App launch params.
60
+ let launchParams: LaunchParams;
61
+
62
+ if (launchParamsRaw) {
63
+ launchParams = launchParamsRaw instanceof URLSearchParams || typeof launchParamsRaw === 'string'
64
+ ? parseLaunchParams(launchParamsRaw)
65
+ : launchParamsRaw;
66
+ } else {
67
+ launchParams = retrieveLaunchParams();
68
+ }
69
+
70
+ const {
71
+ initData,
72
+ initDataRaw,
73
+ version,
74
+ platform,
75
+ themeParams: lpThemeParams,
76
+ } = launchParams;
77
+ const {
78
+ backgroundColor = '#ffffff',
79
+ buttonColor = '#000000',
80
+ buttonTextColor = '#ffffff',
81
+ } = lpThemeParams;
82
+
83
+ const createRequestId = createRequestIdGenerator();
84
+ const postEvent = createPostEvent(checkCompat, version);
85
+ const themeParams = createSyncedThemeParams(lpThemeParams);
86
+ const webApp = createWebApp(backgroundColor, version, platform, createRequestId, postEvent);
87
+
88
+ const {
89
+ themeParams: createThemeParamsCSSVars,
90
+ viewport: createViewportCSSVars,
91
+ webApp: createWebAppCSSVars,
92
+ } = parseCSSVarsOptions(cssVars);
93
+
94
+ if (createWebAppCSSVars) {
95
+ bindWebAppVariables(webApp, themeParams);
96
+ }
97
+
98
+ if (createThemeParamsCSSVars) {
99
+ bindThemeCSSVariables(themeParams);
100
+ }
101
+
102
+ const viewport = await createViewport(platform, postEvent);
103
+
104
+ // Apply viewport CSS variables.
105
+ if (createViewportCSSVars) {
106
+ bindViewportCSSVariables(viewport);
107
+ }
108
+
109
+ // In case, we are currently in iframe, it is required to listen to
110
+ // messages, coming from parent source to apply requested changes.
111
+ // The only one case, when current application was placed into iframe is
112
+ // web version of Telegram.
113
+ if (acceptCustomStyles && isIframe()) {
114
+ // Create special style element which is responsible for application
115
+ // style controlled by external app.
116
+ const styleElement = document.createElement('style');
117
+ styleElement.id = 'telegram-custom-styles';
118
+ document.head.appendChild(styleElement);
119
+
120
+ // Listen to custom style changes.
121
+ on('set_custom_style', (html) => {
122
+ styleElement.innerHTML = html;
123
+ });
124
+
125
+ // Notify Telegram, iframe is ready. This will result in sending style
126
+ // tag html from native application.
127
+ postEvent('iframe_ready');
128
+ }
129
+
130
+ const result: InitResult = {
131
+ backButton: createBackButton(version, postEvent),
132
+ closingBehavior: createClosingBehavior(postEvent),
133
+ cloudStorage: new CloudStorage(version, createRequestId, postEvent),
134
+ haptic: new HapticFeedback(version, postEvent),
135
+ mainButton: createMainButton(buttonColor, buttonTextColor, postEvent),
136
+ popup: new Popup(version, postEvent),
137
+ postEvent,
138
+ qrScanner: new QRScanner(version, postEvent),
139
+ themeParams,
140
+ viewport,
141
+ webApp,
142
+ };
143
+
144
+ // Init data could be missing in case, application was launched via InlineKeyboardButton.
145
+ if (initData !== undefined) {
146
+ const { authDate, hash, ...restInitData } = initData;
147
+ result.initData = new InitData(authDate, hash, restInitData);
148
+ result.initDataRaw = initDataRaw;
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Initializes all SDK components.
156
+ * @param options - initialization options.
157
+ */
158
+ export function init(options: InitOptions = {}): Promise<InitResult> {
159
+ return withTimeout(actualInit(options), options.timeout || 1000);
160
+ }
@@ -0,0 +1,133 @@
1
+ import type { PostEvent } from '@tma.js/bridge';
2
+
3
+ import type {
4
+ BackButton,
5
+ ClosingBehaviour,
6
+ CloudStorage,
7
+ HapticFeedback,
8
+ InitData,
9
+ MainButton,
10
+ Popup,
11
+ QRScanner,
12
+ ThemeParams,
13
+ Viewport,
14
+ WebApp,
15
+ } from '../components/index.js';
16
+ import type { LaunchParams } from '../launch-params.js';
17
+
18
+ export type InitResult = {
19
+ backButton: BackButton;
20
+ closingBehavior: ClosingBehaviour;
21
+ cloudStorage: CloudStorage;
22
+ haptic: HapticFeedback;
23
+ initData?: InitData;
24
+ initDataRaw?: string;
25
+ mainButton: MainButton;
26
+ popup: Popup;
27
+ postEvent: PostEvent;
28
+ qrScanner: QRScanner;
29
+ themeParams: ThemeParams;
30
+ viewport: Viewport;
31
+ webApp: WebApp;
32
+ };
33
+
34
+ export type InitCSSVarsSpecificOption = {
35
+ /**
36
+ * Enables theme parameters CSS variables:
37
+ * - `--tg-theme-bg-color`
38
+ * - `--tg-theme-button-color`
39
+ * - `--tg-theme-button-text-color`
40
+ * - `--tg-theme-hint-color`
41
+ * - `--tg-theme-link-color`
42
+ * - `--tg-theme-secondary-bg-color`
43
+ * - `--tg-theme-text-color`
44
+ *
45
+ * @see bindThemeCSSVariables
46
+ */
47
+ themeParams?: true;
48
+
49
+ /**
50
+ * Enables viewport CSS variables:
51
+ * - `--tg-viewport-height`
52
+ * - `--tg-viewport-stable-height`
53
+ *
54
+ * @see bindViewportCSSVariables
55
+ */
56
+ viewport?: true;
57
+
58
+ /**
59
+ * Enables web app CSS variables:
60
+ * - `--tg-bg-color`
61
+ * - `--tg-header-color`
62
+ *
63
+ * @see bindWebAppVariables
64
+ */
65
+ webApp?: true;
66
+ };
67
+
68
+ export type InitCSSVarsOption = boolean | InitCSSVarsSpecificOption;
69
+
70
+ export interface InitOptions {
71
+ /**
72
+ * @deprecated Use acceptCustomStyles
73
+ */
74
+ acceptScrollbarStyle?: boolean;
75
+
76
+ /**
77
+ * Should SDK accept styles sent from the Telegram web application.
78
+ * This option is only used in Web versions of Telegram.
79
+ *
80
+ * @default true
81
+ */
82
+ acceptCustomStyles?: boolean;
83
+
84
+ /**
85
+ * Should SDK throw an error in case, unsupported by current version of
86
+ * Web App method was called. It is highly recommended not to disable this
87
+ * feature as long as it helps developer to find issues related to usage
88
+ * of unsupported methods.
89
+ *
90
+ * @default true
91
+ */
92
+ checkCompat?: boolean;
93
+
94
+ /**
95
+ * Should SDK create global CSS variables related to current Telegram
96
+ * application colors.
97
+ *
98
+ * Possible values:
99
+ * - `false` - no CSS variables will be created.
100
+ * - `true` - all CSS variables will be created.
101
+ * - object - applies specific CSS variables.
102
+ *
103
+ * @default false
104
+ */
105
+ cssVars?: InitCSSVarsOption;
106
+
107
+ /**
108
+ * Enable debug mode.
109
+ *
110
+ * @default false
111
+ */
112
+ debug?: boolean;
113
+
114
+ /**
115
+ * Launch parameters presented as query parameters or already parsed
116
+ * object. In case, this value is not specified, init
117
+ * function will try to retrieve launch parameters from window.location.hash
118
+ * via retrieveLaunchParams function.
119
+ */
120
+ launchParams?: string | URLSearchParams | LaunchParams;
121
+
122
+ /**
123
+ * Sets new targetOrigin, used by bridge's `postEvent` function.
124
+ * @see setTargetOrigin
125
+ */
126
+ targetOrigin?: string;
127
+
128
+ /**
129
+ * Initialization process timeout.
130
+ * @default 1000
131
+ */
132
+ timeout?: number;
133
+ }
@@ -0,0 +1,70 @@
1
+ import { searchParams, string } from '@tma.js/parsing';
2
+ import { initData, type InitData } from '@tma.js/init-data';
3
+
4
+ import { parseThemeParams, type ThemeParamsType } from './theme-params.js';
5
+ import { saveStorageValue, getStorageValue } from './storage.js';
6
+
7
+ import type { Platform } from './types.js';
8
+
9
+ export interface LaunchParams {
10
+ version: string;
11
+ initData?: InitData;
12
+ initDataRaw?: string;
13
+ platform: Platform;
14
+ themeParams: ThemeParamsType;
15
+ }
16
+
17
+ const launchParamsParser = searchParams<LaunchParams>({
18
+ version: { type: string(), from: 'tgWebAppVersion' },
19
+ initData: { type: initData.optional(), from: 'tgWebAppData' },
20
+ initDataRaw: { type: string().optional(), from: 'tgWebAppData' },
21
+ platform: { type: string(), from: 'tgWebAppPlatform' },
22
+ themeParams: { type: parseThemeParams, from: 'tgWebAppThemeParams' },
23
+ });
24
+
25
+ /**
26
+ * Parses query parameters as launch parameters.
27
+ * @param query - query parameters presented as string or URLSearchParams
28
+ * instance.
29
+ */
30
+ export function parseLaunchParams(query: string | URLSearchParams): LaunchParams {
31
+ return launchParamsParser.parse(query);
32
+ }
33
+
34
+ /**
35
+ * Extracts launch params from the current environment.
36
+ */
37
+ export function retrieveLaunchParams(): LaunchParams {
38
+ const errors: string[] = [];
39
+
40
+ // Try to extract Web App data from hash. This block of code covers usual flow, when
41
+ // application was firstly opened by the user and its hash always contains required parameters.
42
+ try {
43
+ const launchParamsRaw = window.location.hash.slice(1);
44
+ const launchParamsParsed = parseLaunchParams(launchParamsRaw);
45
+
46
+ // Previous line of code worked fine. Then, we can save taken launch params raw value.
47
+ saveStorageValue('launch-params', launchParamsRaw);
48
+
49
+ return launchParamsParsed;
50
+ } catch (e) {
51
+ errors.push(e instanceof Error ? e.message : 'unknown error');
52
+ }
53
+
54
+ // Web Apps allows reloading current page. In this case, window.location.reload() will be
55
+ // called which means, that init will be called again. As the result, current window
56
+ // location will lose Web App data. To solve this problem, we are extracting launch
57
+ // params saved previously.
58
+ try {
59
+ const launchParamsRaw = getStorageValue('launch-params');
60
+ if (launchParamsRaw) {
61
+ return parseLaunchParams(launchParamsRaw);
62
+ }
63
+
64
+ errors.push('Launch params are missing in local storage');
65
+ } catch (e) {
66
+ errors.push(e instanceof Error ? e.message : 'unknown error');
67
+ }
68
+
69
+ throw new Error(`Unable to extract launch params. Errors: "${errors.join('", "')}"`);
70
+ }
@@ -0,0 +1,53 @@
1
+ import type { EventEmitter } from '@tma.js/event-emitter';
2
+
3
+ import type { StateEvents, StringKeys } from './types.js';
4
+
5
+ /**
6
+ * Represents state which is observable via passed EventEmitter.
7
+ */
8
+ export class State<S extends object> {
9
+ constructor(private readonly state: S, private readonly ee?: EventEmitter<StateEvents<S>>) {
10
+ }
11
+
12
+ private emit(key: string, value?: unknown) {
13
+ if (this.ee) {
14
+ (this.ee as any).emit(key, value);
15
+ }
16
+ }
17
+
18
+ private internalSet<K extends StringKeys<S>>(key: K, value: S[K]): boolean {
19
+ if (this.state[key] === value) {
20
+ return false;
21
+ }
22
+
23
+ this.state[key] = value;
24
+ this.emit(`${key}Changed`, value);
25
+
26
+ return true;
27
+ }
28
+
29
+ set<K extends StringKeys<S>>(key: K, value: S[K]): void;
30
+ set(state: Partial<S>): void;
31
+ set(keyOrState: any, value?: any): void {
32
+ let didChange = false;
33
+
34
+ if (typeof keyOrState === 'string') {
35
+ didChange = this.internalSet(keyOrState as any, value);
36
+ } else {
37
+ // eslint-disable-next-line
38
+ for (const key in keyOrState) {
39
+ if (this.internalSet(key as any, keyOrState[key])) {
40
+ didChange = true;
41
+ }
42
+ }
43
+ }
44
+
45
+ if (didChange) {
46
+ this.emit('changed');
47
+ }
48
+ }
49
+
50
+ get<K extends StringKeys<S>>(key: K): Readonly<S[K]> {
51
+ return this.state[key];
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ export * from './State.js';
2
+ export * from './types.js';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Computes state property changed event.
3
+ */
4
+ export type PropChangedEvent<K extends string> = `${K}Changed`;
5
+
6
+ /**
7
+ * Returns object string keys.
8
+ */
9
+ export type StringKeys<T extends object> = Extract<keyof T, string>;
10
+
11
+ /**
12
+ * Extracts state property type by its computed change event name.
13
+ */
14
+ export type PropertyType<State extends object, Event extends string> = {
15
+ [Key in StringKeys<State>]: Event extends PropChangedEvent<Key> ? State[Key] : never;
16
+ }[StringKeys<State>];
17
+
18
+ /**
19
+ * Creates map, where key is event name which is used, when state property was changed.
20
+ * Value is according listener.
21
+ */
22
+ export type PropChangedEventsMap<State extends object> = {
23
+ [Event in `${StringKeys<State>}Changed`]: (value: PropertyType<State, Event>) => void;
24
+ };
25
+
26
+ /**
27
+ * Creates map with all events emitted by state.
28
+ */
29
+ export type StateEvents<State extends object> = PropChangedEventsMap<State> & {
30
+ /**
31
+ * Being called whenever any property was updated.
32
+ */
33
+ changed: () => void;
34
+ };
package/src/storage.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type { HeaderColorKey } from '@tma.js/bridge';
2
+ import type { RGB } from '@tma.js/colors';
3
+
4
+ /**
5
+ * Describes storage keys and according values.
6
+ */
7
+ interface StorageParams {
8
+ 'back-button': {
9
+ isVisible: boolean
10
+ };
11
+ 'closing-behavior': {
12
+ isConfirmationNeeded: boolean;
13
+ };
14
+ 'main-button': {
15
+ backgroundColor: RGB;
16
+ isEnabled: boolean;
17
+ isVisible: boolean;
18
+ isProgressVisible: boolean;
19
+ text: string;
20
+ textColor: RGB;
21
+ };
22
+ viewport: {
23
+ height: number;
24
+ isExpanded: boolean;
25
+ stableHeight: number;
26
+ width: number;
27
+ };
28
+ 'web-app': {
29
+ backgroundColor: RGB;
30
+ headerColor: HeaderColorKey | RGB;
31
+ };
32
+ 'launch-params': string;
33
+ }
34
+
35
+ /**
36
+ * Key which could be used to store data in storage.
37
+ */
38
+ type StorageKey = keyof StorageParams;
39
+
40
+ /**
41
+ * Formats key which could be used during the communication with the storage.
42
+ * @param key - session storage key.
43
+ */
44
+ function formatKey(key: StorageKey): string {
45
+ return `telegram-web-apps-${key}`;
46
+ }
47
+
48
+ /**
49
+ * Saves value in sessionStorage.
50
+ * @param key - storage key.
51
+ * @param value - storage value.
52
+ */
53
+ export function saveStorageValue<K extends StorageKey>(key: K, value: StorageParams[K]): void {
54
+ sessionStorage.setItem(formatKey(key), JSON.stringify(value));
55
+ }
56
+
57
+ /**
58
+ * Extracts value from the sessionStorage.
59
+ * @param key - storage key.
60
+ */
61
+ export function getStorageValue<K extends StorageKey>(key: K): StorageParams[K] | null {
62
+ const value = sessionStorage.getItem(formatKey(key));
63
+
64
+ return value ? JSON.parse(value) : null;
65
+ }