Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/some fixes #230

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chatty-wombats-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tma.js/sdk": patch
---

Add source = window.parent in emitEvent function. Fix types in merge class names utility
12 changes: 5 additions & 7 deletions packages/sdk/src/bridge/events/__tests__/onTelegramEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -25,13 +20,15 @@ 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());
expect(window).not.toHaveProperty('Telegram');
});

it('should call passed callback with event type and data in case, external environment generated event.', () => {
createWindow();
const callback = vi.fn();
onTelegramEvent(callback);

Expand All @@ -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);

Expand Down
8 changes: 7 additions & 1 deletion packages/sdk/src/bridge/events/onTelegramEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
}

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/classnames/__tests__/mergeClassNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
40 changes: 16 additions & 24 deletions packages/sdk/src/classnames/mergeClassNames.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { classNames } from './classNames.js';
import { isRecord } from '~/misc/index.js';

type FilterUnion<U> = Exclude<U, number | string | null | undefined | any[] | boolean>;
import { classNames } from './classNames.js';

/**
* Returns union keys removing those, which values are not strings.
*/
type UnionFilteredKeys<U> = U extends U
type UnionStringKeys<U> = 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;

Expand All @@ -16,32 +16,24 @@ type UnionFilteredKeys<U> = U extends U
*/
type UnionRequiredKeys<U> = U extends U
? {
[K in UnionFilteredKeys<U>]-?: ({} extends { [P in K]: U[K] } ? never : K)
}[UnionFilteredKeys<U>]
[K in UnionStringKeys<U>]: ({} extends Pick<U, K> ? never : K)
}[UnionStringKeys<U>]
: never;

/**
* Returns union optional keys.
*/
type UnionOptionalKeys<U> = Exclude<UnionFilteredKeys<U>, UnionRequiredKeys<U>>;
type UnionOptionalKeys<U> = Exclude<UnionStringKeys<U>, UnionRequiredKeys<U>>;

type MergeClassNames<Tuple extends any[]> = Tuple[number] extends infer Union
? FilterUnion<Union> extends infer UnionFiltered
type MergeClassNames<Tuple extends any[]> =
// Removes all types from union which will be ignored by the mergeClassNames function.
Exclude<Tuple[number], number | string | null | undefined | any[] | boolean> extends infer Union
? {
[K in UnionRequiredKeys<UnionFiltered>]: string;
} & {
[K in UnionOptionalKeys<UnionFiltered>]?: string;
}
: never
: never;

/**
* Returns true in case, passed value is Record.
* @param value
*/
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(null);
}
[K in UnionRequiredKeys<Union>]: string;
} & {
[K in UnionOptionalKeys<Union>]?: string;
}
: never;

/**
* Merges 2 sets of parameters. Function expects passing an array of objects with values, which
Expand All @@ -51,7 +43,7 @@ function isObject(value: unknown): value is Record<string, unknown> {
*/
export function mergeClassNames<T extends any[]>(...partials: T): MergeClassNames<T> {
return partials.reduce<MergeClassNames<T>>((acc, partial) => {
if (!isObject(partial)) {
if (!isRecord(partial)) {
return acc;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/sdk/test-utils/createWindow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vi, type SpyInstance } from 'vitest';
import { type SpyInstance, vi } from 'vitest';

import { createDomEmitter } from './createDomEmitter.js';

Expand All @@ -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 },
} : {}),
};

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/test-utils/dispatchWindowMessageEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
}
Loading