@tma.js/sdk 1.4.1 → 1.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tma.js/sdk",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "TypeScript Source Development Kit for Telegram Mini Apps client application.",
5
5
  "author": "Vladislav Kibenko <wolfram.deus@gmail.com>",
6
6
  "homepage": "https://github.com/Telegram-Mini-Apps/tma.js#readme",
@@ -0,0 +1,23 @@
1
+ import { it, expect } from 'vitest';
2
+
3
+ import { parseMessage } from '../parseMessage';
4
+
5
+ it('should parse value as JSON with properties { eventType: string; eventData?: unknown }', () => {
6
+ expect(parseMessage({ eventType: 1 })).toEqual({ eventType: '1' });
7
+ expect(parseMessage({ eventType: 'test' })).toEqual({ eventType: 'test' });
8
+ expect(parseMessage({ eventType: 'test', eventData: 123 })).toEqual({
9
+ eventType: 'test',
10
+ eventData: 123,
11
+ });
12
+
13
+ expect(parseMessage('{"eventType":1}')).toEqual({ eventType: '1' });
14
+ expect(parseMessage('{"eventType":"test"}')).toEqual({ eventType: 'test' });
15
+ expect(parseMessage('{"eventType":"test","eventData":123}')).toEqual({
16
+ eventType: 'test',
17
+ eventData: 123,
18
+ });
19
+ });
20
+
21
+ it('should throw if eventType property is missing', () => {
22
+ expect(() => parseMessage({})).toThrow();
23
+ });
@@ -4,17 +4,12 @@ import { createWindow, type WindowSpy } from '../../../../test-utils/createWindo
4
4
  import { dispatchWindowMessageEvent } from '../../../../test-utils/dispatchWindowMessageEvent';
5
5
  import { onTelegramEvent } from '../onTelegramEvent';
6
6
 
7
- let windowSpy: WindowSpy;
8
-
9
- beforeEach(() => {
10
- windowSpy = createWindow();
11
- });
12
-
13
7
  afterEach(() => {
14
- windowSpy.mockRestore();
8
+ vi.restoreAllMocks()
15
9
  });
16
10
 
