├── playgrounds ├── solid │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── Link │ │ │ │ └── Link.css │ │ │ ├── Page │ │ │ │ ├── Page.css │ │ │ │ └── Page.tsx │ │ │ ├── RGB │ │ │ │ ├── RGB.css │ │ │ │ └── RGB.tsx │ │ │ └── DisplayData │ │ │ │ └── DisplayData.css │ │ ├── pages │ │ │ ├── InitDataPage │ │ │ │ └── InitDataPage.css │ │ │ ├── IndexPage │ │ │ │ └── IndexPage.css │ │ │ └── TonConnectPage │ │ │ │ └── TonConnectPage.css │ │ ├── tonconnect │ │ │ ├── useTonConnectUI.ts │ │ │ ├── TonConnectButton.tsx │ │ │ ├── TonConnectUIContext.ts │ │ │ └── useTonWallet.ts │ │ ├── index.css │ │ └── index.tsx │ ├── assets │ │ └── ssl-warning.png │ ├── .github │ │ └── deployment-branches.png │ ├── public │ │ └── tonconnect-manifest.json │ ├── tsconfig.node.json │ ├── .eslintrc.cjs │ ├── index.html │ └── tsconfig.json ├── react │ ├── src │ │ ├── vite-env.d.ts │ │ ├── components │ │ │ ├── Link │ │ │ │ └── Link.css │ │ │ ├── Page │ │ │ │ ├── Page.css │ │ │ │ └── Page.tsx │ │ │ ├── RGB │ │ │ │ ├── RGB.css │ │ │ │ └── RGB.tsx │ │ │ └── DisplayData │ │ │ │ └── DisplayData.css │ │ ├── index.css │ │ ├── pages │ │ │ ├── InitDataPage │ │ │ │ └── InitDataPage.css │ │ │ ├── TONConnectPage │ │ │ │ └── TONConnectPage.css │ │ │ ├── IndexPage │ │ │ │ └── IndexPage.css │ │ │ └── ThemeParamsPage │ │ │ │ └── ThemeParamsPage.tsx │ │ └── index.tsx │ ├── assets │ │ ├── application.png │ │ └── ssl-warning.png │ ├── .github │ │ └── deployment-branches.png │ ├── public │ │ └── tonconnect-manifest.json │ ├── tsconfig.node.json │ ├── .gitignore │ ├── .eslintrc.cjs │ ├── index.html │ └── tsconfig.json ├── next │ ├── .eslintrc.json │ ├── src │ │ ├── app │ │ │ ├── _assets │ │ │ │ └── globals.css │ │ │ ├── favicon.ico │ │ │ ├── error.tsx │ │ │ ├── ton-connect │ │ │ │ └── styles.css │ │ │ ├── layout.tsx │ │ │ ├── theme-params │ │ │ │ └── page.tsx │ │ │ └── launch-params │ │ │ │ └── page.tsx │ │ ├── components │ │ │ ├── Link │ │ │ │ └── styles.css │ │ │ ├── Root │ │ │ │ └── styles.css │ │ │ ├── RGB │ │ │ │ ├── styles.css │ │ │ │ └── RGB.tsx │ │ │ ├── DisplayData │ │ │ │ └── styles.css │ │ │ └── ErrorPage.tsx │ │ └── hooks │ │ │ ├── useClientOnce.ts │ │ │ └── useDidMount.ts │ ├── assets │ │ └── ssl-warning.png │ ├── next.config.mjs │ ├── public │ │ └── tonconnect-manifest.json │ ├── postcss.config.mjs │ ├── .gitignore │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── package.json └── custom │ ├── tsconfig.node.json │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── index.html │ └── vite.config.ts ├── packages ├── cli-utils │ ├── src │ │ └── index.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── package.json ├── create-mini-app │ ├── tsconfig.eslint.json │ ├── src │ │ ├── utils │ │ │ ├── usePointer.ts │ │ │ ├── usePromptPrefix.ts │ │ │ ├── lines.ts │ │ │ ├── spaces.ts │ │ │ └── useInputPrefix.ts │ │ ├── isGitInstalled.ts │ │ ├── types.ts │ │ ├── theme.ts │ │ └── prompts │ │ │ └── promptTemplate │ │ │ └── types.ts │ ├── tsconfig.json │ └── vite.config.ts ├── solid-router-integration │ ├── src │ │ └── index.ts │ ├── tsconfig.eslint.json │ ├── CHANGELOG.md │ ├── tsconfig.node.json │ ├── tsconfig.json │ └── vite.config.ts ├── init-data-node │ ├── tsconfig.eslint.json │ ├── tsconfig.node.json │ ├── tsconfig.json │ ├── src │ │ ├── hashToken.ts │ │ ├── hashToken.test.ts │ │ ├── entries │ │ │ └── shared.ts │ │ └── types.ts │ └── CHANGELOG.md ├── react-router-integration │ ├── tsconfig.eslint.json │ ├── src │ │ └── index.ts │ ├── CHANGELOG.md │ ├── tsconfig.node.json │ ├── tsconfig.json │ └── vite.config.ts ├── sdk │ ├── tsconfig.eslint.json │ ├── src │ │ ├── events │ │ │ ├── types.ts │ │ │ ├── onWindow.ts │ │ │ └── onWindow.test.ts │ │ ├── version │ │ │ ├── types.ts │ │ │ └── compareVersions.test.ts │ │ ├── bridge │ │ │ ├── methods │ │ │ │ └── types │ │ │ │ │ └── index.ts │ │ │ ├── events │ │ │ │ ├── event-handlers │ │ │ │ │ ├── cleanupEventHandlers.ts │ │ │ │ │ ├── cleanupEventHandlers.test.ts │ │ │ │ │ ├── emitMiniAppsEvent.ts │ │ │ │ │ ├── emitMiniAppsEvent.test.ts │ │ │ │ │ └── defineEventHandlers.test.ts │ │ │ │ ├── listening │ │ │ │ │ ├── off.ts │ │ │ │ │ ├── unsubscribe.ts │ │ │ │ │ ├── subscribe.ts │ │ │ │ │ └── on.ts │ │ │ │ └── event-emitter │ │ │ │ │ └── singleton.test.ts │ │ │ ├── captureSameReq.test.ts │ │ │ ├── captureSameReq.ts │ │ │ ├── target-origin.test.ts │ │ │ ├── parseMessage.ts │ │ │ ├── target-origin.ts │ │ │ └── parseMessage.test.ts │ │ ├── env │ │ │ ├── isSSR.ts │ │ │ ├── isIframe.ts │ │ │ ├── isSSR.test.ts │ │ │ ├── isTMA.ts │ │ │ ├── hasExternalNotify.ts │ │ │ ├── hasExternalNotify.test.ts │ │ │ ├── hasWebviewProxy.test.ts │ │ │ ├── hasWebviewProxy.ts │ │ │ └── isIframe.test.ts │ │ ├── supports │ │ │ ├── types.ts │ │ │ └── createSupportsFn.ts │ │ ├── colors │ │ │ ├── types.ts │ │ │ ├── isRGB.ts │ │ │ ├── isRGBShort.ts │ │ │ ├── isRGB.test.ts │ │ │ ├── isRGBShort.test.ts │ │ │ ├── isColorDark.test.ts │ │ │ ├── toRGB.test.ts │ │ │ └── isColorDark.ts │ │ ├── types │ │ │ ├── index.ts │ │ │ ├── misc.ts │ │ │ ├── platform.ts │ │ │ ├── methods.ts │ │ │ ├── logical.ts │ │ │ ├── unions.ts │ │ │ └── utils.ts │ │ ├── navigation │ │ │ ├── go.test.ts │ │ │ ├── ensurePrefix.ts │ │ │ ├── isPageReload.ts │ │ │ ├── getPathname.ts │ │ │ ├── ensurePrefix.test.ts │ │ │ ├── getHash.test.ts │ │ │ ├── getFirstNavigationEntry.ts │ │ │ ├── urlToPath.test.ts │ │ │ ├── getHash.ts │ │ │ ├── createSafeURL.ts │ │ │ ├── BrowserNavigator │ │ │ │ ├── basicItemToBrowser.ts │ │ │ │ └── basicItemToBrowser.test.ts │ │ │ ├── urlToPath.ts │ │ │ ├── getFirstNavigationEntry.test.ts │ │ │ ├── createSafeURL.test.ts │ │ │ ├── getPathname.test.ts │ │ │ ├── isPageReload.test.ts │ │ │ └── go.ts │ │ ├── css-vars │ │ │ ├── setCSSVar.ts │ │ │ └── setCSSVar.test.ts │ │ ├── timeout │ │ │ ├── sleep.ts │ │ │ ├── createTimeoutError.ts │ │ │ ├── createTimeoutError.test.ts │ │ │ ├── sleep.test.ts │ │ │ └── withTimeout.ts │ │ ├── misc │ │ │ ├── isRecord.ts │ │ │ ├── objectFromKeys.ts │ │ │ ├── objectFromKeys.test.ts │ │ │ ├── isRecord.test.ts │ │ │ ├── createCleanup.ts │ │ │ └── createSingleton.ts │ │ ├── errors │ │ │ ├── isSDKError.ts │ │ │ ├── SDKError.ts │ │ │ ├── isSDKError.test.ts │ │ │ ├── isSDKErrorOfType.ts │ │ │ ├── createError.ts │ │ │ ├── isSDKErrorOfType.test.ts │ │ │ └── createError.test.ts │ │ ├── request-id │ │ │ ├── createRequestIdGenerator.ts │ │ │ ├── types.ts │ │ │ └── createRequestIdGenerator.test.ts │ │ ├── components │ │ │ ├── InitData │ │ │ │ ├── parseInitData.ts │ │ │ │ ├── initInitData.ts │ │ │ │ └── parsers │ │ │ │ │ └── chat.ts │ │ │ ├── Popup │ │ │ │ └── initPopup.ts │ │ │ ├── Invoice │ │ │ │ ├── initInvoice.ts │ │ │ │ └── types.ts │ │ │ ├── ThemeParams │ │ │ │ ├── parsing │ │ │ │ │ ├── parseThemeParams.ts │ │ │ │ │ ├── serializeThemeParams.ts │ │ │ │ │ └── themeParams.ts │ │ │ │ ├── initThemeParams.ts │ │ │ │ ├── keys.ts │ │ │ │ ├── requestThemeParams.ts │ │ │ │ └── keys.test.ts │ │ │ ├── QRScanner │ │ │ │ ├── initQRScanner.ts │ │ │ │ └── types.ts │ │ │ ├── HapticFeedback │ │ │ │ └── initHapticFeedback.ts │ │ │ ├── Utils │ │ │ │ └── initUtils.ts │ │ │ ├── BackButton │ │ │ │ ├── initBackButton.ts │ │ │ │ └── types.ts │ │ │ ├── CloudStorage │ │ │ │ └── initCloudStorage.ts │ │ │ ├── ClosingBehavior │ │ │ │ ├── initClosingBehavior.ts │ │ │ │ └── types.ts │ │ │ ├── SettingsButton │ │ │ │ ├── initSettingsButton.ts │ │ │ │ └── types.ts │ │ │ ├── SwipeBehavior │ │ │ │ ├── initSwipeBehavior.ts │ │ │ │ └── types.ts │ │ │ ├── MiniApp │ │ │ │ ├── initMiniApp.ts │ │ │ │ └── parsing │ │ │ │ │ └── contact.test.ts │ │ │ ├── BiometryManager │ │ │ │ └── requestBiometryInfo.ts │ │ │ ├── MainButton │ │ │ │ └── initMainButton.ts │ │ │ └── Viewport │ │ │ │ └── requestViewport.ts │ │ ├── parsing │ │ │ ├── parsers │ │ │ │ ├── array.ts │ │ │ │ ├── date.ts │ │ │ │ ├── rgb.ts │ │ │ │ ├── string.ts │ │ │ │ ├── json.ts │ │ │ │ ├── number.ts │ │ │ │ ├── boolean.ts │ │ │ │ ├── rgb.test.ts │ │ │ │ ├── number.test.ts │ │ │ │ ├── date.test.ts │ │ │ │ └── string.test.ts │ │ │ ├── createTypeError.ts │ │ │ ├── toRecord.test.ts │ │ │ ├── createValueParserGenerator.ts │ │ │ ├── ArrayParser │ │ │ │ ├── ArrayParser.test.ts │ │ │ │ └── types.ts │ │ │ ├── types.ts │ │ │ └── toRecord.ts │ │ ├── launch-params │ │ │ ├── retrieveFromLocation.ts │ │ │ ├── saveToStorage.ts │ │ │ ├── retrieveFromStorage.ts │ │ │ ├── retrieveFromPerformance.ts │ │ │ ├── retrieveFromUrl.ts │ │ │ ├── saveToStorage.test.ts │ │ │ ├── retrieveFromLocation.test.ts │ │ │ └── retrieveFromPerformance.test.ts │ │ ├── classes │ │ │ ├── WithTrackableState.ts │ │ │ ├── WithStateUtils.ts │ │ │ ├── WithSupportsAndTrackableState.ts │ │ │ ├── WithSupports.ts │ │ │ └── State │ │ │ │ └── types.ts │ │ └── classnames │ │ │ ├── classNames.test.ts │ │ │ └── mergeClassNames.test.ts │ ├── vite.iife.config.ts │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── test-utils │ │ ├── types.ts │ │ └── dispatchWindowMessageEvent.ts │ ├── vite.iife.low-level.config.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── tsconfig.test.json │ └── CHANGELOG.md ├── test-utils │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── document.ts │ │ ├── performance.ts │ │ ├── toSearchParams.ts │ │ ├── session-storage.ts │ │ └── window.ts │ └── package.json ├── tsconfig │ ├── package.json │ ├── esnext.json │ ├── esnext-dom.json │ └── base.json ├── sdk-solid │ ├── tsconfig.node.json │ ├── tsconfig.json │ ├── src │ │ ├── hooks-hocs │ │ │ ├── popup.ts │ │ │ ├── utils.ts │ │ │ ├── invoice.ts │ │ │ ├── mini-app.ts │ │ │ ├── viewport.ts │ │ │ ├── init-data.ts │ │ │ ├── qr-scanner.ts │ │ │ ├── back-button.ts │ │ │ ├── main-button.ts │ │ │ ├── theme-params.ts │ │ │ ├── cloud-storage.ts │ │ │ ├── swipe-behavior.ts │ │ │ ├── haptic-feedback.ts │ │ │ ├── settings-button.ts │ │ │ ├── biometry-manager.ts │ │ │ └── closing-behavior.ts │ │ ├── SDKProvider │ │ │ └── SDKContext.ts │ │ └── createHOC.tsx │ ├── vite.config.ts │ └── CHANGELOG.md └── sdk-react │ ├── tsconfig.node.json │ ├── tsconfig.json │ ├── src │ ├── hooks-hocs │ │ ├── popup.ts │ │ ├── utils.ts │ │ ├── invoice.ts │ │ ├── mini-app.ts │ │ ├── init-data.ts │ │ ├── viewport.ts │ │ ├── qr-scanner.ts │ │ ├── main-button.ts │ │ ├── theme-params.ts │ │ ├── back-button.ts │ │ ├── cloud-storage.ts │ │ ├── swipe-behavior.ts │ │ ├── haptic-feedback.ts │ │ ├── settings-button.ts │ │ ├── biometry-manager.ts │ │ ├── closing-behavior.ts │ │ └── launch-params.ts │ └── SDKProvider │ │ └── SDKContext.ts │ ├── vite.config.ts │ └── CHANGELOG.md ├── .npmrc ├── apps └── docs │ ├── public │ ├── google9b165606060ff733.html │ ├── favicon.ico │ ├── logo.db0268ac.png │ ├── components │ │ ├── popup.png │ │ ├── back-button.png │ │ ├── main-button.png │ │ ├── viewport │ │ │ ├── views.png │ │ │ ├── views@2x.png │ │ │ ├── expansion.png │ │ │ └── expansion@2x.png │ │ └── settings-button.png │ ├── functionality │ │ ├── theming.png │ │ ├── swipe-behavior.png │ │ └── closing-confirmation.png │ ├── untrusted-cert-warning.png │ └── thumbnail-1200x630.6b8f54aa217a6baed4703ad5af866677.png │ ├── .vitepress │ └── theme │ │ ├── index.ts │ │ └── custom.css │ ├── package.json │ ├── packages │ └── telegram-apps-sdk │ │ └── init-data │ │ └── chat.md │ ├── platform │ ├── popup.md │ ├── swipe-behavior.md │ ├── closing-behavior.md │ ├── settings-button.md │ └── back-button.md │ └── index.md ├── pnpm-workspace.yaml ├── vitest.workspace.ts ├── .changeset ├── config.json └── README.md ├── turbo.json ├── .gitignore └── .github └── workflows └── test-pull-request.yml /playgrounds/solid/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pem 3 | node_modules 4 | dist -------------------------------------------------------------------------------- /packages/cli-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './spawnWithSpinner.js'; 2 | -------------------------------------------------------------------------------- /playgrounds/react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/cli-utils/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /playgrounds/next/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-mini-app/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /packages/solid-router-integration/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createRouter.js'; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | always-auth=true 3 | registry="https://registry.npmjs.org" -------------------------------------------------------------------------------- /packages/init-data-node/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src" 4 | ] 5 | } -------------------------------------------------------------------------------- /apps/docs/public/google9b165606060ff733.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google9b165606060ff733.html -------------------------------------------------------------------------------- /packages/react-router-integration/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /packages/solid-router-integration/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | - "playgrounds/*" 5 | -------------------------------------------------------------------------------- /packages/react-router-integration/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useIntegration } from './useIntegration.js'; 2 | -------------------------------------------------------------------------------- /apps/docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/favicon.ico -------------------------------------------------------------------------------- /packages/cli-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext.json", 3 | "include": [ 4 | "src" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/sdk/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "include": [ 4 | "src" 5 | ] 6 | } -------------------------------------------------------------------------------- /playgrounds/next/src/app/_assets/globals.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: var(--tg-theme-secondary-bg-color, white); 3 | } 4 | -------------------------------------------------------------------------------- /apps/docs/public/logo.db0268ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/logo.db0268ac.png -------------------------------------------------------------------------------- /packages/test-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "include": [ 4 | "src" 5 | ] 6 | } -------------------------------------------------------------------------------- /playgrounds/next/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/next/src/app/favicon.ico -------------------------------------------------------------------------------- /playgrounds/next/src/components/Link/styles.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: var(--tg-theme-link-color); 4 | } -------------------------------------------------------------------------------- /playgrounds/react/src/components/Link/Link.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: var(--tg-theme-link-color); 4 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/components/Link/Link.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: var(--tg-theme-link-color); 4 | } -------------------------------------------------------------------------------- /apps/docs/public/components/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/popup.png -------------------------------------------------------------------------------- /playgrounds/next/assets/ssl-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/next/assets/ssl-warning.png -------------------------------------------------------------------------------- /apps/docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import './custom.css'; 3 | 4 | export default DefaultTheme; -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.2", 4 | "private": true, 5 | "license": "MIT" 6 | } 7 | -------------------------------------------------------------------------------- /playgrounds/next/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /playgrounds/react/assets/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/react/assets/application.png -------------------------------------------------------------------------------- /playgrounds/react/assets/ssl-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/react/assets/ssl-warning.png -------------------------------------------------------------------------------- /playgrounds/react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: var(--tg-theme-secondary-bg-color, white); 3 | padding: 0; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /playgrounds/solid/assets/ssl-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/solid/assets/ssl-warning.png -------------------------------------------------------------------------------- /apps/docs/public/components/back-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/back-button.png -------------------------------------------------------------------------------- /apps/docs/public/components/main-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/main-button.png -------------------------------------------------------------------------------- /apps/docs/public/functionality/theming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/functionality/theming.png -------------------------------------------------------------------------------- /apps/docs/public/untrusted-cert-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/untrusted-cert-warning.png -------------------------------------------------------------------------------- /playgrounds/next/src/app/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ErrorPage } from '@/components/ErrorPage'; 4 | 5 | export default ErrorPage; 6 | -------------------------------------------------------------------------------- /apps/docs/public/components/viewport/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/viewport/views.png -------------------------------------------------------------------------------- /packages/tsconfig/esnext.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "ESNext", 4 | "extends": "./base.json" 5 | } -------------------------------------------------------------------------------- /apps/docs/public/components/settings-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/settings-button.png -------------------------------------------------------------------------------- /apps/docs/public/components/viewport/views@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/viewport/views@2x.png -------------------------------------------------------------------------------- /apps/docs/public/functionality/swipe-behavior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/functionality/swipe-behavior.png -------------------------------------------------------------------------------- /playgrounds/react/.github/deployment-branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/react/.github/deployment-branches.png -------------------------------------------------------------------------------- /playgrounds/solid/.github/deployment-branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/playgrounds/solid/.github/deployment-branches.png -------------------------------------------------------------------------------- /apps/docs/public/components/viewport/expansion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/viewport/expansion.png -------------------------------------------------------------------------------- /playgrounds/next/public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://ton.vote", 3 | "name": "TON Vote", 4 | "iconUrl": "https://ton.vote/logo.png" 5 | } -------------------------------------------------------------------------------- /playgrounds/react/public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://ton.vote", 3 | "name": "TON Vote", 4 | "iconUrl": "https://ton.vote/logo.png" 5 | } -------------------------------------------------------------------------------- /playgrounds/solid/public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://ton.vote", 3 | "name": "TON Vote", 4 | "iconUrl": "https://ton.vote/logo.png" 5 | } -------------------------------------------------------------------------------- /apps/docs/public/components/viewport/expansion@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/components/viewport/expansion@2x.png -------------------------------------------------------------------------------- /packages/sdk/src/events/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function, which removes bound event listener. 3 | */ 4 | export interface RemoveEventListenerFn { 5 | (): void; 6 | } 7 | -------------------------------------------------------------------------------- /packages/sdk/src/version/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Telegram Mini Apps version in format like "\d+.\d+". 3 | * @example "7.0" 4 | */ 5 | export type Version = string; 6 | -------------------------------------------------------------------------------- /apps/docs/public/functionality/closing-confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/functionality/closing-confirmation.png -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config'; 2 | 3 | export default defineWorkspace([ 4 | 'packages/*', 5 | 'packages/*/vite.config.ts', 6 | ]); -------------------------------------------------------------------------------- /packages/create-mini-app/src/utils/usePointer.ts: -------------------------------------------------------------------------------- 1 | import { theme } from '../theme.js'; 2 | 3 | export function usePointer(): string { 4 | return theme.prefixes.pointer; 5 | } -------------------------------------------------------------------------------- /packages/sdk/vite.iife.config.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from './build/getConfig'; 2 | 3 | export default getConfig({ 4 | input: 'src/index.ts', 5 | formats: ['iife'], 6 | }); -------------------------------------------------------------------------------- /playgrounds/react/src/components/Page/Page.css: -------------------------------------------------------------------------------- 1 | .page { 2 | padding: 0 10px; 3 | box-sizing: border-box; 4 | } 5 | 6 | .page__disclaimer { 7 | margin-bottom: 16px; 8 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/components/Page/Page.css: -------------------------------------------------------------------------------- 1 | .page { 2 | padding: 0 10px; 3 | box-sizing: border-box; 4 | } 5 | 6 | .page__disclaimer { 7 | margin-bottom: 16px; 8 | } -------------------------------------------------------------------------------- /packages/sdk/src/bridge/methods/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './custom-methods.js'; 2 | export * from './haptic.js'; 3 | export * from './methods.js'; 4 | export * from './popup.js'; 5 | -------------------------------------------------------------------------------- /packages/sdk/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "build" 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/sdk/src/env/isSSR.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @returns True, if current environment is server. 3 | */ 4 | export function isSSR(): boolean { 5 | return typeof window === 'undefined'; 6 | } 7 | -------------------------------------------------------------------------------- /packages/sdk/src/supports/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function which returns true in case, specified method is supported. 3 | */ 4 | export type SupportsFn = (method: M) => boolean; 5 | -------------------------------------------------------------------------------- /packages/sdk/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from './build/getConfig'; 2 | 3 | export default getConfig({ 4 | input: 'src/index.ts', 5 | formats: ['es', 'cjs'], 6 | declarations: true, 7 | }); -------------------------------------------------------------------------------- /playgrounds/next/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /apps/docs/public/thumbnail-1200x630.6b8f54aa217a6baed4703ad5af866677.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNaokiDev/telegram-Apps/HEAD/apps/docs/public/thumbnail-1200x630.6b8f54aa217a6baed4703ad5af866677.png -------------------------------------------------------------------------------- /packages/test-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './document'; 2 | export * from './performance'; 3 | export * from './session-storage'; 4 | export * from './toSearchParams'; 5 | export * from './window'; 6 | -------------------------------------------------------------------------------- /packages/react-router-integration/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/react-router-integration 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [54adc6f] 8 | - @telegram-apps/sdk@1.1.0 9 | -------------------------------------------------------------------------------- /packages/solid-router-integration/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/solid-router-integration 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [54adc6f] 8 | - @telegram-apps/sdk@1.1.0 9 | -------------------------------------------------------------------------------- /packages/solid-router-integration/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "vite.config.ts" 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/sdk/src/colors/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Color in format #RGB. 3 | */ 4 | export type RGBShort = `#${string}`; 5 | 6 | /** 7 | * Color in format #RRGGBB. 8 | */ 9 | export type RGB = `#${string}`; 10 | -------------------------------------------------------------------------------- /playgrounds/react/src/pages/InitDataPage/InitDataPage.css: -------------------------------------------------------------------------------- 1 | .init-data-page__section + .init-data-page__section { 2 | margin-top: 12px; 3 | } 4 | 5 | .init-data-page__section-title { 6 | margin-bottom: 4px; 7 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/pages/InitDataPage/InitDataPage.css: -------------------------------------------------------------------------------- 1 | .init-data-page__section + .init-data-page__section { 2 | margin-top: 12px; 3 | } 4 | 5 | .init-data-page__section-title { 6 | margin-bottom: 4px; 7 | } -------------------------------------------------------------------------------- /packages/sdk-solid/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "vite.config.ts", 8 | "package.json" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/sdk/test-utils/types.ts: -------------------------------------------------------------------------------- 1 | import { SpyInstance } from 'vitest'; 2 | 3 | /** 4 | * Converts function to spy. 5 | */ 6 | export type FnToSpy any> = SpyInstance, ReturnType>; -------------------------------------------------------------------------------- /packages/create-mini-app/src/utils/usePromptPrefix.ts: -------------------------------------------------------------------------------- 1 | import { theme } from '../theme.js'; 2 | 3 | export function usePromptPrefix(completed: boolean): string { 4 | return theme.prefixes[completed ? 'completed' : 'pending']; 5 | } -------------------------------------------------------------------------------- /packages/create-mini-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "types": ["node"] 6 | }, 7 | "include": [ 8 | "src" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/sdk-react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "vite.config.ts", 8 | "package.json" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/sdk/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logical.js'; 2 | export * from './methods.js'; 3 | export * from './misc.js'; 4 | export * from './platform.js'; 5 | export * from './unions.js'; 6 | export * from './utils.js'; 7 | -------------------------------------------------------------------------------- /packages/sdk/vite.iife.low-level.config.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from './build/getConfig'; 2 | 3 | export default getConfig({ 4 | input: 'src/index.low-level.ts', 5 | filename: 'index.low-level', 6 | formats: ['iife'], 7 | }); -------------------------------------------------------------------------------- /packages/init-data-node/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "vite.config.ts", 8 | "package.json" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/create-mini-app/src/utils/lines.ts: -------------------------------------------------------------------------------- 1 | type Item = string | null | boolean | undefined; 2 | 3 | export function lines(...arr: (Item | Item[])[]): string { 4 | return arr.flat(1).filter(v => typeof v === 'string').join('\n'); 5 | } 6 | -------------------------------------------------------------------------------- /packages/create-mini-app/src/utils/spaces.ts: -------------------------------------------------------------------------------- 1 | type Item = string | null | boolean | undefined; 2 | 3 | export function spaces(...arr: (Item | Item[])[]): string { 4 | return arr.flat(1).filter(v => typeof v === 'string').join(' '); 5 | } 6 | -------------------------------------------------------------------------------- /packages/sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.test.json" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/solid-router-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "references": [ 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /playgrounds/custom/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "module": "NodeNext" 6 | }, 7 | "include": [ 8 | "vite.config.ts" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/react-router-integration/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": [ 7 | "vite.config.ts", 8 | "package.json" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/react-router-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "references": [ 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next/src/components/Root/styles.css: -------------------------------------------------------------------------------- 1 | .root__loading { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | } -------------------------------------------------------------------------------- /packages/sdk/src/navigation/go.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { go } from './go.js'; 4 | 5 | // TODO: Add more tests. 6 | 7 | it('should return true if delta is 0', () => { 8 | expect(go(0)).resolves.toBe(true); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/sdk/src/types/misc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Any function. 3 | */ 4 | export interface AnyFn { 5 | (...args: any): any; 6 | } 7 | 8 | /** 9 | * Function which performs some cleanup. 10 | */ 11 | export interface CleanupFn { 12 | (): void; 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/next/src/components/RGB/styles.css: -------------------------------------------------------------------------------- 1 | .rgb { 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 5px; 5 | } 6 | 7 | .rgb__icon { 8 | width: 18px; 9 | aspect-ratio: 1; 10 | border: 1px solid #555; 11 | border-radius: 50%; 12 | } -------------------------------------------------------------------------------- /playgrounds/react/src/components/RGB/RGB.css: -------------------------------------------------------------------------------- 1 | .rgb { 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 5px; 5 | } 6 | 7 | .rgb__icon { 8 | width: 18px; 9 | aspect-ratio: 1; 10 | border: 1px solid #555; 11 | border-radius: 50%; 12 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/components/RGB/RGB.css: -------------------------------------------------------------------------------- 1 | .rgb { 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 5px; 5 | } 6 | 7 | .rgb__icon { 8 | width: 18px; 9 | aspect-ratio: 1; 10 | border: 1px solid #555; 11 | border-radius: 50%; 12 | } -------------------------------------------------------------------------------- /packages/tsconfig/esnext-dom.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "ESNext DOM", 4 | "extends": "./esnext.json", 5 | "compilerOptions": { 6 | "lib": [ 7 | "esnext", 8 | "dom" 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /packages/init-data-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "src/**/*.test.ts" 8 | ], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.node.json" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/create-mini-app/src/utils/useInputPrefix.ts: -------------------------------------------------------------------------------- 1 | import figures from 'figures'; 2 | 3 | import { theme } from '../theme.js'; 4 | 5 | export function useInputPrefix(completed: boolean): string { 6 | return theme.style.muted(completed ? figures.ellipsis : figures.pointerSmall); 7 | } -------------------------------------------------------------------------------- /playgrounds/next/src/hooks/useClientOnce.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | export function useClientOnce(fn: () => void): void { 4 | const canCall = useRef(true); 5 | if (typeof window !== 'undefined' && canCall.current) { 6 | canCall.current = false; 7 | fn(); 8 | } 9 | } -------------------------------------------------------------------------------- /packages/init-data-node/src/hashToken.ts: -------------------------------------------------------------------------------- 1 | import type { CreateHmacFn } from './types.js'; 2 | 3 | export function hashToken>( 4 | token: string | Buffer, 5 | createHmac: H 6 | ): ReturnType { 7 | return createHmac(token, 'WebAppData') as ReturnType; 8 | } 9 | -------------------------------------------------------------------------------- /packages/sdk-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx" 5 | }, 6 | "include": [ 7 | "src" 8 | ], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.node.json" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/sdk/src/css-vars/setCSSVar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets CSS variable globally. 3 | * @param name - variable name. 4 | * @param value - variable value. 5 | */ 6 | export function setCSSVar(name: string, value: string): void { 7 | document.documentElement.style.setProperty(name, value); 8 | } 9 | -------------------------------------------------------------------------------- /packages/sdk/src/timeout/sleep.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Awaits for specified amount of time. 3 | * @param duration - duration in ms to await. 4 | */ 5 | export function sleep(duration: number): Promise { 6 | return new Promise((res) => { 7 | setTimeout(res, duration); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk/src/types/platform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Telegram application platform name. 3 | */ 4 | export type Platform = 5 | | 'android' 6 | | 'android_x' 7 | | 'ios' 8 | | 'macos' 9 | | 'tdesktop' 10 | | 'unigram' 11 | | 'unknown' 12 | | 'web' 13 | | 'weba' 14 | | string; 15 | -------------------------------------------------------------------------------- /playgrounds/solid/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isRGB.ts: -------------------------------------------------------------------------------- 1 | import type { RGB } from './types.js'; 2 | 3 | /** 4 | * Returns true in case, passed value has #RRGGBB format. 5 | * @param value - value to check. 6 | */ 7 | export function isRGB(value: string): value is RGB { 8 | return /^#[\da-f]{6}$/i.test(value); 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk/src/env/isIframe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://stackoverflow.com/a/326076 3 | * @returns True, if current environment is iframe. 4 | */ 5 | export function isIframe(): boolean { 6 | try { 7 | return window.self !== window.top; 8 | } catch { 9 | return true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/isRecord.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * States that passed value is Record and not Array. 3 | * @param value - value to check. 4 | */ 5 | export function isRecord(value: unknown): value is Record { 6 | return typeof value === 'object' && value !== null && !Array.isArray(value); 7 | } 8 | -------------------------------------------------------------------------------- /packages/sdk/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": [ 6 | "./src/*" 7 | ] 8 | } 9 | }, 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "src/**/*.test.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /playgrounds/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/test-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-utils", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "", 6 | "main": "src/index.ts", 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "tsconfig": "workspace:*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isRGBShort.ts: -------------------------------------------------------------------------------- 1 | import type { RGBShort } from './types.js'; 2 | 3 | /** 4 | * Returns true in case, passed value has #RGB format. 5 | * @param value - value to check. 6 | */ 7 | export function isRGBShort(value: string): value is RGBShort { 8 | return /^#[\da-f]{3}$/i.test(value); 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/isSDKError.ts: -------------------------------------------------------------------------------- 1 | import { SDKError } from './SDKError.js'; 2 | 3 | /** 4 | * @returns True, if passed value is an instance of SDKError. 5 | * @param value - value to check. 6 | */ 7 | export function isSDKError(value: unknown): value is SDKError { 8 | return value instanceof SDKError; 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-handlers/cleanupEventHandlers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes global event handlers, used by the package. 3 | */ 4 | export function cleanupEventHandlers(): void { 5 | ['TelegramGameProxy_receiveEvent', 'TelegramGameProxy', 'Telegram'].forEach((prop) => { 6 | delete window[prop as keyof Window]; 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/objectFromKeys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates object with specified keys and value. 3 | * @param keys - object keys. 4 | * @param value - keys value. 5 | */ 6 | export function objectFromKeys(keys: K[], value: V): Record { 7 | return Object.fromEntries(keys.map((k) => [k, value])) as Record; 8 | } 9 | -------------------------------------------------------------------------------- /packages/sdk/src/request-id/createRequestIdGenerator.ts: -------------------------------------------------------------------------------- 1 | import type { CreateRequestIdFn } from './types.js'; 2 | 3 | /** 4 | * Creates function which generated request identifiers. 5 | */ 6 | export function createRequestIdGenerator(): CreateRequestIdFn { 7 | let requestId = 0; 8 | return () => (requestId += 1).toString(); 9 | } 10 | -------------------------------------------------------------------------------- /playgrounds/next/src/components/DisplayData/styles.css: -------------------------------------------------------------------------------- 1 | .display-data__header { 2 | font-weight: 400; 3 | } 4 | 5 | .display-data__line { 6 | padding: 16px 24px; 7 | } 8 | 9 | .display-data__line-title { 10 | color: var(--tg-theme-subtitle-text-color); 11 | } 12 | 13 | .display-data__line-value { 14 | word-break: break-word; 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/popup.ts: -------------------------------------------------------------------------------- 1 | import { initPopup } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [usePopupRaw, usePopup] = createHooks(initPopup); 7 | 8 | export const [withPopupRaw, withPopup] = createHOCs(usePopupRaw, usePopup); 9 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/utils.ts: -------------------------------------------------------------------------------- 1 | import { initUtils } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useUtilsRaw, useUtils] = createHooks(initUtils); 7 | 8 | export const [withUtilsRaw, withUtils] = createHOCs(useUtilsRaw, useUtils); 9 | -------------------------------------------------------------------------------- /playgrounds/react/src/components/DisplayData/DisplayData.css: -------------------------------------------------------------------------------- 1 | .display-data__header { 2 | font-weight: 400; 3 | } 4 | 5 | .display-data__line { 6 | padding: 16px 24px; 7 | } 8 | 9 | .display-data__line-title { 10 | color: var(--tg-theme-subtitle-text-color); 11 | } 12 | 13 | .display-data__line-value { 14 | word-break: break-word; 15 | } 16 | -------------------------------------------------------------------------------- /packages/test-utils/src/utils.ts: -------------------------------------------------------------------------------- 1 | export type MockImplementation = T | (() => T); 2 | 3 | /** 4 | * Formats mock implementation. 5 | * @param impl - mock implementation. 6 | */ 7 | export function formatImplementation(impl: MockImplementation): () => T { 8 | return typeof impl === 'function' 9 | ? impl as any 10 | : () => impl; 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next/src/hooks/useDidMount.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | /** 4 | * @return True, if component was mounted. 5 | */ 6 | export function useDidMount(): boolean { 7 | const [didMount, setDidMount] = useState(false); 8 | 9 | useEffect(() => { 10 | setDidMount(true); 11 | }, []); 12 | 13 | return didMount; 14 | } -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/invoice.ts: -------------------------------------------------------------------------------- 1 | import { initInvoice } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useInvoiceRaw, useInvoice] = createHooks(initInvoice); 7 | 8 | export const [withInvoiceRaw, withInvoice] = createHOCs(useInvoiceRaw, useInvoice); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/request-id/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Request identifier which should be generated locally. Native Telegram application 3 | * uses it to generate a response to called method. 4 | */ 5 | export type RequestId = string; 6 | 7 | /** 8 | * Function which generates unique request identifiers. 9 | */ 10 | export type CreateRequestIdFn = () => RequestId; 11 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/mini-app.ts: -------------------------------------------------------------------------------- 1 | import { initMiniApp } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useMiniAppRaw, useMiniApp] = createHooks(initMiniApp); 7 | 8 | export const [withMiniAppRaw, withMiniApp] = createHOCs(useMiniAppRaw, useMiniApp); 9 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/init-data.ts: -------------------------------------------------------------------------------- 1 | import { initInitData } from '@telegram-apps/sdk'; 2 | 3 | import { createHooks } from '../createHooks.js'; 4 | import { createHOCs } from '../createHOCs.js'; 5 | 6 | export const [useInitDataRaw, useInitData] = createHooks(initInitData); 7 | 8 | export const [withInitDataRaw, withInitData] = createHOCs(useInitDataRaw, useInitData); 9 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/viewport.ts: -------------------------------------------------------------------------------- 1 | import { initViewport } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useViewportRaw, useViewport] = createHooks(initViewport); 7 | 8 | export const [withViewportRaw, withViewport] = createHOCs(useViewportRaw, useViewport); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/components/InitData/parseInitData.ts: -------------------------------------------------------------------------------- 1 | import { initData } from './parsers/initData.js'; 2 | import type { InitDataParsed } from './types.js'; 3 | 4 | /** 5 | * Parses incoming value as init data. 6 | * @param value - value to parse. 7 | */ 8 | export function parseInitData(value: unknown): InitDataParsed { 9 | return initData().parse(value); 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/SDKError.ts: -------------------------------------------------------------------------------- 1 | import type { ErrorType } from './errors.js'; 2 | 3 | /** 4 | * Error used across the SDK. 5 | */ 6 | export class SDKError extends Error { 7 | constructor(public readonly type: ErrorType, message?: string, cause?: unknown) { 8 | super(message, { cause }); 9 | Object.setPrototypeOf(this, SDKError.prototype); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next/src/app/ton-connect/styles.css: -------------------------------------------------------------------------------- 1 | .ton-connect-page__placeholder { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | } 9 | 10 | .ton-connect-page__button { 11 | margin: 16px auto 0; 12 | } 13 | 14 | .ton-connect-page__button-connected { 15 | margin: 16px 24px 16px auto; 16 | } 17 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/qr-scanner.ts: -------------------------------------------------------------------------------- 1 | import { initQRScanner } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useQRScannerRaw, useQRScanner] = createHooks(initQRScanner); 7 | 8 | export const [withQRScannerRaw, withQRScanner] = createHOCs(useQRScannerRaw, useQRScanner); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/array.ts: -------------------------------------------------------------------------------- 1 | import { ArrayParser } from '@/parsing/ArrayParser/ArrayParser.js'; 2 | 3 | /** 4 | * Parses incoming value as an array. 5 | * @param parserTypeName - parser type name. 6 | */ 7 | export function array(parserTypeName?: string): ArrayParser { 8 | return new ArrayParser((value) => value, false, parserTypeName); 9 | } 10 | -------------------------------------------------------------------------------- /packages/init-data-node/src/hashToken.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, vi } from 'vitest'; 2 | import { hashToken } from './hashToken'; 3 | 4 | it('should call the second argument with the first argument and string "WebAppData"', () => { 5 | const spy = vi.fn(); 6 | hashToken('abc', spy); 7 | expect(spy).toHaveBeenCalledOnce(); 8 | expect(spy).toHaveBeenCalledWith('abc', 'WebAppData'); 9 | }); -------------------------------------------------------------------------------- /playgrounds/react/src/pages/TONConnectPage/TONConnectPage.css: -------------------------------------------------------------------------------- 1 | .ton-connect-page__placeholder { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | } 9 | 10 | .ton-connect-page__button { 11 | margin: 16px auto 0; 12 | } 13 | 14 | .ton-connect-page__button-connected { 15 | margin: 16px 24px 16px auto; 16 | } 17 | -------------------------------------------------------------------------------- /packages/create-mini-app/src/isGitInstalled.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'node:child_process'; 2 | 3 | /** 4 | * @returns Promise with true if git is currently installed. 5 | */ 6 | export function isGitInstalled(): Promise { 7 | return new Promise((res) => { 8 | spawn('git --version', { shell: true }).on('exit', (code) => { 9 | res(!code); 10 | }); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/main-button.ts: -------------------------------------------------------------------------------- 1 | import { initMainButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useMainButtonRaw, useMainButton] = createHooks(initMainButton); 7 | 8 | export const [withMainButtonRaw, withMainButton] = createHOCs(useMainButtonRaw, useMainButton); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/captureSameReq.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { captureSameReq } from './captureSameReq.js'; 4 | 5 | it('should return function which returns true, if initially passed value equals the dynamic one', () => { 6 | expect(captureSameReq('abc')({ req_id: 'abc' })).toBe(true); 7 | expect(captureSameReq('abc')({ req_id: '111' })).toBe(false); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/ensurePrefix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensures, that specified value starts with the specified prefix. If it doesn't, function appends 3 | * prefix. 4 | * @param value - value to check. 5 | * @param prefix - prefix to add. 6 | */ 7 | export function ensurePrefix(value: string, prefix: string): string { 8 | return value.startsWith(prefix) ? value : `${prefix}${value}`; 9 | } 10 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/isPageReload.ts: -------------------------------------------------------------------------------- 1 | import { getFirstNavigationEntry } from './getFirstNavigationEntry.js'; 2 | 3 | /** 4 | * @returns True, if current page was reloaded. 5 | * @see https://stackoverflow.com/a/36444134/11894710 6 | */ 7 | export function isPageReload(): boolean { 8 | const entry = getFirstNavigationEntry(); 9 | return !!(entry && entry.type === 'reload'); 10 | } 11 | -------------------------------------------------------------------------------- /packages/init-data-node/src/entries/shared.ts: -------------------------------------------------------------------------------- 1 | export { 2 | type Chat, 3 | type ChatType, 4 | type InitDataParsed, 5 | parseInitData as parse, 6 | type User, 7 | } from '@telegram-apps/sdk'; 8 | export { initDataToSearchParams } from '../initDataToSearchParams.js'; 9 | export type { ValidateOptions } from '../validate.js'; 10 | export type { SignData, Text, CreateHmacFn } from '../types.js'; 11 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/theme-params.ts: -------------------------------------------------------------------------------- 1 | import { initThemeParams } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useThemeParamsRaw, useThemeParams] = createHooks(initThemeParams); 7 | 8 | export const [withThemeParamsRaw, withThemeParams] = createHOCs(useThemeParamsRaw, useThemeParams); 9 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getPathname.ts: -------------------------------------------------------------------------------- 1 | import { createSafeURL } from '@/navigation/createSafeURL.js'; 2 | import type { URLLike } from '@/navigation/BrowserNavigator/types.js'; 3 | 4 | /** 5 | * Extracts pathname from the value. 6 | * @param value - source value. 7 | */ 8 | export function getPathname(value: string | Partial): string { 9 | return createSafeURL(value).pathname; 10 | } 11 | -------------------------------------------------------------------------------- /playgrounds/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | *.pem 27 | -------------------------------------------------------------------------------- /packages/sdk/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "declaration": true, 6 | "paths": { 7 | "@/*": [ 8 | "./src/*" 9 | ], 10 | "@test-utils/*": [ 11 | "./test-utils/*" 12 | ] 13 | } 14 | }, 15 | "include": [ 16 | "src", 17 | "test-utils" 18 | ], 19 | "exclude": [] 20 | } -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/back-button.ts: -------------------------------------------------------------------------------- 1 | import { initBackButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useBackButtonRaw, useBackButton] = createHooks(initBackButton); 7 | 8 | export const [withBackButtonRaw, withBackButton] = createHOCs( 9 | useBackButtonRaw, 10 | useBackButton, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/components/Popup/initPopup.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { Popup } from './Popup.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `Popup` class. 7 | * @see Popup 8 | */ 9 | export const initPopup = createComponentInitFn( 10 | ({ postEvent, version }) => new Popup(false, version, postEvent), 11 | ); 12 | -------------------------------------------------------------------------------- /playgrounds/custom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/esnext-dom.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": [ 6 | "../../packages/*/src/index.ts" 7 | ] 8 | } 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ], 13 | "include": [ 14 | "src" 15 | ], 16 | "references": [ 17 | { 18 | "path": "./tsconfig.node.json" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/sdk-solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "tsconfig/esnext-dom.json", 4 | "compilerOptions": { 5 | "allowSyntheticDefaultImports": true, 6 | "jsx": "preserve", 7 | "jsxImportSource": "solid-js" 8 | }, 9 | "include": [ 10 | "src" 11 | ], 12 | "references": [ 13 | { 14 | "path": "./tsconfig.node.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isRGB.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isRGB } from './isRGB.js'; 4 | 5 | it('should return true for correct full RGB representation', () => { 6 | expect(isRGB('#ffffff')).toBe(true); 7 | }); 8 | 9 | it('should return false for any other value', () => { 10 | ['abc', '#ffff', '#fff', '#fffffg'].forEach((v) => { 11 | expect(isRGB(v)).toBe(false); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/sdk/src/components/Invoice/initInvoice.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { Invoice } from './Invoice.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `Invoice` class. 7 | * @see Invoice 8 | */ 9 | export const initInvoice = createComponentInitFn( 10 | ({ version, postEvent }) => new Invoice(false, version, postEvent), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/test-utils/src/document.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | import { formatImplementation, type MockImplementation } from './utils'; 4 | 5 | /** 6 | * Mocks document. 7 | * @param impl - window getter implementation. 8 | */ 9 | export function mockDocument(impl: MockImplementation) { 10 | return vi 11 | .spyOn(global, 'document', 'get') 12 | .mockImplementation(formatImplementation(impl)); 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/custom/README.md: -------------------------------------------------------------------------------- 1 | # Local Playground 2 | 3 | This application can be used by developers to test the code, written in the current monorepo. 4 | We created this playground to avoid writing the same code from one package to another, adding 5 | `index.html` and `vite.config.ts` files. 6 | 7 | ## Usage 8 | 9 | 1. First of all just import any code from the other packages. 10 | 2. Run `pnpm run dev`. 11 | 3. Open URL from the console. -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/cloud-storage.ts: -------------------------------------------------------------------------------- 1 | import { initCloudStorage } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useCloudStorageRaw, useCloudStorage] = createHooks(initCloudStorage); 7 | 8 | export const [withCloudStorageRaw, withCloudStorage] = createHOCs( 9 | useCloudStorageRaw, 10 | useCloudStorage, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/captureSameReq.ts: -------------------------------------------------------------------------------- 1 | type CaptureSameReqFn = (payload: { req_id: string }) => boolean; 2 | 3 | /** 4 | * Returns a function which can be used in `request` function `capture` property to capture 5 | * the event with the same request identifier. 6 | * @param reqId - request identifier. 7 | */ 8 | export function captureSameReq(reqId: string): CaptureSameReqFn { 9 | return ({ req_id }) => req_id === reqId; 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/parsing/parseThemeParams.ts: -------------------------------------------------------------------------------- 1 | import { themeParams } from '@/components/ThemeParams/parsing/themeParams.js'; 2 | 3 | import type { ThemeParamsParsed } from '../types.js'; 4 | 5 | /** 6 | * Parses incoming value as theme parameters. 7 | * @param value - value to parse. 8 | */ 9 | export function parseThemeParams(value: unknown): ThemeParamsParsed { 10 | return themeParams().parse(value); 11 | } 12 | -------------------------------------------------------------------------------- /packages/sdk/src/env/isSSR.test.ts: -------------------------------------------------------------------------------- 1 | import { vi, expect, it } from 'vitest'; 2 | import { isSSR } from './isSSR.js'; 3 | 4 | it('should return true, if typeof window is undefined', () => { 5 | const spy = vi 6 | .spyOn(window, 'window', 'get') 7 | .mockImplementationOnce(() => undefined as any); 8 | expect(isSSR()).toBe(true); 9 | 10 | spy.mockImplementationOnce(() => ({}) as any); 11 | expect(isSSR()).toBe(false); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/isSDKError.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isSDKError } from './isSDKError.js'; 4 | import { SDKError } from './SDKError.js'; 5 | 6 | it('should return true if passed value is instance of SDKError', () => { 7 | expect(isSDKError('')).toBe(false); 8 | expect(isSDKError(new Error())).toBe(false); 9 | expect(isSDKError(new SDKError('ERR_METHOD_UNSUPPORTED'))).toBe(true); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/ensurePrefix.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { ensurePrefix } from './ensurePrefix.js'; 4 | 5 | it('should prepend the second argument to the first one if it does not start with it', () => { 6 | expect(ensurePrefix('value', '/')).toBe('/value'); 7 | expect(ensurePrefix('/value', '/')).toBe('/value'); 8 | expect(ensurePrefix('value', '/complex/')).toBe('/complex/value'); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/isSDKErrorOfType.ts: -------------------------------------------------------------------------------- 1 | import { isSDKError } from './isSDKError.js'; 2 | import type { ErrorType } from './errors.js'; 3 | 4 | /** 5 | * Returns true if passed value is an SDK error of specified type. 6 | * @param value - value to check. 7 | * @param type - error type. 8 | */ 9 | export function isSDKErrorOfType(value: unknown, type: ErrorType): boolean { 10 | return isSDKError(value) && value.type === type; 11 | } 12 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/swipe-behavior.ts: -------------------------------------------------------------------------------- 1 | import { initSwipeBehavior } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useSwipeBehaviorRaw, useSwipeBehavior] = createHooks(initSwipeBehavior); 7 | 8 | export const [withSwipeBehaviorRaw, withSwipeBehavior] = createHOCs( 9 | useSwipeBehaviorRaw, 10 | useSwipeBehavior, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isRGBShort.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isRGBShort } from './isRGBShort.js'; 4 | 5 | it('should return true for correct short RGB representation', () => { 6 | expect(isRGBShort('#fff')).toBe(true); 7 | }); 8 | 9 | it('should return false for any other value', () => { 10 | ['abc', '#ffff', '#ffffff', '#ggg'].forEach((v) => { 11 | expect(isRGBShort(v)).toBe(false); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/createTypeError.ts: -------------------------------------------------------------------------------- 1 | import { createError } from '@/errors/createError.js'; 2 | import { ERR_UNEXPECTED_TYPE } from '@/errors/errors.js'; 3 | import type { SDKError } from '@/errors/SDKError.js'; 4 | 5 | /** 6 | * Creates instance of TypeError stating, that value has unexpected type. 7 | */ 8 | export function createTypeError(): SDKError { 9 | return createError(ERR_UNEXPECTED_TYPE, 'Value has unexpected type'); 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/toRecord.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { toRecord } from './toRecord.js'; 4 | 5 | it('should throw an error in case, passed value is not JSON object or not JSON object converted to string', () => { 6 | expect(() => toRecord('')).toThrow(); 7 | expect(() => toRecord(true)).toThrow(); 8 | expect(() => toRecord('{}')).not.toThrow(); 9 | expect(() => toRecord({})).not.toThrow(); 10 | }); 11 | -------------------------------------------------------------------------------- /playgrounds/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-playground", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "dev:host": "vite --host", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "tsconfig": "workspace:*" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-basic-ssl": "^1.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/cli-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-utils", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "", 6 | "main": "src/index.ts", 7 | "type": "module", 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/node": "^20.14.10", 13 | "tsconfig": "workspace:*" 14 | }, 15 | "dependencies": { 16 | "chalk": "^5.3.0", 17 | "ora": "^8.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/haptic-feedback.ts: -------------------------------------------------------------------------------- 1 | import { initHapticFeedback } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useHapticFeedbackRaw, useHapticFeedback] = createHooks(initHapticFeedback); 7 | 8 | export const [withHapticFeedbackRaw, withHapticFeedback] = createHOCs( 9 | useHapticFeedbackRaw, 10 | useHapticFeedback, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/settings-button.ts: -------------------------------------------------------------------------------- 1 | import { initSettingsButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useSettingsButtonRaw, useSettingsButton] = createHooks(initSettingsButton); 7 | 8 | export const [withSettingsButtonRaw, withSettingsButton] = createHOCs( 9 | useSettingsButtonRaw, 10 | useSettingsButton, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/components/InitData/initInitData.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { InitData } from './InitData.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `InitData` class or undefined. 7 | * @see InitData 8 | */ 9 | export const initInitData = createComponentInitFn( 10 | ({ initData }) => (initData ? new InitData(initData) : undefined), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/components/QRScanner/initQRScanner.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { QRScanner } from './QRScanner.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `QRScanner` class. 7 | * @see QRScanner 8 | */ 9 | export const initQRScanner = createComponentInitFn( 10 | ({ version, postEvent }) => new QRScanner(false, version, postEvent), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getHash.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { getHash } from './getHash.js'; 4 | 5 | it('should return part after the first hash tag. If hash is missing, return null', () => { 6 | expect(getHash('abc#my-hash')).toBe('my-hash'); 7 | expect(getHash('#my-hash')).toBe('my-hash'); 8 | expect(getHash('#my-hash#my-hash')).toBe('my-hash#my-hash'); 9 | expect(getHash('my-hash')).toBe(null); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/sdk/src/timeout/createTimeoutError.ts: -------------------------------------------------------------------------------- 1 | import { createError } from '@/errors/createError.js'; 2 | import { ERR_TIMED_OUT } from '@/errors/errors.js'; 3 | import type { SDKError } from '@/errors/SDKError.js'; 4 | 5 | /** 6 | * Creates new timeout error. 7 | * @param timeout - timeout in ms. 8 | */ 9 | export function createTimeoutError(timeout: number): SDKError { 10 | return createError(ERR_TIMED_OUT, `Timeout reached: ${timeout}ms`); 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [ 11 | "custom-playground", 12 | "nextjs-template", 13 | "reactjs-template", 14 | "solidjs-template" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getFirstNavigationEntry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the first navigation entry from window.performance. 3 | * @returns First navigation entry or null, in case performance functionality is not supported 4 | * or navigation entry was not found. 5 | */ 6 | export function getFirstNavigationEntry(): PerformanceNavigationTiming | undefined { 7 | return performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined; 8 | } 9 | -------------------------------------------------------------------------------- /packages/create-mini-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import shebang from 'rollup-plugin-preserve-shebang'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | shebang({ 7 | shebang: '#!/usr/bin/env node', 8 | }), 9 | ], 10 | build: { 11 | emptyOutDir: true, 12 | ssr: true, 13 | lib: { 14 | entry: 'src/index.ts', 15 | formats: ['es'], 16 | fileName: 'index', 17 | }, 18 | }, 19 | }); -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/biometry-manager.ts: -------------------------------------------------------------------------------- 1 | import { initBiometryManager } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useBiometryManagerRaw, useBiometryManager] = createHooks(initBiometryManager); 7 | 8 | export const [withBiometryManagerRaw, withBiometryManager] = createHOCs( 9 | useBiometryManagerRaw, 10 | useBiometryManager, 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/closing-behavior.ts: -------------------------------------------------------------------------------- 1 | import { initClosingBehavior } from '@telegram-apps/sdk'; 2 | 3 | import { createHOCs } from '../createHOCs.js'; 4 | import { createHooks } from '../createHooks.js'; 5 | 6 | export const [useClosingBehaviorRaw, useClosingBehavior] = createHooks(initClosingBehavior); 7 | 8 | export const [withClosingBehaviorRaw, withClosingBehavior] = createHOCs( 9 | useClosingBehaviorRaw, 10 | useClosingBehavior, 11 | ); 12 | -------------------------------------------------------------------------------- /playgrounds/custom/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * You can import any code from other packages here. There are currently 2 shortcuts: 3 | * 4 | * 1. "@packages/*". Provides access to "packages" directory: 5 | * import { postEvent } from '@packages/sdk/src/index.js'; 6 | * 7 | * 2. "@/*". Provides easy access to packages' index files: 8 | * import { postEvent } from '@/sdk'; 9 | */ 10 | 11 | import { postEvent } from '@/sdk'; 12 | 13 | postEvent('web_app_expand'); 14 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/popup.ts: -------------------------------------------------------------------------------- 1 | import { initPopup } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the Popup component instance. 8 | */ 9 | export const usePopup = createHook(initPopup); 10 | 11 | /** 12 | * HOC to pass the Popup component instance to the wrapped component. 13 | */ 14 | export const withPopup = createHOC(usePopup); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/utils.ts: -------------------------------------------------------------------------------- 1 | import { initUtils } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the Utils component instance. 8 | */ 9 | export const useUtils = createHook(initUtils); 10 | 11 | /** 12 | * HOC to pass the Utils component instance to the wrapped component. 13 | */ 14 | export const withUtils = createHOC(useUtils); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/createError.ts: -------------------------------------------------------------------------------- 1 | import { SDKError } from './SDKError.js'; 2 | import type { ErrorType } from './errors.js'; 3 | 4 | /** 5 | * Creates new error using specified type and message. 6 | * @param type - error code. 7 | * @param message - error message. 8 | * @param cause - original error. 9 | */ 10 | export function createError(type: ErrorType, message: string, cause?: unknown): SDKError { 11 | return new SDKError(type, message, cause); 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | 3 | import { Root } from '@/components/Root'; 4 | 5 | // Uncomment this import in case, you would like to develop the application even outside 6 | // the Telegram application, just in your browser. 7 | import './mockEnv.ts'; 8 | 9 | import '@telegram-apps/telegram-ui/dist/styles.css'; 10 | import './index.css'; 11 | 12 | ReactDOM.createRoot(document.getElementById('root')!).render(); 13 | -------------------------------------------------------------------------------- /playgrounds/react/src/pages/IndexPage/IndexPage.css: -------------------------------------------------------------------------------- 1 | .index-page__links { 2 | list-style: none; 3 | padding-left: 0; 4 | } 5 | 6 | .index-page__link { 7 | font-weight: bold; 8 | display: inline-flex; 9 | gap: 5px; 10 | } 11 | 12 | .index-page__link-item + .index-page__link-item { 13 | margin-top: 10px; 14 | } 15 | 16 | .index-page__link-icon { 17 | width: 20px; 18 | display: block; 19 | } 20 | 21 | .index-page__link-icon svg { 22 | display: block; 23 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/pages/IndexPage/IndexPage.css: -------------------------------------------------------------------------------- 1 | .index-page__links { 2 | list-style: none; 3 | padding-left: 0; 4 | } 5 | 6 | .index-page__link { 7 | font-weight: bold; 8 | display: inline-flex; 9 | gap: 5px; 10 | } 11 | 12 | .index-page__link-item + .index-page__link-item { 13 | margin-top: 10px; 14 | } 15 | 16 | .index-page__link-icon { 17 | width: 20px; 18 | display: block; 19 | } 20 | 21 | .index-page__link-icon svg { 22 | display: block; 23 | } -------------------------------------------------------------------------------- /playgrounds/solid/src/tonconnect/useTonConnectUI.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'solid-js'; 2 | 3 | import { 4 | TonConnectUIContext, 5 | type TonConnectUIContextType, 6 | } from '@/tonconnect/TonConnectUIContext.js'; 7 | 8 | export function useTonConnectUI(): TonConnectUIContextType { 9 | const context = useContext(TonConnectUIContext); 10 | if (!context) { 11 | throw new Error('Unable to get TonConnectUIContext'); 12 | } 13 | return context; 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-mini-app/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface TemplateRepository { 2 | clone: { 3 | https: string; 4 | ssh: string; 5 | }; 6 | link: string; 7 | } 8 | 9 | export interface Template { 10 | sdk: SDK; 11 | language: Language; 12 | framework: Framework; 13 | repository: TemplateRepository; 14 | } 15 | 16 | export type Language = 'js' | 'ts'; 17 | export type SDK = 'tsdk' | 'telegramApps'; 18 | export type Framework = 'solid' | 'react' | 'next'; 19 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromLocation.ts: -------------------------------------------------------------------------------- 1 | import { retrieveFromUrl } from './retrieveFromUrl.js'; 2 | import type { LaunchParams } from './types.js'; 3 | 4 | /** 5 | * @returns Launch parameters from the current window location hash. 6 | * @throws Error if function was unable to extract launch parameters from the window location hash. 7 | */ 8 | export function retrieveFromLocation(): LaunchParams { 9 | return retrieveFromUrl(window.location.href); 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/request-id/createRequestIdGenerator.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { createRequestIdGenerator } from './createRequestIdGenerator.js'; 4 | 5 | it('should return function, which returns value higher by one than returned previously. Values returned are numeric strings, starting from 1', () => { 6 | const fn = createRequestIdGenerator(); 7 | expect(fn()).toBe('1'); 8 | expect(fn()).toBe('2'); 9 | expect(fn()).toBe('3'); 10 | }) -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "dev": "vitepress dev", 10 | "build": "vitepress build", 11 | "preview": "vitepress preview" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "vitepress": "1.2.0" 18 | } 19 | } -------------------------------------------------------------------------------- /packages/sdk/src/bridge/target-origin.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, describe, expect, it, vi } from 'vitest'; 2 | 3 | import { setTargetOrigin, targetOrigin } from '@/bridge/target-origin.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | describe('setTargetOrigin', () => { 10 | it('should return set value via targetOrigin() function', () => { 11 | setTargetOrigin('my test'); 12 | expect(targetOrigin()).toEqual('my test'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/urlToPath.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { urlToPath } from '@/navigation/urlToPath.js'; 3 | 4 | it('should preserve trailing slash in the beginning of the path', () => { 5 | expect(urlToPath('a?b#c')).toBe('a?b#c'); 6 | expect(urlToPath('/a?b#c')).toBe('/a?b#c'); 7 | expect(urlToPath({ pathname: 'a', search: 'b', hash: 'c' })).toBe('a?b#c'); 8 | expect(urlToPath({ pathname: '/a', search: 'b', hash: 'c' })).toBe('/a?b#c'); 9 | }); -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/invoice.ts: -------------------------------------------------------------------------------- 1 | import { initInvoice } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the Invoice component instance. 8 | */ 9 | export const useInvoice = createHook(initInvoice); 10 | 11 | /** 12 | * HOC to pass the Invoice component instance to the wrapped component. 13 | */ 14 | export const withInvoice = createHOC(useInvoice); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/mini-app.ts: -------------------------------------------------------------------------------- 1 | import { initMiniApp } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the MiniApp component instance. 8 | */ 9 | export const useMiniApp = createHook(initMiniApp); 10 | 11 | /** 12 | * HOC to pass the MiniApp component instance to the wrapped component. 13 | */ 14 | export const withMiniApp = createHOC(useMiniApp); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/components/HapticFeedback/initHapticFeedback.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { HapticFeedback } from './HapticFeedback.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `HapticFeedback` class. 7 | * @see HapticFeedback 8 | */ 9 | export const initHapticFeedback = createComponentInitFn( 10 | ({ version, postEvent }) => new HapticFeedback(version, postEvent), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/components/Utils/initUtils.ts: -------------------------------------------------------------------------------- 1 | import { Utils } from '@/components/Utils/Utils.js'; 2 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 3 | 4 | /** 5 | * @returns A new initialized instance of the `Utils` class. 6 | * @see Utils 7 | */ 8 | export const initUtils = createComponentInitFn( 9 | ({ version, postEvent, createRequestId }) => { 10 | return new Utils(version, createRequestId, postEvent); 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/viewport.ts: -------------------------------------------------------------------------------- 1 | import { initViewport } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the Viewport component instance. 8 | */ 9 | export const useViewport = createHook(initViewport); 10 | 11 | /** 12 | * HOC to pass the Viewport component instance to the wrapped component. 13 | */ 14 | export const withViewport = createHOC(useViewport); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/init-data.ts: -------------------------------------------------------------------------------- 1 | import { initInitData } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the InitData component instance. 8 | */ 9 | export const useInitData = createHook(initInitData); 10 | 11 | /** 12 | * HOC to pass the InitData component instance to the wrapped component. 13 | */ 14 | export const withInitData = createHOC(useInitData); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/qr-scanner.ts: -------------------------------------------------------------------------------- 1 | import { initQRScanner } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the QRScanner component instance. 8 | */ 9 | export const useQRScanner = createHook(initQRScanner); 10 | 11 | /** 12 | * HOC to pass the QRScanner component instance to the wrapped component. 13 | */ 14 | export const withQRScanner = createHOC(useQRScanner); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/saveToStorage.ts: -------------------------------------------------------------------------------- 1 | import { setStorageValue } from '@/storage/storage.js'; 2 | 3 | import { serializeLaunchParams } from './serializeLaunchParams.js'; 4 | import type { LaunchParams } from './types.js'; 5 | 6 | /** 7 | * Saves specified launch parameters in the session storage. 8 | * @param value - launch params to save. 9 | */ 10 | export function saveToStorage(value: LaunchParams): void { 11 | setStorageValue('launchParams', serializeLaunchParams(value)); 12 | } 13 | -------------------------------------------------------------------------------- /packages/test-utils/src/performance.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | import { formatImplementation, type MockImplementation } from './utils'; 4 | 5 | /** 6 | * Mocks performance.getEntriesByType. 7 | * @param impl - method implementation. 8 | */ 9 | export function mockPerformanceGetEntriesByType( 10 | impl: MockImplementation = [], 11 | ) { 12 | return vi 13 | .spyOn(performance, 'getEntriesByType') 14 | .mockImplementation(formatImplementation(impl)); 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/back-button.ts: -------------------------------------------------------------------------------- 1 | import { initBackButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the BackButton component instance. 8 | */ 9 | export const useBackButton = createHook(initBackButton); 10 | 11 | /** 12 | * HOC to pass the BackButton component instance to the wrapped component. 13 | */ 14 | export const withBackButton = createHOC(useBackButton); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/main-button.ts: -------------------------------------------------------------------------------- 1 | import { initMainButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the MainButton component instance. 8 | */ 9 | export const useMainButton = createHook(initMainButton); 10 | 11 | /** 12 | * HOC to pass the MainButton component instance to the wrapped component. 13 | */ 14 | export const withMainButton = createHOC(useMainButton); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/date.ts: -------------------------------------------------------------------------------- 1 | import { createValueParserGenerator } from '../createValueParserGenerator.js'; 2 | import { number } from './number.js'; 3 | import type { ValueParserGenerator } from '../createValueParserGenerator.js'; 4 | 5 | /** 6 | * Returns parser to parse value as Date. 7 | */ 8 | export const date: ValueParserGenerator = createValueParserGenerator((value) => ( 9 | value instanceof Date 10 | ? value 11 | : new Date(number().parse(value) * 1000) 12 | ), 'Date'); 13 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/theme-params.ts: -------------------------------------------------------------------------------- 1 | import { initThemeParams } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the ThemeParams component instance. 8 | */ 9 | export const useThemeParams = createHook(initThemeParams); 10 | 11 | /** 12 | * HOC to pass the ThemeParams component instance to the wrapped component. 13 | */ 14 | export const withThemeParams = createHOC(useThemeParams); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/types/methods.ts: -------------------------------------------------------------------------------- 1 | import type { PostEvent } from '@/bridge/methods/postEvent.js'; 2 | 3 | export interface ExecuteWithTimeout { 4 | /** 5 | * Timeout to execute method. 6 | */ 7 | timeout?: number; 8 | } 9 | 10 | export interface ExecuteWithPostEvent { 11 | /** 12 | * postEvent function to use to call Telegram Mini Apps methods. 13 | */ 14 | postEvent?: PostEvent; 15 | } 16 | 17 | export interface ExecuteWithOptions extends ExecuteWithTimeout, ExecuteWithPostEvent { 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/react/src/components/Page/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren, ReactNode } from 'react'; 2 | 3 | import './Page.css'; 4 | 5 | export interface PageProps extends PropsWithChildren { 6 | title: string; 7 | disclaimer?: ReactNode; 8 | } 9 | 10 | export const Page: FC = ({ title, children, disclaimer }) => ( 11 |
12 |

{title}

13 | {disclaimer &&
{disclaimer}
} 14 | {children} 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /playgrounds/solid/src/components/DisplayData/DisplayData.css: -------------------------------------------------------------------------------- 1 | .display-data__line { 2 | display: flex; 3 | align-items: center; 4 | margin-bottom: 8px; 5 | gap: 10px; 6 | flex-flow: wrap; 7 | } 8 | 9 | .display-data__line-title { 10 | border: 1px solid var(--tg-theme-accent-text-color); 11 | background-color: var(--tg-theme-bg-color); 12 | border-radius: 5px; 13 | padding: 2px 8px 4px; 14 | box-sizing: border-box; 15 | } 16 | 17 | .display-data__line-value { 18 | word-break: break-word; 19 | } 20 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/cloud-storage.ts: -------------------------------------------------------------------------------- 1 | import { initCloudStorage } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the CloudStorage component instance. 8 | */ 9 | export const useCloudStorage = createHook(initCloudStorage); 10 | 11 | /** 12 | * HOC to pass the CloudStorage component instance to the wrapped component. 13 | */ 14 | export const withCloudStorage = createHOC(useCloudStorage); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/components/BackButton/initBackButton.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { BackButton } from './BackButton.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `BackButton` class. 7 | * @see BackButton 8 | */ 9 | export const initBackButton = createComponentInitFn('backButton', ({ 10 | postEvent, 11 | version, 12 | state = { isVisible: false }, 13 | }) => new BackButton(state.isVisible, version, postEvent)); 14 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/objectFromKeys.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { objectFromKeys } from '@/misc/objectFromKeys.js'; 4 | 5 | it('should create with specified keys and value', () => { 6 | expect(objectFromKeys(['a', 'b', 'c'], 'value')).toStrictEqual({ 7 | a: 'value', 8 | b: 'value', 9 | c: 'value', 10 | }); 11 | 12 | const fn = () => null; 13 | expect(objectFromKeys(['a', 'b', 'c'], fn)).toStrictEqual({ 14 | a: fn, 15 | b: fn, 16 | c: fn, 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/sdk/src/components/CloudStorage/initCloudStorage.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { CloudStorage } from './CloudStorage.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `CloudStorage` class. 7 | * @see CloudStorage 8 | */ 9 | export const initCloudStorage = createComponentInitFn( 10 | ({ createRequestId, postEvent, version }) => { 11 | return new CloudStorage(version, createRequestId, postEvent); 12 | }, 13 | ); 14 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/swipe-behavior.ts: -------------------------------------------------------------------------------- 1 | import { initSwipeBehavior } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the SwipeBehavior component instance. 8 | */ 9 | export const useSwipeBehavior = createHook(initSwipeBehavior); 10 | 11 | /** 12 | * HOC to pass the SwipeBehavior component instance to the wrapped component. 13 | */ 14 | export const withSwipeBehavior = createHOC(useSwipeBehavior); 15 | -------------------------------------------------------------------------------- /packages/sdk/test-utils/dispatchWindowMessageEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Uses window.dispatchEvent function with specified message event 3 | * to dispatch event to imitate its creation by Telegram. 4 | * @param eventType - event name. 5 | * @param eventData - event payload. 6 | */ 7 | export function dispatchWindowMessageEvent(eventType: string, eventData?: unknown): void { 8 | window.dispatchEvent(new MessageEvent('message', { 9 | data: JSON.stringify({ eventType, eventData }), 10 | source: window.parent, 11 | })); 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/next/src/components/RGB/RGB.tsx: -------------------------------------------------------------------------------- 1 | import { classNames, type RGB as RGBType } from '@telegram-apps/sdk-react'; 2 | import type { FC } from 'react'; 3 | 4 | import './styles.css'; 5 | 6 | export type RGBProps = JSX.IntrinsicElements['div'] & { 7 | color: RGBType; 8 | }; 9 | 10 | export const RGB: FC = ({ color, className, ...rest }) => ( 11 | 12 | 13 | {color} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /playgrounds/react/src/components/RGB/RGB.tsx: -------------------------------------------------------------------------------- 1 | import { classNames, type RGB as RGBType } from '@telegram-apps/sdk-react'; 2 | import type { FC } from 'react'; 3 | 4 | import './RGB.css'; 5 | 6 | export type RGBProps = JSX.IntrinsicElements['div'] & { 7 | color: RGBType; 8 | }; 9 | 10 | export const RGB: FC = ({ color, className, ...rest }) => ( 11 | 12 | 13 | {color} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /packages/sdk-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import dts from 'vite-plugin-dts'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dts({ outDir: 'dist/dts' }), 7 | ], 8 | build: { 9 | outDir: 'dist', 10 | emptyOutDir: true, 11 | sourcemap: true, 12 | rollupOptions: { 13 | external: ['react', 'react/jsx-runtime'], 14 | }, 15 | lib: { 16 | entry: 'src/index.ts', 17 | formats: ['es', 'cjs'], 18 | fileName: 'index', 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/haptic-feedback.ts: -------------------------------------------------------------------------------- 1 | import { initHapticFeedback } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the HapticFeedback component instance. 8 | */ 9 | export const useHapticFeedback = createHook(initHapticFeedback); 10 | 11 | /** 12 | * HOC to pass the HapticFeedback component instance to the wrapped component. 13 | */ 14 | export const withHapticFeedback = createHOC(useHapticFeedback); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/settings-button.ts: -------------------------------------------------------------------------------- 1 | import { initSettingsButton } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the SettingsButton component instance. 8 | */ 9 | export const useSettingsButton = createHook(initSettingsButton); 10 | 11 | /** 12 | * HOC to pass the SettingsButton component instance to the wrapped component. 13 | */ 14 | export const withSettingsButton = createHOC(useSettingsButton); 15 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/listening/off.ts: -------------------------------------------------------------------------------- 1 | import { miniAppsEventEmitter } from '../event-emitter/singleton.js'; 2 | import type { MiniAppsEventListener, MiniAppsEventName } from '../types.js'; 3 | 4 | /** 5 | * Removes listener from specified event. 6 | * @param event - event to listen. 7 | * @param listener - event listener to remove. 8 | */ 9 | export function off( 10 | event: E, 11 | listener: MiniAppsEventListener, 12 | ): void { 13 | miniAppsEventEmitter().off(event, listener); 14 | } 15 | -------------------------------------------------------------------------------- /packages/sdk/src/components/InitData/parsers/chat.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@/parsing/parsers/json.js'; 2 | import { number } from '@/parsing/parsers/number.js'; 3 | import { string } from '@/parsing/parsers/string.js'; 4 | 5 | import type { Chat } from '../types.js'; 6 | 7 | export const chat = json({ 8 | id: number(), 9 | type: string(), 10 | title: string(), 11 | photoUrl: { 12 | type: string().optional(), 13 | from: 'photo_url', 14 | }, 15 | username: string().optional(), 16 | }, 'Chat') 17 | .optional(); 18 | -------------------------------------------------------------------------------- /packages/sdk/src/env/isTMA.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/bridge/request.js'; 2 | import { hasWebviewProxy } from '@/env/hasWebviewProxy.js'; 3 | 4 | /** 5 | * Returns true in case, current environment is Telegram Mini Apps. 6 | */ 7 | export async function isTMA(): Promise { 8 | if (hasWebviewProxy(window)) { 9 | return true; 10 | } 11 | try { 12 | await request({ method: 'web_app_request_theme', event: 'theme_changed', timeout: 100 }); 13 | return true; 14 | } catch { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/rgb.ts: -------------------------------------------------------------------------------- 1 | import { toRGB } from '@/colors/toRGB.js'; 2 | import type { RGB } from '@/colors/types.js'; 3 | 4 | import { createValueParserGenerator } from '../createValueParserGenerator.js'; 5 | import { string } from './string.js'; 6 | import type { ValueParserGenerator } from '../createValueParserGenerator.js'; 7 | 8 | /** 9 | * Returns parser to parse value as RGB color. 10 | */ 11 | export const rgb: ValueParserGenerator = createValueParserGenerator((value) => toRGB(string().parse(value)), 'rgb'); 12 | -------------------------------------------------------------------------------- /packages/sdk/src/timeout/createTimeoutError.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { SDKError } from '@/errors/SDKError.js'; 4 | 5 | import { createTimeoutError } from './createTimeoutError.js'; 6 | 7 | it('should be instance of SDKError with type "ERR_TIMED_OUT" and message "Timeout reached: {timeout}ms"', () => { 8 | const err = createTimeoutError(100); 9 | expect(err).toBeInstanceOf(SDKError); 10 | expect(err.type).toBe('ERR_TIMED_OUT'); 11 | expect(err.message).toBe('Timeout reached: 100ms'); 12 | }); 13 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /playgrounds/solid/src/components/Page/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { Component, JSX, ParentProps } from 'solid-js'; 2 | 3 | import './Page.css'; 4 | 5 | export interface PageProps extends ParentProps { 6 | title: string; 7 | disclaimer?: JSX.Element; 8 | } 9 | 10 | export const Page: Component = (props) => { 11 | return ( 12 |
13 |

{props.title}

14 | {props.disclaimer &&
{props.disclaimer}
} 15 | {props.children} 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/biometry-manager.ts: -------------------------------------------------------------------------------- 1 | import { initBiometryManager } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the BiometryManager component instance. 8 | */ 9 | export const useBiometryManager = createHook(initBiometryManager); 10 | 11 | /** 12 | * HOC to pass the BiometryManager component instance to the wrapped component. 13 | */ 14 | export const withBiometryManager = createHOC(useBiometryManager); 15 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/hooks-hocs/closing-behavior.ts: -------------------------------------------------------------------------------- 1 | import { initClosingBehavior } from '@telegram-apps/sdk'; 2 | 3 | import { createHOC } from '../createHOC.js'; 4 | import { createHook } from '../createHook.js'; 5 | 6 | /** 7 | * Hook to receive the ClosingBehavior component instance. 8 | */ 9 | export const useClosingBehavior = createHook(initClosingBehavior); 10 | 11 | /** 12 | * HOC to pass the ClosingBehavior component instance to the wrapped component. 13 | */ 14 | export const withClosingBehavior = createHOC(useClosingBehavior); 15 | -------------------------------------------------------------------------------- /packages/solid-router-integration/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import dts from 'vite-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dts({ outDir: 'dist/dts' }), 7 | ], 8 | build: { 9 | outDir: 'dist', 10 | emptyOutDir: true, 11 | sourcemap: true, 12 | rollupOptions: { external: ['@solidjs/router', '@telegram-apps/sdk-solid'] }, 13 | lib: { 14 | entry: 'src/index.ts', 15 | formats: ['es', 'cjs'], 16 | fileName: 'index', 17 | }, 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/isSDKErrorOfType.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isSDKErrorOfType } from './isSDKErrorOfType.js'; 4 | import { SDKError } from './SDKError.js'; 5 | 6 | it('should return true if passed value is instance of SDKError', () => { 7 | expect(isSDKErrorOfType('', 'ERR_METHOD_UNSUPPORTED')).toBe(false); 8 | expect(isSDKErrorOfType(new Error(), 'ERR_METHOD_UNSUPPORTED')).toBe(false); 9 | expect(isSDKErrorOfType(new SDKError('ERR_METHOD_UNSUPPORTED'), 'ERR_METHOD_UNSUPPORTED')).toBe(true); 10 | }); 11 | -------------------------------------------------------------------------------- /playgrounds/custom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /packages/init-data-node/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/init-data-node 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [0ac4c50] 8 | - @telegram-apps/sdk@1.1.3 9 | 10 | ## 1.0.3 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [fd30596] 15 | - @telegram-apps/sdk@1.1.2 16 | 17 | ## 1.0.2 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [2dbbd42] 22 | - @telegram-apps/sdk@1.1.1 23 | 24 | ## 1.0.1 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies [54adc6f] 29 | - @telegram-apps/sdk@1.1.0 30 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/parsing/serializeThemeParams.ts: -------------------------------------------------------------------------------- 1 | import { keyToExternal } from '../keys.js'; 2 | import type { ThemeParamsParsed } from '../types.js'; 3 | 4 | /** 5 | * Serializes theme parameters to representation sent from the Telegram application. 6 | */ 7 | export function serializeThemeParams(themeParams: ThemeParamsParsed): string { 8 | return JSON.stringify( 9 | Object.fromEntries( 10 | Object 11 | .entries(themeParams) 12 | .map(([key, value]) => [keyToExternal(key), value]), 13 | ), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/sdk 2 | 3 | ## 1.1.3 4 | 5 | ### Patch Changes 6 | 7 | - 0ac4c50: Re-use captured errors in `retrieveLaunchParams`. Make error more clear. 8 | 9 | ## 1.1.2 10 | 11 | ### Patch Changes 12 | 13 | - fd30596: Make the `postEvent` function intellisense a bit better. 14 | 15 | ## 1.1.1 16 | 17 | ### Patch Changes 18 | 19 | - 2dbbd42: Make retrieveLaunchParams error message clearer, add link to docs 20 | 21 | ## 1.1.0 22 | 23 | ### Minor Changes 24 | 25 | - 54adc6f: Add the `SwipeBehavior` component, related hooks and HOCs. 26 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isColorDark.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isColorDark } from './isColorDark.js'; 4 | 5 | it('should return true in case, the value which is equal to Math.sqrt(0.299 * R * R + 0.587 * G * G + 0.114 * B * B) is less than 120 and false otherwise', () => { 6 | expect(isColorDark('#17212b')).toBe(true); 7 | expect(isColorDark('#f5f5f5')).toBe(false); 8 | }); 9 | 10 | it('should throw an error in case, passed value has not convertable to RGB format', () => { 11 | expect(() => isColorDark('abc')).toThrow(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/sdk/src/components/Invoice/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | 3 | /** 4 | * Invoice internal state. 5 | */ 6 | export interface InvoiceState { 7 | isOpened: boolean; 8 | } 9 | 10 | /** 11 | * Invoice events. 12 | */ 13 | export type InvoiceEvents = StateEvents; 14 | 15 | /** 16 | * Invoice event name. 17 | */ 18 | export type InvoiceEventName = keyof InvoiceEvents; 19 | 20 | /** 21 | * Invoice event listener. 22 | */ 23 | export type InvoiceEventListener = InvoiceEvents[E]; 24 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromStorage.ts: -------------------------------------------------------------------------------- 1 | import { parseLaunchParams } from '@/launch-params/parseLaunchParams.js'; 2 | import { getStorageValue } from '@/storage/storage.js'; 3 | import type { LaunchParams } from '@/launch-params/types.js'; 4 | 5 | /** 6 | * @returns Launch parameters stored in the session storage. 7 | * @throws Error if function was unable to extract launch parameters from the window location hash. 8 | */ 9 | export function retrieveFromStorage(): LaunchParams { 10 | return parseLaunchParams(getStorageValue('launchParams') || ''); 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-router-integration/vite.config.ts: -------------------------------------------------------------------------------- 1 | import dts from 'vite-plugin-dts'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dts({ outDir: 'dist/dts' }), 7 | ], 8 | build: { 9 | outDir: 'dist', 10 | emptyOutDir: true, 11 | sourcemap: true, 12 | rollupOptions: { 13 | external: ['@telegram-apps/sdk-react', 'react', 'react-router-dom'], 14 | }, 15 | lib: { 16 | entry: 'src/index.ts', 17 | formats: ['es', 'cjs'], 18 | fileName: 'index', 19 | }, 20 | }, 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /packages/sdk/src/css-vars/setCSSVar.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, expect, it, vi } from 'vitest'; 2 | 3 | import { setCSSVar } from './setCSSVar.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should call document.documentElement.style.setProperty with passed "name" and "value" arguments', () => { 10 | const spy = vi.spyOn(document.documentElement.style, 'setProperty').mockImplementation(() => {}); 11 | 12 | setCSSVar('tma', 'is cool'); 13 | expect(spy).toHaveBeenCalledOnce(); 14 | expect(spy).toHaveBeenCalledWith('tma', 'is cool'); 15 | }); 16 | -------------------------------------------------------------------------------- /playgrounds/solid/src/components/RGB/RGB.tsx: -------------------------------------------------------------------------------- 1 | import { classNames, type RGB as RGBType } from '@telegram-apps/sdk-solid'; 2 | import { type Component, type JSX, splitProps } from 'solid-js'; 3 | 4 | import './RGB.css'; 5 | 6 | export type RGBProps = JSX.IntrinsicElements['span'] & { 7 | color: RGBType; 8 | }; 9 | 10 | export const RGB: Component = (props) => ( 11 | 12 | 13 | {props.color} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/string.ts: -------------------------------------------------------------------------------- 1 | import { createTypeError } from '../createTypeError.js'; 2 | import { createValueParserGenerator } from '../createValueParserGenerator.js'; 3 | import type { ValueParserGenerator } from '../createValueParserGenerator.js'; 4 | 5 | /** 6 | * Returns parser to parse value as string. 7 | */ 8 | export const string: ValueParserGenerator = createValueParserGenerator((value) => { 9 | if (typeof value === 'string' || typeof value === 'number') { 10 | return value.toString(); 11 | } 12 | throw createTypeError(); 13 | }, 'string'); 14 | -------------------------------------------------------------------------------- /packages/sdk-react/src/SDKProvider/SDKContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | import type { SDKContextType } from './SDKProvider.types.js'; 4 | 5 | export const SDKContext = createContext(undefined); 6 | 7 | /** 8 | * @throws Error useSDK was used outside SDKProvider. 9 | * @returns SDK context. 10 | */ 11 | export function useSDK(): SDKContextType { 12 | const context = useContext(SDKContext); 13 | if (!context) { 14 | throw new Error('useSDK was used outside the SDKProvider.'); 15 | } 16 | return context; 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/SDKProvider/SDKContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'solid-js'; 2 | 3 | import type { SDKContextType } from './SDKProvider.types.js'; 4 | 5 | export const SDKContext = createContext(); 6 | 7 | /** 8 | * @throws Error useSDK was used outside SDKProvider. 9 | * @returns Function to register a factory. 10 | */ 11 | export function useSDK(): SDKContextType { 12 | const context = useContext(SDKContext); 13 | if (!context) { 14 | throw new Error('useSDK was used outside of SDKProvider.'); 15 | } 16 | return context; 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk-solid/vite.config.ts: -------------------------------------------------------------------------------- 1 | import solidPlugin from 'vite-plugin-solid'; 2 | import dts from 'vite-plugin-dts'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | dts({ outDir: 'dist/dts' }), 8 | solidPlugin(), 9 | ], 10 | build: { 11 | outDir: 'dist', 12 | emptyOutDir: true, 13 | sourcemap: true, 14 | rollupOptions: { 15 | external: ['solid-js'], 16 | }, 17 | lib: { 18 | entry: 'src/index.ts', 19 | formats: ['es', 'cjs'], 20 | fileName: 'index', 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ClosingBehavior/initClosingBehavior.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { ClosingBehavior } from './ClosingBehavior.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `ClosingBehavior` class. 7 | * @see ClosingBehavior 8 | */ 9 | export const initClosingBehavior = createComponentInitFn( 10 | 'closingBehavior', 11 | ({ 12 | postEvent, 13 | state = { isConfirmationNeeded: false }, 14 | }) => new ClosingBehavior(state.isConfirmationNeeded, postEvent), 15 | ); 16 | -------------------------------------------------------------------------------- /packages/sdk/src/components/SettingsButton/initSettingsButton.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { SettingsButton } from './SettingsButton.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `SettingsButton` class. 7 | * @see SettingsButton 8 | */ 9 | export const initSettingsButton = createComponentInitFn( 10 | 'settingsButton', 11 | ({ 12 | version, 13 | postEvent, 14 | state = { isVisible: false }, 15 | }) => new SettingsButton(state.isVisible, version, postEvent), 16 | ); 17 | -------------------------------------------------------------------------------- /packages/sdk/src/errors/createError.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { createError } from '@/errors/createError.js'; 4 | import { ERR_METHOD_UNSUPPORTED } from '@/errors/errors.js'; 5 | import { SDKError } from '@/errors/SDKError.js'; 6 | 7 | it('should return instance of SDK error with specified type and message properties', () => { 8 | const error = createError(ERR_METHOD_UNSUPPORTED, 'my message'); 9 | expect(error).toBeInstanceOf(SDKError); 10 | expect(error.type).toBe(ERR_METHOD_UNSUPPORTED); 11 | expect(error.message).toBe('my message'); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/initThemeParams.ts: -------------------------------------------------------------------------------- 1 | import { ThemeParams } from '@/components/ThemeParams/ThemeParams.js'; 2 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 3 | 4 | /** 5 | * @returns A new initialized instance of the `ThemeParams` class. 6 | * @see ThemeParams 7 | */ 8 | export const initThemeParams = createComponentInitFn( 9 | 'themeParams', 10 | ({ themeParams, state = themeParams, addCleanup }) => { 11 | const tp = new ThemeParams(state); 12 | addCleanup(tp.listen()); 13 | return tp; 14 | }, 15 | ); 16 | -------------------------------------------------------------------------------- /packages/sdk/src/env/hasExternalNotify.ts: -------------------------------------------------------------------------------- 1 | import { isRecord } from '@/misc/isRecord.js'; 2 | 3 | /** 4 | * Returns true in case, passed value contains path `external.notify` property and `notify` is a 5 | * function. 6 | * @param value - value to check. 7 | */ 8 | export function hasExternalNotify(value: T): value is ( 9 | T & { 10 | external: { 11 | notify: (...args: any) => any; 12 | }; 13 | }) { 14 | return 'external' in value 15 | && isRecord(value.external) 16 | && 'notify' in value.external 17 | && typeof value.external.notify === 'function'; 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk/src/components/SwipeBehavior/initSwipeBehavior.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { SwipeBehavior } from './SwipeBehavior.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `SwipeBehavior` class. 7 | * @see SwipeBehavior 8 | */ 9 | export const initSwipeBehavior = createComponentInitFn( 10 | 'swipeBehavior', 11 | ({ 12 | postEvent, 13 | state = { isVerticalSwipeEnabled: true }, 14 | version 15 | }) => new SwipeBehavior(state.isVerticalSwipeEnabled, version, postEvent), 16 | ); 17 | -------------------------------------------------------------------------------- /playgrounds/solid/src/tonconnect/TonConnectButton.tsx: -------------------------------------------------------------------------------- 1 | import { onCleanup, onMount, type Component } from 'solid-js'; 2 | 3 | import { useTonConnectUI } from '@/tonconnect/TonConnectUIContext.js'; 4 | 5 | export const TonConnectButton: Component = () => { 6 | const [, { setUIOptions }] = useTonConnectUI(); 7 | const buttonRootId = 'ton-connect-button'; 8 | 9 | onMount(() => { 10 | setUIOptions({ buttonRootId }); 11 | }); 12 | 13 | onCleanup(() => { 14 | setUIOptions({ buttonRootId: null }); 15 | }); 16 | 17 | return
; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/sdk/src/env/hasExternalNotify.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { hasExternalNotify } from './hasExternalNotify.js'; 4 | 5 | it('should return true if passed object contains path property "external.notify" and "notify" is a function property.', () => { 6 | expect(hasExternalNotify({})).toBe(false); 7 | expect(hasExternalNotify({ external: {} })).toBe(false); 8 | expect(hasExternalNotify({ external: { notify: [] } })).toBe(false); 9 | expect(hasExternalNotify({ 10 | external: { 11 | notify: () => { 12 | }, 13 | }, 14 | })).toBe(true); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/isRecord.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { isRecord } from './isRecord.js'; 4 | 5 | it('should return false for non-object value', () => { 6 | [true, 123, 'abc'].forEach((v) => { 7 | expect(isRecord(v)).toBe(false); 8 | }); 9 | }); 10 | 11 | it('should return false for null value', () => { 12 | expect(isRecord(null)).toBe(false); 13 | }); 14 | 15 | it('should return false for array', () => { 16 | expect(isRecord([])).toBe(false); 17 | }); 18 | 19 | it('should return true for object', () => { 20 | expect(isRecord({ a: true })).toBe(true); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getHash.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param value - string to take hash part from. 3 | * @returns String after the first met "#" symbol. In case, value doesn't contain hashtag, the 4 | * function will return null. 5 | * 6 | * @example No hash. 7 | * getHash('/path'); // null 8 | * 9 | * @example Has hash. 10 | * getHash('/path#abc'); // 'abc' 11 | * 12 | * @example Has double hash. 13 | * getHash('/path#abc#another'); // 'abc#another' 14 | */ 15 | export function getHash(value: string): string | null { 16 | const match = value.match(/#(.+)/); 17 | return match ? match[1] : null; 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .idea 39 | 40 | certificates -------------------------------------------------------------------------------- /packages/sdk/src/parsing/createValueParserGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ValueParser } from './ValueParser/ValueParser.js'; 2 | import type { Parser } from './types.js'; 3 | 4 | export type ValueParserGenerator = () => ValueParser; 5 | 6 | /** 7 | * Creates function which generates new scalar value parser based on the specified one. 8 | * @param parser - parser to use as basic. 9 | * @param type - type name. 10 | */ 11 | export function createValueParserGenerator( 12 | parser: Parser, 13 | type?: string, 14 | ): ValueParserGenerator { 15 | return () => new ValueParser(parser, false, type); 16 | } 17 | -------------------------------------------------------------------------------- /playgrounds/next/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /playgrounds/solid/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | line-height: 1.5; 8 | 9 | background: var(--tg-theme-secondary-bg-color, white); 10 | color: var(--tg-theme-text-color, black); 11 | } 12 | 13 | blockquote { 14 | margin: 0; 15 | } 16 | 17 | blockquote p { 18 | padding: 15px; 19 | background: #eee; 20 | border-radius: 5px; 21 | } 22 | 23 | pre { 24 | overflow: auto; 25 | } -------------------------------------------------------------------------------- /apps/docs/packages/telegram-apps-sdk/init-data/chat.md: -------------------------------------------------------------------------------- 1 | # `Chat` 2 | 3 | ### `id` 4 | 5 | Type: `number` 6 | 7 | Unique identifier for this chat. 8 | 9 | ### `photoUrl` 10 | 11 | Type: `string`, _optional_ 12 | 13 | URL of the chat’s photo. The photo can be in .jpeg or .svg formats. 14 | Only returned for Mini Apps launched from the attachment menu. 15 | 16 | ### `type` 17 | 18 | Type: `'group' | 'supergroup' | 'channel' | string` 19 | 20 | Type of chat. 21 | 22 | ### `title` 23 | 24 | Type: `string` 25 | 26 | Title of the chat. 27 | 28 | ### `username` 29 | 30 | Type: `string`, _optional_ 31 | 32 | Username of the chat. -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": [ 4 | "**/.env.*local" 5 | ], 6 | "tasks": { 7 | "build": { 8 | "dependsOn": [ 9 | "^build" 10 | ] 11 | }, 12 | "dev": { 13 | "cache": false, 14 | "persistent": true 15 | }, 16 | "lint": { 17 | "cache": true 18 | }, 19 | "lint:fix": { 20 | "cache": true 21 | }, 22 | "test": { 23 | "cache": true 24 | }, 25 | "typecheck": { 26 | "dependsOn": [ 27 | "^typecheck" 28 | ], 29 | "cache": true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/docs/platform/popup.md: -------------------------------------------------------------------------------- 1 | # Popup 2 | 3 | ![Popup](/components/popup.png) 4 | 5 | Popup is a component that is located on top of the Mini App. Its classic use case is a request for 6 | user confirmation to perform an action. Telegram Mini Apps allows specifying popup title, message 7 | and the list of up to 3 configurable buttons. 8 | 9 | To show the popup, developer can utilize 10 | the [web_app_open_popup](methods.md#web-app-open-popup) Telegram Mini Apps 11 | method. When user presses any popup button, Telegram application emits 12 | the [popup_closed](events.md#popup-closed) event passing the identifier of the 13 | clicked button. 14 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/keys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a palette key from the Telegram application to the representation used by the package. 3 | * @param key - palette key. 4 | */ 5 | export function keyToLocal(key: string): string { 6 | return key.replace(/_[a-z]/g, (match) => match[1].toUpperCase()); 7 | } 8 | 9 | /** 10 | * Converts palette key from the local representation to the representation sent from the 11 | * Telegram application. 12 | * @param key - palette key. 13 | */ 14 | export function keyToExternal(key: string): string { 15 | return key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`); 16 | } 17 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/listening/unsubscribe.ts: -------------------------------------------------------------------------------- 1 | import { miniAppsEventEmitter, resetMiniAppsEventEmitter } from '../event-emitter/singleton.js'; 2 | import type { MiniAppsSubscribeListener } from '../types.js'; 3 | 4 | /** 5 | * Removes global event listener. 6 | * @param listener - event listener. 7 | */ 8 | export function unsubscribe(listener: MiniAppsSubscribeListener): void { 9 | const ee = miniAppsEventEmitter(); 10 | const { count } = ee; 11 | ee.unsubscribe(listener); 12 | 13 | // If event emitter now has no listeners, we can make a cleanup. 14 | if (count && !ee.count) { 15 | resetMiniAppsEventEmitter(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/env/hasWebviewProxy.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { hasWebviewProxy } from './hasWebviewProxy.js'; 4 | 5 | it('should return true if passed object contains path property "TelegramWebviewProxy.postEvent" and "postEvent" is a function property.', () => { 6 | expect(hasWebviewProxy({})).toBe(false); 7 | expect(hasWebviewProxy({ TelegramWebviewProxy: {} })).toBe(false); 8 | expect(hasWebviewProxy({ TelegramWebviewProxy: { postEvent: [] } })).toBe(false); 9 | expect(hasWebviewProxy({ 10 | TelegramWebviewProxy: { 11 | postEvent: () => { 12 | }, 13 | }, 14 | })).toBe(true); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/sdk-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/sdk-react 2 | 3 | ## 1.1.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [0ac4c50] 8 | - @telegram-apps/sdk@1.1.3 9 | 10 | ## 1.1.2 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [fd30596] 15 | - @telegram-apps/sdk@1.1.2 16 | 17 | ## 1.1.1 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [2dbbd42] 22 | - @telegram-apps/sdk@1.1.1 23 | 24 | ## 1.1.0 25 | 26 | ### Minor Changes 27 | 28 | - 54adc6f: Add the `SwipeBehavior` component, related hooks and HOCs. 29 | 30 | ### Patch Changes 31 | 32 | - Updated dependencies [54adc6f] 33 | - @telegram-apps/sdk@1.1.0 34 | -------------------------------------------------------------------------------- /packages/sdk-solid/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @telegram-apps/sdk-solid 2 | 3 | ## 1.1.3 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [0ac4c50] 8 | - @telegram-apps/sdk@1.1.3 9 | 10 | ## 1.1.2 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [fd30596] 15 | - @telegram-apps/sdk@1.1.2 16 | 17 | ## 1.1.1 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [2dbbd42] 22 | - @telegram-apps/sdk@1.1.1 23 | 24 | ## 1.1.0 25 | 26 | ### Minor Changes 27 | 28 | - 54adc6f: Add the `SwipeBehavior` component, related hooks and HOCs. 29 | 30 | ### Patch Changes 31 | 32 | - Updated dependencies [54adc6f] 33 | - @telegram-apps/sdk@1.1.0 34 | -------------------------------------------------------------------------------- /packages/sdk/src/components/MiniApp/initMiniApp.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { MiniApp } from './MiniApp.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `MiniApp` class. 7 | * @see MiniApp 8 | */ 9 | export const initMiniApp = createComponentInitFn( 10 | 'miniApp', 11 | ({ 12 | themeParams, 13 | botInline = false, 14 | state = { 15 | bgColor: themeParams.bgColor || '#ffffff', 16 | headerColor: themeParams.headerBgColor || '#000000', 17 | }, 18 | ...rest 19 | }) => new MiniApp({ ...rest, ...state, botInline }), 20 | ); 21 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/createSafeURL.ts: -------------------------------------------------------------------------------- 1 | import { ensurePrefix } from '@/navigation/ensurePrefix.js'; 2 | import type { URLLike } from '@/navigation/BrowserNavigator/types.js'; 3 | 4 | /** 5 | * Safely creates new instance of URL with some predefined protocol and hostname. 6 | * @param urlOrPath - URL instance or path. 7 | */ 8 | export function createSafeURL(urlOrPath: string | Partial): URL { 9 | return new URL( 10 | typeof urlOrPath === 'string' 11 | ? urlOrPath 12 | : `${urlOrPath.pathname || ''}${ensurePrefix(urlOrPath.search || '', '?')}${ensurePrefix(urlOrPath.hash || '', '#')}`, 13 | 'http://a', 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /playgrounds/next/src/components/ErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | export function ErrorPage({ 4 | error, 5 | reset, 6 | }: { 7 | error: Error & { digest?: string } 8 | reset?: () => void 9 | }) { 10 | useEffect(() => { 11 | // Log the error to an error reporting service 12 | console.error(error); 13 | }, [error]); 14 | 15 | return ( 16 |
17 |

An unhandled error occurred!

18 |
19 | 20 | {error.message} 21 | 22 |
23 | {reset && } 24 |
25 | ); 26 | } -------------------------------------------------------------------------------- /packages/create-mini-app/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme } from '@inquirer/core'; 2 | import chalk from 'chalk'; 3 | import figures from 'figures'; 4 | 5 | export const theme = makeTheme({ 6 | style: { 7 | error(text: string): string { 8 | return chalk.red(text); 9 | }, 10 | success(text: string): string { 11 | return chalk.green(text); 12 | }, 13 | muted(text: string): string { 14 | return chalk.dim(text); 15 | }, 16 | }, 17 | prefixes: { 18 | pending: chalk.blue('?'), 19 | info: chalk.blue('i'), 20 | completed: chalk.green(figures.tick), 21 | pointer: chalk.green(figures.pointer), 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/BrowserNavigator/basicItemToBrowser.ts: -------------------------------------------------------------------------------- 1 | import type { BasicNavigatorHistoryItem } from '@/navigation/BasicNavigator/types.js'; 2 | import type { 3 | BrowserNavigatorHistoryItem, 4 | BrowserNavigatorHistoryItemParams, 5 | } from '@/navigation/BrowserNavigator/types.js'; 6 | 7 | /** 8 | * Converts basic navigator entry to browser navigator entry. 9 | */ 10 | export function basicItemToBrowser( 11 | { 12 | params, 13 | ...rest 14 | }: BasicNavigatorHistoryItem>, 15 | ): BrowserNavigatorHistoryItem { 16 | return { ...(params || { hash: '', search: '' }), ...rest }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/json.ts: -------------------------------------------------------------------------------- 1 | import { parseBySchema } from '../parseBySchema.js'; 2 | import { toRecord } from '../toRecord.js'; 3 | import { ValueParser } from '../ValueParser/ValueParser.js'; 4 | import type { Schema } from '../types.js'; 5 | 6 | /** 7 | * Creates new Json parser according to passed schema. 8 | * @param schema - object schema. 9 | * @param type - parser type name. 10 | */ 11 | export function json(schema: Schema, type?: string): ValueParser { 12 | return new ValueParser((value) => { 13 | const record = toRecord(value); 14 | return parseBySchema(schema, (field) => record[field]); 15 | }, false, type); 16 | } 17 | -------------------------------------------------------------------------------- /packages/sdk/src/timeout/sleep.test.ts: -------------------------------------------------------------------------------- 1 | import { vi, expect, it, beforeAll, afterAll } from 'vitest'; 2 | import { sleep } from '@/timeout/sleep.js'; 3 | 4 | beforeAll(() => { 5 | vi.useFakeTimers(); 6 | }); 7 | 8 | afterAll(() => { 9 | vi.useRealTimers(); 10 | }); 11 | 12 | it('should resolve promise after specified timeout', () => { 13 | let done = false; 14 | sleep(1000).then(() => done = true); 15 | vi.advanceTimersByTime(999); 16 | expect(done).toBe(false); 17 | 18 | Promise 19 | .resolve() 20 | .then(() => vi.advanceTimersByTime(2)) 21 | .then(() => expect(done).toBe(true)) 22 | .finally(() => vi.advanceTimersToNextTimer()) 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/urlToPath.ts: -------------------------------------------------------------------------------- 1 | import { createSafeURL } from '@/navigation/createSafeURL.js'; 2 | import type { URLLike } from '@/navigation/BrowserNavigator/types.js'; 3 | 4 | /** 5 | * Extracts path part from a URL. 6 | * @param urlOrPath - URL instance or path. 7 | */ 8 | export function urlToPath(urlOrPath: string | Partial): string { 9 | const isAbsolute = typeof urlOrPath === 'string' 10 | ? urlOrPath.startsWith('/') 11 | : !!(urlOrPath.pathname && urlOrPath.pathname.startsWith('/')); 12 | const url = createSafeURL(urlOrPath); 13 | 14 | return `${isAbsolute ? url.pathname : url.pathname.slice(1)}${url.search}${url.hash}`; 15 | } 16 | -------------------------------------------------------------------------------- /playgrounds/custom/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import basicSsl from '@vitejs/plugin-basic-ssl'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | // Allows using the compilerOptions.paths property in tsconfig.json. 8 | // https://www.npmjs.com/package/vite-tsconfig-paths 9 | tsconfigPaths(), 10 | // Allows using self-signed certificates to run the dev server using HTTPS. 11 | // https://www.npmjs.com/package/@vitejs/plugin-basic-ssl 12 | basicSsl(), 13 | ], 14 | build: { 15 | outDir: 'dist', 16 | emptyOutDir: true, 17 | }, 18 | }); 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getFirstNavigationEntry.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, expect, it, vi } from 'vitest'; 2 | 3 | import { getFirstNavigationEntry } from './getFirstNavigationEntry.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should return the first entry from the performance', () => { 10 | const expected = {}; 11 | const spy = vi 12 | .spyOn(performance, 'getEntriesByType') 13 | .mockImplementationOnce(() => [expected] as any); 14 | 15 | expect(getFirstNavigationEntry()).toStrictEqual(expected); 16 | spy.mockClear().mockImplementation(() => []); 17 | 18 | expect(getFirstNavigationEntry()).toBeUndefined(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/test-utils/src/toSearchParams.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates search parameters from the specified JSON object. 3 | * @param json - JSON object. 4 | */ 5 | export function toSearchParams(json: Record): string { 6 | const params = new URLSearchParams(); 7 | 8 | Object.entries(json).forEach(([key, value]) => { 9 | if (typeof value === 'object') { 10 | params.set(key, JSON.stringify(value)); 11 | return; 12 | } 13 | 14 | if (typeof value === 'boolean') { 15 | params.set(key, String(Number(value))); 16 | return; 17 | } 18 | 19 | params.set(key, String(value)); 20 | }); 21 | 22 | return params.toString(); 23 | } 24 | -------------------------------------------------------------------------------- /playgrounds/solid/src/tonconnect/TonConnectUIContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'solid-js'; 2 | import type { TonConnectUI, TonConnectUiOptions } from '@tonconnect/ui'; 3 | 4 | export type TonConnectUIContextType = [ 5 | get: () => TonConnectUI, 6 | { 7 | setUIOptions(options: TonConnectUiOptions): void; 8 | }, 9 | ]; 10 | 11 | export const TonConnectUIContext = createContext(); 12 | 13 | export function useTonConnectUI(): TonConnectUIContextType { 14 | const context = useContext(TonConnectUIContext); 15 | if (!context) { 16 | throw new Error('Unable to get TonConnectUIContext'); 17 | } 18 | return context; 19 | } 20 | -------------------------------------------------------------------------------- /packages/sdk/src/components/SwipeBehavior/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | 3 | /** 4 | * SwipeBehavior internal state. 5 | */ 6 | export interface SwipeBehaviorState { 7 | isVerticalSwipeEnabled: boolean; 8 | } 9 | 10 | /** 11 | * SwipeBehavior trackable events. 12 | */ 13 | export type SwipeBehaviorEvents = StateEvents; 14 | 15 | /** 16 | * SwipeBehavior event name. 17 | */ 18 | export type SwipeBehaviorEventName = keyof SwipeBehaviorEvents; 19 | 20 | /** 21 | * SwipeBehavior event listener. 22 | */ 23 | export type SwipeBehaviorEventListener = 24 | SwipeBehaviorEvents[E]; 25 | -------------------------------------------------------------------------------- /packages/sdk/src/env/hasWebviewProxy.ts: -------------------------------------------------------------------------------- 1 | import { isRecord } from '@/misc/isRecord.js'; 2 | 3 | /** 4 | * Returns true in case, passed value contains path `TelegramWebviewProxy.postEvent` property and 5 | * `postEvent` is a function. 6 | * @param value - value to check. 7 | */ 8 | export function hasWebviewProxy(value: T): value is ( 9 | T & { 10 | TelegramWebviewProxy: { 11 | postEvent: (...args: unknown[]) => unknown; 12 | } 13 | }) { 14 | return 'TelegramWebviewProxy' in value 15 | && isRecord(value.TelegramWebviewProxy) 16 | && 'postEvent' in value.TelegramWebviewProxy 17 | && typeof value.TelegramWebviewProxy.postEvent === 'function'; 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/requestThemeParams.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/bridge/request.js'; 2 | import type { ExecuteWithOptions } from '@/types/index.js'; 3 | 4 | import { parseThemeParams } from './parsing/parseThemeParams.js'; 5 | import type { ThemeParamsParsed } from './types.js'; 6 | 7 | /** 8 | * Requests current theme parameters from the Telegram application. 9 | * @param options - request options. 10 | */ 11 | export function requestThemeParams(options: ExecuteWithOptions = {}): Promise { 12 | return request({ 13 | ...options, 14 | method: 'web_app_request_theme', 15 | event: 'theme_changed', 16 | }).then(parseThemeParams); 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/createSafeURL.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { createSafeURL } from './createSafeURL.js'; 3 | 4 | it('should create a url with passed path pathname, hash and search', () => { 5 | expect(createSafeURL('/a?b#c')).toMatchObject({ 6 | pathname: '/a', 7 | search: '?b', 8 | hash: '#c', 9 | }); 10 | expect(createSafeURL({ pathname: 'a', search: 'b', hash: 'c' })).toMatchObject({ 11 | pathname: '/a', 12 | search: '?b', 13 | hash: '#c', 14 | }); 15 | expect(createSafeURL({ pathname: '/a', search: '?b', hash: '#c' })).toMatchObject({ 16 | pathname: '/a', 17 | search: '?b', 18 | hash: '#c', 19 | }); 20 | }); -------------------------------------------------------------------------------- /playgrounds/solid/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | 4 | import './index.css'; 5 | 6 | import { Root } from '@/components/Root.js'; 7 | 8 | // Uncomment this import in case, you would like to develop the application even outside 9 | // the Telegram application, just in your browser. 10 | import './mockEnv.js'; 11 | 12 | const root = document.getElementById('root'); 13 | 14 | if (import.meta.env.DEV && !(root instanceof HTMLElement)) { 15 | throw new Error( 16 | 'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?', 17 | ); 18 | } 19 | 20 | render(() => (), root!); 21 | 22 | -------------------------------------------------------------------------------- /apps/docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | /* In mobile version we would like to move hashtag ("#") to the right side of section title. */ 3 | .vp-doc h1, 4 | .vp-doc h2, 5 | .vp-doc h3, 6 | .vp-doc h4, 7 | .vp-doc h5, 8 | .vp-doc h6 { 9 | padding-right: 1em; 10 | } 11 | 12 | .vp-doc h1 .header-anchor, 13 | .vp-doc h2 .header-anchor, 14 | .vp-doc h3 .header-anchor, 15 | .vp-doc h4 .header-anchor, 16 | .vp-doc h5 .header-anchor, 17 | .vp-doc h6 .header-anchor { 18 | left: auto; 19 | top: auto; 20 | bottom: 0; 21 | margin-left: .3em; 22 | } 23 | } 24 | 25 | .guides-image { 26 | width: 100%; 27 | border-radius: 10px; 28 | } -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-emitter/singleton.test.ts: -------------------------------------------------------------------------------- 1 | import { createWindow } from '@test-utils/createWindow.js'; 2 | import { afterEach, beforeEach, expect, it } from 'vitest'; 3 | import type { WindowSpy } from '@test-utils/createWindow.js'; 4 | 5 | import { miniAppsEventEmitter } from './singleton.js'; 6 | 7 | let windowSpy: WindowSpy; 8 | 9 | beforeEach(() => { 10 | windowSpy = createWindow({ 11 | innerWidth: 1920, 12 | innerHeight: 1080, 13 | }); 14 | }); 15 | 16 | afterEach(() => { 17 | windowSpy.mockRestore(); 18 | }); 19 | 20 | it('should return the same instance of emitter', () => { 21 | expect(miniAppsEventEmitter()).toEqual(miniAppsEventEmitter()); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/sdk/src/events/onWindow.ts: -------------------------------------------------------------------------------- 1 | import type { RemoveEventListenerFn } from './types.js'; 2 | 3 | /** 4 | * Adds new event listener using window.addEventListener. 5 | * @param type - event name. 6 | * @param listener - event listener. 7 | * @param options - listening options. 8 | * @returns Function to remove event listener. 9 | */ 10 | export function onWindow( 11 | type: K, 12 | listener: (this: Window, ev: WindowEventMap[K]) => any, 13 | options?: boolean | AddEventListenerOptions, 14 | ): RemoveEventListenerFn { 15 | window.addEventListener(type, listener, options); 16 | return () => window.removeEventListener(type, listener, options); 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/ArrayParser/ArrayParser.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { string } from '../parsers/string.js'; 4 | import { ArrayParser } from './ArrayParser.js'; 5 | 6 | describe('constructor', () => { 7 | it('should apply parser value directly if it is function', () => { 8 | const parser = new ArrayParser(() => 'Hello!', false); 9 | 10 | expect(parser.parse(['abc'])).toStrictEqual(['Hello!']); 11 | }); 12 | 13 | it('should apply parser "parse" method directly if it is ValueParser', () => { 14 | const parser = new ArrayParser(string(), false); 15 | 16 | expect(parser.parse(['abc'])).toStrictEqual(['abc']); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ClosingBehavior/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | 3 | /** 4 | * ClosingBehavior internal state. 5 | */ 6 | export interface ClosingBehaviorState { 7 | isConfirmationNeeded: boolean; 8 | } 9 | 10 | /** 11 | * ClosingBehavior trackable events. 12 | */ 13 | export type ClosingBehaviorEvents = StateEvents; 14 | 15 | /** 16 | * ClosingBehavior event name. 17 | */ 18 | export type ClosingBehaviorEventName = keyof ClosingBehaviorEvents; 19 | 20 | /** 21 | * ClosingBehavior event listener. 22 | */ 23 | export type ClosingBehaviorEventListener = 24 | ClosingBehaviorEvents[E]; 25 | -------------------------------------------------------------------------------- /packages/create-mini-app/src/prompts/promptTemplate/types.ts: -------------------------------------------------------------------------------- 1 | import type { Framework, Language, SDK } from '../../types.js'; 2 | 3 | interface CreateSection { 4 | title: string; 5 | name: N; 6 | choices: Choice[]; 7 | } 8 | 9 | export interface Choice { 10 | title: string; 11 | value: V; 12 | defaultChecked?: boolean; 13 | } 14 | 15 | export type Section = 16 | | CreateSection<'framework', Framework> 17 | | CreateSection<'sdk', SDK> 18 | | CreateSection<'language', Language>; 19 | 20 | export interface Cell { 21 | title: string; 22 | length: number; 23 | } 24 | 25 | export interface SelectedChoices { 26 | framework: Framework; 27 | sdk: SDK; 28 | language: Language; 29 | } -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/listening/subscribe.ts: -------------------------------------------------------------------------------- 1 | import type { RemoveEventListenerFn } from '@/events/types.js'; 2 | 3 | import { miniAppsEventEmitter } from '../event-emitter/singleton.js'; 4 | import { unsubscribe } from '../listening/unsubscribe.js'; 5 | import type { MiniAppsSubscribeListener } from '../types.js'; 6 | 7 | /** 8 | * Subscribes to all events sent from the native Telegram application. 9 | * @param listener - event listener to bind. 10 | * @returns Function to remove bound event listener. 11 | */ 12 | export function subscribe(listener: MiniAppsSubscribeListener): RemoveEventListenerFn { 13 | miniAppsEventEmitter().subscribe(listener); 14 | return () => unsubscribe(listener); 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/number.ts: -------------------------------------------------------------------------------- 1 | import { createTypeError } from '../createTypeError.js'; 2 | import { createValueParserGenerator } from '../createValueParserGenerator.js'; 3 | import type { ValueParserGenerator } from '../createValueParserGenerator.js'; 4 | 5 | /** 6 | * Returns parser to parse value as number. 7 | */ 8 | export const number: ValueParserGenerator = createValueParserGenerator((value) => { 9 | if (typeof value === 'number') { 10 | return value; 11 | } 12 | 13 | if (typeof value === 'string') { 14 | const num = Number(value); 15 | 16 | if (!Number.isNaN(num)) { 17 | return num; 18 | } 19 | } 20 | 21 | throw createTypeError(); 22 | }, 'number'); 23 | -------------------------------------------------------------------------------- /playgrounds/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/sdk/src/classes/WithTrackableState.ts: -------------------------------------------------------------------------------- 1 | import { WithStateUtils } from '@/classes/WithStateUtils.js'; 2 | import type { StateEvents } from '@/classes/State/types.js'; 3 | import type { EventEmitter } from '@/events/event-emitter/EventEmitter.js'; 4 | 5 | type Emitter = EventEmitter>; 6 | 7 | export class WithTrackableState 8 | extends WithStateUtils { 9 | /** 10 | * Adds a new event listener. 11 | */ 12 | on: Emitter['on'] = this.state.on.bind(this.state); 13 | 14 | /** 15 | * Removes the event listener. 16 | */ 17 | off: Emitter['off'] = this.state.off.bind(this.state); 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/solid/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:solid/typescript' 10 | ], 11 | overrides: [ 12 | { 13 | env: { 14 | node: true 15 | }, 16 | files: [ 17 | '.eslintrc.{js,cjs}' 18 | ], 19 | parserOptions: { 20 | sourceType: 'script' 21 | } 22 | } 23 | ], 24 | parser: '@typescript-eslint/parser', 25 | parserOptions: { 26 | ecmaVersion: 'latest', 27 | sourceType: 'module' 28 | }, 29 | plugins: [ 30 | '@typescript-eslint' 31 | ], 32 | rules: {} 33 | }; 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | dist 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # local env files 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | # vercel 36 | .vercel 37 | 38 | .idea 39 | .docusaurus 40 | 41 | # Apps for local development. 42 | apps-local 43 | 44 | apps/docs/.vitepress/cache 45 | apps/docs/.vitepress/dist 46 | 47 | jsr.json -------------------------------------------------------------------------------- /packages/sdk/src/components/BackButton/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | 3 | /** 4 | * BackButton internal state. 5 | */ 6 | export interface BackButtonState { 7 | isVisible: boolean; 8 | } 9 | 10 | /** 11 | * BackButton trackable events. 12 | */ 13 | export interface BackButtonEvents extends StateEvents { 14 | /** 15 | * The BackButton was clicked. 16 | */ 17 | click: () => void; 18 | } 19 | 20 | /** 21 | * BackButton event name. 22 | */ 23 | export type BackButtonEventName = keyof BackButtonEvents; 24 | 25 | /** 26 | * BackButton event listener. 27 | */ 28 | export type BackButtonEventListener = BackButtonEvents[E]; 29 | -------------------------------------------------------------------------------- /playgrounds/next/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import type { Metadata } from 'next'; 3 | 4 | import { Root } from '@/components/Root/Root'; 5 | 6 | import '@telegram-apps/telegram-ui/dist/styles.css'; 7 | import 'normalize.css/normalize.css'; 8 | import './_assets/globals.css'; 9 | 10 | export const metadata: Metadata = { 11 | title: 'Your Application Title Goes Here', 12 | description: 'Your application description goes here', 13 | }; 14 | 15 | export default function RootLayout({ children }: PropsWithChildren) { 16 | return ( 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/sdk/src/classnames/classNames.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { classNames } from './classNames.js'; 4 | 5 | it('should ignore all non-empty strings and objects', () => { 6 | expect(classNames('', 2, 'b', null, undefined, false, true, [], 'a', 'c')).toBe('b a c'); 7 | }); 8 | 9 | it('should apply classNames function to the item, if it is an array', () => { 10 | expect(classNames(['a', 'b', 'c'])).toBe('a b c'); 11 | }); 12 | 13 | it('should pick only keys which values are truthy', () => { 14 | expect(classNames({ 15 | a: true, 16 | b: null, 17 | c: false, 18 | d: undefined, 19 | e: {}, 20 | f: 3, 21 | g: 0, 22 | h: '', 23 | })).toBe('a e f'); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromPerformance.ts: -------------------------------------------------------------------------------- 1 | import { getFirstNavigationEntry } from '@/navigation/getFirstNavigationEntry.js'; 2 | 3 | import { retrieveFromUrl } from './retrieveFromUrl.js'; 4 | import type { LaunchParams } from './types.js'; 5 | 6 | /** 7 | * @returns Launch parameters based on the first navigation entry. 8 | * @throws Error if function was unable to extract launch parameters from the navigation entry. 9 | */ 10 | export function retrieveFromPerformance(): LaunchParams { 11 | const navigationEntry = getFirstNavigationEntry(); 12 | if (!navigationEntry) { 13 | throw new Error('Unable to get first navigation entry.'); 14 | } 15 | 16 | return retrieveFromUrl(navigationEntry.name); 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/types/logical.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents classic "if" condition. 3 | */ 4 | export type If = Cond extends true ? True : False; 5 | 6 | /** 7 | * Represents classic "not" logical operation. 8 | */ 9 | export type Not = If; 10 | 11 | /** 12 | * Represents classic "and" logical operation. 13 | */ 14 | export type And = A extends true 15 | ? B extends true 16 | ? true 17 | : false 18 | : false; 19 | 20 | /** 21 | * Represents classic "or" logical operation. 22 | */ 23 | export type Or = A extends true 24 | ? true 25 | : (B extends true ? true : false); 26 | -------------------------------------------------------------------------------- /packages/sdk/src/components/BiometryManager/requestBiometryInfo.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/bridge/request.js'; 2 | import type { ExecuteWithOptions } from '@/types/index.js'; 3 | 4 | import { formatEvent } from './formatEvent.js'; 5 | import type { FormatBiometryInfoResult } from './formatEvent.js'; 6 | 7 | /** 8 | * Requests biometry information. 9 | * @param options - additional execution options. 10 | */ 11 | export async function requestBiometryInfo( 12 | options?: ExecuteWithOptions, 13 | ): Promise { 14 | return formatEvent( 15 | await request({ 16 | ...(options || {}), 17 | method: 'web_app_biometry_get_info', 18 | event: 'biometry_info_received', 19 | }), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/sdk/src/version/compareVersions.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { compareVersions } from './compareVersions.js'; 4 | 5 | it('should return 1 in case "a" is greater than "b"', () => { 6 | expect(compareVersions('6.1', '6.0')).toBe(1); 7 | expect(compareVersions('6.1', '6')).toBe(1); 8 | }); 9 | 10 | it('should return 0 in case "a" is equal to "b"', () => { 11 | expect(compareVersions('6', '6')).toBe(0); 12 | expect(compareVersions('6', '6.0')).toBe(0); 13 | expect(compareVersions('6.0', '6')).toBe(0); 14 | }); 15 | 16 | it('should return -1 in case "a" is lower than "b"', () => { 17 | expect(compareVersions('5', '6.0')).toBe(-1); 18 | expect(compareVersions('6.0', '6.1')).toBe(-1); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/sdk/src/timeout/withTimeout.ts: -------------------------------------------------------------------------------- 1 | import { createTimeoutError } from '@/timeout/createTimeoutError.js'; 2 | 3 | /** 4 | * Runs passed function or promise with specified deadline presented via timeout argument. 5 | * @param funcOrPromise - function to execute or pending promise. 6 | * @param timeout - completion timeout. 7 | */ 8 | export function withTimeout( 9 | funcOrPromise: Promise | (() => Promise), 10 | timeout: number, 11 | ): Promise { 12 | return Promise.race([ 13 | typeof funcOrPromise === 'function' ? funcOrPromise() : funcOrPromise, 14 | new Promise((_, rej) => { 15 | setTimeout(() => { 16 | rej(createTimeoutError(timeout)); 17 | }, timeout); 18 | }), 19 | ]); 20 | } 21 | -------------------------------------------------------------------------------- /packages/sdk/src/classes/WithStateUtils.ts: -------------------------------------------------------------------------------- 1 | import { State } from '@/classes/State/State.js'; 2 | 3 | export class WithStateUtils { 4 | protected state: State; 5 | 6 | constructor(shape: Shape) { 7 | this.state = new State(shape); 8 | this.set = this.state.set.bind(this.state); 9 | this.get = this.state.get.bind(this.state); 10 | this.clone = this.state.clone.bind(this.state); 11 | } 12 | 13 | /** 14 | * Gets the state value. 15 | */ 16 | protected get: State['get']; 17 | 18 | /** 19 | * Sets the state value. 20 | */ 21 | protected set: State['set']; 22 | 23 | /** 24 | * Clones the current state. 25 | */ 26 | protected clone: State['clone']; 27 | } 28 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/parseMessage.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@/parsing/parsers/json.js'; 2 | import { string } from '@/parsing/parsers/string.js'; 3 | 4 | /** 5 | * Message format used in communication between client and Telegram applications. 6 | */ 7 | export interface MiniAppsMessage { 8 | /** 9 | * Event name. 10 | */ 11 | eventType: string; 12 | /** 13 | * Event parameters. 14 | */ 15 | eventData?: unknown; 16 | } 17 | 18 | /** 19 | * Parses value as a message between client and Telegram applications. 20 | * @param value - value to parse. 21 | */ 22 | export function parseMessage(value: unknown): MiniAppsMessage { 23 | return json({ 24 | eventType: string(), 25 | eventData: (v) => v, 26 | }).parse(value); 27 | } 28 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-handlers/cleanupEventHandlers.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, vi, afterEach } from 'vitest'; 2 | 3 | import { cleanupEventHandlers } from './cleanupEventHandlers.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should delete window properties: TelegramGameProxy_receiveEvent, TelegramGameProxy, Telegram', () => { 10 | const wnd: Record = {}; 11 | vi 12 | .spyOn(window, 'window', 'get') 13 | .mockImplementation(() => ({ 14 | TelegramGameProxy_receiveEvent: {}, 15 | TelegramGameProxy: { receiveEvent: {} }, 16 | Telegram: { WebView: { receiveEvent: {} } }, 17 | } as any)); 18 | 19 | cleanupEventHandlers(); 20 | 21 | expect(wnd).toStrictEqual({}); 22 | }); -------------------------------------------------------------------------------- /packages/sdk/src/classnames/mergeClassNames.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { mergeClassNames } from './mergeClassNames.js'; 4 | 5 | it('should ignore non-object values', () => { 6 | expect(mergeClassNames({}, null, undefined, false, true, { tma: 'good' })).toStrictEqual({ tma: 'good' }); 7 | }); 8 | 9 | it('should merge objects keys by values applying classNames function', () => { 10 | expect(mergeClassNames( 11 | { a: 'hey there', b: 'space' }, 12 | { a: 'John', b: 'station' }, 13 | { c: 'wowow' }, 14 | { f: { mod: true, ignore: false, add: 'non empty string' } }, 15 | )).toStrictEqual({ 16 | a: 'hey there John', 17 | b: 'space station', 18 | c: 'wowow', 19 | f: 'mod add', 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/getPathname.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { getPathname } from '@/navigation/getPathname.js'; 3 | 4 | it('should convert passed value to the URL and take its pathname part', () => { 5 | expect(getPathname('pathname')).toBe('/pathname'); 6 | expect(getPathname('/pathname')).toBe('/pathname'); 7 | expect(getPathname('/pathname/a/b/c')).toBe('/pathname/a/b/c'); 8 | expect(getPathname('http://example.com/pathname')).toBe('/pathname'); 9 | expect(getPathname('/pathname#hash')).toBe('/pathname'); 10 | expect(getPathname('/pathname?query')).toBe('/pathname'); 11 | expect(getPathname({ pathname: '/pathname' })).toBe('/pathname'); 12 | expect(getPathname({ pathname: 'pathname' })).toBe('/pathname'); 13 | }); -------------------------------------------------------------------------------- /packages/sdk/src/components/MainButton/initMainButton.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInitFn } from '@/misc/createComponentInitFn/createComponentInitFn.js'; 2 | 3 | import { MainButton } from './MainButton.js'; 4 | 5 | /** 6 | * @returns A new initialized instance of the `MainButton` class. 7 | * @see MainButton 8 | */ 9 | export const initMainButton = createComponentInitFn( 10 | 'mainButton', 11 | ({ 12 | postEvent, 13 | themeParams, 14 | state = { 15 | isVisible: false, 16 | isEnabled: false, 17 | text: '', 18 | isLoaderVisible: false, 19 | textColor: themeParams.buttonTextColor || '#ffffff', 20 | bgColor: themeParams.buttonColor || '#000000', 21 | }, 22 | }) => new MainButton({ ...state, postEvent }), 23 | ); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/createCleanup.ts: -------------------------------------------------------------------------------- 1 | import { CleanupFn } from '@/types/index.js'; 2 | 3 | /** 4 | * Returns a tuple, containing function to add cleanup, call cleanup, and flag showing whether 5 | * cleanup was called. Cleanup will not be performed in case, it was done before. 6 | */ 7 | export function createCleanup(...fns: (CleanupFn | CleanupFn[])[]): [ 8 | add: (fn: CleanupFn) => void, 9 | cleanup: () => void, 10 | cleanedUp: boolean, 11 | ] { 12 | let cleanedUp = false; 13 | const cache = fns.flat(1); 14 | 15 | return [ 16 | (fn) => !cleanedUp && cache.push(fn), 17 | () => { 18 | if (!cleanedUp) { 19 | cleanedUp = true; 20 | cache.forEach(clean => clean()); 21 | } 22 | }, 23 | cleanedUp, 24 | ]; 25 | } -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/keys.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { keyToExternal, keyToLocal } from './keys.js'; 4 | 5 | describe('keyToLocal', () => { 6 | it('should replace _[a-z] with [A-Z]', () => { 7 | expect(keyToLocal('bg_color')).toBe('bgColor'); 8 | expect(keyToLocal('secondary_bg_color')).toBe('secondaryBgColor'); 9 | expect(keyToLocal('text_color')).toBe('textColor'); 10 | }); 11 | }); 12 | 13 | describe('keyToExternal', () => { 14 | it('should replace [A-Z] with _[a-z]', () => { 15 | expect(keyToExternal('bgColor')).toBe('bg_color'); 16 | expect(keyToExternal('secondaryBgColor')).toBe('secondary_bg_color'); 17 | expect(keyToExternal('textColor')).toBe('text_color'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromUrl.ts: -------------------------------------------------------------------------------- 1 | import { parseLaunchParams } from './parseLaunchParams.js'; 2 | import type { LaunchParams } from './types.js'; 3 | 4 | /** 5 | * @param urlString - URL to extract launch parameters from. 6 | * @returns Launch parameters from the specified URL. 7 | * @throws Error if function was unable to extract launch parameters from the passed URL. 8 | */ 9 | export function retrieveFromUrl(urlString: string): LaunchParams { 10 | return parseLaunchParams( 11 | urlString 12 | // Replace everything before this first hashtag or question sign. 13 | .replace(/^[^?#]*[?#]/, '') 14 | // Replace all hashtags and question signs to make it look like some search params. 15 | .replace(/[?#]/g, '&'), 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/boolean.ts: -------------------------------------------------------------------------------- 1 | import { createTypeError } from '../createTypeError.js'; 2 | import { createValueParserGenerator } from '../createValueParserGenerator.js'; 3 | import type { ValueParserGenerator } from '../createValueParserGenerator.js'; 4 | 5 | /** 6 | * Returns parser to parse value as boolean. 7 | */ 8 | export const boolean: ValueParserGenerator = createValueParserGenerator((value) => { 9 | if (typeof value === 'boolean') { 10 | return value; 11 | } 12 | const asString = String(value); 13 | 14 | if (asString === '1' || asString === 'true') { 15 | return true; 16 | } 17 | 18 | if (asString === '0' || asString === 'false') { 19 | return false; 20 | } 21 | 22 | throw createTypeError(); 23 | }, 'boolean'); 24 | -------------------------------------------------------------------------------- /playgrounds/solid/src/pages/TonConnectPage/TonConnectPage.css: -------------------------------------------------------------------------------- 1 | .ton-connect-page__button-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: flex-end; 5 | } 6 | 7 | .ton-connect-page__provider { 8 | display: flex; 9 | align-items: center; 10 | gap: 15px; 11 | margin-bottom: 16px; 12 | } 13 | 14 | .ton-connect-page__provider-image { 15 | border-radius: 5px; 16 | } 17 | 18 | .ton-connect-page__provider-meta { 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .ton-connect-page__provider-wallet-name { 24 | font-weight: bold; 25 | font-size: 20px; 26 | margin: 0; 27 | } 28 | 29 | .ton-connect-page__provider-app-name { 30 | opacity: .4; 31 | font-weight: 400; 32 | font-size: 14px; 33 | vertical-align: top; 34 | } -------------------------------------------------------------------------------- /apps/docs/platform/swipe-behavior.md: -------------------------------------------------------------------------------- 1 | # Swipe Behavior 2 | 3 | ![Swipe behavior](/functionality/swipe-behavior.png) 4 | 5 | Mini Apps sometimes use swipe gestures that may conflict with the gestures for minimizing and closing the app. 6 | For users, this could appear as accidentally swiping down to dock a Mini App while interacting with it. 7 | 8 | To prevent this, developers have the option to configure the swipe behavior, 9 | so the application won't hide in the dock when swiping the application itself. 10 | 11 | In any case, users will still be able to minimize and close the Mini App by swiping the Mini App's header. 12 | 13 | To disable vertical page swipes, Telegram Mini Apps provides a method 14 | called [web_app_setup_swipe_behavior](methods.md#web-app-setup-swipe-behavior). -------------------------------------------------------------------------------- /packages/sdk/src/types/unions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns union keys removing those, which values are not strings. 3 | */ 4 | export type UnionStringKeys = U extends U 5 | ? { 6 | [K in keyof U]-?: U[K] extends string | undefined ? K : never; 7 | }[keyof U] 8 | : never; 9 | 10 | /** 11 | * Returns union required keys. 12 | */ 13 | export type UnionRequiredKeys = U extends U 14 | ? { 15 | [K in UnionStringKeys]: ({} extends Pick ? never : K) 16 | }[UnionStringKeys] 17 | : never; 18 | 19 | /** 20 | * Returns union optional keys. 21 | */ 22 | export type UnionOptionalKeys = Exclude, UnionRequiredKeys>; 23 | 24 | /** 25 | * Returns union object keys. 26 | */ 27 | export type UnionKeys = T extends T ? keyof T : never; 28 | -------------------------------------------------------------------------------- /packages/sdk-react/src/hooks-hocs/launch-params.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { type LaunchParams, retrieveLaunchParams } from '@telegram-apps/sdk'; 3 | 4 | /** 5 | * @returns Launch parameters. 6 | */ 7 | export function useLaunchParams(ssr: true): LaunchParams | undefined; 8 | 9 | /** 10 | * @returns Launch parameters. 11 | */ 12 | export function useLaunchParams(ssr?: false): LaunchParams; 13 | 14 | export function useLaunchParams(ssr?: boolean): LaunchParams | undefined { 15 | const [lp, setLp] = useState(() => { 16 | return ssr ? undefined : retrieveLaunchParams(); 17 | }); 18 | 19 | useEffect(() => { 20 | if (ssr) { 21 | setLp(retrieveLaunchParams()); 22 | } 23 | }, []); 24 | 25 | return lp; 26 | } -------------------------------------------------------------------------------- /playgrounds/react/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:react/recommended' 10 | ], 11 | overrides: [ 12 | { 13 | env: { 14 | node: true 15 | }, 16 | files: [ 17 | '.eslintrc.{js,cjs}' 18 | ], 19 | parserOptions: { 20 | 'sourceType': 'script' 21 | } 22 | } 23 | ], 24 | parser: '@typescript-eslint/parser', 25 | parserOptions: { 26 | ecmaVersion: 'latest', 27 | sourceType: 'module' 28 | }, 29 | plugins: [ 30 | '@typescript-eslint', 31 | 'react' 32 | ], 33 | rules: { 34 | 'react/react-in-jsx-scope': 0, 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /packages/sdk/src/components/SettingsButton/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | 3 | /** 4 | * SettingsButton internal state. 5 | */ 6 | export interface SettingsButtonState { 7 | isVisible: boolean; 8 | } 9 | 10 | /** 11 | * SettingsButton trackable events. 12 | */ 13 | export interface SettingsButtonEvents extends StateEvents { 14 | /** 15 | * The SettingsButton was clicked. 16 | */ 17 | click: () => void; 18 | } 19 | 20 | /** 21 | * SettingsButton event name. 22 | */ 23 | export type SettingsButtonEventName = keyof SettingsButtonEvents; 24 | 25 | /** 26 | * SettingsButton event listener. 27 | */ 28 | export type SettingsButtonEventListener = 29 | SettingsButtonEvents[E]; 30 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents any known parser. 3 | */ 4 | export type AnyParser = Parser | { parse: Parser }; 5 | 6 | /** 7 | * Detailed schema field options. 8 | */ 9 | export interface SchemaFieldDetailed { 10 | /** 11 | * Source property name. 12 | * @default Original property name in schema. 13 | */ 14 | from?: string; 15 | 16 | /** 17 | * Function which should parse incoming value and return expected type. 18 | */ 19 | type: AnyParser; 20 | } 21 | 22 | /** 23 | * Function which parses incoming value. 24 | */ 25 | export type Parser = (value: unknown) => T; 26 | 27 | /** 28 | * Object schema definition. 29 | */ 30 | export type Schema = { 31 | [K in keyof T]: AnyParser | SchemaFieldDetailed; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/toRGB.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { toRGB } from './toRGB.js'; 4 | 5 | it('should return same value in case, full version of RGB is passed', () => { 6 | expect(toRGB('#ffffff')).toBe('#ffffff'); 7 | }); 8 | 9 | it('should return full RGB value in case, its short presentation is passed', () => { 10 | expect(toRGB('#abc')).toBe('#aabbcc'); 11 | }); 12 | 13 | it('should return RGB representation of rgb(*,*,*) pattern', () => { 14 | expect(toRGB('rgb(6,56,11)')).toBe('#06380b'); 15 | }); 16 | 17 | it('should return RGB representation of rgba(*,*,*) pattern', () => { 18 | expect(toRGB('rgba(6,56,11,22)')).toBe('#06380b'); 19 | }); 20 | 21 | it('should throw an error in other cases', () => { 22 | expect(() => toRGB('abc')).toThrow(); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-handlers/emitMiniAppsEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Emits event sent from Telegram native application like it was sent in 3 | * default web environment between 2 iframes. It dispatches new MessageEvent 4 | * and expects it to be handled via `window.addEventListener('message', ...)` 5 | * as developer would do it to handle messages sent from the parent iframe. 6 | * @param eventType - event name. 7 | * @param eventData - event payload. 8 | */ 9 | export function emitMiniAppsEvent(eventType: string, eventData: unknown): void { 10 | window.dispatchEvent(new MessageEvent('message', { 11 | data: JSON.stringify({ eventType, eventData }), 12 | // We specify window.parent to imitate the case, the parent iframe sent us this event. 13 | source: window.parent, 14 | })); 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk/src/components/ThemeParams/parsing/themeParams.ts: -------------------------------------------------------------------------------- 1 | import { createValueParserGenerator, type ValueParserGenerator } from '@/parsing/createValueParserGenerator.js'; 2 | import { rgb } from '@/parsing/parsers/rgb.js'; 3 | import { toRecord } from '@/parsing/toRecord.js'; 4 | 5 | import { keyToLocal } from '../keys.js'; 6 | import type { ThemeParamsParsed } from '../types.js'; 7 | 8 | export const themeParams: ValueParserGenerator = createValueParserGenerator( 9 | (value) => { 10 | const rgbOptional = rgb().optional(); 11 | 12 | return Object 13 | .entries(toRecord(value)) 14 | .reduce((acc, [k, v]) => { 15 | acc[keyToLocal(k)] = rgbOptional.parse(v); 16 | return acc; 17 | }, {}); 18 | }, 19 | 'ThemeParams', 20 | ); 21 | -------------------------------------------------------------------------------- /packages/test-utils/src/session-storage.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | import { formatImplementation, type MockImplementation } from './utils'; 4 | 5 | /** 6 | * Mocks sessionStorage.getItem. 7 | * @param impl - method implementation. 8 | */ 9 | export function mockSessionStorageGetItem( 10 | impl: MockImplementation = null, 11 | ) { 12 | return vi 13 | .spyOn(sessionStorage, 'getItem') 14 | .mockImplementation(formatImplementation(impl)); 15 | } 16 | 17 | /** 18 | * Mocks sessionStorage.setItem. 19 | * @param impl - method implementation. 20 | */ 21 | export function mockSessionStorageSetItem(impl?: () => void) { 22 | const spy = vi.spyOn(sessionStorage, 'setItem'); 23 | if (impl) { 24 | spy.mockImplementation(impl); 25 | } 26 | 27 | return spy; 28 | } 29 | -------------------------------------------------------------------------------- /playgrounds/next/src/app/theme-params/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useThemeParams } from '@telegram-apps/sdk-react'; 4 | import { List } from '@telegram-apps/telegram-ui'; 5 | 6 | import { DisplayData } from '@/components/DisplayData/DisplayData'; 7 | 8 | export default function ThemeParamsPage() { 9 | const themeParams = useThemeParams(); 10 | 11 | return ( 12 | 13 | ({ 18 | title: title 19 | .replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`) 20 | .replace(/background/, 'bg'), 21 | value, 22 | })) 23 | } 24 | /> 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /.github/workflows/test-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Test pull request 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | types: [opened, reopened] 7 | branches: 8 | - "master" 9 | 10 | jobs: 11 | validate: 12 | name: Test pull request 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 20 22 | registry-url: 'https://registry.npmjs.org' 23 | 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v2 26 | id: pnpm-install 27 | with: 28 | version: 9 29 | run_install: false 30 | 31 | - name: Rollup 32 | run: pnpm run ci:packages:rollup -------------------------------------------------------------------------------- /packages/sdk/src/classes/WithSupportsAndTrackableState.ts: -------------------------------------------------------------------------------- 1 | import { WithSupportsAndStateUtils } from '@/classes/WithSupportsAndStateUtils.js'; 2 | import type { StateEvents } from '@/classes/State/types.js'; 3 | import type { EventEmitter } from '@/events/event-emitter/EventEmitter.js'; 4 | 5 | type Emitter = EventEmitter>; 6 | 7 | export class WithSupportsAndTrackableState 8 | extends WithSupportsAndStateUtils { 9 | /** 10 | * Adds a new event listener. 11 | */ 12 | on: Emitter['on'] = this.state.on.bind(this.state); 13 | 14 | /** 15 | * Removes the event listener. 16 | */ 17 | off: Emitter['off'] = this.state.off.bind(this.state); 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/rgb.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { rgb } from './rgb.js'; 4 | 5 | it('should return value in case, it represents RGB color', () => { 6 | expect(rgb().parse('#fff')).toBe('#ffffff'); 7 | }); 8 | 9 | it('should throw an error in case, passed value is not of type string', () => { 10 | expect(() => rgb().parse(true)).toThrow(); 11 | expect(() => rgb().parse({})).toThrow(); 12 | }); 13 | 14 | it('should throw an error in case, passed value does not represent RGB string', () => { 15 | expect(() => rgb().parse('my custom string')).toThrow(); 16 | }); 17 | 18 | describe('optional', () => { 19 | it('should return undefined if value is undefined', () => { 20 | expect(rgb().optional().parse(undefined)).toBe(undefined); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/listening/on.ts: -------------------------------------------------------------------------------- 1 | import type { RemoveEventListenerFn } from '@/events/types.js'; 2 | 3 | import { miniAppsEventEmitter } from '../event-emitter/singleton.js'; 4 | import type { MiniAppsEventListener, MiniAppsEventName } from '../types.js'; 5 | 6 | /** 7 | * Adds new listener to the specified event. Returns handler 8 | * which allows to stop listening to event. 9 | * @param event - event name. 10 | * @param listener - event listener. 11 | * @param once - should listener be called only once. 12 | * @returns Function to remove bound event listener. 13 | */ 14 | export function on( 15 | event: E, 16 | listener: MiniAppsEventListener, 17 | once?: boolean, 18 | ): RemoveEventListenerFn { 19 | return miniAppsEventEmitter().on(event, listener, once); 20 | } 21 | -------------------------------------------------------------------------------- /packages/sdk/src/misc/createSingleton.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates resettable singleton. We mostly need it for test purposes. 3 | * @param create - function which creates singleton entity. 4 | * @param onReset - function which will be called in case, singleton was reset. 5 | */ 6 | export function createSingleton( 7 | create: (reset: () => void) => T, 8 | onReset?: (entity: T) => void, 9 | ): [ 10 | /** 11 | * Returns singleton entity. 12 | */ 13 | get: () => T, 14 | /** 15 | * Resets last stored entity. 16 | */ 17 | reset: () => void, 18 | ] { 19 | let cached: T | undefined; 20 | const reset = () => { 21 | cached !== undefined && onReset && onReset(cached); 22 | cached = undefined; 23 | }; 24 | 25 | return [() => (cached === undefined ? cached = create(reset) : cached), reset]; 26 | } 27 | -------------------------------------------------------------------------------- /packages/test-utils/src/window.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | 3 | import { formatImplementation, type MockImplementation } from './utils'; 4 | 5 | export type Wnd = Window & typeof globalThis; 6 | 7 | /** 8 | * Mocks window getter. 9 | * @param impl - window getter implementation. 10 | */ 11 | export function mockWindow(impl: MockImplementation) { 12 | return vi 13 | .spyOn(global, 'window', 'get') 14 | .mockImplementation(formatImplementation(impl)); 15 | } 16 | 17 | /** 18 | * Mocks window.location.hash getter. 19 | * @param impl - hash getter implementation. 20 | */ 21 | export function mockWindowLocationHash( 22 | impl: MockImplementation = '', 23 | ) { 24 | return vi 25 | .spyOn(window.location, 'hash', 'get') 26 | .mockImplementation(formatImplementation(impl)); 27 | } 28 | -------------------------------------------------------------------------------- /playgrounds/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Telegram Mini App 8 | 9 | 10 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/sdk/src/colors/isColorDark.ts: -------------------------------------------------------------------------------- 1 | import { toRGB } from './toRGB.js'; 2 | 3 | /** 4 | * Returns true in case, the color is recognized as dark. 5 | * @param color - color in any format acceptable by toRGB function. 6 | * @see toRGB 7 | */ 8 | export function isColorDark(color: string): boolean { 9 | // Convert color to RGB. 10 | const rgb = toRGB(color); 11 | 12 | // Real formula: hsp = Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b) 13 | // See: https://stackoverflow.com/a/596243 14 | return Math.sqrt( 15 | [0.299, 0.587, 0.114].reduce((acc, modifier, idx) => { 16 | // Extract part of #RRGGBB pattern and convert it to DEC. 17 | const dec = parseInt(rgb.slice(1 + idx * 2, 1 + (idx + 1) * 2), 16); 18 | return acc + dec * dec * modifier; 19 | }, 0), 20 | ) < 120; 21 | } 22 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/BrowserNavigator/basicItemToBrowser.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { basicItemToBrowser } from './basicItemToBrowser.js'; 3 | 4 | it('should convert BasicNavigator history item to the one, appropriate for BrowserNavigator', () => { 5 | expect(basicItemToBrowser({ 6 | id: 'abc', 7 | pathname: '/a', 8 | })).toStrictEqual({ 9 | id: 'abc', 10 | hash: '', 11 | search: '', 12 | pathname: '/a', 13 | }); 14 | 15 | expect(basicItemToBrowser({ 16 | id: 'abc', 17 | pathname: '/a', 18 | params: { 19 | hash: 'hash', 20 | search: 'search', 21 | state: 'TEST', 22 | }, 23 | })).toStrictEqual({ 24 | id: 'abc', 25 | hash: 'hash', 26 | search: 'search', 27 | state: 'TEST', 28 | pathname: '/a', 29 | }); 30 | }); -------------------------------------------------------------------------------- /apps/docs/platform/closing-behavior.md: -------------------------------------------------------------------------------- 1 | # Closing Behavior 2 | 3 | ![Closing confirmation](/functionality/closing-confirmation.png) 4 | 5 | Mini Apps are intended to handle different, and at times, complex scenarios where the user can 6 | navigate deep into the application architecture. It's a common situation when a user is following a 7 | specific workflow, such as purchasing an airplane ticket, which involves multiple steps. 8 | 9 | Accidentally closing a Mini App with data loss can be a significant disappointment for the user. To 10 | prevent this, developers have the option to configure the closing behavior and prompt the user 11 | before closing the application. 12 | 13 | To enable closing confirmation, Telegram Mini Apps provides a method 14 | called [web_app_setup_closing_behavior](methods.md#web-app-setup-closing-behavior). 15 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": false, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "inlineSources": true, 10 | "isolatedModules": true, 11 | "lib": [ 12 | "esnext" 13 | ], 14 | "module": "NodeNext", 15 | "moduleResolution": "NodeNext", 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "outDir": "dist", 19 | "preserveWatchOutput": true, 20 | "resolveJsonModule": true, 21 | "skipLibCheck": true, 22 | "sourceMap": true, 23 | "strict": true, 24 | "target": "ESNext", 25 | "useDefineForClassFields": true 26 | }, 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /packages/sdk/src/classes/WithSupports.ts: -------------------------------------------------------------------------------- 1 | import { createSupportsFn } from '@/supports/createSupportsFn.js'; 2 | import type { MiniAppsMethodName } from '@/bridge/methods/types/methods.js'; 3 | import type { SupportsFn } from '@/supports/types.js'; 4 | import type { Version } from '@/version/types.js'; 5 | 6 | export class WithSupports { 7 | constructor( 8 | /** 9 | * Mini Apps version. 10 | */ 11 | version: Version, 12 | /** 13 | * Supports method schema. 14 | */ 15 | supportsSchema: Record, 16 | ) { 17 | this.supports = createSupportsFn(version, supportsSchema); 18 | } 19 | 20 | /** 21 | * @returns True, if specified method is supported by the current component. 22 | */ 23 | supports: SupportsFn; 24 | } 25 | -------------------------------------------------------------------------------- /packages/sdk/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true in case, T is never. 3 | */ 4 | export type IsNever = [T] extends [never] ? true : false; 5 | 6 | /** 7 | * Returns true in case, T is undefined. 8 | */ 9 | export type IsUndefined = [T] extends [undefined] ? true : false; 10 | 11 | /** 12 | * Returns object string keys. 13 | */ 14 | export type StringKeys = Extract; 15 | 16 | /** 17 | * Marks specified properties as optional. 18 | */ 19 | export type PartialBy = Omit & Partial>; 20 | 21 | /** 22 | * Marks specified properties as required. 23 | */ 24 | export type RequiredBy = Omit & Required>; 25 | 26 | /** 27 | * Appends `null` and `undefined`. 28 | */ 29 | export type Maybe = T | null | undefined; 30 | -------------------------------------------------------------------------------- /playgrounds/react/src/pages/ThemeParamsPage/ThemeParamsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useThemeParams } from '@telegram-apps/sdk-react'; 2 | import type { FC } from 'react'; 3 | import { List } from '@telegram-apps/telegram-ui'; 4 | 5 | import { DisplayData } from '@/components/DisplayData/DisplayData.tsx'; 6 | 7 | export const ThemeParamsPage: FC = () => { 8 | const themeParams = useThemeParams(); 9 | 10 | return ( 11 | 12 | ({ 17 | title: title 18 | .replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`) 19 | .replace(/background/, 'bg'), 20 | value, 21 | })) 22 | } 23 | /> 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/saveToStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, vi, afterEach } from 'vitest'; 2 | import { saveToStorage } from '@/launch-params/saveToStorage.js'; 3 | 4 | afterEach(() => { 5 | vi.restoreAllMocks(); 6 | }); 7 | 8 | it('should call sessionStorage.setItem with "telegram-apps/launch-params" and serialized launch params', () => { 9 | const spy = vi.spyOn(sessionStorage, 'setItem').mockImplementation(() => undefined); 10 | saveToStorage({ 11 | version: '7.0', 12 | platform: 'android', 13 | themeParams: { 14 | bgColor: '#ffffff', 15 | }, 16 | }); 17 | expect(spy).toHaveBeenCalledOnce(); 18 | expect(spy).toHaveBeenCalledWith( 19 | 'telegram-apps/launch-params', 20 | '"tgWebAppPlatform=android&tgWebAppThemeParams=%7B%22bg_color%22%3A%22%23ffffff%22%7D&tgWebAppVersion=7.0"', 21 | ); 22 | }); -------------------------------------------------------------------------------- /playgrounds/solid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Solid + TS 8 | 9 | 10 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /playgrounds/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowImportingTsExtensions": true, 4 | "isolatedModules": true, 5 | "jsx": "react-jsx", 6 | "lib": [ 7 | "ES2020", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "module": "ESNext", 12 | "moduleResolution": "bundler", 13 | "noEmit": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "paths": { 18 | "@/*": [ 19 | "./src/*" 20 | ] 21 | }, 22 | "resolveJsonModule": true, 23 | "skipLibCheck": true, 24 | "strict": true, 25 | "target": "ES2020", 26 | "useDefineForClassFields": true 27 | }, 28 | "include": [ 29 | "src" 30 | ], 31 | "references": [ 32 | { 33 | "path": "./tsconfig.node.json" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/docs/platform/settings-button.md: -------------------------------------------------------------------------------- 1 | # Settings Button 2 | 3 | ![Popup](/components/settings-button.png) 4 | 5 | The Settings Button is a component displayed in the top right menu of the Mini App interface. Like 6 | many other Telegram Mini Apps components, this button does not have a built-in action, and it's up 7 | to the developer to decide how to handle a click on this button. 8 | 9 | Until Telegram Mini Apps version `6.10`, this button was only displayed for Mini Apps belonging to 10 | Telegram Ads' major advertisers' bots. Starting from version `6.10`, all developers have the option 11 | to display this button using 12 | the [web_app_setup_settings_button](methods.md#web-app-setup-settings-button) 13 | method. 14 | 15 | When a user clicks the Settings Button, the Telegram application emits 16 | the [settings_button_pressed](events.md#settings-button-pressed) event. -------------------------------------------------------------------------------- /packages/sdk/src/supports/createSupportsFn.ts: -------------------------------------------------------------------------------- 1 | import { supports } from '@/bridge/supports.js'; 2 | import type { MiniAppsMethodName } from '@/bridge/methods/types/methods.js'; 3 | import type { SupportsFn } from '@/supports/types.js'; 4 | import type { Version } from '@/version/types.js'; 5 | 6 | export type SupportsSchema = Record; 7 | 8 | /** 9 | * Returns function, which accepts predefined method name and checks if it is supported 10 | * via passed schema and version. 11 | * @param schema - object which contains methods names and TWA method as a dependency. 12 | * @param version - platform version. 13 | */ 14 | export function createSupportsFn( 15 | version: Version, 16 | schema: SupportsSchema, 17 | ): SupportsFn { 18 | return (method) => supports(schema[method], version); 19 | } 20 | -------------------------------------------------------------------------------- /packages/sdk/src/components/Viewport/requestViewport.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/bridge/request.js'; 2 | import type { ExecuteWithOptions } from '@/types/index.js'; 3 | 4 | export interface RequestViewportResult { 5 | height: number; 6 | isStateStable: boolean; 7 | isExpanded: boolean; 8 | width: number; 9 | } 10 | 11 | /** 12 | * Requests viewport actual information from the Telegram application. 13 | * @param options - request options. 14 | */ 15 | export async function requestViewport( 16 | options: ExecuteWithOptions = {}, 17 | ): Promise { 18 | const { 19 | is_expanded: isExpanded, 20 | is_state_stable: isStateStable, 21 | ...rest 22 | } = await request({ 23 | ...options, 24 | method: 'web_app_request_viewport', 25 | event: 'viewport_changed', 26 | }); 27 | 28 | return { ...rest, isExpanded, isStateStable }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/init-data-node/src/types.ts: -------------------------------------------------------------------------------- 1 | import { InitDataParsed } from '@telegram-apps/sdk'; 2 | 3 | import type { SignDataOptions } from './signData.js'; 4 | 5 | export type Text = string | Buffer; 6 | 7 | export type SignData = Omit; 8 | 9 | export interface SignDataSyncFn { 10 | (data: Text, key: Text, options?: SignDataOptions): string; 11 | } 12 | 13 | export interface SignDataAsyncFn { 14 | (data: Text, key: Text, options?: SignDataOptions): Promise; 15 | } 16 | 17 | /** 18 | * SHA-256 hashing function. 19 | */ 20 | export interface CreateHmacFn { 21 | (data: Text, key: Text): Async extends true 22 | ? Promise 23 | : Buffer; 24 | } 25 | 26 | export interface SharedOptions { 27 | /** 28 | * True, if token is already hashed. 29 | * @default false 30 | */ 31 | tokenHashed?: boolean; 32 | } 33 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromLocation.test.ts: -------------------------------------------------------------------------------- 1 | import { it, vi, expect, afterEach } from 'vitest'; 2 | 3 | import { retrieveFromLocation } from './retrieveFromLocation.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should retrieve launch params from the window.location.href. Throw an error if data is invalid or missing', () => { 10 | const spy = vi 11 | .spyOn(window.location, 'href', 'get') 12 | .mockImplementationOnce(() => { 13 | return '/abc?tgWebAppStartParam=START#tgWebAppPlatform=tdesktop&tgWebAppVersion=7.0&tgWebAppThemeParams=%7B%7D'; 14 | }); 15 | 16 | expect(retrieveFromLocation()).toStrictEqual({ 17 | platform: 'tdesktop', 18 | version: '7.0', 19 | themeParams: {}, 20 | startParam: 'START', 21 | }); 22 | 23 | spy.mockImplementationOnce(() => ''); 24 | expect(() => retrieveFromLocation()).toThrow(); 25 | }); -------------------------------------------------------------------------------- /packages/sdk/src/navigation/isPageReload.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, expect, it, vi } from 'vitest'; 2 | 3 | import { isPageReload } from './isPageReload.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should return true if the first navigation entry type is "reload"', () => { 10 | const expected = { type: 'reload' }; 11 | const spy = vi 12 | .spyOn(performance, 'getEntriesByType') 13 | .mockImplementationOnce(() => [expected] as any); 14 | 15 | expect(isPageReload()).toBe(true); 16 | spy.mockClear().mockImplementation(() => [{ type: 'navigation' }] as any); 17 | 18 | expect(isPageReload()).toBe(false); 19 | }); 20 | 21 | it('should return false if the first navigation entry is missing', () => { 22 | vi 23 | .spyOn(performance, 'getEntriesByType') 24 | .mockImplementationOnce(() => []); 25 | 26 | expect(isPageReload()).toBe(false); 27 | }); 28 | -------------------------------------------------------------------------------- /apps/docs/platform/back-button.md: -------------------------------------------------------------------------------- 1 | # Back Button 2 | 3 | ![Back Button](/components/back-button.png) 4 | 5 | The main task that the Back Button performs is to provide a seemingly native way to navigate back in 6 | the routing history. However, Telegram does not restrict the developer in the ways of using the Back 7 | Button and allows them to handle the component click event as required in the application. 8 | 9 | When working with this component, it is important to understand that, like other Telegram Mini Apps 10 | components, clicking it does not inherently trigger any built-in action. Its handling is the 11 | responsibility of the developer. 12 | 13 | To show the Back Button, it is required to 14 | call [web_app_setup_back_button](methods.md#web-app-setup-back-button) 15 | method. When user clicks this component, Telegram application 16 | emits [back_button_pressed](events.md#back-button-pressed) event. 17 | 18 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/target-origin.ts: -------------------------------------------------------------------------------- 1 | const INITIAL_TARGET_ORIGIN = 'https://web.telegram.org' 2 | 3 | let currentTargetOrigin = INITIAL_TARGET_ORIGIN; 4 | 5 | /** 6 | * Sets a new global targetOrigin, used by the `postEvent` method. 7 | * The default value is "https://web.telegram.org". 8 | * You don't need to use this method until you know what you are doing. 9 | * 10 | * This method could be used for test purposes. 11 | * @param value - new target origin. 12 | * @see postEvent 13 | */ 14 | export function setTargetOrigin(value: string): void { 15 | currentTargetOrigin = value; 16 | } 17 | 18 | /** 19 | * Sets the initial target origin. 20 | */ 21 | export function resetTargetOrigin(): void { 22 | setTargetOrigin(INITIAL_TARGET_ORIGIN); 23 | } 24 | 25 | /** 26 | * Returns current global target origin. 27 | */ 28 | export function targetOrigin(): string { 29 | return currentTargetOrigin; 30 | } 31 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/number.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { number } from './number.js'; 4 | 5 | it('should return value in case, it has type number', () => { 6 | expect(number().parse(9992)).toBe(9992); 7 | }); 8 | 9 | it('should return value converted to number in case, it is number converted to string', () => { 10 | expect(number().parse('9992')).toBe(9992); 11 | }); 12 | 13 | it('should throw an error in case, passed value is not of type number or does not represent number converted to string', () => { 14 | expect(() => number().parse(true)).toThrow(); 15 | expect(() => number().parse('vvv')).toThrow(); 16 | expect(() => number().parse({})).toThrow(); 17 | }); 18 | 19 | describe('optional', () => { 20 | it('should return undefined if value is undefined', () => { 21 | expect(number().optional().parse(undefined)).toBe(undefined); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/sdk/src/launch-params/retrieveFromPerformance.test.ts: -------------------------------------------------------------------------------- 1 | import { it, vi, expect, afterEach } from 'vitest'; 2 | 3 | import { retrieveFromPerformance } from './retrieveFromPerformance.js'; 4 | 5 | afterEach(() => { 6 | vi.restoreAllMocks(); 7 | }); 8 | 9 | it('should retrieve launch params from the window.performance. Throw an error if data is invalid or missing', () => { 10 | const spy = vi 11 | .spyOn(performance, 'getEntriesByType') 12 | .mockImplementationOnce(() => [{ 13 | name: '/abc?tgWebAppStartParam=START#tgWebAppPlatform=tdesktop&tgWebAppVersion=7.0&tgWebAppThemeParams=%7B%7D', 14 | }] as any); 15 | 16 | expect(retrieveFromPerformance()).toStrictEqual({ 17 | platform: 'tdesktop', 18 | version: '7.0', 19 | themeParams: {}, 20 | startParam: 'START', 21 | }); 22 | 23 | spy.mockImplementationOnce(() => []); 24 | expect(() => retrieveFromPerformance()).toThrow(); 25 | }); -------------------------------------------------------------------------------- /packages/sdk/src/parsing/toRecord.ts: -------------------------------------------------------------------------------- 1 | import { createTypeError } from './createTypeError.js'; 2 | 3 | /** 4 | * Converts value to record. 5 | * @param value - value to convert. 6 | * @throws {Error} Value passed as a string does not represent JSON object. 7 | * @throws {Error} Value is not convertable. 8 | */ 9 | export function toRecord(value: unknown): Record { 10 | let formattedValue: any = value; 11 | 12 | // Convert value to JSON in case, it is string. We expect value to be JSON string. 13 | if (typeof formattedValue === 'string') { 14 | formattedValue = JSON.parse(formattedValue); 15 | } 16 | 17 | // We expect json to be usual object. 18 | if ( 19 | typeof formattedValue !== 'object' 20 | || formattedValue === null 21 | || Array.isArray(formattedValue) 22 | ) { 23 | throw createTypeError(); 24 | } 25 | 26 | return formattedValue as Record; 27 | } 28 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-handlers/emitMiniAppsEvent.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, vi } from 'vitest'; 2 | 3 | import { emitMiniAppsEvent } from './emitMiniAppsEvent.js'; 4 | 5 | it('should call window.dispatchEvent with the Message event containing properties "data" property equal to { eventType, eventData } stringified and "source" property equal to window.parent', () => { 6 | const dispatch = vi.spyOn(window, 'dispatchEvent').mockImplementationOnce(() => null as any); 7 | vi.spyOn(window, 'parent', 'get').mockImplementationOnce(() => 'PARENT' as any); 8 | emitMiniAppsEvent('test', 'some-data'); 9 | expect(dispatch).toHaveBeenCalledOnce(); 10 | 11 | expect(dispatch.mock.calls[0][0]).toBeInstanceOf(MessageEvent); 12 | expect(dispatch.mock.calls[0][0]).toMatchObject({ 13 | type: 'message', 14 | data: '{"eventType":"test","eventData":"some-data"}', 15 | source: 'PARENT' as any, 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/date.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { date } from './date.js'; 4 | 5 | it('should return value in case, it is instance of Date', () => { 6 | const d = new Date(); 7 | expect(date().parse(d)).toBe(d); 8 | }); 9 | 10 | it('should throw an error in case, passed value cannot be converted to number', () => { 11 | expect(() => date().parse('true')).toThrow(); 12 | expect(() => date().parse({})).toThrow(); 13 | }); 14 | 15 | it('should create date multiplying value by 1000 in case this value can be converted to number', () => { 16 | const a = new Date(1000); 17 | expect(date().parse('1')).toStrictEqual(a); 18 | expect(date().parse(1)).toStrictEqual(a); 19 | }); 20 | 21 | describe('optional', () => { 22 | it('should return undefined if value is undefined', () => { 23 | expect(date().optional().parse(undefined)).toBe(undefined); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/sdk-solid/src/createHOC.tsx: -------------------------------------------------------------------------------- 1 | import { type Component, mergeProps } from 'solid-js'; 2 | import type { PartialBy } from '@telegram-apps/sdk'; 3 | 4 | import type { Hook } from './createHook.js'; 5 | 6 | export interface HOC> { 7 | }>( 8 | prop: PropKey, 9 | Component: Component, 10 | ...args: Parameters 11 | ): Component>; 12 | } 13 | 14 | /** 15 | * Based on the passed hook, creates function returning HOC. Created HOC passes hook result 16 | * to the wrapped component. 17 | * @param hook - hook returning component. 18 | */ 19 | export function createHOC>(hook: H): HOC { 20 | return (prop, Component, ...args) => { 21 | return (props) => { 22 | return ; 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/sdk/src/env/isIframe.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, afterEach, expect, it, vi } from 'vitest'; 2 | 3 | import { isIframe } from './isIframe.js'; 4 | 5 | const windowSpy = vi.spyOn(window, 'window', 'get'); 6 | 7 | afterEach(() => { 8 | windowSpy.mockReset(); 9 | }); 10 | 11 | afterAll(() => { 12 | windowSpy.mockRestore(); 13 | }); 14 | 15 | it('should return true in case window.self !== window.top. Otherwise, false.', () => { 16 | windowSpy.mockImplementation(() => ({ self: 900, top: 1000 }) as any); 17 | expect(isIframe()).toBe(true); 18 | 19 | windowSpy.mockImplementation(() => ({ self: 900, top: 900 }) as any); 20 | expect(isIframe()).toBe(false); 21 | }); 22 | 23 | it('should return true in case window.self getter threw an error', () => { 24 | windowSpy.mockImplementation(() => ({ 25 | get self() { 26 | throw new Error(); 27 | }, 28 | }) as any); 29 | expect(isIframe()).toBe(true); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/ArrayParser/types.ts: -------------------------------------------------------------------------------- 1 | import type { AnyParser } from '../types.js'; 2 | import type { ValueParserOverrides } from '../ValueParser/types.js'; 3 | 4 | export type ArrayParserOfResult = ArrayParserType< 5 | BaseClass, 6 | ItemType, 7 | IsOptional 8 | >; 9 | 10 | export interface ArrayParserOverrides< 11 | BaseClass, 12 | ItemType, 13 | IsOptional extends boolean, 14 | > extends ValueParserOverrides { 15 | /** 16 | * Specifies parser for each array item. 17 | * @param parser - item parser. 18 | */ 19 | of(parser: AnyParser): ArrayParserOfResult; 20 | } 21 | 22 | export type ArrayParserType = 23 | Omit> 24 | & ArrayParserOverrides; 25 | -------------------------------------------------------------------------------- /playgrounds/solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "isolatedModules": true, 6 | "jsx": "preserve", 7 | "jsxImportSource": "solid-js", 8 | "lib": [ 9 | "ES2020", 10 | "DOM", 11 | "DOM.Iterable" 12 | ], 13 | "module": "NodeNext", 14 | "moduleResolution": "NodeNext", 15 | "noEmit": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "paths": { 20 | "@/*": [ 21 | "./src/*" 22 | ] 23 | }, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "strict": true, 27 | "target": "ESNext", 28 | "types": [ 29 | "vite/client" 30 | ] 31 | }, 32 | "include": [ 33 | "src" 34 | ], 35 | "references": [ 36 | { 37 | "path": "./tsconfig.node.json" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/sdk/src/parsing/parsers/string.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { string } from './string.js'; 4 | 5 | it('should return value in case, it has type string', () => { 6 | expect(string().parse('abc')).toBe('abc'); 7 | }); 8 | 9 | it('should convert value to string in case, it has type number', () => { 10 | expect(string().parse(1)).toBe('1'); 11 | }); 12 | 13 | it('should throw an error in case, passed value is not string or number', () => { 14 | expect(() => string().parse({})).toThrow(); 15 | expect(() => string().parse([])).toThrow(); 16 | expect(() => string().parse(false)).toThrow(); 17 | expect(() => string().parse(null)).toThrow(); 18 | expect(() => string().parse(undefined)).toThrow(); 19 | }); 20 | 21 | describe('optional', () => { 22 | it('should return undefined if value is undefined', () => { 23 | expect(string().optional().parse(undefined)).toBe(undefined); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/sdk/src/classes/State/types.ts: -------------------------------------------------------------------------------- 1 | import type { StringKeys } from '@/types/utils.js'; 2 | 3 | /** 4 | * Creates a map with all events emitted by BaseComponent. 5 | * @example 6 | * ```ts 7 | * type A = StateEvents<{ 8 | * name: string; 9 | * age: number; 10 | * isRegistered: boolean; 11 | * }>; 12 | * 13 | * // Will be: 14 | * { 15 | * "change:name": (currentValue: string) => void; 16 | * "change:age": (currentValue: number) => void; 17 | * "change:isRegistered": (currentValue: boolean) => void; 18 | * "change": () => void; 19 | * } 20 | * ``` 21 | */ 22 | export type StateEvents = { 23 | [Event in `change:${StringKeys}`]: ( 24 | currentValue: Event extends `change:${infer T extends StringKeys}` 25 | ? State[T] 26 | : never, 27 | ) => void; 28 | } & { 29 | /** 30 | * Something has changed in the state. 31 | */ 32 | change: (currentState: State) => void; 33 | }; 34 | -------------------------------------------------------------------------------- /packages/sdk/src/components/QRScanner/types.ts: -------------------------------------------------------------------------------- 1 | import type { StateEvents } from '@/classes/State/types.js'; 2 | import type { RequestCaptureEventFn } from '@/bridge/request.js'; 3 | 4 | /** 5 | * QRScanner internal state. 6 | */ 7 | export interface QRScannerState { 8 | isOpened: boolean; 9 | } 10 | 11 | /** 12 | * QRScanner trackable events. 13 | */ 14 | export type QRScannerEvents = StateEvents; 15 | 16 | /** 17 | * QRScanner event name. 18 | */ 19 | export type QRScannerEventName = keyof QRScannerEvents; 20 | 21 | /** 22 | * QRScanner event listener. 23 | */ 24 | export type QRScannerEventListener = QRScannerEvents[E]; 25 | 26 | export interface QRScannerOpenOptions { 27 | /** 28 | * Title to be displayed. 29 | */ 30 | text?: string; 31 | /** 32 | * Function, which should return true, if QR should be captured. 33 | */ 34 | capture?: RequestCaptureEventFn<'qr_text_received'>; 35 | } 36 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/events/event-handlers/defineEventHandlers.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, vi, afterEach } from 'vitest'; 2 | import { emitMiniAppsEvent } from '@/bridge/events/event-handlers/emitMiniAppsEvent.js'; 3 | 4 | import { defineEventHandlers } from './defineEventHandlers.js'; 5 | 6 | afterEach(() => { 7 | vi.restoreAllMocks(); 8 | }); 9 | 10 | it('should should specify emitMiniAppsEvent function by paths [window.TelegramGameProxy_receiveEvent, window.TelegramGameProxy.receiveEvent, window.Telegram.WebView.receiveEvent]', () => { 11 | const wnd: Record = {}; 12 | vi 13 | .spyOn(window, 'window', 'get') 14 | .mockImplementation(() => wnd as any); 15 | 16 | defineEventHandlers(); 17 | 18 | expect(wnd).toStrictEqual({ 19 | TelegramGameProxy_receiveEvent: emitMiniAppsEvent, 20 | TelegramGameProxy: { receiveEvent: emitMiniAppsEvent }, 21 | Telegram: { WebView: { receiveEvent: emitMiniAppsEvent } }, 22 | }); 23 | }); -------------------------------------------------------------------------------- /packages/sdk/src/events/onWindow.test.ts: -------------------------------------------------------------------------------- 1 | import { mockWindow } from 'test-utils'; 2 | import { expect, it, vi } from 'vitest'; 3 | 4 | import { onWindow } from '@/events/onWindow.js'; 5 | 6 | it('should add event listener using window.addEventListener', () => { 7 | const addEventListener = vi.fn(); 8 | const listener = vi.fn(); 9 | mockWindow(() => ({ addEventListener }) as any); 10 | onWindow('message', listener); 11 | 12 | expect(addEventListener).toHaveBeenCalledWith('message', listener, undefined); 13 | }); 14 | 15 | it('should return function which calls window.removeEventListener with arguments specified initially', () => { 16 | const removeEventListener = vi.fn(); 17 | const listener = vi.fn(); 18 | mockWindow(() => ({ 19 | addEventListener: vi.fn(), 20 | removeEventListener, 21 | }) as any); 22 | onWindow('message', listener)(); 23 | 24 | expect(removeEventListener).toHaveBeenCalledWith('message', listener, undefined); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/sdk/src/components/MiniApp/parsing/contact.test.ts: -------------------------------------------------------------------------------- 1 | import { toSearchParams } from 'test-utils'; 2 | import { expect, it } from 'vitest'; 3 | 4 | import { contact } from './contact.js'; 5 | 6 | it('should return parsed value in case, passed value satisfies schema', () => { 7 | expect( 8 | contact().parse(toSearchParams({ 9 | contact: { 10 | user_id: 123, 11 | phone_number: '+79292', 12 | first_name: 'Wolfram', 13 | last_name: 'Deus', 14 | }, 15 | auth_date: 1000, 16 | hash: 'my-hash', 17 | })), 18 | ).toMatchObject({ 19 | contact: { 20 | userId: 123, 21 | phoneNumber: '+79292', 22 | firstName: 'Wolfram', 23 | lastName: 'Deus', 24 | }, 25 | authDate: new Date(1000000), 26 | hash: 'my-hash', 27 | }); 28 | }); 29 | 30 | it('should throw an error in case, passed value does not satisfy schema', () => { 31 | expect(() => contact().parse({})).toThrow(); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/sdk/src/bridge/parseMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { parseMessage } from './parseMessage.js'; 4 | 5 | it('should parse value as JSON with properties { eventType: string; eventData?: unknown }', () => { 6 | expect(parseMessage({ eventType: 1 })).toEqual({ eventType: '1' }); 7 | expect(parseMessage({ eventType: 'test' })).toEqual({ eventType: 'test' }); 8 | expect(parseMessage({ eventType: 'test', eventData: 123 })).toEqual({ 9 | eventType: 'test', 10 | eventData: 123, 11 | }); 12 | 13 | expect(parseMessage('{"eventType":1}')).toEqual({ eventType: '1' }); 14 | expect(parseMessage('{"eventType":"test"}')).toEqual({ eventType: 'test' }); 15 | expect(parseMessage('{"eventType":"test","eventData":123}')).toEqual({ 16 | eventType: 'test', 17 | eventData: 123, 18 | }); 19 | }); 20 | 21 | it('should throw if eventType property is missing', () => { 22 | expect(() => parseMessage({})).toThrow(); 23 | }); 24 | -------------------------------------------------------------------------------- /apps/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | title: Home 6 | description: Documentation home page. 7 | 8 | hero: 9 | name: "Telegram Mini Apps" 10 | text: "Full fledged web applications inside Telegram" 11 | tagline: Simple, flexible, native-like web applications to enhance user experience 12 | actions: 13 | - theme: brand 14 | text: Platform 15 | link: /platform/about 16 | - theme: alt 17 | text: Packages 18 | link: /packages/telegram-apps-sdk 19 | 20 | features: 21 | - icon: 💻 22 | title: Multiplatform 23 | details: Works in all Telegram official applications, including Web and desktop versions 24 | - icon: 🌐 25 | title: Web based 26 | details: Platform requires knowledge of only web-based technologies 27 | - icon: 🧑 28 | title: Better user experience 29 | details: Simplifies user communication with a project connected with Telegram 30 | --- 31 | 32 | -------------------------------------------------------------------------------- /playgrounds/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-template", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "dev:https": "next dev --experimental-https", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@telegram-apps/telegram-ui": "^2.1.4", 14 | "@telegram-apps/react-router-integration": "workspace:*", 15 | "@telegram-apps/sdk-react": "workspace:*", 16 | "@tonconnect/ui-react": "^2.0.5", 17 | "eruda": "^3.0.1", 18 | "next": "14.2.4", 19 | "normalize.css": "^8.0.1", 20 | "react": "^18", 21 | "react-dom": "^18" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20", 25 | "@types/react": "^18", 26 | "@types/react-dom": "^18", 27 | "eslint": "^8", 28 | "eslint-config-next": "14.2.4", 29 | "postcss": "^8", 30 | "tailwindcss": "^3.4.1", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /playgrounds/solid/src/tonconnect/useTonWallet.ts: -------------------------------------------------------------------------------- 1 | import { createEffect, createSignal, onCleanup, type Accessor } from 'solid-js'; 2 | import type { Wallet, WalletInfoWithOpenMethod } from '@tonconnect/ui'; 3 | 4 | import { useTonConnectUI } from './useTonConnectUI.js'; 5 | 6 | /** 7 | * Use it to get user's current ton wallet. If wallet is not connected hook will return null. 8 | * @see Original React code: 9 | * https://github.com/ton-connect/sdk/blob/main/packages/ui-react/src/hooks/useTonWallet.ts 10 | */ 11 | export function useTonWallet(): Accessor { 12 | const [tonConnectUI] = useTonConnectUI(); 13 | const [wallet, setWallet] = createSignal( 14 | tonConnectUI().wallet || null, 15 | ); 16 | 17 | createEffect(() => onCleanup( 18 | tonConnectUI().onStatusChange((value) => { 19 | setWallet(value); 20 | }), 21 | )); 22 | 23 | return wallet; 24 | } 25 | -------------------------------------------------------------------------------- /packages/sdk/src/navigation/go.ts: -------------------------------------------------------------------------------- 1 | import { onWindow } from '@/events/onWindow.js'; 2 | 3 | /** 4 | * Performs window.history.go operation waiting for it to be completed. 5 | * @param delta - history change delta. 6 | */ 7 | export async function go(delta: number): Promise { 8 | if (delta === 0) { 9 | return true; 10 | } 11 | 12 | // We expect popstate event to occur during some time. Yeah, this seems tricky and not stable, 13 | // but it seems like we have no other way out. Waiting for Navigation API to be implemented in 14 | // browsers. 15 | return Promise.race([ 16 | new Promise((res) => { 17 | const remove = onWindow('popstate', () => { 18 | remove(); 19 | res(true); 20 | }); 21 | 22 | window.history.go(delta); 23 | }), 24 | 25 | // Usually, it takes about 1ms to emit this event, but we use some buffer. 26 | new Promise((res) => { 27 | setTimeout(res, 50, false); 28 | }), 29 | ]); 30 | } 31 | -------------------------------------------------------------------------------- /playgrounds/next/src/app/launch-params/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useLaunchParams } from '@telegram-apps/sdk-react'; 4 | import { List } from '@telegram-apps/telegram-ui'; 5 | 6 | import { DisplayData } from '@/components/DisplayData/DisplayData'; 7 | 8 | export default function LaunchParamsPage() { 9 | const lp = useLaunchParams(); 10 | 11 | return ( 12 | 13 | 24 | 25 | ); 26 | }; 27 | --------------------------------------------------------------------------------