@tma.js/sdk 1.2.1 → 1.4.0

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 (97) hide show
  1. package/dist/dts/index.d.ts +1 -1
  2. package/dist/dts/init/creators/createViewport.d.ts +2 -9
  3. package/dist/dts/init/init.d.ts +2 -0
  4. package/dist/dts/init/types.d.ts +7 -4
  5. package/dist/dts/launch-params/index.d.ts +1 -0
  6. package/dist/dts/launch-params/retrieveFromUrl.d.ts +6 -0
  7. package/dist/dts/launch-params/types.d.ts +12 -8
  8. package/dist/dts/types/platform.d.ts +1 -1
  9. package/dist/dts/viewport/index.d.ts +1 -0
  10. package/dist/dts/viewport/isStableViewportPlatform.d.ts +7 -0
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.iife.js +1 -1
  14. package/dist/index.iife.js.map +1 -1
  15. package/dist/index.mjs +469 -467
  16. package/dist/index.mjs.map +1 -1
  17. package/package.json +2 -2
  18. package/src/__tests__/globals.ts +39 -0
  19. package/src/back-button/__tests__/BackButton.ts +129 -0
  20. package/src/bridge/__tests__/request.ts +236 -0
  21. package/src/bridge/env/__tests__/hasExternalNotify.ts +15 -0
  22. package/src/bridge/env/__tests__/hasWebviewProxy.ts +15 -0
  23. package/src/bridge/env/__tests__/isIframe.ts +30 -0
  24. package/src/bridge/events/__tests__/createEmitter.ts +143 -0
  25. package/src/bridge/events/__tests__/off.ts +34 -0
  26. package/src/bridge/events/__tests__/on.ts +49 -0
  27. package/src/bridge/events/__tests__/onTelegramEvent.ts +51 -0
  28. package/src/bridge/events/__tests__/once.ts +64 -0
  29. package/src/bridge/events/__tests__/singletonEmitter.ts +22 -0
  30. package/src/bridge/events/__tests__/subscribe.ts +49 -0
  31. package/src/bridge/events/__tests__/unsubscribe.ts +34 -0
  32. package/src/bridge/events/parsers/__tests__/clipboardTextReceived.ts +21 -0
  33. package/src/bridge/events/parsers/__tests__/invoiceClosed.ts +12 -0
  34. package/src/bridge/events/parsers/__tests__/popupClosed.ts +10 -0
  35. package/src/bridge/events/parsers/__tests__/qrTextReceived.ts +9 -0
  36. package/src/bridge/events/parsers/__tests__/theme-changed.ts +42 -0
  37. package/src/bridge/events/parsers/__tests__/viewportChanged.ts +49 -0
  38. package/src/bridge/methods/__tests__/createPostEvent.ts +37 -0
  39. package/src/bridge/methods/__tests__/postEvent.ts +137 -0
  40. package/src/classnames/__tests__/classNames.ts +20 -0
  41. package/src/classnames/__tests__/mergeClassNames.ts +21 -0
  42. package/src/closing-behavior/__tests__/ClosingBehavior.ts +86 -0
  43. package/src/colors/__tests__/isColorDark.ts +12 -0
  44. package/src/colors/__tests__/isRGB.ts +13 -0
  45. package/src/colors/__tests__/isRGBShort.ts +13 -0
  46. package/src/colors/__tests__/toRGB.ts +23 -0
  47. package/src/event-emitter/__tests__/EventEmitter.ts +145 -0
  48. package/src/haptic-feedback/__tests__/HapticFeedback.ts +68 -0
  49. package/src/index.ts +1 -0
  50. package/src/init/creators/__tests__/createViewport.ts +96 -0
  51. package/src/init/creators/createViewport.ts +60 -81
  52. package/src/init/init.ts +13 -15
  53. package/src/init/types.ts +8 -4
  54. package/src/init-data/__tests__/InitData.ts +98 -0
  55. package/src/init-data/__tests__/chatParser.ts +102 -0
  56. package/src/init-data/__tests__/initDataParser.ts +136 -0
  57. package/src/init-data/__tests__/parseInitData.ts +136 -0
  58. package/src/init-data/__tests__/userParser.ts +96 -0
  59. package/src/launch-params/__tests__/retrieveFromUrl.ts +19 -0
  60. package/src/launch-params/index.ts +1 -0
  61. package/src/launch-params/launchParamsParser.ts +4 -0
  62. package/src/launch-params/retrieveFromLocation.ts +2 -2
  63. package/src/launch-params/retrieveFromPerformance.ts +2 -7
  64. package/src/launch-params/retrieveFromUrl.ts +19 -0
  65. package/src/launch-params/types.ts +13 -8
  66. package/src/logger/__tests__/Logger.ts +107 -0
  67. package/src/main-button/__tests__/MainButton.ts +346 -0
  68. package/src/mini-app/__tests__/MiniApp.ts +140 -0
  69. package/src/misc/__tests__/isRecord.ts +21 -0
  70. package/src/navigation/HashNavigator/__tests__/HashNavigator.ts +144 -0
  71. package/src/navigation/HashNavigator/__tests__/drop.ts +42 -0
  72. package/src/navigation/HashNavigator/__tests__/go.ts +9 -0
  73. package/src/parsing/__tests__/ArrayValueParser.ts +18 -0
  74. package/src/parsing/__tests__/toRecord.ts +10 -0
  75. package/src/parsing/parsers/__tests__/array.ts +39 -0
  76. package/src/parsing/parsers/__tests__/boolean.ts +31 -0
  77. package/src/parsing/parsers/__tests__/date.ts +25 -0
  78. package/src/parsing/parsers/__tests__/json.ts +80 -0
  79. package/src/parsing/parsers/__tests__/number.ts +23 -0
  80. package/src/parsing/parsers/__tests__/rgb.ts +22 -0
  81. package/src/parsing/parsers/__tests__/searchParams.ts +105 -0
  82. package/src/parsing/parsers/__tests__/string.ts +25 -0
  83. package/src/popup/__tests__/Popup.ts +130 -0
  84. package/src/popup/__tests__/preparePopupParams.ts +85 -0
  85. package/src/supports/__tests__/supports.ts +123 -0
  86. package/src/theme-params/__tests__/keys.ts +19 -0
  87. package/src/theme-params/__tests__/parseThemeParams.ts +29 -0
  88. package/src/theme-params/__tests__/serializeThemeParams.ts +29 -0
  89. package/src/theme-params/__tests__/themeParamsParser.ts +29 -0
  90. package/src/timeout/__tests__/isTimeoutError.ts +9 -0
  91. package/src/timeout/__tests__/withTimeout.ts +28 -0
  92. package/src/types/platform.ts +2 -2
  93. package/src/version/__tests__/compareVersions.ts +19 -0
  94. package/src/viewport/__tests__/isStableViewportPlatform.ts +15 -0
  95. package/src/viewport/__tests__/utils.ts +12 -0
  96. package/src/viewport/index.ts +1 -0
  97. package/src/viewport/isStableViewportPlatform.ts +10 -0
