From 9da90bfee62b15e713b2d1ae25be24389d9ca5f6 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 29 Jan 2025 23:51:17 +0500 Subject: [PATCH 01/11] feat(transformers): implement isLaunchParamsQuery --- packages/transformers/src/index.ts | 1 + .../validation/isLaunchParamsQuery.test.ts | 27 +++++++++++++++++++ .../src/validation/isLaunchParamsQuery.ts | 14 ++++++++++ 3 files changed, 42 insertions(+) create mode 100644 packages/transformers/src/validation/isLaunchParamsQuery.test.ts create mode 100644 packages/transformers/src/validation/isLaunchParamsQuery.ts diff --git a/packages/transformers/src/index.ts b/packages/transformers/src/index.ts index 032670773..cc2a9f8fd 100644 --- a/packages/transformers/src/index.ts +++ b/packages/transformers/src/index.ts @@ -50,4 +50,5 @@ export { transformQueryUsing, type TransformQueryUsingAction, } from './transformers/transformQueryUsing.js'; +export { isLaunchParamsQuery } from './validation/isLaunchParamsQuery.js'; export { isRGB, toRGB, isRGBShort } from './validation/rgb.js'; diff --git a/packages/transformers/src/validation/isLaunchParamsQuery.test.ts b/packages/transformers/src/validation/isLaunchParamsQuery.test.ts new file mode 100644 index 000000000..811725cc3 --- /dev/null +++ b/packages/transformers/src/validation/isLaunchParamsQuery.test.ts @@ -0,0 +1,27 @@ +import { it, expect } from 'vitest'; + +import { isLaunchParamsQuery } from '@/validation/isLaunchParamsQuery.js'; + +it('should return true if valid value passed', () => { + // Complete set of params. + expect(isLaunchParamsQuery('tgWebAppData=user%3D%257B%2522id%2522%253A279058397%252C%2522first_name%2522%253A%2522Vladislav%2522%252C%2522last_name%2522%253A%2522Kibenko%2522%252C%2522username%2522%253A%2522vdkfrost%2522%252C%2522language_code%2522%253A%2522ru%2522%252C%2522is_premium%2522%253Atrue%252C%2522allows_write_to_pm%2522%253Atrue%252C%2522photo_url%2522%253A%2522https%253A%255C%252F%255C%252Ft.me%255C%252Fi%255C%252Fuserpic%255C%252F320%255C%252F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%2522%257D%26chat_instance%3D-9019086117643313246%26chat_type%3Dsender%26auth_date%3D1736409902%26signature%3DFNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA%26hash%3D4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90&tgWebAppVersion=8.0&tgWebAppPlatform=tdesktop&tgWebAppThemeParams=%7B%22accent_text_color%22%3A%22%236ab3f2%22%2C%22bg_color%22%3A%22%2317212b%22%2C%22bottom_bar_bg_color%22%3A%22%2317212b%22%2C%22button_color%22%3A%22%235289c1%22%2C%22button_text_color%22%3A%22%23ffffff%22%2C%22destructive_text_color%22%3A%22%23ec3942%22%2C%22header_bg_color%22%3A%22%2317212b%22%2C%22hint_color%22%3A%22%23708599%22%2C%22link_color%22%3A%22%236ab3f3%22%2C%22secondary_bg_color%22%3A%22%23232e3c%22%2C%22section_bg_color%22%3A%22%2317212b%22%2C%22section_header_text_color%22%3A%22%236ab3f3%22%2C%22section_separator_color%22%3A%22%23111921%22%2C%22subtitle_text_color%22%3A%22%23708599%22%2C%22text_color%22%3A%22%23f5f5f5%22%7D')).toBe(true); + + // Minimal set of params. + expect(isLaunchParamsQuery('tgWebAppVersion=8.0&tgWebAppPlatform=tdesktop&tgWebAppThemeParams=%7B%22accent_text_color%22%3A%22%236ab3f2%22%2C%22bg_color%22%3A%22%2317212b%22%2C%22bottom_bar_bg_color%22%3A%22%2317212b%22%2C%22button_color%22%3A%22%235289c1%22%2C%22button_text_color%22%3A%22%23ffffff%22%2C%22destructive_text_color%22%3A%22%23ec3942%22%2C%22header_bg_color%22%3A%22%2317212b%22%2C%22hint_color%22%3A%22%23708599%22%2C%22link_color%22%3A%22%236ab3f3%22%2C%22secondary_bg_color%22%3A%22%23232e3c%22%2C%22section_bg_color%22%3A%22%2317212b%22%2C%22section_header_text_color%22%3A%22%236ab3f3%22%2C%22section_separator_color%22%3A%22%23111921%22%2C%22subtitle_text_color%22%3A%22%23708599%22%2C%22text_color%22%3A%22%23f5f5f5%22%7D')).toBe(true); + + // Insufficient set of params (missing version). + expect(isLaunchParamsQuery('tgWebAppPlatform=tdesktop&tgWebAppThemeParams=%7B%22accent_text_color%22%3A%22%236ab3f2%22%2C%22bg_color%22%3A%22%2317212b%22%2C%22bottom_bar_bg_color%22%3A%22%2317212b%22%2C%22button_color%22%3A%22%235289c1%22%2C%22button_text_color%22%3A%22%23ffffff%22%2C%22destructive_text_color%22%3A%22%23ec3942%22%2C%22header_bg_color%22%3A%22%2317212b%22%2C%22hint_color%22%3A%22%23708599%22%2C%22link_color%22%3A%22%236ab3f3%22%2C%22secondary_bg_color%22%3A%22%23232e3c%22%2C%22section_bg_color%22%3A%22%2317212b%22%2C%22section_header_text_color%22%3A%22%236ab3f3%22%2C%22section_separator_color%22%3A%22%23111921%22%2C%22subtitle_text_color%22%3A%22%23708599%22%2C%22text_color%22%3A%22%23f5f5f5%22%7D')).toBe(false); + + // Minimal set of params + unknown parameters. + expect(isLaunchParamsQuery('tgWebAppVersion=8.0&tgWebAppPlatform=tdesktop&tgWebAppThemeParams=%7B%22accent_text_color%22%3A%22%236ab3f2%22%2C%22bg_color%22%3A%22%2317212b%22%2C%22bottom_bar_bg_color%22%3A%22%2317212b%22%2C%22button_color%22%3A%22%235289c1%22%2C%22button_text_color%22%3A%22%23ffffff%22%2C%22destructive_text_color%22%3A%22%23ec3942%22%2C%22header_bg_color%22%3A%22%2317212b%22%2C%22hint_color%22%3A%22%23708599%22%2C%22link_color%22%3A%22%236ab3f3%22%2C%22secondary_bg_color%22%3A%22%23232e3c%22%2C%22section_bg_color%22%3A%22%2317212b%22%2C%22section_header_text_color%22%3A%22%236ab3f3%22%2C%22section_separator_color%22%3A%22%23111921%22%2C%22subtitle_text_color%22%3A%22%23708599%22%2C%22text_color%22%3A%22%23f5f5f5%22%7D&hey=Pavel&say=hi')).toBe(true); + + // Minimal set of params as URLSearchParams + unknown parameters. + expect(isLaunchParamsQuery(new URLSearchParams([ + ['tgWebAppVersion', '8'], + ['tgWebAppPlatform', 'tdesktop'], + ['tgWebAppThemeParams', + '{"accent_text_color":"#6ab3f2","bg_color":"#17212b","bottom_bar_bg_color":"#17212b","button_color":"#5289c1","button_text_color":"#ffffff","destructive_text_color":"#ec3942","header_bg_color":"#17212b","hint_color":"#708599","link_color":"#6ab3f3","secondary_bg_color":"#232e3c","section_bg_color":"#17212b","section_header_text_color":"#6ab3f3","section_separator_color":"#111921","subtitle_text_color":"#708599","text_color":"#f5f5f5"}'], + ['hey', 'Pavel'], + ['say', 'hi'], + ]))).toBe(true); +}); \ No newline at end of file diff --git a/packages/transformers/src/validation/isLaunchParamsQuery.ts b/packages/transformers/src/validation/isLaunchParamsQuery.ts new file mode 100644 index 000000000..4a4c6a19e --- /dev/null +++ b/packages/transformers/src/validation/isLaunchParamsQuery.ts @@ -0,0 +1,14 @@ +import { is } from 'valibot'; + +import { launchParamsQuery } from '@/generators/launchParamsQuery.js'; + +/** + * @returns True if the passed value contains valid launch parameters query. + */ +export function isLaunchParamsQuery(value: string | URLSearchParams): boolean { + try { + return is(launchParamsQuery(), value); + } catch { + return false; + } +} \ No newline at end of file From 96ea274cbbeee71fcc5b8d684a332f018ebaa322 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 29 Jan 2025 23:53:27 +0500 Subject: [PATCH 02/11] feat(bridge): implement retrieveRawInitData --- .../src/launch-params/forEachLpSource.ts | 38 +++++++++++++ .../src/launch-params/retrieveLaunchParams.ts | 55 ++++--------------- .../launch-params/retrieveRawInitData.test.ts | 45 +++++++++++++++ .../src/launch-params/retrieveRawInitData.ts | 28 ++++++++++ .../bridge/src/launch-params/storage.test.ts | 48 ---------------- packages/bridge/src/launch-params/storage.ts | 24 -------- 6 files changed, 123 insertions(+), 115 deletions(-) create mode 100644 packages/bridge/src/launch-params/forEachLpSource.ts create mode 100644 packages/bridge/src/launch-params/retrieveRawInitData.test.ts create mode 100644 packages/bridge/src/launch-params/retrieveRawInitData.ts delete mode 100644 packages/bridge/src/launch-params/storage.test.ts delete mode 100644 packages/bridge/src/launch-params/storage.ts diff --git a/packages/bridge/src/launch-params/forEachLpSource.ts b/packages/bridge/src/launch-params/forEachLpSource.ts new file mode 100644 index 000000000..c10eb6bf3 --- /dev/null +++ b/packages/bridge/src/launch-params/forEachLpSource.ts @@ -0,0 +1,38 @@ +import { getStorageValue } from '@telegram-apps/toolkit'; + +/** + * @param urlString - URL to extract launch parameters from. + * @returns Launch parameters from the specified URL. + * @throws Error if function was unable to extract launch parameters from the passed URL. + */ +function fromURL(urlString: string): string { + return urlString + // Replace everything before this first hashtag or question sign. + .replace(/^[^?#]*[?#]/, '') + // Replace all hashtags and question signs to make it look like some search params. + .replace(/[?#]/g, '&'); +} + +/** + * Runs the specified function for each value, where the value is one stored in any known + * launch parameters source. + * @param fn - function to run. Should return false when the execution must be stopped. + */ +export function forEachLpSource(fn: (value: string) => boolean): void { + for (const retrieve of [ + // Try to retrieve launch parameters from the current location. This method can return + // nothing in case, location was changed, and then the page was reloaded. + () => fromURL(window.location.href), + // Then, try using the lower level API - window.performance. + () => { + const navigationEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined; + return navigationEntry ? fromURL(navigationEntry.name) : undefined; + }, + () => getStorageValue('launchParams') || '', + ]) { + const v = retrieve(); + if (v && !fn(v)) { + return; + } + } +} \ No newline at end of file diff --git a/packages/bridge/src/launch-params/retrieveLaunchParams.ts b/packages/bridge/src/launch-params/retrieveLaunchParams.ts index 0c22231bb..0a3fa68db 100644 --- a/packages/bridge/src/launch-params/retrieveLaunchParams.ts +++ b/packages/bridge/src/launch-params/retrieveLaunchParams.ts @@ -2,44 +2,17 @@ import { LaunchParamsSchema, parseLaunchParamsQuery } from '@telegram-apps/trans import { type DeepConvertSnakeKeysToCamelCase, deepSnakeToCamelObjKeys, + setStorageValue, } from '@telegram-apps/toolkit'; import type { InferOutput } from 'valibot'; import { LaunchParamsRetrieveError } from '@/errors.js'; -import { retrieveFromStorage, saveToStorage } from '@/launch-params/storage.js'; +import { forEachLpSource } from '@/launch-params/forEachLpSource.js'; export type RetrieveLPResult = InferOutput; export type RetrieveLPResultCamelCased = DeepConvertSnakeKeysToCamelCase>; -/** - * @param urlString - URL to extract launch parameters from. - * @returns Launch parameters from the specified URL. - * @throws Error if function was unable to extract launch parameters from the passed URL. - */ -function fromURL(urlString: string): RetrieveLPResult { - return parseLaunchParamsQuery( - urlString - // Replace everything before this first hashtag or question sign. - .replace(/^[^?#]*[?#]/, '') - // Replace all hashtags and question signs to make it look like some search params. - .replace(/[?#]/g, '&'), - ); -} - -/** - * @returns Launch parameters based on the first navigation entry. - * @throws Error if function was unable to extract launch parameters from the navigation entry. - */ -function retrieveFromPerformance(): RetrieveLPResult { - const navigationEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined; - if (!navigationEntry) { - throw new Error('Unable to get first navigation entry.'); - } - - return fromURL(navigationEntry.name); -} - /** * @returns Launch parameters from any known source. * @param camelCase - should the output be camel-cased. @@ -65,24 +38,20 @@ export function retrieveLaunchParams(camelCase?: boolean): | RetrieveLPResult | RetrieveLPResultCamelCased { const errors: unknown[] = []; + let launchParams: RetrieveLPResult | undefined; - for (const retrieve of [ - // Try to retrieve launch parameters from the current location. This method can return - // nothing in case, location was changed, and then the page was reloaded. - () => fromURL(window.location.href), - // Then, try using the lower level API - window.performance. - retrieveFromPerformance, - // Finally, try to extract launch parameters from the session storage. - retrieveFromStorage, - ]) { + forEachLpSource(v => { try { - const lp = retrieve(); - saveToStorage(lp); - return camelCase ? deepSnakeToCamelObjKeys(lp) : lp; + launchParams = parseLaunchParamsQuery(v); + setStorageValue('launchParams', v); + return false; } catch (e) { errors.push(e); + return true; } + }); + if (!launchParams) { + throw new LaunchParamsRetrieveError(errors); } - - throw new LaunchParamsRetrieveError(errors); + return camelCase ? deepSnakeToCamelObjKeys(launchParams) : launchParams; } diff --git a/packages/bridge/src/launch-params/retrieveRawInitData.test.ts b/packages/bridge/src/launch-params/retrieveRawInitData.test.ts new file mode 100644 index 000000000..aa59228ff --- /dev/null +++ b/packages/bridge/src/launch-params/retrieveRawInitData.test.ts @@ -0,0 +1,45 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { retrieveRawInitData } from '@/launch-params/retrieveRawInitData.js'; + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('window.location.href contains init data', () => { + it('should retrieve init data from the window.location.href. Throw an error if data is invalid or missing', () => { + vi + .spyOn(window.location, 'href', 'get') + .mockImplementationOnce(() => { + return '/abc?tgWebAppStartParam=location_hash#tgWebAppPlatform=tdesktop&tgWebAppVersion=7.0&tgWebAppThemeParams=%7B%7D&tgWebAppData=user%3D%257B%2522id%2522%253A279058397%252C%2522first_name%2522%253A%2522Vladislav%2522%252C%2522last_name%2522%253A%2522Kibenko%2522%252C%2522username%2522%253A%2522vdkfrost%2522%252C%2522language_code%2522%253A%2522ru%2522%252C%2522is_premium%2522%253Atrue%252C%2522allows_write_to_pm%2522%253Atrue%252C%2522photo_url%2522%253A%2522https%253A%255C%252F%255C%252Ft.me%255C%252Fi%255C%252Fuserpic%255C%252F320%255C%252F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%2522%257D%26chat_instance%3D-9019086117643313246%26chat_type%3Dsender%26auth_date%3D1736409902%26signature%3DFNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA%26hash%3D4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90'; + }); + expect(retrieveRawInitData()).toBe('user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D&chat_instance=-9019086117643313246&chat_type=sender&auth_date=1736409902&signature=FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA&hash=4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90'); + }); +}); + +describe('first navigation entry contains init data', () => { + it('should retrieve init data from the window.performance. Throw an error if data is invalid or missing', () => { + vi + .spyOn(performance, 'getEntriesByType') + .mockImplementationOnce(() => [{ + name: '/abc?tgWebAppStartParam=performance#tgWebAppPlatform=macos&tgWebAppVersion=7.3&tgWebAppThemeParams=%7B%7D&tgWebAppData=user%3D%257B%2522id%2522%253A279058397%252C%2522first_name%2522%253A%2522Vladislav%2522%252C%2522last_name%2522%253A%2522Kibenko%2522%252C%2522username%2522%253A%2522vdkfrost%2522%252C%2522language_code%2522%253A%2522ru%2522%252C%2522is_premium%2522%253Atrue%252C%2522allows_write_to_pm%2522%253Atrue%252C%2522photo_url%2522%253A%2522https%253A%255C%252F%255C%252Ft.me%255C%252Fi%255C%252Fuserpic%255C%252F320%255C%252F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%2522%257D%26chat_instance%3D-9019086117643313246%26chat_type%3Dsender%26auth_date%3D1736409902%26signature%3DFNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA%26hash%3D4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d33', + }] as any); + + expect(retrieveRawInitData()).toBe('user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D&chat_instance=-9019086117643313246&chat_type=sender&auth_date=1736409902&signature=FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA&hash=4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d33'); + }); +}); + +describe('session storage contains init data', () => { + it('should return launch parameters from the session storage tapps/launchParams key. If data is missing or invalid, throw an error', () => { + const spy = vi + .spyOn(sessionStorage, 'getItem') + .mockImplementationOnce(() => ''); + expect(() => retrieveRawInitData()).toThrow(); + + spy.mockClear(); + spy.mockImplementationOnce(() => { + return '"tgWebAppPlatform=android&tgWebAppThemeParams=%7B%22bg_color%22%3A%22%23ffffff%22%7D&tgWebAppVersion=7.5&tgWebAppData=user%3D%257B%2522id%2522%253A279058397%252C%2522first_name%2522%253A%2522Vladislav%2522%252C%2522last_name%2522%253A%2522Kibenko%2522%252C%2522username%2522%253A%2522vdkfrost%2522%252C%2522language_code%2522%253A%2522ru%2522%252C%2522is_premium%2522%253Atrue%252C%2522allows_write_to_pm%2522%253Atrue%252C%2522photo_url%2522%253A%2522https%253A%255C%252F%255C%252Ft.me%255C%252Fi%255C%252Fuserpic%255C%252F320%255C%252F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%2522%257D%26chat_instance%3D-9019086117643313246%26chat_type%3Dsender%26auth_date%3D1736409902%26signature%3DFNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA%26hash%3D4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90"'; + }); + expect(retrieveRawInitData()).toBe('user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D&chat_instance=-9019086117643313246&chat_type=sender&auth_date=1736409902&signature=FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA&hash=4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90'); + }); +}); \ No newline at end of file diff --git a/packages/bridge/src/launch-params/retrieveRawInitData.ts b/packages/bridge/src/launch-params/retrieveRawInitData.ts new file mode 100644 index 000000000..37d153ec3 --- /dev/null +++ b/packages/bridge/src/launch-params/retrieveRawInitData.ts @@ -0,0 +1,28 @@ +import { isLaunchParamsQuery } from '@telegram-apps/transformers'; + +import { forEachLpSource } from '@/launch-params/forEachLpSource.js'; +import { InitDataRetrieveError } from '@/errors.js'; +import { retrieveLaunchParams } from '@/launch-params/retrieveLaunchParams.js'; + +/** + * @returns Raw init data from any known source. + * @throws {InitDataRetrieveError} Unable to retrieve init data from any known source. + */ +export function retrieveRawInitData(): string | undefined { + // Init data depends on the launch parameters. We also want them to be saved. + try { + retrieveLaunchParams(); + } catch { + throw new InitDataRetrieveError(); + } + + let initData: string | undefined; + forEachLpSource(v => { + if (isLaunchParamsQuery(v)) { + initData = new URLSearchParams(v).get('tgWebAppData') || undefined; + return false; + } + return true; + }); + return initData; +} \ No newline at end of file diff --git a/packages/bridge/src/launch-params/storage.test.ts b/packages/bridge/src/launch-params/storage.test.ts deleted file mode 100644 index 3c842e4d5..000000000 --- a/packages/bridge/src/launch-params/storage.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { retrieveFromStorage, saveToStorage } from './storage.js'; - -afterEach(() => { - vi.restoreAllMocks(); -}); - -describe('retrieveFromStorage', () => { - it('should return launch parameters from the session storage tapps/launchParams key. If data is missing or invalid, throw an error', () => { - const spy = vi - .spyOn(sessionStorage, 'getItem') - .mockImplementationOnce(() => ''); - expect(() => retrieveFromStorage()).toThrow(); - - spy.mockClear(); - spy.mockImplementationOnce(() => { - return '"tgWebAppPlatform=android&tgWebAppThemeParams=%7B%22bg_color%22%3A%22%23ffffff%22%7D&tgWebAppVersion=7.0"'; - }); - expect(retrieveFromStorage()).toStrictEqual({ - tgWebAppVersion: '7.0', - tgWebAppPlatform:'android', - tgWebAppThemeParams: { - bg_color: '#ffffff', - }, - }); - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith('tapps/launchParams'); - }); -}); - -describe('retrieveFromStorage', () => { - it('should call sessionStorage.setItem with "tapps/launchParams" and serialized launch params', () => { - const spy = vi.spyOn(sessionStorage, 'setItem').mockImplementation(() => undefined); - saveToStorage({ - tgWebAppVersion: '7.0', - tgWebAppPlatform:'android', - tgWebAppThemeParams: { - bg_color: '#ffffff', - }, - }); - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith( - 'tapps/launchParams', - '"tgWebAppVersion=7.0&tgWebAppPlatform=android&tgWebAppThemeParams=%7B%22bg_color%22%3A%22%23ffffff%22%7D"', - ); - }); -}); diff --git a/packages/bridge/src/launch-params/storage.ts b/packages/bridge/src/launch-params/storage.ts deleted file mode 100644 index 1a16353ff..000000000 --- a/packages/bridge/src/launch-params/storage.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getStorageValue, setStorageValue } from '@telegram-apps/toolkit'; -import { - serializeLaunchParamsQuery, - parseLaunchParamsQuery, - LaunchParamsLike, -} from '@telegram-apps/transformers'; - -const STORAGE_KEY = 'launchParams'; - -/** - * @returns Launch parameters stored in the session storage. - * @throws Error if function was unable to extract launch parameters from the window location hash. - */ -export function retrieveFromStorage() { - return parseLaunchParamsQuery(getStorageValue(STORAGE_KEY) || ''); -} - -/** - * Saves specified launch parameters in the session storage. - * @param value - launch params to save. - */ -export function saveToStorage(value: LaunchParamsLike): void { - setStorageValue(STORAGE_KEY, serializeLaunchParamsQuery(value)); -} From c818021822de6383ad5cc56e9ac150690495a090 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 29 Jan 2025 23:57:24 +0500 Subject: [PATCH 03/11] fix(bridge): require tgWebAppData passed in raw format in mockTelegramEnv --- .../bridge/src/env/mockTelegramEnv.test.ts | 48 +++++++++++-------- packages/bridge/src/env/mockTelegramEnv.ts | 43 ++++++++++++----- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/packages/bridge/src/env/mockTelegramEnv.test.ts b/packages/bridge/src/env/mockTelegramEnv.test.ts index 8b98fd4b4..da8e48a7e 100644 --- a/packages/bridge/src/env/mockTelegramEnv.test.ts +++ b/packages/bridge/src/env/mockTelegramEnv.test.ts @@ -28,34 +28,44 @@ it('should store launch parameters retuning them from retrieveLaunchParams', () subtitle_text_color: '#708499', text_color: '#f5f5f5', }, - tgWebAppData: { - auth_date: new Date(1716922846000), - chat_instance: '8428209589180549439', - chat_type: 'sender', - hash: '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31', - start_param: 'debug', - user: { - allows_write_to_pm: true, - first_name: 'Andrew', - id: 99281932, - is_premium: true, - language_code: 'en', - last_name: 'Rogue', - username: 'rogue', - }, - signature: 'abc', - }, + tgWebAppData: 'chat_type=sender&auth_date=1736409902&signature=FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA&hash=4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90v', tgWebAppVersion: '7.2', tgWebAppPlatform: 'tdesktop', tgWebAppBotInline: false, tgWebAppShowSettings: false, } as const; - createWindow(); + createWindow({ location: { href: '' } } as any); expect(retrieveLaunchParams).toThrow(); mockTelegramEnv({ launchParams }); - expect(retrieveLaunchParams()).toStrictEqual(launchParams); + expect(retrieveLaunchParams()).toStrictEqual({ + tgWebAppThemeParams: { + accent_text_color: '#6ab2f2', + bg_color: '#17212b', + button_color: '#5288c1', + button_text_color: '#ffffff', + destructive_text_color: '#ec3942', + header_bg_color: '#17212b', + hint_color: '#708499', + link_color: '#6ab3f3', + secondary_bg_color: '#232e3c', + section_bg_color: '#17212b', + section_header_text_color: '#6ab3f3', + subtitle_text_color: '#708499', + text_color: '#f5f5f5', + }, + tgWebAppData: { + chat_type: 'sender', + auth_date: new Date(1736409902000), + signature: 'FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA', + hash: '4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90v', + }, + tgWebAppVersion: '7.2', + tgWebAppPlatform: 'tdesktop', + tgWebAppBotInline: false, + tgWebAppShowSettings: false, + }); }); describe('env is iframe', () => { diff --git a/packages/bridge/src/env/mockTelegramEnv.ts b/packages/bridge/src/env/mockTelegramEnv.ts index d416fccc1..27f1c8fc0 100644 --- a/packages/bridge/src/env/mockTelegramEnv.ts +++ b/packages/bridge/src/env/mockTelegramEnv.ts @@ -1,16 +1,17 @@ import { is, parse, pipe, string } from 'valibot'; import { + isLaunchParamsQuery, jsonParse, - parseLaunchParamsQuery, type LaunchParamsLike, MiniAppsMessageSchema, + serializeLaunchParamsQuery, } from '@telegram-apps/transformers'; -import type { If, IsNever } from '@telegram-apps/toolkit'; +import { If, IsNever, setStorageValue } from '@telegram-apps/toolkit'; import { logInfo } from '@/debug.js'; import { isIframe } from '@/env/isIframe.js'; -import { saveToStorage } from '@/launch-params/storage.js'; import type { MethodName, MethodParams } from '@/methods/types/index.js'; +import { InvalidLaunchParamsError } from '@/errors.js'; /** * Mocks the environment and imitates Telegram Mini Apps behavior. @@ -19,8 +20,14 @@ export function mockTelegramEnv({ launchParams, onEvent }: { /** * Launch parameters to mock. They will be saved in the session storage making * the `retrieveLaunchParams` function return them. + * + * Note that this value must have tgWebAppData presented in a raw format as long as you will + * need it when retrieving init data in this format. Otherwise, init data may be broken. */ - launchParams?: LaunchParamsLike | string | URLSearchParams; + launchParams?: + | (Omit & { tgWebAppData?: string | URLSearchParams }) + | string + | URLSearchParams; /** * Function that will be called if a Mini Apps method call was requested by the mini app. * @param event - event information. @@ -34,13 +41,27 @@ export function mockTelegramEnv({ launchParams, onEvent }: { next: () => void, ) => void; } = {}): void { - // If launch parameters were passed, save them in the session storage, so - // the retrieveLaunchParams function would return them. - launchParams && saveToStorage( - typeof launchParams === 'string' || launchParams instanceof URLSearchParams - ? parseLaunchParamsQuery(launchParams) - : launchParams, - ); + if (launchParams) { + // If launch parameters were passed, save them in the session storage, so + // the retrieveLaunchParams function would return them. + const launchParamsQuery = + typeof launchParams === 'string' || launchParams instanceof URLSearchParams + ? launchParams.toString() + : ( + // Here we have to trick serializeLaunchParamsQuery into thinking, it serializes a valid + // value. We are doing it because we are working with tgWebAppData presented as a + // string, not an object as serializeLaunchParamsQuery requires. + serializeLaunchParamsQuery({ ...launchParams, tgWebAppData: undefined }) + // Then, we just append init data. + + (launchParams.tgWebAppData ? `&tgWebAppData=${encodeURIComponent(launchParams.tgWebAppData.toString())}` : '') + ); + + // Remember to check if launch params are valid. + if (!isLaunchParamsQuery(launchParamsQuery)) { + throw new InvalidLaunchParamsError(launchParamsQuery); + } + setStorageValue('launchParams', launchParamsQuery); + } // Original postEvent firstly checks if the current environment is iframe. // That's why we have a separate branch for this environment here too. From 8d9b2f25dd97bd494d4ef41a91030fd8ac5da1c6 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 29 Jan 2025 23:57:32 +0500 Subject: [PATCH 04/11] feat(bridge): introduce new errors --- packages/bridge/src/errors.ts | 24 +++++++++++++++++++----- packages/bridge/src/index.ts | 5 +++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/bridge/src/errors.ts b/packages/bridge/src/errors.ts index d24825337..435f1ead5 100644 --- a/packages/bridge/src/errors.ts +++ b/packages/bridge/src/errors.ts @@ -21,19 +21,33 @@ export const [ ], ); +const retrieveLaunchParamsError = [ + 'Unable to retrieve launch parameters from any known source. Perhaps, you have opened your app outside Telegram?', + '📖 Refer to docs for more information:', + 'https://docs.telegram-mini-apps.com/packages/telegram-apps-bridge/environment', +].join('\n'); + export const [ LaunchParamsRetrieveError, isLaunchParamsRetrieveError, ] = errorClassWithData( 'LaunchParamsRetrieveError', errors => errors, - [ - 'Unable to retrieve launch parameters from any known source. Perhaps, you have opened your app outside Telegram?', - '📖 Refer to docs for more information:', - 'https://docs.telegram-mini-apps.com/packages/telegram-apps-bridge/environment', - ].join('\n') + retrieveLaunchParamsError, ); +export const [ + InvalidLaunchParamsError, + isInvalidLaunchParamsError, +] = errorClass<[string]>('InvalidLaunchParamsError', value => [ + `Invalid value for launch params: ${value}`, +]); + +export const [ + InitDataRetrieveError, + isInitDataRetrieveError, +] = errorClass('InitDataRetrieveError', retrieveLaunchParamsError); + export const [UnknownEnvError, isUnknownEnvError] = errorClass('UnknownEnvError'); export const [ diff --git a/packages/bridge/src/index.ts b/packages/bridge/src/index.ts index 642a0b691..9e7b98594 100644 --- a/packages/bridge/src/index.ts +++ b/packages/bridge/src/index.ts @@ -13,6 +13,7 @@ export { type RetrieveLPResultCamelCased, type RetrieveLPResult, } from '@/launch-params/retrieveLaunchParams.js'; +export { retrieveRawInitData } from '@/launch-params/retrieveRawInitData.js'; export type * from '@/methods/types/index.js'; export { targetOrigin } from '@/methods/targetOrigin.js'; @@ -54,5 +55,9 @@ export { isMethodMethodParameterUnsupportedError, UnknownEnvError, isUnknownEnvError, + InitDataRetrieveError, + isInitDataRetrieveError, + InvalidLaunchParamsError, + isInvalidLaunchParamsError, } from '@/errors.js'; export { resetPackageState } from '@/resetPackageState.js'; From 76305bd0b4ca3ff8cc1ffcb39d74597ab3d269c9 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:05:41 +0500 Subject: [PATCH 05/11] fix(sdk): fix improper behavior of initData.restore() --- .../components/init-data/init-data.test.ts | 96 ++++--------------- .../scopes/components/init-data/init-data.ts | 8 +- 2 files changed, 22 insertions(+), 82 deletions(-) diff --git a/packages/sdk/src/scopes/components/init-data/init-data.test.ts b/packages/sdk/src/scopes/components/init-data/init-data.test.ts index 1456c23e1..905713056 100644 --- a/packages/sdk/src/scopes/components/init-data/init-data.test.ts +++ b/packages/sdk/src/scopes/components/init-data/init-data.test.ts @@ -1,97 +1,39 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { InitData } from '@telegram-apps/types'; import { resetPackageState } from '@test-utils/utils.js'; import { raw, state, restore } from './init-data.js'; -vi.mock('@telegram-apps/bridge', async () => { - const m = await vi.importActual('@telegram-apps/bridge'); - - return { - ...m, - postEvent() { - }, - retrieveLaunchParams: () => ({ - tgWebAppData: { - auth_date: new Date(1000), - can_send_after: 60, - chat: { - id: 999, - photo_url: 'photo', - type: 'group', - title: 'Title', - }, - chat_type: 'sender', - chat_instance: 'abc', - hash: 'joke', - query_id: 'query id', - receiver: { - id: 1000, - photo_url: 'receiver photo', - first_name: 'a', - last_name: 'b', - username: 'c', - is_bot: false, - is_premium: false, - language_code: 'en', - }, - start_param: 'param', - user: { - id: 2000, - photo_url: 'user photo', - first_name: 'a', - last_name: 'b', - username: 'c', - language_code: 'en', - }, - signature: '' - } satisfies InitData, - }), - }; -}); - beforeEach(() => { resetPackageState(); }); describe('restore', () => { it('should set state based on init data from launch params', () => { + vi + .spyOn(window.location, 'href', 'get') + .mockImplementationOnce(() => { + return '/abc?tgWebAppData=user%3D%257B%2522id%2522%253A279058397%252C%2522first_name%2522%253A%2522Vladislav%2522%252C%2522last_name%2522%253A%2522Kibenko%2522%252C%2522username%2522%253A%2522vdkfrost%2522%252C%2522language_code%2522%253A%2522ru%2522%252C%2522is_premium%2522%253Atrue%252C%2522allows_write_to_pm%2522%253Atrue%252C%2522photo_url%2522%253A%2522https%253A%255C%252F%255C%252Ft.me%255C%252Fi%255C%252Fuserpic%255C%252F320%255C%252F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%2522%257D%26chat_instance%3D-9019086117643313246%26chat_type%3Dsender%26auth_date%3D1736409902%26signature%3DFNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA%26hash%3D4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90&tgWebAppVersion=8.0&tgWebAppPlatform=tdesktop&tgWebAppThemeParams=%7B%22accent_text_color%22%3A%22%236ab3f2%22%2C%22bg_color%22%3A%22%2317212b%22%2C%22bottom_bar_bg_color%22%3A%22%2317212b%22%2C%22button_color%22%3A%22%235289c1%22%2C%22button_text_color%22%3A%22%23ffffff%22%2C%22destructive_text_color%22%3A%22%23ec3942%22%2C%22header_bg_color%22%3A%22%2317212b%22%2C%22hint_color%22%3A%22%23708599%22%2C%22link_color%22%3A%22%236ab3f3%22%2C%22secondary_bg_color%22%3A%22%23232e3c%22%2C%22section_bg_color%22%3A%22%2317212b%22%2C%22section_header_text_color%22%3A%22%236ab3f3%22%2C%22section_separator_color%22%3A%22%23111921%22%2C%22subtitle_text_color%22%3A%22%23708599%22%2C%22text_color%22%3A%22%23f5f5f5%22%7D'; + }); + restore(); expect(state()).toStrictEqual({ - auth_date: new Date(1000), - can_send_after: 60, - chat: { - id: 999, - photo_url: 'photo', - type: 'group', - title: 'Title', - }, + auth_date: new Date(1736409902000), + chat_instance: '-9019086117643313246', chat_type: 'sender', - chat_instance: 'abc', - hash: 'joke', - query_id: 'query id', - receiver: { - id: 1000, - photo_url: 'receiver photo', - first_name: 'a', - last_name: 'b', - username: 'c', - is_bot: false, - is_premium: false, - language_code: 'en', - }, - start_param: 'param', + hash: '4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90', + signature: 'FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA', user: { - id: 2000, - photo_url: 'user photo', - first_name: 'a', - last_name: 'b', - username: 'c', - language_code: 'en', + allows_write_to_pm: true, + first_name: 'Vladislav', + id: 279058397, + is_premium: true, + language_code: 'ru', + last_name: 'Kibenko', + photo_url: 'https://t.me/i/userpic/320/4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg', + username: 'vdkfrost', }, - signature: '' }); - expect(raw()).toBe('auth_date=1&can_send_after=60&chat=%7B%22id%22%3A999%2C%22photo_url%22%3A%22photo%22%2C%22type%22%3A%22group%22%2C%22title%22%3A%22Title%22%7D&chat_type=sender&chat_instance=abc&hash=joke&query_id=query+id&receiver=%7B%22id%22%3A1000%2C%22photo_url%22%3A%22receiver+photo%22%2C%22first_name%22%3A%22a%22%2C%22last_name%22%3A%22b%22%2C%22username%22%3A%22c%22%2C%22is_bot%22%3Afalse%2C%22is_premium%22%3Afalse%2C%22language_code%22%3A%22en%22%7D&start_param=param&user=%7B%22id%22%3A2000%2C%22photo_url%22%3A%22user+photo%22%2C%22first_name%22%3A%22a%22%2C%22last_name%22%3A%22b%22%2C%22username%22%3A%22c%22%2C%22language_code%22%3A%22en%22%7D&signature='); + expect(raw()).toBe('user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%2C%22photo_url%22%3A%22https%3A%5C%2F%5C%2Ft.me%5C%2Fi%5C%2Fuserpic%5C%2F320%5C%2F4FPEE4tmP3ATHa57u6MqTDih13LTOiMoKoLDRG4PnSA.svg%22%7D&chat_instance=-9019086117643313246&chat_type=sender&auth_date=1736409902&signature=FNWSy6kv5n4kkmYYmfTbrgRtswTvwXgHTRWBVjp-YOv2srtMFSYCWZ9nGr_PohWZeWcooFo_oQgsnTJge3JdBA&hash=4c710b1d446dd4fd301c0efbf7c31627eca193a2e657754c9e0612cb1eb71d90'); }); }); \ No newline at end of file diff --git a/packages/sdk/src/scopes/components/init-data/init-data.ts b/packages/sdk/src/scopes/components/init-data/init-data.ts index 75a610f9f..ad891001b 100644 --- a/packages/sdk/src/scopes/components/init-data/init-data.ts +++ b/packages/sdk/src/scopes/components/init-data/init-data.ts @@ -1,9 +1,8 @@ import type { Computed } from '@telegram-apps/signals'; -import { retrieveLaunchParams } from '@telegram-apps/bridge'; +import { retrieveLaunchParams, retrieveRawInitData } from '@telegram-apps/bridge'; import type { InitData } from '@telegram-apps/types'; import { createComputed, createSignalsTuple } from '@/signals-registry.js'; -import { serializeInitDataQuery } from '@telegram-apps/transformers'; /** * Complete component state. @@ -81,9 +80,8 @@ export const receiver = fromState('receiver'); */ export function restore(): void { const lp = retrieveLaunchParams(); - const initData = lp.tgWebAppData; - _state.set(initData); - initData && _raw.set(serializeInitDataQuery(initData)); + _state.set(lp.tgWebAppData); + _raw.set(retrieveRawInitData()); } /** From 21310898c47b1cff09e54a817d2c53010a096106 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:05:50 +0500 Subject: [PATCH 06/11] feat(sdk): add more exports from bridge --- packages/sdk/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 7b74f8a96..c9a0c9036 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -55,6 +55,7 @@ export { on, off, retrieveLaunchParams, + retrieveRawInitData, type RetrieveLPResult, type RetrieveLPResultCamelCased, type RequestOptions, @@ -112,6 +113,10 @@ export { type RequestResult, isLaunchParamsRetrieveError, LaunchParamsRetrieveError, + InvalidLaunchParamsError, + InitDataRetrieveError, + isInitDataRetrieveError, + isInvalidLaunchParamsError, type BackgroundColor, type PopupParams, type BottomBarColor, From 0a76b44c63719fe77ae54a3238634d0a0795f63c Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:07:36 +0500 Subject: [PATCH 07/11] chore(deps): bump error-kid --- packages/init-data-node/package.json | 2 +- pnpm-lock.yaml | 27 +++++++++++---------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/init-data-node/package.json b/packages/init-data-node/package.json index 232b4593a..c5155e53d 100644 --- a/packages/init-data-node/package.json +++ b/packages/init-data-node/package.json @@ -61,6 +61,6 @@ "dependencies": { "@telegram-apps/transformers": "workspace:^", "@telegram-apps/types": "workspace:^", - "error-kid": "^0.0.3" + "error-kid": "^0.0.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f67e3bd1..c989fd3c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -174,8 +174,8 @@ importers: specifier: workspace:^ version: link:../types error-kid: - specifier: ^0.0.3 - version: 0.0.3 + specifier: ^0.0.4 + version: 0.0.4 devDependencies: '@types/node': specifier: ^22.9.0 @@ -3709,9 +3709,6 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - error-kid@0.0.3: - resolution: {integrity: sha512-HI7kpQzlqELtvzSqKTw0FPWyaYIxkdgQes04TSJhpYDf2/A8a2Prz2iJU6H46fdVzCzIC9LxD5uHkiujFZ+LRQ==} - error-kid@0.0.4: resolution: {integrity: sha512-x+yQhY56SorLMnX6kOf+z3JCv2QBurcWEDcIjgxYtVr4fGeCfAtOdZOCyWttkHHDFPtL2PqnaRUmphbmALJd9w==} @@ -10639,8 +10636,6 @@ snapshots: is-arrayish: 0.2.1 optional: true - error-kid@0.0.3: {} - error-kid@0.0.4: {} error-stack-parser-es@0.1.5: {} @@ -10822,8 +10817,8 @@ snapshots: '@typescript-eslint/parser': 8.15.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -10846,37 +10841,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.15.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -10887,7 +10882,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 From cc94a1e694a6cff28af20d37d63d32a74305989b Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:10:07 +0500 Subject: [PATCH 08/11] docs(changeset): Add `isLaunchParamsQuery` utility. --- .changeset/strange-dryers-shake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strange-dryers-shake.md diff --git a/.changeset/strange-dryers-shake.md b/.changeset/strange-dryers-shake.md new file mode 100644 index 000000000..30e38eed7 --- /dev/null +++ b/.changeset/strange-dryers-shake.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/transformers": minor +--- + +Add `isLaunchParamsQuery` utility. From c3749fa9371019f1f00f6dfa6c7c83763fa9cd91 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:10:21 +0500 Subject: [PATCH 09/11] docs(changeset): Bump `error-kid` --- .changeset/chilly-badgers-think.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilly-badgers-think.md diff --git a/.changeset/chilly-badgers-think.md b/.changeset/chilly-badgers-think.md new file mode 100644 index 000000000..17c05fa04 --- /dev/null +++ b/.changeset/chilly-badgers-think.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/init-data-node": patch +--- + +Bump `error-kid` From 0e8b869189cc9897486faba101b5cf4e4129dd10 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:11:09 +0500 Subject: [PATCH 10/11] docs(changeset): Add `retrieveRawInitData` utility. Set launchParams.tgWebAppData to string or URLSearchParams in `mockTelegramEnv` --- .changeset/dry-starfishes-promise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-starfishes-promise.md diff --git a/.changeset/dry-starfishes-promise.md b/.changeset/dry-starfishes-promise.md new file mode 100644 index 000000000..cf18ed833 --- /dev/null +++ b/.changeset/dry-starfishes-promise.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/bridge": minor +--- + +Add `retrieveRawInitData` utility. Set launchParams.tgWebAppData to string or URLSearchParams in `mockTelegramEnv` From c1d3b5697551f844c38c03e8373a475b3f84392e Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 00:17:32 +0500 Subject: [PATCH 11/11] docs(changeset): Fix incorrect `initData.restore()` behavior. Add more exports from bridge. --- .changeset/warm-roses-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/warm-roses-beg.md diff --git a/.changeset/warm-roses-beg.md b/.changeset/warm-roses-beg.md new file mode 100644 index 000000000..f52d56873 --- /dev/null +++ b/.changeset/warm-roses-beg.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/sdk": minor +--- + +Fix incorrect `initData.restore()` behavior. Add more exports from bridge.