17
11
  it('should call passed callback with event type and data in case, window generated "message" event with data, presented as object with properties "eventType" (string) and "eventData" (unknown). Object is converted to string.', () => {
12
+ createWindow({ env: 'iframe' });
18
13
  const callback = vi.fn();
19
14
  onTelegramEvent(callback);
20
15
 
@@ -25,6 +20,7 @@ it('should call passed callback with event type and data in case, window generat
25
20
  });
26
21
 
27
22
  it('should not define event handlers twice in case, window object contains "TelegramGameProxy_receiveEvent" property.', () => {
23
+ createWindow();
28
24
  (window as any).TelegramGameProxy_receiveEvent = true;
29
25
 
30
26
  onTelegramEvent(vi.fn());
@@ -32,6 +28,7 @@ it('should not define event handlers twice in case, window object contains "Tele
32
28
  });
33
29
 
34
30
  it('should call passed callback with event type and data in case, external environment generated event.', () => {
31
+ createWindow();
35
32
  const callback = vi.fn();
36
33
  onTelegramEvent(callback);
37
34
 
@@ -42,6 +39,7 @@ it('should call passed callback with event type and data in case, external envir
42
39
  });
43
40
 
44
41
  it('should ignore a message event with unexpected data', () => {
42
+ createWindow();
45
43
  const callback = vi.fn();
46
44
  onTelegramEvent(callback);
47
45
 
@@ -1,24 +1,18 @@
1
- import { json, string } from '~/parsing/index.js';
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
- });
1
+ import { parseMessage } from '~/bridge/parseMessage.js';
10
2
 
11
3
  /**
12
4
  * Emits event sent from Telegram native application like it was sent in
13
5
  * default web environment between 2 iframes. It dispatches new MessageEvent
14
6
  * and expects it to be handled via `window.addEventListener('message', ...)`
15
- * as developer would do it to handle messages sent from parent iframe.
7
+ * as developer would do it to handle messages sent from the parent iframe.
16
8
  * @param eventType - event name.
17
9
  * @param eventData - event payload.
18
10
  */
19
11
  function emitEvent(eventType: string, eventData: unknown): void {
20
12
  window.dispatchEvent(new MessageEvent('message', {
21
13
  data: JSON.stringify({ eventType, eventData }),
14
+ // We specify window.parent to imitate the case, it sent us this event.
15
+ source: window.parent,
22
16
  }));
23
17
  }
24
18
 
@@ -73,8 +67,12 @@ export function onTelegramEvent(cb: (eventType: string, eventData: unknown) => v
73
67
 
74
68
  // We expect Telegram to send us new event through "message" event.
75
69
  window.addEventListener('message', (event) => {
70
+ if (event.source !== window.parent) {
71
+ return;
72
+ }
73
+
76
74
  try {
77
- const { eventType, eventData } = eventDataJson.parse(event.data);
75
+ const { eventType, eventData } = parseMessage(event.data);
78
76
  cb(eventType, eventData);
79
77
  } catch {
80
78
  // We ignore incorrect messages as they could be generated by any other code.
@@ -2,5 +2,6 @@ export * from './env/index.js';
2
2
  export * from './errors/index.js';
3
3
  export * from './events/index.js';
4
4
  export * from './methods/index.js';
5
- export * from './invoke-custom-method.js';
5
+ export * from './invokeCustomMethod.js';
6
+ export * from './parseMessage.js';
6
7
  export * from './request.js';
@@ -0,0 +1,28 @@
1
+ import { json, string } from '~/parsing/index.js';
2
+
3
+ /**
4
+ * Message format used in communication between client and Telegram applications.
5
+ */
6
+ export interface MiniAppsMessage {
7
+ /**
8
+ * Event name.
9
+ */
10
+ eventType: string;
11
+ /**
12
+ * Event parameters.
13
+ */
14
+ eventData?: unknown;
15
+ }
16
+
17
+ const parser = json<MiniAppsMessage>({
18
+ eventType: string(),
19
+ eventData: (value) => value,
20
+ });
21
+
22
+ /**
23
+ * Parses value as a message between client and Telegram applications.
24
+ * @param value - value to parse.
25
+ */
26
+ export function parseMessage(value: unknown): MiniAppsMessage {
27
+ return parser.parse(value);
28
+ }
@@ -3,7 +3,7 @@ import { expect, it } from 'vitest';
3
3
  import { mergeClassNames } from '../mergeClassNames';
4
4
 
5
5
  it('should ignore non-object values', () => {
6
- expect(mergeClassNames({}, null, undefined, false, true, {}));
6
+ expect(mergeClassNames({}, null, undefined, false, true, { tma: 'good' })).toStrictEqual({ tma: 'good' });
7
7
  });
8
8
 
9
9
  it('should merge objects keys by values applying classNames function', () => {
@@ -1,13 +1,13 @@
1
- import { classNames } from './classNames.js';
1
+ import { isRecord } from '~/misc/index.js';
2
2
 
3
- type FilterUnion<U> = Exclude<U, number | string | null | undefined | any[] | boolean>;
3
+ import { classNames } from './classNames.js';
4
4
 
5
5
  /**
6
6
  * Returns union keys removing those, which values are not strings.
7
7
  */
8
- type UnionFilteredKeys<U> = U extends U
8
+ type UnionStringKeys<U> = U extends U
9
9
  ? {
10
- [K in keyof U]: U[K] extends string ? K : never
10
+ [K in keyof U]-?: U[K] extends string | undefined ? K : never;
11
11
  }[keyof U]
12
12
  : never;
13
13
 
@@ -16,32 +16,24 @@ type UnionFilteredKeys<U> = U extends U
16
16
  */
17
17
  type UnionRequiredKeys<U> = U extends U
18
18
  ? {
19
- [K in UnionFilteredKeys<U>]-?: ({} extends { [P in K]: U[K] } ? never : K)
20
- }[UnionFilteredKeys<U>]
19
+ [K in UnionStringKeys<U>]: ({} extends Pick<U, K> ? never : K)
20
+ }[UnionStringKeys<U>]
21
21
  : never;
22
22
 
23
23
  /**
24
24
  * Returns union optional keys.
25
25
  */
26
- type UnionOptionalKeys<U> = Exclude<UnionFilteredKeys<U>, UnionRequiredKeys<U>>;
26
+ type UnionOptionalKeys<U> = Exclude<UnionStringKeys<U>, UnionRequiredKeys<U>>;
27
27
 
28
- type MergeClassNames<Tuple extends any[]> = Tuple[number] extends infer Union
29
- ? FilterUnion<Union> extends infer UnionFiltered
28
+ type MergeClassNames<Tuple extends any[]> =
29
+ // Removes all types from union which will be ignored by the mergeClassNames function.
30
+ Exclude<Tuple[number], number | string | null | undefined | any[] | boolean> extends infer Union
30
31
  ? {
31
- [K in UnionRequiredKeys<UnionFiltered>]: string;
32
- } & {
33
- [K in UnionOptionalKeys<UnionFiltered>]?: string;
34
- }
35
- : never
36
- : never;
37
-
38
- /**
39
- * Returns true in case, passed value is Record.
40
- * @param value
41
- */
42
- function isObject(value: unknown): value is Record<string, unknown> {
43
- return typeof value === 'object' && value !== null && !Array.isArray(null);
44
- }
32
+ [K in UnionRequiredKeys<Union>]: string;
33
+ } & {
34
+ [K in UnionOptionalKeys<Union>]?: string;
35
+ }
36
+ : never;
45
37
 
46
38
  /**
47
39
  * Merges 2 sets of parameters. Function expects passing an array of objects with values, which
@@ -51,7 +43,7 @@ function isObject(value: unknown): value is Record<string, unknown> {
51
43
  */
52
44
  export function mergeClassNames<T extends any[]>(...partials: T): MergeClassNames<T> {
53
45
  return partials.reduce<MergeClassNames<T>>((acc, partial) => {
54
- if (!isObject(partial)) {
46
+ if (!isRecord(partial)) {
55
47
  return acc;
56
48
  }
57
49
 
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export {
10
10
  on,
11
11
  off,
12
12
  once,
13
+ parseMessage,
13
14
  postEvent,
14
15
  request,
15
16
  subscribe,