package/src/init/init.ts CHANGED
@@ -7,9 +7,10 @@ import {
7
7
  createClosingBehavior,
8
8
  createMainButton,
9
9
  createMiniApp,
10
- createRequestIdGenerator, createSettingsButton,
11
- createThemeParams, createViewportAsync,
12
- createViewportSync,
10
+ createRequestIdGenerator,
11
+ createSettingsButton,
12
+ createThemeParams,
13
+ createViewport,
13
14
  } from '~/init/creators/index.js';
14
15
  import { processCSSVars } from '~/init/css/index.js';
15
16
  import { InitData } from '~/init-data/index.js';
@@ -21,13 +22,16 @@ import { Utils } from '~/utils/index.js';
21
22
 
22
23
  import type { InitOptions, InitResult } from './types.js';
23
24
 
24
- type ComputedInitResult<O> = O extends { async: true } ? Promise<InitResult> : InitResult;
25
+ type ComputedInitResult<O> = O extends { async: true } | { complete: true }
26
+ ? Promise<InitResult>
27
+ : InitResult;
25
28
 
26
29
  export function init(): InitResult;
27
30
  export function init<O extends InitOptions>(options: O): ComputedInitResult<O>;
28
31
  export function init(options: InitOptions = {}): InitResult | Promise<InitResult> {
29
32
  const {
30
33
  async = false,
34
+ complete = async,
31
35
  cssVars = false,
32
36
  acceptCustomStyles = false,
33
37
  } = options;
@@ -99,12 +103,9 @@ export function init(options: InitOptions = {}): InitResult | Promise<InitResult
99
103
  : {}),
