From c1f5ea283b247c6d816b29690f5de50ede8e1211 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 18 Dec 2024 03:06:47 +0500 Subject: [PATCH 01/13] feat(bridge): add home screen-related methods and events --- packages/bridge/src/events/types/events.ts | 28 +++++++++++++++++++- packages/bridge/src/events/types/misc.ts | 4 ++- packages/bridge/src/methods/types/methods.ts | 15 +++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/bridge/src/events/types/events.ts b/packages/bridge/src/events/types/events.ts index b647ae2e9..321bf8291 100644 --- a/packages/bridge/src/events/types/events.ts +++ b/packages/bridge/src/events/types/events.ts @@ -1,6 +1,6 @@ import type { RGB } from '@telegram-apps/types'; -import { +import type { PhoneRequestedStatus, InvoiceStatus, WriteAccessRequestedStatus, @@ -11,6 +11,7 @@ import { FullScreenErrorStatus, EmojiStatusAccessRequestedStatus, EmojiStatusFailedError, + HomeScreenStatus, } from './misc.js'; /** @@ -199,6 +200,31 @@ export interface Events { */ error: FullScreenErrorStatus; }; + /** + * The mini application was added to the device's home screen. + * @since v8.0 + * @see https://docs.telegram-mini-apps.com/platform/events#home_screen_added + */ + home_screen_added: never; + /** + * The status of the mini application being added to the home screen has been checked. + * @since v8.0 + * @see https://docs.telegram-mini-apps.com/platform/events#home_screen_checked + */ + home_screen_checked: { + /** + * The status of the mini application being added to the home screen. + * + * Possible values: + * - `unsupported` – the feature is not supported, and it is not possible to add the icon to the home + * screen, + * - `unknown` – the feature is supported, and the icon can be added, but it is not possible to + * determine if the icon has already been added, + * - `added` – the icon has already been added to the home screen, + * - `missed` – the icon has not been added to the home screen. + */ + status?: HomeScreenStatus; + }; /** * An invoice was closed. * @see https://docs.telegram-mini-apps.com/platform/events#invoice-closed diff --git a/packages/bridge/src/events/types/misc.ts b/packages/bridge/src/events/types/misc.ts index 8e74f23a7..1c2b3d0a7 100644 --- a/packages/bridge/src/events/types/misc.ts +++ b/packages/bridge/src/events/types/misc.ts @@ -33,4 +33,6 @@ export interface SafeAreaInsets { bottom: number; left: number; right: number; -} \ No newline at end of file +} + +export type HomeScreenStatus = 'unsupported' | 'unknown' | 'added' | 'missed' | string; \ No newline at end of file diff --git a/packages/bridge/src/methods/types/methods.ts b/packages/bridge/src/methods/types/methods.ts index 4e838710e..3b48ec7ee 100644 --- a/packages/bridge/src/methods/types/methods.ts +++ b/packages/bridge/src/methods/types/methods.ts @@ -68,6 +68,14 @@ export interface Methods { * @see https://docs.telegram-mini-apps.com/platform/methods#iframe-will-reload */ iframe_will_reload: CreateMethodParams; + /** + * Prompts the user to add the Mini App to the home screen. Note that if the device cannot + * determine the installation status, the event may not be received even if the icon has + * been added. + * @since v8.0 + * @see https://docs.telegram-mini-apps.com/platform/methods#web-app-add-to-home-screen + */ + web_app_add_to_home_screen: CreateMethodParams; /** * Emitted by bot mini apps to ask the client to initialize the biometric authentication manager * object for the current bot, emitting a `biometry_info_received` event on completion. @@ -148,6 +156,13 @@ export interface Methods { */ token: string; }>; + /** + * Sends a request to the native Telegram application to check if the current mini + * application is added to the device's home screen. + * @since v8.0 + * @see https://docs.telegram-mini-apps.com/platform/methods#web-app-check-home-screen + */ + web_app_check_home_screen: CreateMethodParams; /** * Closes Mini App. * @see https://docs.telegram-mini-apps.com/platform/methods#web-app-close From 720e014ecf3d269b228ad3066bbdd5dc458e2859 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Wed, 18 Dec 2024 03:07:03 +0500 Subject: [PATCH 02/13] docs(events,methods): add home screen-related methods and events --- apps/docs/platform/events.md | 23 +++++++++++++++++++++++ apps/docs/platform/methods.md | 15 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/apps/docs/platform/events.md b/apps/docs/platform/events.md index 617c616f0..2c7178508 100644 --- a/apps/docs/platform/events.md +++ b/apps/docs/platform/events.md @@ -237,6 +237,29 @@ Occurs whenever the mini app enters or exits the fullscreen mode. |-------|----------|---------------------------------------------------------------------------------------| | error | `string` | Fullscreen mode status error. Possible values: `UNSUPPORTED` or `ALREADY_FULLSCREEN`. | +### `home_screen_added` + +Available since: **v8.0** + +The mini application was added to the device's home screen. + +### `home_screen_checked` + +Available since: **v8.0** + +The status of the mini application being added to the home screen has been checked. + +| Field | Type | Description | +|--------|----------|------------------------------------------------------------------------------------------------------------------------------------| +| status | `string` | The status of the mini application being added to the home screen. Possible values: `unsupported`, `unknown`, `added` and `missed` | + +- `unsupported` – the feature is not supported, and it is not possible to add the icon to the home + screen, +- `unknown` – the feature is supported, and the icon can be added, but it is not possible to + determine if the icon has already been added, +- `added` – the icon has already been added to the home screen, +- `missed` – the icon has not been added to the home screen. + ### `invoice_closed` An invoice was closed. diff --git a/apps/docs/platform/methods.md b/apps/docs/platform/methods.md index 537713e12..6b6b9df65 100644 --- a/apps/docs/platform/methods.md +++ b/apps/docs/platform/methods.md @@ -114,6 +114,14 @@ event. Notifies parent iframe about the current iframe is going to reload. +### `web_app_add_to_home_screen` + +Available since: **v8.0** + +Prompts the user to add the Mini App to the home screen. Note that if the device cannot +determine the installation status, the event may not be received even if the icon has +been added. + ### `web_app_biometry_get_info` Available since: **v7.2** @@ -162,6 +170,13 @@ string. |-------|----------|-------------------------------------------------| | token | `string` | Token to store. Has max length of 1024 symbols. | +### `web_app_check_home_screen` + +Available since: **v8.0** + +Sends a request to the native Telegram application to check if the current mini +application is added to the device's home screen. + ### `web_app_close` Closes Mini App. From 1a92c9d0b464d0bb09eb830e19a119a6ce94a705 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 00:44:03 +0500 Subject: [PATCH 03/13] feat(bridge): add home_screen_failed event --- packages/bridge/src/events/types/events.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bridge/src/events/types/events.ts b/packages/bridge/src/events/types/events.ts index 321bf8291..d3a9270fe 100644 --- a/packages/bridge/src/events/types/events.ts +++ b/packages/bridge/src/events/types/events.ts @@ -225,6 +225,12 @@ export interface Events { */ status?: HomeScreenStatus; }; + /** + * User declined the request to add the current mini application to the device's home screen. + * @since v8.0 + * @see https://docs.telegram-mini-apps.com/platform/events#home_screen_failed + */ + home_screen_failed: never; /** * An invoice was closed. * @see https://docs.telegram-mini-apps.com/platform/events#invoice-closed From bf4251ab4e48c1d72dc11507950588e79726245e Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 00:44:25 +0500 Subject: [PATCH 04/13] feat(bridge): add web_app_add_to_home_screen and web_app_check_home_screen to supports --- packages/bridge/src/methods/supports.test.ts | 2 ++ packages/bridge/src/methods/supports.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/bridge/src/methods/supports.test.ts b/packages/bridge/src/methods/supports.test.ts index 95ac16e5f..b75319e8a 100644 --- a/packages/bridge/src/methods/supports.test.ts +++ b/packages/bridge/src/methods/supports.test.ts @@ -121,6 +121,8 @@ describe.each<[ 'web_app_exit_fullscreen', 'web_app_set_emoji_status', 'web_app_request_emoji_status_access', + 'web_app_add_to_home_screen', + 'web_app_check_home_screen', ]], ])('%s', (version, methods) => { const higher = increaseVersion(version, 1); diff --git a/packages/bridge/src/methods/supports.ts b/packages/bridge/src/methods/supports.ts index f58b68c60..92628e445 100644 --- a/packages/bridge/src/methods/supports.ts +++ b/packages/bridge/src/methods/supports.ts @@ -106,6 +106,8 @@ export function supports( case 'web_app_request_fullscreen': case 'web_app_exit_fullscreen': case 'web_app_set_emoji_status': + case 'web_app_add_to_home_screen': + case 'web_app_check_home_screen': case 'web_app_request_emoji_status_access': return versionLessOrEqual('8.0', paramOrVersion); default: From 3a77334fa4dbe6accfd089c70a88355a89348525 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 00:46:48 +0500 Subject: [PATCH 05/13] feat(sdk): implement home screen-related functionality --- packages/sdk/src/index.ts | 1 + .../home-screen/addToHomeScreen.test.ts | 17 ++++++ .../utilities/home-screen/addToHomeScreen.ts | 26 ++++++++ .../home-screen/checkHomeScreenStatus.test.ts | 17 ++++++ .../home-screen/checkHomeScreenStatus.ts | 60 +++++++++++++++++++ .../scopes/utilities/home-screen/exports.ts | 7 +++ packages/sdk/test-utils/reset/reset.ts | 2 + .../sdk/test-utils/reset/resetHomeScreen.ts | 14 +++++ 8 files changed, 144 insertions(+) create mode 100644 packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts create mode 100644 packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts create mode 100644 packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts create mode 100644 packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts create mode 100644 packages/sdk/src/scopes/utilities/home-screen/exports.ts create mode 100644 packages/sdk/test-utils/reset/resetHomeScreen.ts diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 0d1204cc0..6c1755d6c 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -18,6 +18,7 @@ export * from '@/scopes/components/swipe-behavior/exports.js'; export * from '@/scopes/components/theme-params/exports.js'; export * from '@/scopes/components/viewport/exports.js'; export * from '@/scopes/utilities/emoji-status/exports.js'; +export * from '@/scopes/utilities/home-screen/exports.js'; export * from '@/scopes/utilities/links/exports.js'; export * from '@/scopes/utilities/privacy/exports.js'; export * from '@/scopes/utilities/uncategorized/exports.js'; diff --git a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts b/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts new file mode 100644 index 000000000..3712e5c6a --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts @@ -0,0 +1,17 @@ +import { beforeEach, describe, vi } from 'vitest'; + +import { testSafety } from '@test-utils/predefined/testSafety.js'; +import { resetPackageState } from '@test-utils/reset/reset.js'; +import { mockPostEvent } from '@test-utils/mockPostEvent.js'; + +import { addToHomeScreen } from './addToHomeScreen.js'; + +beforeEach(() => { + resetPackageState(); + vi.restoreAllMocks(); + mockPostEvent(); +}); + +describe('safety', () => { + testSafety(addToHomeScreen, 'addToHomeScreen', { minVersion: '8.0' }); +}); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts b/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts new file mode 100644 index 000000000..73a08f6c4 --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts @@ -0,0 +1,26 @@ +import { postEvent } from '@/scopes/globals.js'; +import { wrapSafe } from '@/scopes/toolkit/wrapSafe.js'; + +const METHOD = 'web_app_add_to_home_screen'; + +/** + * Prompts the user to add the Mini App to the home screen. Note that if the device cannot + * determine the installation status, a corresponding event may not be received even if the icon + * has been added. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example Using `isAvailable` + * if (addToHomeScreen.isAvailable()) { + * addToHomeScreen(); + * } + * @example Using `ifAvailable` + * addToHomeScreen.ifAvailable() + */ +export const addToHomeScreen = wrapSafe( + 'addToHomeScreen', + () => { + postEvent(METHOD); + }, { isSupported: METHOD }, +); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts new file mode 100644 index 000000000..3712e5c6a --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts @@ -0,0 +1,17 @@ +import { beforeEach, describe, vi } from 'vitest'; + +import { testSafety } from '@test-utils/predefined/testSafety.js'; +import { resetPackageState } from '@test-utils/reset/reset.js'; +import { mockPostEvent } from '@test-utils/mockPostEvent.js'; + +import { addToHomeScreen } from './addToHomeScreen.js'; + +beforeEach(() => { + resetPackageState(); + vi.restoreAllMocks(); + mockPostEvent(); +}); + +describe('safety', () => { + testSafety(addToHomeScreen, 'addToHomeScreen', { minVersion: '8.0' }); +}); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts new file mode 100644 index 000000000..aaa5632b8 --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts @@ -0,0 +1,60 @@ +import { computed, signal } from '@telegram-apps/signals'; +import { + type AsyncOptions, + type CancelablePromise, + type HomeScreenStatus, + TypedError, +} from '@telegram-apps/bridge'; + +import { ERR_ALREADY_REQUESTING } from '@/errors.js'; +import { request } from '@/scopes/globals.js'; +import { wrapSafe } from '@/scopes/toolkit/wrapSafe.js'; +import { signalifyAsyncFn } from '@/scopes/signalifyAsyncFn.js'; + +const METHOD = 'web_app_check_home_screen'; + +/** + * Signal containing the home screen status check request promise. + */ +export const checkHomeScreenStatusPromise = signal | undefined>(); + +/** + * Signal containing the home screen status check request error. + */ +export const checkHomeScreenStatusError = signal(); + +/** + * Signal indicating if the home screen status check is currently being requested. + */ +export const isCheckingHomeScreenStatus = computed(() => !!checkHomeScreenStatusPromise()); + +/** + * Sends a request to the native Telegram application to check if the current mini + * application is added to the device's home screen. + * @param options - additional options. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_ALREADY_REQUESTING + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (checkHomeScreenStatus.isAvailable()) { + * const status = await checkHomeScreenStatus(); + * } + */ +export const checkHomeScreenStatus = wrapSafe( + 'checkHomeScreenStatus', + signalifyAsyncFn( + (options?: AsyncOptions): CancelablePromise => { + return request(METHOD, 'home_screen_checked', options) + .then(r => r.status || 'unknown'); + }, + () => new TypedError( + ERR_ALREADY_REQUESTING, + 'Check home screen status request is currently in progress', + ), + checkHomeScreenStatusPromise, + checkHomeScreenStatusError, + ), + { isSupported: METHOD }, +); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/exports.ts b/packages/sdk/src/scopes/utilities/home-screen/exports.ts new file mode 100644 index 000000000..85f636235 --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/exports.ts @@ -0,0 +1,7 @@ +export { addToHomeScreen } from './addToHomeScreen.js'; +export { + checkHomeScreenStatusError, + checkHomeScreenStatusPromise, + isCheckingHomeScreenStatus, + checkHomeScreenStatus, +} from './checkHomeScreenStatus.js'; \ No newline at end of file diff --git a/packages/sdk/test-utils/reset/reset.ts b/packages/sdk/test-utils/reset/reset.ts index 02645c19f..57b1e6314 100644 --- a/packages/sdk/test-utils/reset/reset.ts +++ b/packages/sdk/test-utils/reset/reset.ts @@ -17,6 +17,7 @@ import { resetSwipeBehavior } from '@test-utils/reset/resetSwipeBehavior.js'; import { resetThemeParams } from '@test-utils/reset/resetThemeParams.js'; import { resetViewport } from '@test-utils/reset/resetViewport.js'; import { resetPrivacy } from '@test-utils/reset/resetPrivacy.js'; +import { resetHomeScreen } from '@test-utils/reset/resetHomeScreen.js'; export function resetSignal(s: Signal | Computed) { s.unsubAll(); @@ -41,6 +42,7 @@ export function resetPackageState() { resetSwipeBehavior, resetThemeParams, resetViewport, + resetHomeScreen, ].forEach(reset => reset()); [$postEvent, $version, $createRequestId].forEach(resetSignal); } diff --git a/packages/sdk/test-utils/reset/resetHomeScreen.ts b/packages/sdk/test-utils/reset/resetHomeScreen.ts new file mode 100644 index 000000000..e5b235325 --- /dev/null +++ b/packages/sdk/test-utils/reset/resetHomeScreen.ts @@ -0,0 +1,14 @@ +import { + isCheckingHomeScreenStatus, + checkHomeScreenStatusError, + checkHomeScreenStatusPromise, +} from '@/scopes/utilities/home-screen/checkHomeScreenStatus.js'; +import { resetSignal } from '@test-utils/reset/reset.js'; + +export function resetHomeScreen() { + [ + isCheckingHomeScreenStatus, + checkHomeScreenStatusError, + checkHomeScreenStatusPromise, + ].forEach(resetSignal); +} \ No newline at end of file From 71e88f45c2f4192e0774bc68342d5d22a00f33bc Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 00:48:34 +0500 Subject: [PATCH 06/13] feat(toolkit): set name for TypedError class --- packages/toolkit/src/errors/TypedError.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolkit/src/errors/TypedError.ts b/packages/toolkit/src/errors/TypedError.ts index 81aca9e89..14f43f7d8 100644 --- a/packages/toolkit/src/errors/TypedError.ts +++ b/packages/toolkit/src/errors/TypedError.ts @@ -13,6 +13,7 @@ export class TypedError extends Error { cause: typeof messageOrOptions === 'object' ? messageOrOptions.cause : cause, }, ); + this.name = 'TypedError'; Object.setPrototypeOf(this, TypedError.prototype); } } From e7566758411e6155c4cc264739e6d1b41a390233 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 00:48:45 +0500 Subject: [PATCH 07/13] docs(events): add home_screen_failed --- apps/docs/platform/events.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/docs/platform/events.md b/apps/docs/platform/events.md index 2c7178508..6ac0f2640 100644 --- a/apps/docs/platform/events.md +++ b/apps/docs/platform/events.md @@ -260,6 +260,12 @@ The status of the mini application being added to the home screen has been check - `added` – the icon has already been added to the home screen, - `missed` – the icon has not been added to the home screen. +### `home_screen_failed` + +Available since: **v8.0** + +User declined the request to add the current mini application to the device's home screen. + ### `invoice_closed` An invoice was closed. From 90726e4a628374e983a61c47909411d01eaf0d04 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:40:01 +0500 Subject: [PATCH 08/13] feat(sdk): add onAddedToHomeScreen, onAddToHomeScreenFailed, offAddedToHomeScreen and offAddToHomeScreenFailed --- .../utilities/home-screen/addToHomeScreen.ts | 26 --- .../home-screen/checkHomeScreenStatus.test.ts | 17 -- .../home-screen/checkHomeScreenStatus.ts | 60 ------ .../scopes/utilities/home-screen/exports.ts | 8 +- ...HomeScreen.test.ts => home-screen.test.ts} | 18 +- .../utilities/home-screen/home-screen.ts | 181 ++++++++++++++++++ 6 files changed, 204 insertions(+), 106 deletions(-) delete mode 100644 packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts delete mode 100644 packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts delete mode 100644 packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts rename packages/sdk/src/scopes/utilities/home-screen/{addToHomeScreen.test.ts => home-screen.test.ts} (50%) create mode 100644 packages/sdk/src/scopes/utilities/home-screen/home-screen.ts diff --git a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts b/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts deleted file mode 100644 index 73a08f6c4..000000000 --- a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { postEvent } from '@/scopes/globals.js'; -import { wrapSafe } from '@/scopes/toolkit/wrapSafe.js'; - -const METHOD = 'web_app_add_to_home_screen'; - -/** - * Prompts the user to add the Mini App to the home screen. Note that if the device cannot - * determine the installation status, a corresponding event may not be received even if the icon - * has been added. - * @since Mini Apps v8.0 - * @throws {TypedError} ERR_UNKNOWN_ENV - * @throws {TypedError} ERR_NOT_INITIALIZED - * @throws {TypedError} ERR_NOT_SUPPORTED - * @example Using `isAvailable` - * if (addToHomeScreen.isAvailable()) { - * addToHomeScreen(); - * } - * @example Using `ifAvailable` - * addToHomeScreen.ifAvailable() - */ -export const addToHomeScreen = wrapSafe( - 'addToHomeScreen', - () => { - postEvent(METHOD); - }, { isSupported: METHOD }, -); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts deleted file mode 100644 index 3712e5c6a..000000000 --- a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { beforeEach, describe, vi } from 'vitest'; - -import { testSafety } from '@test-utils/predefined/testSafety.js'; -import { resetPackageState } from '@test-utils/reset/reset.js'; -import { mockPostEvent } from '@test-utils/mockPostEvent.js'; - -import { addToHomeScreen } from './addToHomeScreen.js'; - -beforeEach(() => { - resetPackageState(); - vi.restoreAllMocks(); - mockPostEvent(); -}); - -describe('safety', () => { - testSafety(addToHomeScreen, 'addToHomeScreen', { minVersion: '8.0' }); -}); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts b/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts deleted file mode 100644 index aaa5632b8..000000000 --- a/packages/sdk/src/scopes/utilities/home-screen/checkHomeScreenStatus.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { computed, signal } from '@telegram-apps/signals'; -import { - type AsyncOptions, - type CancelablePromise, - type HomeScreenStatus, - TypedError, -} from '@telegram-apps/bridge'; - -import { ERR_ALREADY_REQUESTING } from '@/errors.js'; -import { request } from '@/scopes/globals.js'; -import { wrapSafe } from '@/scopes/toolkit/wrapSafe.js'; -import { signalifyAsyncFn } from '@/scopes/signalifyAsyncFn.js'; - -const METHOD = 'web_app_check_home_screen'; - -/** - * Signal containing the home screen status check request promise. - */ -export const checkHomeScreenStatusPromise = signal | undefined>(); - -/** - * Signal containing the home screen status check request error. - */ -export const checkHomeScreenStatusError = signal(); - -/** - * Signal indicating if the home screen status check is currently being requested. - */ -export const isCheckingHomeScreenStatus = computed(() => !!checkHomeScreenStatusPromise()); - -/** - * Sends a request to the native Telegram application to check if the current mini - * application is added to the device's home screen. - * @param options - additional options. - * @since Mini Apps v8.0 - * @throws {TypedError} ERR_ALREADY_REQUESTING - * @throws {TypedError} ERR_UNKNOWN_ENV - * @throws {TypedError} ERR_NOT_INITIALIZED - * @throws {TypedError} ERR_NOT_SUPPORTED - * @example - * if (checkHomeScreenStatus.isAvailable()) { - * const status = await checkHomeScreenStatus(); - * } - */ -export const checkHomeScreenStatus = wrapSafe( - 'checkHomeScreenStatus', - signalifyAsyncFn( - (options?: AsyncOptions): CancelablePromise => { - return request(METHOD, 'home_screen_checked', options) - .then(r => r.status || 'unknown'); - }, - () => new TypedError( - ERR_ALREADY_REQUESTING, - 'Check home screen status request is currently in progress', - ), - checkHomeScreenStatusPromise, - checkHomeScreenStatusError, - ), - { isSupported: METHOD }, -); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/exports.ts b/packages/sdk/src/scopes/utilities/home-screen/exports.ts index 85f636235..43f2ed6d7 100644 --- a/packages/sdk/src/scopes/utilities/home-screen/exports.ts +++ b/packages/sdk/src/scopes/utilities/home-screen/exports.ts @@ -1,7 +1,11 @@ -export { addToHomeScreen } from './addToHomeScreen.js'; export { checkHomeScreenStatusError, checkHomeScreenStatusPromise, isCheckingHomeScreenStatus, checkHomeScreenStatus, -} from './checkHomeScreenStatus.js'; \ No newline at end of file + offAddedToHomeScreen, + onAddedToHomeScreen, + addToHomeScreen, + offAddToHomeScreenFailed, + onAddToHomeScreenFailed, +} from './home-screen.js'; \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts b/packages/sdk/src/scopes/utilities/home-screen/home-screen.test.ts similarity index 50% rename from packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts rename to packages/sdk/src/scopes/utilities/home-screen/home-screen.test.ts index 3712e5c6a..ab6a9c6e4 100644 --- a/packages/sdk/src/scopes/utilities/home-screen/addToHomeScreen.test.ts +++ b/packages/sdk/src/scopes/utilities/home-screen/home-screen.test.ts @@ -4,7 +4,12 @@ import { testSafety } from '@test-utils/predefined/testSafety.js'; import { resetPackageState } from '@test-utils/reset/reset.js'; import { mockPostEvent } from '@test-utils/mockPostEvent.js'; -import { addToHomeScreen } from './addToHomeScreen.js'; +import { + addToHomeScreen, + onAddedToHomeScreen, + offAddedToHomeScreen, + checkHomeScreenStatus, +} from './home-screen.js'; beforeEach(() => { resetPackageState(); @@ -14,4 +19,15 @@ beforeEach(() => { describe('safety', () => { testSafety(addToHomeScreen, 'addToHomeScreen', { minVersion: '8.0' }); +}); + +describe.each([ + ['addToHomeScreen', addToHomeScreen], + ['onAddedToHomeScreen', onAddedToHomeScreen], + ['offAddedToHomeScreen', offAddedToHomeScreen], + ['checkHomeScreenStatus', checkHomeScreenStatus], +] as const)('%s', (name, fn) => { + testSafety(fn, name, { + minVersion: '8.0', + }); }); \ No newline at end of file diff --git a/packages/sdk/src/scopes/utilities/home-screen/home-screen.ts b/packages/sdk/src/scopes/utilities/home-screen/home-screen.ts new file mode 100644 index 000000000..48f88c26a --- /dev/null +++ b/packages/sdk/src/scopes/utilities/home-screen/home-screen.ts @@ -0,0 +1,181 @@ +import { + on, + type EventListener, + off, + type CancelablePromise, + type HomeScreenStatus, + type AsyncOptions, + TypedError, +} from '@telegram-apps/bridge'; + +import { postEvent, request } from '@/scopes/globals.js'; +import { wrapSafe } from '@/scopes/toolkit/wrapSafe.js'; +import { computed, signal } from '@telegram-apps/signals'; +import { signalifyAsyncFn } from '@/scopes/signalifyAsyncFn.js'; +import { ERR_ALREADY_REQUESTING } from '@/errors.js'; + +const METHOD = 'web_app_add_to_home_screen'; + +const wrapOptions = { isSupported: METHOD } as const; + +/** + * Signal containing the home screen status check request promise. + */ +export const checkHomeScreenStatusPromise = signal | undefined>(); + +/** + * Signal containing the home screen status check request error. + */ +export const checkHomeScreenStatusError = signal(); + +/** + * Signal indicating if the home screen status check is currently being requested. + */ +export const isCheckingHomeScreenStatus = computed(() => !!checkHomeScreenStatusPromise()); + +/** + * Prompts the user to add the Mini App to the home screen. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example Using `isAvailable` + * if (addToHomeScreen.isAvailable()) { + * addToHomeScreen(); + * } + * @example Using `ifAvailable` + * addToHomeScreen.ifAvailable() + */ +export const addToHomeScreen = wrapSafe( + 'addToHomeScreen', + () => { + postEvent(METHOD); + }, + wrapOptions, +); + +/** + * Sends a request to the native Telegram application to check if the current mini + * application is added to the device's home screen. + * @param options - additional options. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_ALREADY_REQUESTING + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (checkHomeScreenStatus.isAvailable()) { + * const status = await checkHomeScreenStatus(); + * } + */ +export const checkHomeScreenStatus = wrapSafe( + 'checkHomeScreenStatus', + signalifyAsyncFn( + (options?: AsyncOptions): CancelablePromise => { + return request(METHOD, 'home_screen_checked', options) + .then(r => r.status || 'unknown'); + }, + () => new TypedError( + ERR_ALREADY_REQUESTING, + 'Check home screen status request is currently in progress', + ), + checkHomeScreenStatusPromise, + checkHomeScreenStatusError, + ), + wrapOptions, +); + +/** + * Adds the event listener that being called whenever the user adds the current mini app to the + * device's home screen. + * + * Note that if the device cannot determine the installation status, a corresponding event may + * not be received even if the icon has been added. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (onAddedToHomeScreen.isAvailable()) { + * const off = onAddedToHomeScreen(() => { + * console.log('Added'); + * off(); + * }); + * } + */ +export const onAddedToHomeScreen = wrapSafe( + 'onAddedToHomeScreen', + (listener: EventListener<'home_screen_added'>, once?: boolean) => { + return on('home_screen_added', listener, once); + }, + wrapOptions, +); + +/** + * Adds the event listener that being called whenever the user declines the request to add the + * current mini app to the device's home screen. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (onAddToHomeScreenFailed.isAvailable()) { + * const off = onAddToHomeScreenFailed(() => { + * console.log('Failed to add to home screen'); + * off(); + * }); + * } + */ +export const onAddToHomeScreenFailed = wrapSafe( + 'onAddToHomeScreenFailed', + (listener: EventListener<'home_screen_failed'>, once?: boolean) => { + return on('home_screen_failed', listener, once); + }, + wrapOptions, +); + +/** + * Removes add to home screen event listener. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (onAddedToHomeScreen.isAvailable()) { + * const handler = () => { + * console.log('Added'); + * offAddedToHomeScreen(handler); + * }; + * onAddedToHomeScreen(handler); + * } + */ +export const offAddedToHomeScreen = wrapSafe( + 'offAddedToHomeScreen', + (listener: EventListener<'home_screen_added'>) => { + off('home_screen_added', listener); + }, + wrapOptions, +); + +/** + * Removes add to home screen failed event listener. + * @since Mini Apps v8.0 + * @throws {TypedError} ERR_UNKNOWN_ENV + * @throws {TypedError} ERR_NOT_INITIALIZED + * @throws {TypedError} ERR_NOT_SUPPORTED + * @example + * if (offAddToHomeScreenFailed.isAvailable()) { + * const handler = () => { + * console.log('Failed to add'); + * offAddToHomeScreenFailed(handler); + * }; + * onAddToHomeScreenFailed(handler); + * } + */ +export const offAddToHomeScreenFailed = wrapSafe( + 'offAddToHomeScreenFailed', + (listener: EventListener<'home_screen_failed'>) => { + off('home_screen_failed', listener); + }, + wrapOptions, +); From 8d0b294515ce078e42abcd6160a40d776805a9c2 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:41:55 +0500 Subject: [PATCH 09/13] docs(sdk): add home screen-related functionality docs --- apps/docs/.vitepress/packages.ts | 1 + .../2-x/utils/home-screen.md | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 apps/docs/packages/telegram-apps-sdk/2-x/utils/home-screen.md diff --git a/apps/docs/.vitepress/packages.ts b/apps/docs/.vitepress/packages.ts index fd78cd614..6eec390c1 100644 --- a/apps/docs/.vitepress/packages.ts +++ b/apps/docs/.vitepress/packages.ts @@ -110,6 +110,7 @@ export const packagesLinksGenerator = (prefix: string = '') => { ], 'Utilities': [{ url: 'utils', page: false }, fromEntries([ scope('emoji-status'), + scope('home-screen'), scope('links'), scope('privacy'), scope('uncategorized'), diff --git a/apps/docs/packages/telegram-apps-sdk/2-x/utils/home-screen.md b/apps/docs/packages/telegram-apps-sdk/2-x/utils/home-screen.md new file mode 100644 index 000000000..92bd1c3e8 --- /dev/null +++ b/apps/docs/packages/telegram-apps-sdk/2-x/utils/home-screen.md @@ -0,0 +1,68 @@ +# Home Screen + +## `addToHomeScreen` + +To prompt the user to add the Mini App to the home screen, use the `addToHomeScreen` function. + +::: code-group + +```ts [Using isAvailable] +import { addToHomeScreen } from '@telegram-apps/sdk'; + +if (addToHomeScreen.isAvailable()) { + addToHomeScreen(); +} +``` + +```ts [Using ifAvailable] +import { addToHomeScreen } from '@telegram-apps/sdk'; + +addToHomeScreen.ifAvailable(); +``` + +::: + +To track whether the current Mini App is added to the device's home screen, use +the `onAddedToHomeScreen` and `offAddedToHomeScreen` functions: + +```ts +import { + onAddedToHomeScreen, + onAddToHomeScreenFailed, + offAddedToHomeScreen, + offAddToHomeScreenFailed, +} from '@telegram-apps/sdk'; + +function onAdded() { + console.log('Added'); +} + +onAddedToHomeScreen(onAdded); +offAddedToHomeScreen(onAdded); + +function onFailed() { + console.log('User declined the request'); +} + +onAddToHomeScreenFailed(onFailed); +offAddToHomeScreenFailed(onFailed); +``` + +> [!NOTE] +> If the device cannot determine the installation status, the corresponding event may not be +> received even if the icon has been added. + +## `checkHomeScreenStatus` + +The `checkHomeScreenStatus` function checks if the user has already added the Mini App to the +device's home screen. + +```ts +import { checkHomeScreenStatus } from '@telegram-apps/sdk'; + +if (checkHomeScreenStatus.isAvailable()) { + checkHomeScreenStatus().then(status => { + console.log(status); + }); +} +``` From 3a93d64b096e05ce3a49a879f782013b84dc1487 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:43:30 +0500 Subject: [PATCH 10/13] docs(changeset): Set name for the TypedError class. --- .changeset/fair-timers-arrive.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fair-timers-arrive.md diff --git a/.changeset/fair-timers-arrive.md b/.changeset/fair-timers-arrive.md new file mode 100644 index 000000000..1c865ab7f --- /dev/null +++ b/.changeset/fair-timers-arrive.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/toolkit": minor +--- + +Set name for the TypedError class. From bb72227a9441c00825f4af0d739ddbcbe4d8f096 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:44:26 +0500 Subject: [PATCH 11/13] docs(changeset): Implement `home_screen_added`, `home_screen_checked` and `home_screen_failed` events. Implement `web_app_add_to_home_screen` and `web_app_check_home_screen` methods. --- .changeset/real-pigs-deny.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/real-pigs-deny.md diff --git a/.changeset/real-pigs-deny.md b/.changeset/real-pigs-deny.md new file mode 100644 index 000000000..d1cadda8d --- /dev/null +++ b/.changeset/real-pigs-deny.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/bridge": minor +--- + +Implement `home_screen_added`, `home_screen_checked` and `home_screen_failed` events. Implement `web_app_add_to_home_screen` and `web_app_check_home_screen` methods. From ea8151771a161822fb8ffaef5c1c78656de8f95b Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:44:52 +0500 Subject: [PATCH 12/13] docs(changeset): Implement add to home screen-related functionality. --- .changeset/curly-cats-poke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curly-cats-poke.md diff --git a/.changeset/curly-cats-poke.md b/.changeset/curly-cats-poke.md new file mode 100644 index 000000000..6ff4cfa6f --- /dev/null +++ b/.changeset/curly-cats-poke.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/sdk": minor +--- + +Implement add to home screen-related functionality. From a19a68207c3020142eca331c3c7bb745e665d64d Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Fri, 20 Dec 2024 01:47:15 +0500 Subject: [PATCH 13/13] tests(sdk): fix resetHomeScreen --- packages/sdk/test-utils/reset/resetHomeScreen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/test-utils/reset/resetHomeScreen.ts b/packages/sdk/test-utils/reset/resetHomeScreen.ts index e5b235325..a9faaaf4b 100644 --- a/packages/sdk/test-utils/reset/resetHomeScreen.ts +++ b/packages/sdk/test-utils/reset/resetHomeScreen.ts @@ -2,7 +2,7 @@ import { isCheckingHomeScreenStatus, checkHomeScreenStatusError, checkHomeScreenStatusPromise, -} from '@/scopes/utilities/home-screen/checkHomeScreenStatus.js'; +} from '@/scopes/utilities/home-screen/home-screen.js'; import { resetSignal } from '@test-utils/reset/reset.js'; export function resetHomeScreen() {