diff --git a/.changeset/chatty-wombats-enjoy.md b/.changeset/chatty-wombats-enjoy.md new file mode 100644 index 000000000..b868f6dbb --- /dev/null +++ b/.changeset/chatty-wombats-enjoy.md @@ -0,0 +1,5 @@ +--- +"@tma.js/sdk": patch +--- + +Add source = window.parent in emitEvent function. Fix types in merge class names utility diff --git a/packages/sdk/src/bridge/events/__tests__/onTelegramEvent.ts b/packages/sdk/src/bridge/events/__tests__/onTelegramEvent.ts index 1c8f5a679..73bc0f2cd 100644 --- a/packages/sdk/src/bridge/events/__tests__/onTelegramEvent.ts +++ b/packages/sdk/src/bridge/events/__tests__/onTelegramEvent.ts @@ -4,17 +4,12 @@ import { createWindow, type WindowSpy } from '../../../../test-utils/createWindo import { dispatchWindowMessageEvent } from '../../../../test-utils/dispatchWindowMessageEvent'; import { onTelegramEvent } from '../onTelegramEvent'; -let windowSpy: WindowSpy; - -beforeEach(() => { - windowSpy = createWindow(); -}); - afterEach(() => { - windowSpy.mockRestore(); + vi.restoreAllMocks() }); 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.', () => { + createWindow({ env: 'iframe' }); const callback = vi.fn(); onTelegramEvent(callback); @@ -25,6 +20,7 @@ it('should call passed callback with event type and data in case, window generat }); it('should not define event handlers twice in case, window object contains "TelegramGameProxy_receiveEvent" property.', () => { + createWindow(); (window as any).TelegramGameProxy_receiveEvent = true; onTelegramEvent(vi.fn()); @@ -32,6 +28,7 @@ it('should not define event handlers twice in case, window object contains "Tele }); it('should call passed callback with event type and data in case, external environment generated event.', () => { + createWindow(); const callback = vi.fn(); onTelegramEvent(callback); @@ -42,6 +39,7 @@ it('should call passed callback with event type and data in case, external envir }); it('should ignore a message event with unexpected data', () => { + createWindow(); const callback = vi.fn(); onTelegramEvent(callback); diff --git a/packages/sdk/src/bridge/events/onTelegramEvent.ts b/packages/sdk/src/bridge/events/onTelegramEvent.ts index 088890efd..77ba8684b 100644 --- a/packages/sdk/src/bridge/events/onTelegramEvent.ts +++ b/packages/sdk/src/bridge/events/onTelegramEvent.ts @@ -4,13 +4,15 @@ import { parseMessage } from '~/bridge/parseMessage.js'; * Emits event sent from Telegram native application like it was sent in * default web environment between 2 iframes. It dispatches new MessageEvent * and expects it to be handled via `window.addEventListener('message', ...)` - * as developer would do it to handle messages sent from parent iframe. + * as developer would do it to handle messages sent from the parent iframe. * @param eventType - event name. * @param eventData - event payload. */ function emitEvent(eventType: string, eventData: unknown): void { window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify({ eventType, eventData }), + // We specify window.parent to imitate the case, it sent us this event. + source: window.parent, })); } @@ -65,6 +67,10 @@ export function onTelegramEvent(cb: (eventType: string, eventData: unknown) => v // We expect Telegram to send us new event through "message" event. window.addEventListener('message', (event) => { + if (event.source !== window.parent) { + return; + } + try { const { eventType, eventData } = parseMessage(event.data); cb(eventType, eventData); diff --git a/packages/sdk/src/classnames/__tests__/mergeClassNames.ts b/packages/sdk/src/classnames/__tests__/mergeClassNames.ts index 898ef0970..0ef973688 100644 --- a/packages/sdk/src/classnames/__tests__/mergeClassNames.ts +++ b/packages/sdk/src/classnames/__tests__/mergeClassNames.ts @@ -3,7 +3,7 @@ import { expect, it } from 'vitest'; import { mergeClassNames } from '../mergeClassNames'; it('should ignore non-object values', () => { - expect(mergeClassNames({}, null, undefined, false, true, {})); + expect(mergeClassNames({}, null, undefined, false, true, { tma: 'good' })).toStrictEqual({ tma: 'good' }); }); it('should merge objects keys by values applying classNames function', () => { diff --git a/packages/sdk/src/classnames/mergeClassNames.ts b/packages/sdk/src/classnames/mergeClassNames.ts index d691fe870..62ca605f0 100644 --- a/packages/sdk/src/classnames/mergeClassNames.ts +++ b/packages/sdk/src/classnames/mergeClassNames.ts @@ -1,13 +1,13 @@ -import { classNames } from './classNames.js'; +import { isRecord } from '~/misc/index.js'; -type FilterUnion = Exclude; +import { classNames } from './classNames.js'; /** * Returns union keys removing those, which values are not strings. */ -type UnionFilteredKeys = U extends U +type UnionStringKeys = U extends U ? { - [K in keyof U]: U[K] extends string ? K : never + [K in keyof U]-?: U[K] extends string | undefined ? K : never; }[keyof U] : never; @@ -16,32 +16,24 @@ type UnionFilteredKeys = U extends U */ type UnionRequiredKeys = U extends U ? { - [K in UnionFilteredKeys]-?: ({} extends { [P in K]: U[K] } ? never : K) - }[UnionFilteredKeys] + [K in UnionStringKeys]: ({} extends Pick ? never : K) + }[UnionStringKeys] : never; /** * Returns union optional keys. */ -type UnionOptionalKeys = Exclude, UnionRequiredKeys>; +type UnionOptionalKeys = Exclude, UnionRequiredKeys>; -type MergeClassNames = Tuple[number] extends infer Union - ? FilterUnion extends infer UnionFiltered +type MergeClassNames = + // Removes all types from union which will be ignored by the mergeClassNames function. + Exclude extends infer Union ? { - [K in UnionRequiredKeys]: string; - } & { - [K in UnionOptionalKeys]?: string; - } - : never - : never; - -/** - * Returns true in case, passed value is Record. - * @param value - */ -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(null); -} + [K in UnionRequiredKeys]: string; + } & { + [K in UnionOptionalKeys]?: string; + } + : never; /** * 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 { */ export function mergeClassNames(...partials: T): MergeClassNames { return partials.reduce>((acc, partial) => { - if (!isObject(partial)) { + if (!isRecord(partial)) { return acc; } diff --git a/packages/sdk/test-utils/createWindow.ts b/packages/sdk/test-utils/createWindow.ts index bea82d207..dcffd424f 100644 --- a/packages/sdk/test-utils/createWindow.ts +++ b/packages/sdk/test-utils/createWindow.ts @@ -1,4 +1,4 @@ -import { vi, type SpyInstance } from 'vitest'; +import { type SpyInstance, vi } from 'vitest'; import { createDomEmitter } from './createDomEmitter.js'; @@ -20,11 +20,12 @@ export function createWindow(options: CreateWindowOptions = {}): WindowSpy { const wnd = { innerHeight, innerWidth, + // We need this property to correctly re-emit received event from Telegram. + parent: { postMessage: postMessageSpy }, ...createDomEmitter(), ...(env === 'iframe' ? { top: 1, self: 2, - parent: { postMessage: postMessageSpy }, } : {}), }; diff --git a/packages/sdk/test-utils/dispatchWindowMessageEvent.ts b/packages/sdk/test-utils/dispatchWindowMessageEvent.ts index 06f262107..2065c5ae2 100644 --- a/packages/sdk/test-utils/dispatchWindowMessageEvent.ts +++ b/packages/sdk/test-utils/dispatchWindowMessageEvent.ts @@ -7,5 +7,6 @@ export function dispatchWindowMessageEvent(eventType: string, eventData?: unknown): void { window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify({ eventType, eventData }), + source: window.parent, })); }