100
104
  };
101
105
 
102
- const viewport = async
103
- ? createViewportAsync(isPageReload, platform, postEvent)
104
- : createViewportSync(isPageReload, platform, postEvent);
105
-
106
- if (viewport instanceof Promise) {
107
- return viewport.then((vp) => {
106
+ const viewport = createViewport(isPageReload, platform, postEvent, complete);
107
+ if (viewport instanceof Promise || complete) {
108
+ return Promise.resolve(viewport).then((vp) => {
108
109
  processCSSVars(
109
110
  cssVars,
110
111
  result.miniApp,
@@ -112,10 +113,7 @@ export function init(options: InitOptions = {}): InitResult | Promise<InitResult
112
113
  vp,
113
114
  );
114
115
 
115
- return {
116
- ...result,
117
- viewport: vp,
118
- };
116
+ return { ...result, viewport: vp };
119
117
  });
120
118
  }
121
119
 
@@ -128,7 +126,7 @@ export function init(options: InitOptions = {}): InitResult | Promise<InitResult
128
126
 
129
127
  return { ...result, viewport };
130
128
  } catch (e) {
131
- if (async) {
129
+ if (complete) {
132
130
  return Promise.reject(e);
133
131
  }
134
132
  throw e;
package/src/init/types.ts CHANGED
@@ -62,10 +62,7 @@ export type InitCSSVarsOption = boolean | InitCSSVarsSpecificOption;
62
62
 
63
63
  export interface InitOptions {
64
64
  /**
65
- * True if synchronization must be performed asynchronously. This allows init function to
66
- * perform async operations. One of them is the actual viewport state retrieving from the
67
- * Telegram application. Otherwise, viewport state will be retrieved later.
68
- * @default false
65
+ * @deprecated This option name was considered inappropriate. Use `complete` instead.
69
66
  */
70
67
  async?: boolean;
71
68
 
@@ -87,4 +84,11 @@ export interface InitOptions {
87
84
  * @default false
88
85
  */
89
86
  cssVars?: InitCSSVarsOption;
87
+
88
+ /**
89
+ * True if initialization must be performed completely. This includes retrieving some components
90
+ * state from the Telegram application, and as a result, this makes initialization asynchronous.
91
+ * @default false
92
+ */
93
+ complete?: boolean;
90
94
  }
@@ -0,0 +1,98 @@
1
+ import { expect, it } from 'vitest';
2
+
3
+ import { InitData } from '../InitData';
4
+
5
+ it('should return fields specified in constructor', () => {
6
+ const authDate = new Date(123);
7
+ const data1 = new InitData({ authDate, hash: 'hash' });
8
+ expect(data1.authDate).toBe(authDate);
9
+ expect(data1.canSendAfter).toBeUndefined();
10
+ expect(data1.chat).toBeUndefined();
11
+ expect(data1.chatType).toBeUndefined();
12
+ expect(data1.chatInstance).toBeUndefined();
13
+ expect(data1.hash).toBe('hash');
14
+ expect(data1.queryId).toBeUndefined();
15
+ expect(data1.receiver).toBeUndefined();
16
+ expect(data1.startParam).toBeUndefined();
17
+ expect(data1.user).toBeUndefined();
18
+
19
+ const canSendAfter = 1;
20
+ const data2 = new InitData({
21
+ authDate,
22
+ canSendAfter,
23
+ chat: {
24
+ id: 999,
25
+ photoUrl: 'photo',
26
+ type: 'group',
27
+ title: 'Title',
28
+ },
29
+ chatType: 'sender',
30
+ chatInstance: 'abc',
31
+ hash: 'joke',
32
+ queryId: 'query id',
33
+ receiver: {
34
+ id: 1000,
35
+ photoUrl: 'receiver photo',
36
+ firstName: 'a',
37
+ lastName: 'b',
38
+ username: 'c',
39
+ isBot: false,
40
+ isPremium: false,
41
+ languageCode: 'en',
42
+ },
43
+ startParam: 'param',
44
+ user: {
45
+ id: 2000,
46
+ photoUrl: 'user photo',
47
+ firstName: 'a',
48
+ lastName: 'b',
49
+ username: 'c',
50
+ languageCode: 'en',
51
+ },
52
+ });
53
+ expect(data2.authDate).toBe(authDate);
54
+ expect(data2.canSendAfter).toBe(canSendAfter);
55
+ expect(data2.chat).toStrictEqual({
56
+ id: 999,
57
+ photoUrl: 'photo',
58
+ type: 'group',
59
+ title: 'Title',
60
+ });
61
+ expect(data2.chatType).toBe('sender');
62
+ expect(data2.chatInstance).toBe('abc');
63
+ expect(data2.hash).toBe('joke');
64
+ expect(data2.queryId).toBe('query id');
65
+ expect(data2.receiver).toStrictEqual({
66
+ id: 1000,
67
+ photoUrl: 'receiver photo',
68
+ firstName: 'a',
69
+ lastName: 'b',
70
+ username: 'c',
71
+ isBot: false,
72
+ isPremium: false,
73
+ languageCode: 'en',
74
+ });
75
+ expect(data2.startParam).toBe('param');
76
+ expect(data2.user).toStrictEqual({
77
+ id: 2000,
78
+ photoUrl: 'user photo',
79
+ firstName: 'a',
80
+ lastName: 'b',
81
+ username: 'c',
82
+ languageCode: 'en',
83
+ });
84
+ });
85
+
86
+ it('should have canSendAfterData equal to authDate + canSendAfter seconds', () => {
87
+ const authDate = new Date();
88
+ const initData = new InitData({
89
+ authDate,
90
+ hash: 'abc',
91
+ canSendAfter: 32000,
92
+ });
93
+
94
+ expect(initData.canSendAfter).toBe(32000);
95
+ expect(initData.canSendAfterDate).toStrictEqual(
96
+ new Date(authDate.getTime() + 32000000),
97
+ );
98
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { chatParser } from '../chatParser';
4
+
5
+ describe('id', () => {
6
+ it('should throw an error in case, this property is missing', () => {
7
+ expect(
8
+ () => chatParser().parse({
9
+ type: 'group chat',
10
+ title: 'My chat',
11
+ }),
12
+ ).toThrow();
13
+ });
14
+
15
+ it('should parse source property as number and pass it to the "id" property', () => {
16
+ expect(
17
+ chatParser().parse({
18
+ id: 882,
19
+ type: 'group chat',
20
+ title: 'My chat',
21
+ }),
22
+ ).toMatchObject({
23
+ id: 882,
24
+ });
25
+ });
26
+ });
27
+
28
+ describe('type', () => {
29
+ it('should throw an error in case, this property is missing', () => {
30
+ expect(
31
+ () => chatParser().parse({
32
+ id: 223,
33
+ title: 'My chat',
34
+ }),
35
+ ).toThrow();
36
+ });
37
+
38
+ it('should parse source property as number and pass it to the "type" property', () => {
39
+ expect(
40
+ chatParser().parse({
41
+ id: 882,
42
+ type: 'group chat',
43
+ title: 'My chat',
44
+ }),
45
+ ).toMatchObject({
46
+ type: 'group chat',
47
+ });
48
+ });
49
+ });
50
+
51
+ describe('title', () => {
52
+ it('should throw an error in case, this property is missing', () => {
53
+ expect(
54
+ () => chatParser().parse({
55
+ id: 223,
56
+ type: 'group chat',
57
+ }),
58
+ ).toThrow();
59
+ });
60
+
61
+ it('should parse source property as number and pass it to the "title" property', () => {
62
+ expect(
63
+ chatParser().parse({
64
+ id: 882,
65
+ type: 'group chat',
66
+ title: 'My chat',
67
+ }),
68
+ ).toMatchObject({
69
+ title: 'My chat',
70
+ });
71
+ });
72
+ });
73
+
74
+ describe('photo_url', () => {
75
+ it('should parse source property as number and pass it to the "photoUrl" property', () => {
76
+ expect(
77
+ chatParser().parse({
78
+ id: 882,
79
+ type: 'group chat',
80
+ title: 'My chat',
81
+ photo_url: 'https://image.com',
82
+ }),
83
+ ).toMatchObject({
84
+ photoUrl: 'https://image.com',
85
+ });
86
+ });
87
+ });
88
+
89
+ describe('username', () => {
90
+ it('should parse source property as number and pass it to the "username" property', () => {
91
+ expect(
92
+ chatParser().parse({
93
+ id: 882,
94
+ type: 'group chat',
95
+ title: 'My chat',
96
+ username: 'Johny Bravo',
97
+ }),
98
+ ).toMatchObject({
99
+ username: 'Johny Bravo',
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,136 @@
1
+ import { toSearchParams } from 'test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { initDataParser } from '../initDataParser';
5
+
6
+ describe('auth_date', () => {
7
+ it('should throw an error in case, this property is missing', () => {
8
+ expect(() => initDataParser().parse(toSearchParams({ hash: 'abcd' }))).toThrow();
9
+ });
10
+
11
+ it('should parse source property as Date and pass it to the "authDate" property', () => {
12
+ expect(initDataParser().parse(toSearchParams({ auth_date: 1, hash: 'abcd' }))).toMatchObject({
13
+ authDate: new Date(1000),
14
+ });
15
+ });
16
+ });
17
+
18
+ describe('can_send_after', () => {
19
+ it('should parse source property as Date and pass it to the "canSendAfter" property', () => {
20
+ expect(
21
+ initDataParser().parse(toSearchParams({
22
+ auth_date: 1,
23
+ hash: 'abcd',
24
+ can_send_after: 8882,
25
+ })),
26
+ ).toMatchObject({
27
+ canSendAfter: 8882,
28
+ });
29
+ });
30
+ });
31
+
32
+ describe('chat', () => {
33
+ it('should parse source property as Chat and pass it to the "chat" property', () => {
34
+ expect(
35
+ initDataParser().parse(toSearchParams({
36
+ auth_date: 1,
37
+ hash: 'abcd',
38
+ chat: {
39
+ id: 5,
40
+ type: 'group chat',
41
+ title: 'My Chat',
42
+ photo_url: 'https://johny.com',
43
+ username: 'Johny Chat',
44
+ },
45
+ })),
46
+ ).toMatchObject({
47
+ chat: {
48
+ id: 5,
49
+ type: 'group chat',
50
+ title: 'My Chat',
51
+ photoUrl: 'https://johny.com',
52
+ username: 'Johny Chat',
53
+ },
54
+ });
55
+ });
56
+ });
57
+
58
+ describe('hash', () => {
59
+ it('should throw an error in case, this property is missing', () => {
60
+ expect(
61
+ () => initDataParser().parse(toSearchParams({
62
+ auth_date: 1,
63
+ })),
64
+ ).toThrow();
65
+ });
66
+
67
+ it('should parse source property as string and pass it to the "hash" property', () => {
68
+ expect(
69
+ initDataParser().parse(toSearchParams({
70
+ auth_date: 1,
71
+ hash: 'abcd',
72
+ })),
73
+ ).toMatchObject({
74
+ hash: 'abcd',
75
+ });
76
+ });
77
+ });
78
+
79
+ [
80
+ ['chat_instance', 'chatInstance'],
81
+ ['chat_type', 'chatType'],
82
+ ['query_id', 'queryId'],
83
+ ['start_param', 'startParam'],
84
+ ].forEach(([from, to]) => {
85
+ describe(from, () => {
86
+ it(`should parse source property as string and pass it to the "${to}" property`, () => {
87
+ expect(
88
+ initDataParser().parse(toSearchParams({
89
+ auth_date: 1,
90
+ hash: 'abcd',
91
+ [from]: 'my custom property',
92
+ })),
93
+ ).toMatchObject({
94
+ [to]: 'my custom property',
95
+ });
96
+ });
97
+ });
98
+ });
99
+
100
+ ['user', 'receiver'].forEach((property) => {
101
+ describe(property, () => {
102
+ it('should parse source property as User and pass it to the property with the same name', () => {
103
+ expect(
104
+ initDataParser().parse(toSearchParams({
105
+ auth_date: 1,
106
+ hash: 'abcd',
107
+ [property]: {
108
+ added_to_attachment_menu: true,
109
+ allows_write_to_pm: false,
110
+ first_name: 'Johny',
111
+ id: 333,
112
+ is_bot: false,
113
+ is_premium: true,
114
+ language_code: 'en',
115
+ last_name: 'Bravo',
116
+ photo_url: 'https://johny.com',
117
+ username: 'johnybravo',
118
+ },
119
+ })),
120
+ ).toMatchObject({
121
+ [property]: {
122
+ addedToAttachmentMenu: true,
123
+ allowsWriteToPm: false,
124
+ firstName: 'Johny',
125
+ id: 333,
126
+ isBot: false,
127
+ isPremium: true,
128
+ languageCode: 'en',
129
+ lastName: 'Bravo',
130
+ photoUrl: 'https://johny.com',
131
+ username: 'johnybravo',
132
+ },
133
+ });
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,136 @@
1
+ import { toSearchParams } from 'test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { parseInitData } from '../parseInitData';
5
+
6
+ describe('auth_date', () => {
7
+ it('should throw an error in case, this property is missing', () => {
8
+ expect(() => parseInitData(toSearchParams({ hash: 'abcd' }))).toThrow();
9
+ });
10
+
11
+ it('should parse source property as Date and pass it to the "authDate" property', () => {
12
+ expect(parseInitData(toSearchParams({ auth_date: 1, hash: 'abcd' }))).toMatchObject({
13
+ authDate: new Date(1000),
14
+ });
15
+ });
16
+ });
17
+
18
+ describe('can_send_after', () => {
19
+ it('should parse source property as Date and pass it to the "canSendAfter" property', () => {
20
+ expect(
21
+ parseInitData(toSearchParams({
22
+ auth_date: 1,
23
+ hash: 'abcd',
24
+ can_send_after: 8882,
25
+ })),
26
+ ).toMatchObject({
27
+ canSendAfter: 8882,
28
+ });
29
+ });
30
+ });
31
+
32
+ describe('chat', () => {
33
+ it('should parse source property as Chat and pass it to the "chat" property', () => {
34
+ expect(
35
+ parseInitData(toSearchParams({
36
+ auth_date: 1,
37
+ hash: 'abcd',
38
+ chat: {
39
+ id: 5,
40
+ type: 'group chat',
41
+ title: 'My Chat',
42
+ photo_url: 'https://johny.com',
43
+ username: 'Johny Chat',
44
+ },
45
+ })),
46
+ ).toMatchObject({
47
+ chat: {
48
+ id: 5,
49
+ type: 'group chat',
50
+ title: 'My Chat',
51
+ photoUrl: 'https://johny.com',
52
+ username: 'Johny Chat',
53
+ },
54
+ });
55
+ });
56
+ });
57
+
58
+ describe('hash', () => {
59
+ it('should throw an error in case, this property is missing', () => {
60
+ expect(
61
+ () => parseInitData(toSearchParams({
62
+ auth_date: 1,
63
+ })),
64
+ ).toThrow();
65
+ });
66
+
67
+ it('should parse source property as string and pass it to the "hash" property', () => {
68
+ expect(
69
+ parseInitData(toSearchParams({
70
+ auth_date: 1,
71
+ hash: 'abcd',
72
+ })),
73
+ ).toMatchObject({
74
+ hash: 'abcd',
75
+ });
76
+ });
77
+ });
78
+
79
+ [
80
+ ['chat_instance', 'chatInstance'],
81
+ ['chat_type', 'chatType'],
82
+ ['query_id', 'queryId'],
83
+ ['start_param', 'startParam'],
84
+ ].forEach(([from, to]) => {
85
+ describe(from, () => {
86
+ it(`should parse source property as string and pass it to the "${to}" property`, () => {
87
+ expect(
88
+ parseInitData(toSearchParams({
89
+ auth_date: 1,
90
+ hash: 'abcd',
91
+ [from]: 'my custom property',
92
+ })),
93
+ ).toMatchObject({
94
+ [to]: 'my custom property',
95
+ });
96
+ });
97
+ });
98
+ });
99
+
100
+ ['user', 'receiver'].forEach((property) => {
101
+ describe(property, () => {
102
+ it('should parse source property as User and pass it to the property with the same name', () => {
103
+ expect(
104
+ parseInitData(toSearchParams({
105
+ auth_date: 1,
106
+ hash: 'abcd',
107
+ [property]: {
108
+ added_to_attachment_menu: true,
109
+ allows_write_to_pm: false,
110
+ first_name: 'Johny',
111
+ id: 333,
112
+ is_bot: false,
113
+ is_premium: true,
114
+ language_code: 'en',
115
+ last_name: 'Bravo',
116
+ photo_url: 'https://johny.com',
117
+ username: 'johnybravo',
118
+ },
119
+ })),
120
+ ).toMatchObject({
121
+ [property]: {
122
+ addedToAttachmentMenu: true,
123
+ allowsWriteToPm: false,
124
+ firstName: 'Johny',
125
+ id: 333,
126
+ isBot: false,
127
+ isPremium: true,
128
+ languageCode: 'en',
129
+ lastName: 'Bravo',
130
+ photoUrl: 'https://johny.com',
131
+ username: 'johnybravo',
132
+ },
133
+ });
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { userParser } from '../userParser';
4
+
5
+ describe('first_name', () => {
6
+ it('should throw an error in case, this property is missing', () => {
7
+ expect(() => userParser().parse({ id: 123 })).toThrow();
8
+ });
9
+
10
+ it('should parse source property as string and pass it to the "firstName" property', () => {
11
+ expect(
12
+ userParser().parse({ id: 123, first_name: 'Pavel' }),
13
+ ).toMatchObject({
14
+ firstName: 'Pavel',
15
+ });
16
+ });
17
+ });
18
+
19
+ describe('id', () => {
20
+ it('should throw an error in case, this property is missing', () => {
21
+ expect(() => userParser().parse({ first_name: 'Pavel' })).toThrow();
22
+ });
23
+
24
+ it('should parse source property as number and pass it to the "id" property', () => {
25
+ expect(
26
+ userParser().parse({
27
+ id: 123,
28
+ first_name: 'Pavel',
29
+ }),
30
+ ).toMatchObject({
31
+ id: 123,
32
+ });
33
+ });
34
+ });
35
+
36
+ // Check optional booleans.
37
+ [
38
+ ['added_to_attachment_menu', 'addedToAttachmentMenu'],
39
+ ['allows_write_to_pm', 'allowsWriteToPm'],
40
+ ['is_bot', 'isBot'],
41
+ ['is_premium', 'isPremium'],
42
+ ].forEach(([from, to]) => {
43
+ describe(from, () => {
44
+ it(`should parse source property as boolean and pass it to the "${to}" property`, () => {
45
+ expect(
46
+ userParser().parse({
47
+ id: 123,
48
+ first_name: 'Pavel',
49
+ [from]: false,
50
+ }),
51
+ ).toMatchObject({
52
+ [to]: false,
53
+ });
54
+
55
+ expect(
56
+ () => userParser().parse({
57
+ id: 123,
58
+ first_name: 'Pavel',
59
+ [from]: 'non-boolean',
60
+ }),
61
+ ).toThrow();
62
+ });
63
+ });
64
+ });
65
+
66
+ // Check optional strings.
67
+ [
68
+ ['language_code', 'languageCode'],
69
+ ['last_name', 'lastName'],
70
+ ['photo_url', 'photoUrl'],
71
+ ['username', 'username'],
72
+ ].forEach(([from, to]) => {
73
+ describe(from, () => {
74
+ it(`should parse source property as string and pass it to the "${to}" property`, () => {
75
+ expect(
76
+ userParser().parse({
77
+ id: 123,
78
+ first_name: 'Pavel',
79
+ [from]: 'my custom property',
80
+ }),
81
+ ).toMatchObject({
82
+ [to]: 'my custom property',
83
+ });
84
+
85
+ expect(
86
+ () => userParser().parse({
87
+ id: 123,
88
+ first_name: 'Pavel',
89
+ [from]: {
90
+ key: 'cant parse it',
91
+ },
92
+ }),
93
+ ).toThrow();
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,19 @@
1
+ import { expect, it } from 'vitest';
2
+ import { retrieveFromUrl } from '../retrieveFromUrl';
3
+
4
+ it('should return launch parameters based on hash only in case, URL does not contain the query part', () => {
5
+ expect(retrieveFromUrl('#tgWebAppPlatform=tdesktop&tgWebAppVersion=7.0&tgWebAppThemeParams=%7B%7D')).toEqual({
6
+ platform: 'tdesktop',
7
+ version: '7.0',
8
+ themeParams: {},
9
+ });
10
+ });
11
+
12
+ it('should return launch parameters based on query params and hash in case, URL contains both parts', () => {
13
+ expect(retrieveFromUrl('?tgWebAppStartParam=900#tgWebAppPlatform=tdesktop&tgWebAppVersion=7.0&tgWebAppThemeParams=%7B%7D')).toEqual({
14
+ platform: 'tdesktop',
15
+ version: '7.0',
16
+ themeParams: {},
17
+ startParam: '900',
18
+ });
19
+ });
@@ -6,6 +6,7 @@ export * from './parseLaunchParams.js';
6
6
  export * from './retrieveCurrent.js';
7
7
  export * from './retrieveFromLocation.js';
8
8
  export * from './retrieveFromPerformance.js';
9
+ export * from './retrieveFromUrl.js';
9
10
  export * from './retrieveLaunchData.js';
10
11
  export * from './serializeLaunchParams.js';
11
12
  export * from './storage.js';
@@ -29,6 +29,10 @@ export function launchParamsParser() {
29
29
  type: boolean().optional(),
30
30
  from: 'tgWebAppShowSettings',
31
31
  },
32
+ startParam: {
33
+ type: string().optional(),
34
+ from: 'tgWebAppStartParam',
35
+ },
32
36
  themeParams: {
33
37
  type: themeParamsParser(),
34
38
  from: 'tgWebAppThemeParams',