UNPKG

@telegram-apps/bridge

Version:

TypeScript package to provide communication layer between Mini App and Telegram application.

1 lines 76.1 kB
{"version":3,"file":"index.cjs","sources":["../src/base64-url/encodeBase64Url.ts","../src/base64-url/decodeBase64Url.ts","../src/env/hasWebviewProxy.ts","../src/env/isIframe.ts","../src/events/createEmitter.ts","../src/events/emitEvent.ts","../src/debug.ts","../src/logger.ts","../src/helpers/defineWithAccessors.ts","../src/helpers/defineEnhanceableProperty.ts","../src/helpers/defineDefaultProperty.ts","../src/helpers/defineFnComposer.ts","../src/events/emitter.ts","../src/errors.ts","../src/methods/postMessage.ts","../src/methods/targetOrigin.ts","../src/methods/postEvent.ts","../src/utils/request.ts","../src/launch-params/retrieveRawLaunchParams.ts","../src/launch-params/retrieveLaunchParams.ts","../src/env/isTMA.ts","../src/env/mockTelegramEnv.ts","../src/launch-params/retrieveRawInitData.ts","../src/methods/captureSameReq.ts","../src/utils/compareVersions.ts","../src/methods/supports.ts","../src/methods/createPostEvent.ts","../src/start-param/createStartParam.ts","../src/start-param/decodeStartParam.ts","../src/start-param/isSafeToCreateStartParam.ts","../src/utils/invokeCustomMethod.ts","../src/applyPolyfills.ts","../src/resetPackageState.ts"],"sourcesContent":["/**\n * Creates a base-64-url encoded ASCII string from the passed value.\n * @param value - the value to encode.\n * @see Learn more about base64url:\n * https://herongyang.com/Encoding/Base64URL-Encoding-Algorithm.html\n * @see Source:\n * https://developer.mozilla.org/ru/docs/Glossary/Base64#solution_1_–_escaping_the_string_before_encoding_it\n */\nexport function encodeBase64Url(value: string): string {\n // first we use encodeURIComponent to get percent-encoded UTF-8,\n // then we convert the percent encodings into raw bytes which\n // can be fed into btoa.\n return btoa(\n encodeURIComponent(value).replace(/%([0-9A-F]{2})/g, (_, p1) => {\n return String.fromCharCode(parseInt(`0x${p1}`));\n }),\n )\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}","/**\n * Decodes a base-64-url ASCII string.\n * @param value - the value to decode.\n * @throws {DOMException} If the passed value is an invalid base64url string.\n * @see Learn more about base64url:\n * https://herongyang.com/Encoding/Base64URL-Encoding-Algorithm.html\n * @see Source:\n * https://developer.mozilla.org/ru/docs/Glossary/Base64#solution_1_–_escaping_the_string_before_encoding_it\n */\nexport function decodeBase64Url(value: string): string {\n return decodeURIComponent(\n atob(value)\n .replace(/-/g, '+')\n .replace(/_/g, '/')\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n}","import { looseObject, function as fn, is } from 'valibot';\n\n/**\n * Returns true in case, passed value contains path `TelegramWebviewProxy.postEvent` property and\n * `postEvent` is a function.\n * @param value - value to check.\n */\nexport function hasWebviewProxy<T>(value: T): value is T & {\n TelegramWebviewProxy: {\n postEvent: (...args: unknown[]) => unknown;\n };\n} {\n return is(\n looseObject({ TelegramWebviewProxy: looseObject({ postEvent: fn() }) }),\n value,\n );\n}\n","/**\n * @see https://stackoverflow.com/a/326076\n * @returns True, if current environment is iframe.\n */\nexport function isIframe(): boolean {\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n}\n","import mitt, {\n type Emitter,\n type EventHandlerMap,\n type EventType,\n type Handler,\n} from 'mitt';\nimport type { If, IsNever, IsUndefined, Or } from '@telegram-apps/toolkit';\n\nexport type WildcardHandler<E> = Handler<{\n [K in keyof E]: [K, If<Or<IsNever<E[K]>, IsUndefined<E[K]>>, void, E[K]>]\n}[keyof E]>;\n\nexport interface OnFn<E> {\n /**\n * Adds a new listener for the specified event.\n * @param type - event name.\n * @param handler - event listener.\n * @param once - should listener be called only once.\n * @returns Function to remove bound event listener.\n */<K extends keyof E>(type: K, handler: Handler<E[K]>, once?: boolean): VoidFunction;\n /**\n * Adds a listener for all events.\n * @param type - event name.\n * @param handler - event listener.\n * @param once - should listener be called only once.\n * @returns Function to remove bound event listener.\n */\n (type: '*', handler: WildcardHandler<E>, once?: boolean): VoidFunction;\n}\n\nexport interface OffFn<E> {\n /**\n * Removes a listener for the specified event.\n * @param type - event to listen.\n * @param handler - event listener to remove.\n * @param once - had this listener to be called only once.\n */<K extends keyof E>(type: K, handler: Handler<E[K]>, once?: boolean): void;\n /**\n * Removes a listener for all events.\n * @param type - event to stop listening.\n * @param handler - event listener to remove.\n * @param once - had this listener to be called only once.\n */\n (type: '*', handler: WildcardHandler<E>, once?: boolean): void;\n}\n\nexport interface EmitFn<E> {\n <K extends keyof E>(type: K, event: E[K]): void;\n <K extends keyof E>(type: undefined extends E[K] ? K : never): void;\n}\n\n/**\n * Creates a new enhanced event emitter.\n * @param onFirst - will be called when the first event was added.\n * @param onEmpty - will be called when emitter's listeners' map was emptied.\n */\nexport function createEmitter<E extends object>(\n onFirst: VoidFunction,\n onEmpty: VoidFunction,\n): [\n on: OnFn<E>,\n off: OffFn<E>,\n emit: EmitFn<E>,\n clear: VoidFunction\n] {\n // To understand the event handlers concept here, let's tell the underlying idea.\n //\n // We use a Map, where key is an event name, and the value is a Map we call HandlersMap.\n //\n // The HandlersMap is a Map, where the key is an event handler, added by the developer.\n // The corresponding value is a list of tuples, with an internally generated function and a\n // boolean value responsible for determining if the handler must be called only once. So, you\n // can imagine the following map as:\n //\n // HandlersMap {\n // { developer_handler }: Array<[ internally_created_handler, once ]>;\n // }\n //\n // The value for the key represents an array of tuples, as long as a single handler may be added\n // many times, and for each addition we add a new tuple entry.\n //\n // The handler may also be added to be called only once. Trying to remove such kind of handler\n // using a different value of the \"once\" argument will lead to nothing. The developer must\n // specify the same argument value to avoid confusions.\n //\n // Here is the final EventToHandlersMap definition:\n //\n // EventToHandlersMap {\n // { event_name }: HandlersMap {\n // { developer_handler }: Array<[ internally_created_handler, once ]>;\n // }\n // }\n type HandlersMap = Map<\n (...args: any) => void,\n [handler: (...args: any) => void, once: boolean][]\n >;\n\n const eventToHandlersMap = new Map<keyof E | '*', HandlersMap>();\n\n const emitter = (mitt as any as {\n <E extends Record<EventType, unknown>>(all?: EventHandlerMap<E>): Emitter<E>;\n })<E & Record<string | symbol, unknown>>();\n\n const off: OffFn<E> = (event: keyof E | '*', handler: (...args: any) => void, once?: boolean) => {\n once ||= false;\n\n const handlersMap: HandlersMap = eventToHandlersMap.get(event) || new Map();\n eventToHandlersMap.set(event, handlersMap);\n\n const handlers = handlersMap.get(handler) || [];\n handlersMap.set(handler, handlers);\n\n const index = handlers.findIndex(item => item[1] === once);\n if (index >= 0) {\n // Remove the related handler.\n emitter.off(event, handlers[index][0]);\n\n // Remove the handler from the cache array.\n handlers.splice(index, 1);\n\n // If after removal, there are no handlers left, we should remove the entry from the cache.\n if (!handlers.length) {\n handlersMap.delete(handler);\n if (!handlersMap.size) {\n const prevSize = eventToHandlersMap.size;\n eventToHandlersMap.delete(event);\n prevSize && !eventToHandlersMap.size && onEmpty();\n }\n }\n }\n };\n\n return [\n function on(event: keyof E | '*', handler: (...args: any[]) => any, once?: boolean) {\n // The events' map became non-empty. Call the onFirst callback.\n !eventToHandlersMap.size && onFirst();\n\n const cleanup = () => {\n off(event as any, handler, once);\n };\n\n const internalHandler = (...args: any[]) => {\n once && cleanup();\n if (event === '*') {\n handler(args);\n } else {\n handler(...args);\n }\n };\n\n emitter.on(event, internalHandler);\n\n // Add this handler to the cache, so we could remove it using the passed listener.\n const handlersMap: HandlersMap = eventToHandlersMap.get(event) || new Map();\n eventToHandlersMap.set(event, handlersMap);\n\n const handlers = handlersMap.get(handler) || [];\n handlersMap.set(handler, handlers);\n handlers.push([internalHandler, once || false]);\n\n return cleanup;\n },\n off,\n // eslint-disable-next-line @typescript-eslint/unbound-method\n emitter.emit,\n function offAll() {\n const prevSize = eventToHandlersMap.size;\n emitter.all.clear();\n eventToHandlersMap.clear();\n prevSize && onEmpty();\n },\n ];\n}\n","import type { EventWithoutPayload, EventWithPayload, EventPayload } from '@/events/types/index.js';\n\n/**\n * Emits an event without payload sent from the Telegram native application like it was sent in\n * a default web environment between two iframes.\n *\n * It dispatches a new MessageEvent and expects it to be handled via\n * the `window.addEventListener('message', ...)` call, as a developer would do it to handle\n * messages sent from the parent iframe.\n * @param eventType - event name.\n */\nexport function emitEvent<E extends EventWithoutPayload>(eventType: E): void;\n\n/**\n * Emits an event with payload sent from the Telegram native application like it was sent in\n * a default web environment between two iframes.\n *\n * It dispatches a new MessageEvent and expects it to be handled via\n * the `window.addEventListener('message', ...)` call, as a developer would do it to handle\n * messages sent from the parent iframe.\n * @param eventType - event name.\n * @param eventData - event payload.\n */\nexport function emitEvent<E extends EventWithPayload>(\n eventType: E,\n eventData: EventPayload<E>,\n): void;\n\n/**\n * Emits an unknown event sent from the Telegram native application like it was sent in a default\n * web environment between two iframes.\n *\n * It dispatches a new MessageEvent and expects it to be handled via\n * the `window.addEventListener('message', ...)` call, as a developer would do it to handle\n * messages sent from the parent iframe.\n * @param eventType - event name.\n * @param eventData - event payload.\n */\nexport function emitEvent<E extends string>(\n eventType: E,\n eventData: E extends EventWithoutPayload\n ? never\n : E extends EventWithPayload\n ? EventPayload<E>\n : unknown,\n): void;\n\n/**\n * Emits an event sent from the Telegram native application like it was sent in a default web\n * environment between two iframes.\n *\n * It dispatches a new MessageEvent and expects it to be handled via\n * the `window.addEventListener('message', ...)` call, as a developer would do it to handle\n * messages sent from the parent iframe.\n * @param eventType - event name.\n * @param eventData - event payload.\n */\nexport function emitEvent(eventType: string, eventData?: unknown): void {\n window.dispatchEvent(new MessageEvent('message', {\n data: JSON.stringify({ eventType, eventData }),\n // We specify window.parent to imitate the case, the parent iframe sent us this event.\n source: window.parent,\n }));\n}\n","import type { SubscribeListener } from '@/events/types/index.js';\nimport { off, on } from '@/events/emitter.js';\nimport { logger } from '@/logger.js';\n\n/**\n * The package debug mode.\n *\n * Enabling debug mode leads to printing additional messages in the console related to the\n * processes inside the package.\n */\nexport let debug = false;\n\nconst listener: SubscribeListener = event => {\n logger().log('Event received:', event);\n};\n\n/**\n * Sets the package debug mode.\n *\n * Enabling debug mode leads to printing additional messages in the console related to the\n * processes inside the package.\n * @param value - enable debug mode.\n */\nexport function setDebug(value: boolean): void {\n if (value !== debug) {\n debug = value;\n debug ? on('*', listener) : off('*', listener);\n }\n}\n","import { createLogger, type Logger } from '@telegram-apps/toolkit';\nimport { signal } from '@telegram-apps/signals';\n\nimport { debug } from '@/debug.js';\n\nexport const logger = signal<Logger>(createLogger('Bridge', {\n bgColor: '#9147ff',\n textColor: 'white',\n shouldLog() {\n return debug;\n },\n}));\n","/**\n * Defines an enumerable and configurable property with a getter and setter.\n * @param obj - object.\n * @param prop - object property name.\n * @param get - getter to use.\n * @param set - setter to use.\n */\nexport function defineWithAccessors(\n obj: any,\n prop: string,\n get: () => unknown,\n set: (v: any) => void,\n) {\n Object.defineProperty(obj, prop, {\n enumerable: true,\n configurable: true,\n get,\n set,\n });\n}","import { defineWithAccessors } from '@/helpers/defineWithAccessors.js';\n\n/**\n * Wires the specified property in the object preventing it from being overwritten. Instead, it\n * enhances the previous value by merging the current one with the passed one.\n * @param obj - object.\n * @param prop - object property to rewire.\n */\nexport function defineEnhanceableProperty(obj: any, prop: string): void {\n const value = obj[prop];\n defineWithAccessors(obj, prop, () => value, v => {\n Object.entries(v).forEach(([objKey, objValue]) => {\n value[objKey] = objValue;\n });\n });\n}","/**\n * Defines an enumerable, configurable and writable property with initial value.\n * @param obj - object.\n * @param prop - object property name.\n * @param value - value to set.\n */\nexport function defineDefaultProperty(obj: any, prop: string, value: any): void {\n Object.defineProperty(obj, prop, {\n enumerable: true,\n configurable: true,\n writable: true,\n value,\n });\n}","import { defineWithAccessors } from '@/helpers/defineWithAccessors.js';\nimport { defineDefaultProperty } from '@/helpers/defineDefaultProperty.js';\n\n/**\n * Defines a property, that is a functions compose. Trying to set a value in this property\n * will lead to adding it to a function's pool. The property value will always be equal to a\n * function, calling all collected functions in the pool.\n *\n * Returned function performs a cleanup. It does one of the following:\n * 1. Removes the property if no functions were to the pool added other than the initial one.\n * 2. Sets the value equal to the first added function to the pool after the initial one if\n * the only one additional function was added at all. In other words, if the pool length is equal\n * to 2, the second item will be selected as the property value.\n * 3. Leaves the value equal to a function calling all pool functions, but removes the initially\n * added one.\n * @param obj - object.\n * @param prop - object property.\n * @param initialFn - an initial function to set.\n */\nexport function defineFnComposer(\n obj: any,\n prop: string,\n initialFn: (...args: any) => any,\n): void {\n const objProp = obj[prop];\n const pool: any[] = [initialFn];\n\n // Add the function to the pool.\n typeof objProp === 'function' && pool.push(objProp);\n\n // Calls all functions specified in the pool.\n const callPool = (...args: any) => {\n pool.forEach(fn => {\n fn(...args);\n });\n };\n\n // Wrap the callPool function and add \"unwrap\" method to it.\n const callPoolWrapped = Object.assign((...args: any) => {\n callPool(...args);\n }, {\n // Unwraps the composer.\n unwrap() {\n const { length: poolSize } = pool;\n if (poolSize === 1) {\n // Only the initial handler is in the pool. In this case we just remove the property.\n delete obj[prop];\n return;\n }\n if (poolSize === 2) {\n // Only one additional handler was added. We set it as a value for the property.\n defineDefaultProperty(obj, prop, pool[1]);\n return;\n }\n // Many additional handlers were added. In this case we remove the initially added function\n // from the pool and leave the property value almost as is - only \"unwrap\" method will be\n // removed.\n pool.unshift(1);\n defineDefaultProperty(obj, prop, callPool);\n }\n });\n\n // Define the composer.\n defineWithAccessors(\n obj,\n prop,\n () => callPoolWrapped,\n value => {\n pool.push(value);\n },\n );\n return;\n}","import {\n type InferOutput,\n parse,\n pipe,\n string,\n looseObject,\n optional,\n unknown,\n number,\n boolean,\n nullish,\n type BaseSchema,\n} from 'valibot';\nimport { jsonParse, MiniAppsMessageSchema, themeParams } from '@telegram-apps/transformers';\n\nimport { createEmitter } from '@/events/createEmitter.js';\nimport type { EventName, EventPayload, Events } from '@/events/types/index.js';\nimport { emitEvent } from '@/events/emitEvent.js';\nimport { logger } from '@/logger.js';\nimport { defineEnhanceableProperty } from '@/helpers/defineEnhanceableProperty.js';\nimport { defineFnComposer } from '@/helpers/defineFnComposer.js';\n\n/**\n * Transformers for problematic Mini Apps events.\n */\nconst transformers = {\n clipboard_text_received: looseObject({\n req_id: string(),\n data: nullish(string()),\n }),\n custom_method_invoked: looseObject({\n req_id: string(),\n result: optional(unknown()),\n error: optional(string()),\n }),\n popup_closed: nullish(\n looseObject({ button_id: nullish(string(), () => undefined) }),\n {},\n ),\n viewport_changed: looseObject({\n height: number(),\n width: nullish(number(), () => window.innerWidth),\n is_state_stable: boolean(),\n is_expanded: boolean(),\n }),\n theme_changed: looseObject({\n theme_params: themeParams(),\n }),\n} as const satisfies { [E in EventName]?: BaseSchema<unknown, EventPayload<E>, any> };\n\nfunction listener(event: MessageEvent): void {\n // Ignore non-parent window messages.\n if (event.source !== window.parent) {\n return;\n }\n\n // Parse incoming event data.\n let message: InferOutput<typeof MiniAppsMessageSchema>;\n try {\n message = parse(pipe(string(), jsonParse(), MiniAppsMessageSchema), event.data);\n } catch {\n // We ignore incorrect messages as they could be generated by any other code.\n return;\n }\n\n const { eventType, eventData } = message;\n const schema = transformers[eventType as keyof typeof transformers];\n\n let data: unknown;\n try {\n data = schema ? parse(schema, eventData) : eventData;\n } catch (cause) {\n return logger().forceError(\n [\n `An error occurred processing the \"${eventType}\" event from the Telegram application.`,\n 'Please, file an issue here:',\n 'https://github.com/Telegram-Mini-Apps/telegram-apps/issues/new/choose',\n ].join('\\n'),\n message,\n cause,\n );\n }\n emit(eventType as any, data);\n}\n\nexport const [\n on,\n off,\n emit,\n offAll,\n] = createEmitter<Events>(\n () => {\n const wnd = window as any;\n\n // Define all functions responsible for receiving an event from the Telegram client.\n // All these \"ports\" should narrow the communication way to a single specific one - the way\n // accepted by the web version of Telegram between iframes.\n //\n // Here we consider 2 cases:\n // 1. When the Telegram SDK is already connected. In this case the Telegram SDK already\n // installed its own ports, and we should rewire them. The cleanup function should also work\n // properly in this context, removing @telegram-apps/bridge handler only, not\n // the Telegram SDK one.\n // 2. When the Telegram SDK is not connected, but probably will be. We know, that\n // the Telegram SDK is going to overwrite our own handlers. Due to this reason, we should\n // protect them from being overwritten, but still support handlers defined by the Telegram SDK.\n\n // TelegramGameProxy.receiveEvent\n !wnd.TelegramGameProxy && (wnd.TelegramGameProxy = {});\n defineFnComposer(wnd.TelegramGameProxy, 'receiveEvent', emitEvent);\n defineEnhanceableProperty(wnd, 'TelegramGameProxy');\n\n // Telegram.WebView.receiveEvent\n !wnd.Telegram && (wnd.Telegram = {});\n !wnd.Telegram.WebView && (wnd.Telegram.WebView = {});\n defineFnComposer(wnd.Telegram.WebView, 'receiveEvent', emitEvent);\n defineEnhanceableProperty(wnd.Telegram, 'WebView');\n\n // TelegramGameProxy_receiveEvent\n defineFnComposer(wnd, 'TelegramGameProxy_receiveEvent', emitEvent);\n\n // Add a listener handling events sent from the Telegram web application and also events\n // generated by the local emitEvent function.\n // This handler should emit a new event using the library event emitter.\n window.addEventListener('message', listener);\n },\n () => {\n [\n ['TelegramGameProxy_receiveEvent'],\n ['TelegramGameProxy', 'receiveEvent'],\n ['Telegram', 'WebView', 'receiveEvent'],\n ].forEach(path => {\n const wnd = window as any;\n\n // A tuple, where the first value is the receiveEvent function owner, and the second\n // value is the receiveEvent itself.\n let cursor: [any, any] = [undefined, wnd];\n for (const item of path) {\n cursor = [cursor[1], cursor[1][item]];\n if (!cursor[1]) {\n return;\n }\n }\n const [receiveEventOwner, receiveEvent] = cursor;\n if ('unwrap' in receiveEvent) {\n receiveEvent.unwrap();\n if (\n receiveEventOwner\n && receiveEventOwner !== wnd\n && !Object.keys(receiveEventOwner).length\n ) {\n delete wnd[path[0]];\n }\n }\n });\n window.removeEventListener('message', listener);\n },\n);\n","import { errorClass, errorClassWithData } from 'error-kid';\nimport type { Version } from '@telegram-apps/types';\n\nexport const [\n MethodUnsupportedError,\n isMethodUnsupportedError,\n] = errorClass<[method: string, version: Version]>(\n 'MethodUnsupportedError',\n (method, version) => [\n `Method \"${method}\" is unsupported in Mini Apps version ${version}`,\n ],\n);\n\nexport const [\n MethodParameterUnsupportedError,\n isMethodMethodParameterUnsupportedError,\n] = errorClass<[method: string, param: string, version: Version]>(\n 'MethodParameterUnsupportedError',\n (method, param, version) => [\n `Parameter \"${param}\" of \"${method}\" method is unsupported in Mini Apps version ${version}`,\n ],\n);\n\nexport const [\n LaunchParamsRetrieveError,\n isLaunchParamsRetrieveError,\n] = errorClassWithData<\n { errors: [source: string, error: unknown][] },\n [[source: string, error: unknown][]]\n>(\n 'LaunchParamsRetrieveError',\n errors => ({ errors }),\n errors => [\n [\n 'Unable to retrieve launch parameters from any known source. Perhaps, you have opened your app outside Telegram?',\n '📖 Refer to docs for more information:',\n 'https://docs.telegram-mini-apps.com/packages/telegram-apps-bridge/environment',\n '',\n 'Collected errors:',\n ...errors.map(([source, error]) => {\n return `Source: ${source} / ${error instanceof Error ? error.message : String(error)}`;\n }),\n ].join('\\n'),\n ],\n);\n\nexport const [\n InvalidLaunchParamsError,\n isInvalidLaunchParamsError,\n] = errorClass<[launchParams: string, cause: unknown]>(\n 'InvalidLaunchParamsError',\n (launchParams, cause) => [\n `Invalid value for launch params: ${launchParams}`,\n { cause },\n ],\n);\n\nexport const [UnknownEnvError, isUnknownEnvError] = errorClass('UnknownEnvError');\n\nexport const [\n InvokeCustomMethodError,\n isInvokeCustomMethodError,\n] = errorClass<[error: string]>(\n 'InvokeCustomMethodError',\n error => [`Server returned error: ${error}`],\n);","import { signal } from '@telegram-apps/signals';\n\nimport { logger } from '@/logger.js';\n\nexport type PostMessage = typeof window.parent.postMessage;\n\n/**\n * Signal containing a custom implementation of the method to post a message to the parent\n * window. We usually use it to send a message in web versions of Telegram.\n *\n * Initially, this value contains a function behaving like the `window.parent.postMessage` method.\n */\nexport const postMessageImplementation = signal<PostMessage>((...args: any[]) => {\n try {\n window.parent.postMessage(...args as Parameters<PostMessage>);\n } catch (e) {\n if (e instanceof SyntaxError) {\n logger().forceError(\n 'Unable to call window.parent.postMessage due to incorrectly configured target origin. Use the setTargetOrigin method to allow this origin to receive events',\n e,\n );\n } else {\n logger().forceError(e);\n }\n }\n});\n\n/**\n * Posts a message to the parent window. We usually use it to send a message in web versions of\n * Telegram.\n * @param args - `window.parent.postMessage` arguments.\n */\nexport const postMessage: PostMessage = (...args) => {\n return postMessageImplementation()(...args as unknown as Parameters<PostMessage>);\n};\n","import { signal } from '@telegram-apps/signals';\n\nimport { logger } from '@/logger.js';\n\n/**\n * Target origin used by the `postEvent` method.\n *\n * You don't need to override this value until you know what you are doing.\n * @default 'https://web.telegram.org'\n */\nexport const targetOrigin = signal('https://web.telegram.org');\n\n/**\n * Sets a new target origin that is being used when calling the `postEvent` function in Telegram\n * web versions.\n * @param origin - allowed target origin value.\n * @see targetOrigin\n */\nexport function setTargetOrigin(origin: string) {\n targetOrigin.set(origin);\n logger().log('New target origin set', origin);\n}","import { is, looseObject, function as fn } from 'valibot';\n\nimport { logger } from '@/logger.js';\nimport { isIframe } from '@/env/isIframe.js';\nimport { hasWebviewProxy } from '@/env/hasWebviewProxy.js';\nimport { UnknownEnvError } from '@/errors.js';\nimport type {\n MethodName,\n MethodNameWithOptionalParams,\n MethodNameWithoutParams,\n MethodNameWithRequiredParams,\n MethodParams,\n} from '@/methods/types/index.js';\n\nimport { postMessage } from './postMessage.js';\nimport { targetOrigin } from './targetOrigin.js';\n\nexport type PostEventFn = typeof postEvent;\n\n/**\n * Calls Mini Apps methods requiring parameters.\n * @param method - method name.\n * @param params - options along with params.\n * @throws {UnknownEnvError} The environment is unknown.\n */\nexport function postEvent<Method extends MethodNameWithRequiredParams>(\n method: Method,\n params: MethodParams<Method>,\n): void;\n\n/**\n * Calls Mini Apps methods accepting no parameters at all.\n * @param method - method name.\n * @throws {UnknownEnvError} The environment is unknown.\n */\nexport function postEvent(method: MethodNameWithoutParams): void;\n\n/**\n * Calls Mini Apps methods accepting optional parameters.\n * @param method - method name.\n * @param params - options along with params.\n * @throws {UnknownEnvError} The environment is unknown.\n */\nexport function postEvent<Method extends MethodNameWithOptionalParams>(\n method: Method,\n params?: MethodParams<Method>,\n): void;\n\nexport function postEvent(\n eventType: MethodName,\n eventData?: MethodParams<MethodName>,\n): void {\n logger().log('Posting event:', eventData ? { eventType, eventData } : { eventType });\n\n const w = window;\n\n const message = JSON.stringify({ eventType, eventData });\n\n // Telegram Web.\n if (isIframe()) {\n return postMessage(message, targetOrigin());\n }\n\n // Telegram for iOS, macOS, Android and Telegram Desktop.\n if (hasWebviewProxy(w)) {\n w.TelegramWebviewProxy.postEvent(eventType, JSON.stringify(eventData));\n return;\n }\n\n // Telegram for Windows Phone or Android.\n if (is(looseObject({ external: looseObject({ notify: fn() }) }), w)) {\n w.external.notify(message);\n return;\n }\n\n // Otherwise, the current environment is unknown, and we are not able to send event.\n throw new UnknownEnvError();\n}\n","import {\n createCbCollector,\n type If,\n type IsNever,\n} from '@telegram-apps/toolkit';\nimport { AbortablePromise, type PromiseOptions } from 'better-promises';\n\nimport { on } from '@/events/emitter.js';\nimport { postEvent, PostEventFn } from '@/methods/postEvent.js';\nimport type {\n MethodName,\n MethodNameWithOptionalParams,\n MethodNameWithoutParams,\n MethodNameWithRequiredParams,\n MethodParams,\n} from '@/methods/types/index.js';\nimport type { EventName, EventPayload } from '@/events/types/events.js';\n\ntype AnyEventName = EventName | EventName[];\n\nexport type RequestCaptureFnEventsPayload<E extends EventName[]> = E extends (infer U extends EventName)[]\n ? {\n [K in U]: If<\n IsNever<EventPayload<K>>,\n { event: K },\n { event: K; payload: EventPayload<K> }\n >\n }[U]\n : never;\n\nexport type RequestCaptureEventsFn<E extends EventName[]> = (\n payload: RequestCaptureFnEventsPayload<E>,\n) => boolean\n\nexport type RequestCaptureEventFn<E extends EventName> = If<\n IsNever<EventPayload<E>>,\n () => boolean,\n (payload: EventPayload<E>) => boolean\n>;\n\nexport type RequestCaptureFn<E extends AnyEventName> = E extends EventName[]\n ? RequestCaptureEventsFn<E>\n : E extends EventName\n ? RequestCaptureEventFn<E>\n : never;\n\nexport interface RequestOptions<E extends AnyEventName>\n extends Omit<PromiseOptions, 'rejectOnAbort'> {\n /**\n * Should return true if this event should be captured.\n * The first compatible request will be captured if this property is omitted.\n */\n capture?: RequestCaptureFn<E>;\n /**\n * Custom function to call mini apps methods.\n */\n postEvent?: PostEventFn;\n}\n\nexport type RequestResult<E extends AnyEventName> =\n E extends (infer U extends EventName)[]\n ? U extends infer K extends EventName\n ? If<IsNever<EventPayload<K>>, undefined, EventPayload<K>>\n : never\n : E extends EventName\n ? If<IsNever<EventPayload<E>>, undefined, EventPayload<E>>\n : never;\n\nexport type RequestFn = typeof request;\n\n/**\n * Performs a request waiting for specified events to occur.\n *\n * This overriding is used for methods, requiring parameters.\n * @param method - method name.\n * @param eventOrEvents - tracked event or events.\n * @param options - additional options.\n */\nexport function request<M extends MethodNameWithRequiredParams, E extends AnyEventName>(\n method: M,\n eventOrEvents: E,\n options: RequestOptions<E> & { params: MethodParams<M> },\n): AbortablePromise<RequestResult<E>>;\n\n/**\n * Performs a request waiting for specified events to occur.\n *\n * This overriding is used for methods with optional parameters.\n * @param method - method name.\n * @param eventOrEvents - tracked event or events.\n * @param options - additional options.\n */\nexport function request<M extends MethodNameWithOptionalParams, E extends AnyEventName>(\n method: M,\n eventOrEvents: E,\n options?: RequestOptions<E> & { params?: MethodParams<M> },\n): AbortablePromise<RequestResult<E>>;\n\n/**\n * Performs a request waiting for specified events to occur.\n *\n * This overriding is used for methods without parameters.\n * @param method - method name.\n * @param eventOrEvents - tracked event or events.\n * @param options - additional options.\n */\nexport function request<M extends MethodNameWithoutParams, E extends AnyEventName>(\n method: M,\n eventOrEvents: E,\n options?: RequestOptions<E>,\n): AbortablePromise<RequestResult<E>>;\n\nexport function request<M extends MethodName, E extends AnyEventName>(\n method: M,\n eventOrEvents: E,\n options?: RequestOptions<E> & { params?: MethodParams<M> },\n): AbortablePromise<RequestResult<E>> {\n options ||= {};\n const { capture } = options;\n const [addCleanup, cleanup] = createCbCollector();\n\n return new AbortablePromise<RequestResult<E>>((resolve) => {\n // We need to iterate over all tracked events and create their event listeners.\n ((Array.isArray(eventOrEvents) ? eventOrEvents : [eventOrEvents])).forEach(event => {\n // Each event listener waits for the event to occur.\n // Then, if the capture function was passed, we should check if the event should be captured.\n // If the function is omitted, we instantly capture the event.\n addCleanup(\n on(event, payload => {\n if (!capture || (\n Array.isArray(eventOrEvents)\n ? (capture as RequestCaptureEventsFn<EventName[]>)({\n event,\n payload,\n } as RequestCaptureFnEventsPayload<EventName[]>)\n : (capture as RequestCaptureEventFn<EventName>)(payload)\n )) {\n resolve(payload as RequestResult<E>);\n }\n }),\n );\n });\n\n (options.postEvent || postEvent)(method as any, (options as any).params);\n }, options)\n .finally(cleanup);\n}\n","import { isLaunchParamsQuery, parseLaunchParamsQuery } from '@telegram-apps/transformers';\nimport { getStorageValue, setStorageValue } from '@telegram-apps/toolkit';\n\nimport { LaunchParamsRetrieveError } from '@/errors.js';\n\nconst SESSION_STORAGE_KEY = 'launchParams';\n\n/**\n * @param urlString - URL to extract launch parameters from.\n * @returns Launch parameters from the specified URL.\n * @throws Error if function was unable to extract launch parameters from the passed URL.\n */\nfunction fromURL(urlString: string): string {\n return urlString\n // Replace everything before this first hashtag or question sign.\n .replace(/^[^?#]*[?#]/, '')\n // Replace all hashtags and question signs to make it look like some search params.\n .replace(/[?#]/g, '&');\n}\n\n/**\n * @returns Launch parameters in a raw format from any known source.\n * @throws {LaunchParamsRetrieveError} Unable to retrieve launch parameters. They are probably\n * invalid.\n */\nexport function retrieveRawLaunchParams(): string {\n const errors: [source: string, error: unknown][] = [];\n for (const [retrieve, source] of [\n // Try to retrieve launch parameters from the current location. This method can return\n // nothing in case, location was changed, and then the page was reloaded.\n [() => fromURL(window.location.href), 'window.location.href'],\n // Then, try using the lower level API - window.performance.\n [() => {\n const navigationEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n return navigationEntry && fromURL(navigationEntry.name);\n }, 'performance navigation entries'],\n [() => getStorageValue<string>(SESSION_STORAGE_KEY), 'local storage'],\n ] as const) {\n const v = retrieve();\n if (!v) {\n errors.push([source, new Error('Source is empty')]);\n continue;\n }\n if (isLaunchParamsQuery(v)) {\n setStorageValue(SESSION_STORAGE_KEY, v);\n return v;\n }\n try {\n parseLaunchParamsQuery(v);\n } catch (e) {\n errors.push([source, e]);\n }\n }\n throw new LaunchParamsRetrieveError(errors);\n}","import { LaunchParamsSchema, parseLaunchParamsQuery } from '@telegram-apps/transformers';\nimport {\n type DeepConvertSnakeKeysToCamelCase,\n deepSnakeToCamelObjKeys,\n} from '@telegram-apps/toolkit';\nimport type { InferOutput } from 'valibot';\n\nimport { retrieveRawLaunchParams } from '@/launch-params/retrieveRawLaunchParams.js';\n\nexport type RetrieveLPResult = InferOutput<typeof LaunchParamsSchema>;\nexport type RetrieveLPResultCamelCased =\n DeepConvertSnakeKeysToCamelCase<InferOutput<typeof LaunchParamsSchema>>;\n\n/**\n * @returns Launch parameters from any known source.\n * @param camelCase - should the output be camel-cased.\n * @throws {LaunchParamsRetrieveError} Unable to retrieve launch parameters. They are probably\n * invalid.\n */\nexport function retrieveLaunchParams(camelCase?: false): RetrieveLPResult;\n\n/**\n * @returns Launch parameters from any known source.\n * @param camelCase - should the output be camel-cased.\n * @throws {LaunchParamsRetrieveError} Unable to retrieve launch parameters. They are probably\n * invalid.\n */\nexport function retrieveLaunchParams(camelCase: true): RetrieveLPResultCamelCased;\n\n/**\n * @returns Launch parameters from any known source.\n * @throws {LaunchParamsRetrieveError} Unable to retrieve launch parameters. They are probably\n * invalid.\n */\nexport function retrieveLaunchParams(camelCase?: boolean):\n | RetrieveLPResult\n | RetrieveLPResultCamelCased {\n const launchParams = parseLaunchParamsQuery(retrieveRawLaunchParams());\n return camelCase ? deepSnakeToCamelObjKeys(launchParams) : launchParams;\n}\n","import { AbortablePromise, type PromiseOptions } from 'better-promises';\n\nimport { request } from '@/utils/request.js';\nimport { hasWebviewProxy } from '@/env/hasWebviewProxy.js';\nimport { retrieveLaunchParams } from '@/launch-params/retrieveLaunchParams.js';\n\n/**\n * Returns true if the current environment is Telegram Mini Apps.\n *\n * It uses the `retrieveLaunchParams` function to determine if the environment contains\n * launch parameters. In case it does, true will be returned.\n *\n * In case you need stricter checks, use async override of this function.\n */\nexport function isTMA(): boolean;\n\n/**\n * Returns promise with true if the current environment is Telegram Mini Apps.\n *\n * First of all, it checks if the current environment contains traits specific to the\n * Mini Apps environment.\n * Then, it attempts to call a Mini Apps method and waits for a response to be received.\n *\n * In case you need less strict checks, use sync override of this function.\n */\nexport function isTMA(type: 'complete', options?: PromiseOptions): AbortablePromise<boolean>\n\nexport function isTMA(\n type?: 'complete',\n options?: PromiseOptions,\n): boolean | AbortablePromise<boolean> {\n if (!type) {\n try {\n retrieveLaunchParams();\n return true;\n } catch {\n return false;\n }\n }\n\n return AbortablePromise.fn(async context => {\n if (hasWebviewProxy(window)) {\n return true;\n }\n try {\n await request('web_app_request_theme', 'theme_changed', context);\n return true;\n } catch {\n return false;\n }\n }, options || { timeout: 100 });\n}\n","import { is, parse, pipe, string } from 'valibot';\nimport {\n isLaunchParamsQuery,\n jsonParse,\n type LaunchParamsLike,\n MiniAppsMessageSchema,\n serializeLaunchParamsQuery,\n parseLaunchParamsQuery,\n} from '@telegram-apps/transformers';\nimport { If, IsNever, setStorageValue } from '@telegram-apps/toolkit';\n\nimport { logger } from '@/logger.js';\nimport { isIframe } from '@/env/isIframe.js';\nimport type { MethodName, MethodParams } from '@/methods/types/index.js';\nimport { InvalidLaunchParamsError } from '@/errors.js';\nimport { postMessageImplementation } from '@/methods/postMessage.js';\n\n/**\n * Mocks the environment and imitates Telegram Mini Apps behavior.\n *\n * We usually use this function in the following cases:\n * 1. We are developing an application outside the Telegram environment and would like to imitate\n * the Telegram client in order to re-create the same communication behavior.\n * 2. We would like to intercept some Telegram Mini Apps methods' calls in order to enhance them\n * or write a custom behavior. It is extremely useful in some Telegram clients improperly handling\n * Mini Apps methods' calls and not even responding.\n *\n * Note that calling this function in Telegram web clients, the `postMessageImplementation` signal\n * value will be updated with a new one, enhancing previously set signal value to allow wrapping\n * the original `window.parent.postMessage` function. In other words, calling `mockTelegramEnv`\n * function N times, you will effectively wrap previously set implementation N times, so be\n * careful calling this function several times during a single lifecycle of the app. In case you\n * would like to avoid such kind of behavior, use the `resetPostMessage` option.\n */\nexport function mockTelegramEnv({ launchParams, onEvent, resetPostMessage }: {\n /**\n * Launch parameters to mock. They will be saved in the storage, so the SDK functions could\n * retrieve them.\n *\n * Note that this value must have `tgWebAppData` presented in a raw format as long as you will\n * need it when retrieving init data in this format. Otherwise, init data may be broken.\n */\n launchParams?:\n | (Omit<LaunchParamsLike, 'tgWebAppData'> & { tgWebAppData?: string | URLSearchParams })\n | string\n | URLSearchParams;\n /**\n * Function that will be called if a Mini Apps method call was requested by the mini app.\n *\n * It receives a Mini Apps method name along with the passed payload.\n *\n * Note that using the `next` function, in non-web environments it uses the\n * `window.TelegramWebviewProxy.postEvent`.\n *\n * Talking about the web versions of Telegram, the value is a bit more complex - it will\n * equal to the value stored in the `postMessageImplementation` signal set previously. By default,\n * this value contains a function utilizing the `window.parent.postMessage` method.\n * @param event - event information.\n * @param next - function to call the original method used to call a Mini Apps method.\n */\n onEvent?: (\n event: {\n [M in MethodName]: [M, If<IsNever<MethodParams<M>>, void, MethodParams<M>>]\n }[MethodName] | [string, unknown],\n next: () => void,\n ) => void;\n /**\n * Removes all previously set enhancements of the `window.parent.postMessage` function set\n * by other `mockTelegramEnv` calls.\n * @default false\n */\n resetPostMessage?: boolean;\n} = {}): void {\n if (launchParams) {\n // If launch parameters were passed, save them in the session storage, so\n // the retrieveLaunchParams function would return them.\n const launchParamsQuery =\n typeof launchParams === 'string' || launchParams instanceof URLSearchParams\n ? launchParams.toString()\n : (\n // Here we have to trick serializeLaunchParamsQuery into thinking, it serializes a valid\n // value. We are doing it because we are working with tgWebAppData presented as a\n // string, not an object as serializeLaunchParamsQuery requires.\n serializeLaunchParamsQuery({ ...launchParams, tgWebAppData: undefined })\n // Then, we just append init data.\n + (launchParams.tgWebAppData ? `&tgWebAppData=${encodeURIComponent(launchParams.tgWebAppData.toString())}` : '')\n );\n\n // Remember to check if launch params are valid.\n if (!isLaunchParamsQuery(launchParamsQuery)) {\n try {\n parseLaunchParamsQuery(launchParamsQuery);\n } catch (e) {\n throw new InvalidLaunchParamsError(launchParamsQuery, e);\n }\n }\n setStorageValue('launchParams', launchParamsQuery);\n }\n\n // Original postEvent firstly checks if the current environment is iframe.\n // That's why we have a separate branch for this environment here too.\n if (isIframe()) {\n if (!onEvent) {\n return;\n }\n const MiniAppsMessageJson = pipe(\n string(),\n jsonParse(),\n MiniAppsMessageSchema,\n );\n\n // As long as the postEvent function uses the postMessage method, we should rewire it.\n resetPostMessage && postMessageImplementation.reset();\n const original = postMessageImplementation();\n postMessageImplementation.set((...args) => {\n const [message] = args;\n const next = () => {\n (original as any)(...args);\n };\n\n // Pass only Telegram Mini Apps events to the handler. All other calls should be passed\n // to the original handler (window.parent.postMessage likely).\n if (is(MiniAppsMessageJson, message)) {\n const data = parse(MiniAppsMessageJson, message);\n onEvent([data.eventType, data.eventData], next);\n } else {\n next();\n }\n });\n\n return;\n }\n\n // In all other environments, it is enough to define window.TelegramWebviewProxy.postEvent.\n const proxy = (window as any).TelegramWebviewProxy || {};\n const postEventDefaulted = proxy.postEvent || (() => undefined);\n (window as any).TelegramWebviewProxy = {\n ...proxy,\n postEvent(eventType: string, eventData: string) {\n const next = () => {\n postEventDefaulted(eventType, eventData);\n };\n onEvent\n ? onEvent([eventType, eventData ? JSON.parse(eventData) : undefined], next)\n : next();\n },\n };\n\n logger().log('Environment was mocked by the mockTelegramEnv function');\n}\n","import { retrieveRawLaunchParams } from '@/launch-params/retrieveRawLaunchParams.js';\n\n/**\n * @returns Raw init data from any known source.\n * @throws {LaunchParamsRetrieveError} Unable to retrieve launch params from any known source.\n */\nexport function retrieveRawInitData(): string | undefined {\n return new URLSearchParams(retrieveRawLaunchParams()).get('tgWebAppData') || undefined;\n}","type CaptureSameReqFn = (payload: { req_id: string }) => boolean;\n\n/**\n * Returns a function which can be used in `request` function `capture` property to capture\n * the event with the same request identifier.\n * @param reqId - request identifier.\n */\nexport function captureSameReq(reqId: string): CaptureSameReqFn {\n return ({ req_id }) => req_id === reqId;\n}\n","import type { Version } from '@telegram-apps/types';\n\nfunction parts(a: Version): number[] {\n return a.split('.').map(Number);\n}\n\n/**\n * @param a - first version.\n * @param b - second version.\n * @returns\n * - `1` if the version \"a\" is greater than \"b\".\n * - `0` the version \"a\" is equal to \"b\".\n * - `-1` the version \"a\" is lower than \"b\".\n */\nexport function compareVersions(a: Version, b: Version): number {\n const aParts = parts(a);\n const bParts = parts(b);\n const len = Math.max(aParts.length, bParts.length);\n\n // Iterate over each part of versions and compare them. In case, part is\n // missing, assume its value is equal to 0.\n for (let i = 0; i < len; i += 1) {\n const aVal = aParts[i] || 0\n const bVal = bParts[i] || 0;\n\n if (aVal === bVal) {\n continue;\n }\n return aVal > bVal ? 1 : -1;\n }\n return 0;\n}\n","import type { Version } from '@telegram-apps/types';\n\nimport { compareVersions } from '@/utils/compareVersions.js';\nimport type {\n MethodName,\n MethodNameWithVersionedParams,\n MethodVersionedParams,\n} from '@/methods/types/index.js';\n\n/**\n * Returns true if \"a\" version is less than or equal to \"b\" version.\n * @param a\n * @param b\n */\nfunction versionLessOrEqual(a: Version, b: Version): boolean {\n return compareVersions(a, b) <= 0;\n}\n\n/**\n * Returns true in case, passed parameter in specified method is supported.\n * @param method - method name\n * @param param - method parameter\n * @param inVersion - platform version.\n */\nexport function supports<M extends MethodNameWithVersionedParams>(\n method: M,\n param: MethodVersionedParams<M>,\n inVersion: Version,\n): boolean;\n\n/**\n * Returns true in case, specified method is supported in a passed version.\n * @param method - method name.\n * @param inVersion - platform version.\n */\nexport function supports(method: MethodName, inVersion: Version): boolean;\n\nexport function supports(\n method: MethodName,\n paramOrVersion: Version | string,\n inVersion?: string,\n): boolean {\n // Method name, parameter, target version.\n if (typeof inVersion === 'string') {\n if (method === 'web_app_open_link') {\n if (paramOrVersion === 'try_instant_view') {\n return versionLessOrEqual('6.4', inVersion);\n }\n if (paramOrVersion === 'try_browser') {\n return versionLessOrEqual('7.6', inVersion);\n }\n }\n\n if (method === 'web_app_set_header_color') {\n if (paramOrVersion === 'color') {\n return versionLessOrEqual('6.9', inVersion);\n }\n }\n\n if (method === 'web_app_close' && paramOrVersion === 'return_back') {\n return versionLessOrEqual('7.6', inVersion);\n }\n\n if (method === 'web_app_setup_main_button' && paramOrVersion === 'has_shine_effect') {\n return versionLessOrEqual('7.10', inVersion);\n }\n }\n\n switch (method) {\n case 'web_app_open_tg_link':\n case 'web_app_open_invoice':\n case 'web_app_setup_back_button':\n case 'web_app_set_background_color':\n case 'web_app_set_header_color':\n case 'web_app_trigger_haptic_feedback':\n return versionLessOrEqual('6.1', paramOrVersion);\n case 'web_app_open_popup':\n return versionLessOrEqual('6.2', paramOrVersion);\n case 'web_app_close_scan_qr_popup':\n case 'web_app_open_scan_qr_popup':\n case 'web_app_read_text_from_clipboard':\n return versionLessOrEqual('6.4', paramOrVersion);\n case 'web_app_switch_inline_query':\n return versionLessOrEqual('6.7', paramOrVersion);\n case 'web_app_invoke_custom_method':\n case 'web_app_request_write_access