Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix mount problems #491

Merged
merged 14 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-berries-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@telegram-apps/sdk": minor
---

Make mountable components now send their state to the Telegram application on mount.
214 changes: 18 additions & 196 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,205 +1,27 @@
export { classNames } from '@/classnames/classNames.js';
export { mergeClassNames, type MergeClassNames } from '@/classnames/mergeClassNames.js';

export * as backButton from '@/scopes/components/back-button/back-button.js';
export {
hide as hideBackButton,
isVisible as isBackButtonVisible,
isMounted as isBackButtonMounted,
isSupported as isBackButtonSupported,
mount as mountBackButton,
onClick as onBackButtonClick,
offClick as offBackButtonClick,
show as showBackButton,
unmount as unmountBackButton,
} from '@/scopes/components/back-button/back-button.js';

export * as biometry from '@/scopes/components/biometry/instance.js';
export {
authenticate as authenticateBiometry,
isMounting as isBiometryMounting,
isMounted as isBiometryMounted,
isSupported as isBiometrySupported,
mount as mountBiometry,
mountError as biometryMountError,
openSettings as openBiometrySettings,
requestAccess as requestBiometryAccess,
state as biometryState,
unmount as unmountBiometry,
updateToken as updateBiometryToken,
isAuthenticating as isAuthenticatingBiometry,
isRequestingAccess as isRequestingBiometryAccess,
} from '@/scopes/components/biometry/instance.js';
export * as Biometry from '@/scopes/components/biometry/static.js';

export * as closingBehavior from '@/scopes/components/closing-behavior/closing-behavior.js';
export {
disableConfirmation as disableClosingConfirmation,
enableConfirmation as enableClosingConfirmation,
isMounted as isClosingBehaviorMounted,
isConfirmationEnabled as isClosingConfirmationEnabled,
mount as mountClosingBehavior,
unmount as unmountClosingBehavior,
} from '@/scopes/components/closing-behavior/closing-behavior.js';

export * as cloudStorage from '@/scopes/components/cloud-storage/cloud-storage.js';
export {
isSupported as isCloudStorageSupported,
getItem as getCloudStorageItem,
setItem as setCloudStorageItem,
deleteItem as deleteCloudStorageItem,
getKeys as getCloudStorageKeys,
} from '@/scopes/components/cloud-storage/cloud-storage.js';

export * as hapticFeedback from '@/scopes/components/haptic-feedback/haptic-feedback.js';
export {
impactOccurred as hapticFeedbackImpactOccurred,
isSupported as isHapticFeedbackSupported,
notificationOccurred as hapticFeedbackNotificationOccurred,
selectionChanged as hapticFeedbackSelectionChanged,
} from '@/scopes/components/haptic-feedback/haptic-feedback.js';

export * as initData from '@/scopes/components/init-data/instance.js';
export {
state as initDataState,
raw as initDataRaw,
authDate as initDataAuthDate,
chat as initDataChat,
chatInstance as initDataChatInstance,
chatType as initDataChatType,
hash as initDataHash,
canSendAfter as initDataCanSendAfter,
canSendAfterDate as initDataCanSendAfterDate,
queryId as initDataQueryId,
restore as restoreInitData,
user as initDataUser,
receiver as initDataReceiver,
startParam as initDataStartParam,
} from '@/scopes/components/init-data/instance.js';
export * as InitData from '@/scopes/components/init-data/static.js';

export * as invoice from '@/scopes/components/invoice/invoice.js';
export {
isSupported as isInvoiceSupported,
isOpened as isInvoiceOpened,
open as openInvoice,
} from '@/scopes/components/invoice/invoice.js';

export * from '@/scopes/components/back-button/exports.js';
export * from '@/scopes/components/biometry/exports.js';
export * from '@/scopes/components/closing-behavior/exports.js';
export * from '@/scopes/components/cloud-storage/exports.js';
export * from '@/scopes/components/haptic-feedback/exports.js';
export * from '@/scopes/components/init-data/exports.js';
export * from '@/scopes/components/invoice/exports.js';
export * from '@/scopes/components/main-button/exports.js';
export * from '@/scopes/components/mini-app/exports.js';

export * as popup from '@/scopes/components/popup/instance.js';
export {
isOpened as isPopupOpened,
isSupported as isPopupSupported,
open as openPopup,
} from '@/scopes/components/popup/instance.js';
export * as Popup from '@/scopes/components/popup/static.js';

export * as qrScanner from '@/scopes/components/qr-scanner/qr-scanner.js';
export {
close as closeQrScanner,
isSupported as isQrScannerSupported,
isOpened as isQrScannerOpened,
open as openQrScanner,
} from '@/scopes/components/qr-scanner/qr-scanner.js';

export * from '@/scopes/components/popup/exports.js';
export * from '@/scopes/components/qr-scanner/exports.js';
export * from '@/scopes/components/secondary-button/exports.js';

export * as settingsButton from '@/scopes/components/settings-button/settings-button.js';
export {
hide as hideSettingsButton,
isVisible as isSettingsButtonVisible,
isMounted as isSettingsButtonMounted,
isSupported as isSettingsButtonSupported,
mount as mountSettingsButton,
onClick as onSettingsButtonClick,
offClick as offSettingsButtonClick,
show as showSettingsButton,
unmount as unmountSettingsButton,
} from '@/scopes/components/settings-button/settings-button.js';

export * as swipeBehavior from '@/scopes/components/swipe-behavior/swipe-behavior.js';
export {
disableVertical as disableVerticalSwipes,
enableVertical as enableVerticalSwipes,
isMounted as isSwipeBehaviorMounted,
isVerticalEnabled as isVerticalSwipesEnabled,
isSupported as isSwipeBehaviorSupported,
mount as mountSwipeBehavior,
unmount as unmountSwipeBehavior,
} from '@/scopes/components/swipe-behavior/swipe-behavior.js';

export * as themeParams from '@/scopes/components/theme-params/instance.js';
export {
accentTextColor as themeParamsAccentTextColor,
backgroundColor as themeParamsBackgroundColor,
bindCssVars as bindThemeParamsCssVars,
buttonTextColor as themeParamsButtonTextColor,
buttonColor as themeParamsButtonColor,
bottomBarBgColor as themeParamsBottomBarBgColor,
destructiveTextColor as themeParamsDestructiveTextColor,
headerBackgroundColor as themeParamsHeaderBackgroundColor,
hintColor as themeParamsHintColor,
isMounted as isThemeParamsMounted,
isDark as isThemeParamsDark,
isCssVarsBound as isThemeParamsCssVarsBound,
linkColor as themeParamsLinkColor,
mount as mountThemeParams,
state as themeParamsState,
subtitleTextColor as themeParamsSubtitleTextColor,
sectionBackgroundColor as themeParamsSectionBackgroundColor,
secondaryBackgroundColor as themeParamsSecondaryBackgroundColor,
sectionSeparatorColor as themeParamsSectionSeparatorColor,
sectionHeaderTextColor as themeParamsSectionHeaderTextColor,
textColor as themeParamsTextColor,
unmount as unmountThemeParams,
} from '@/scopes/components/theme-params/instance.js';
export * as ThemeParams from '@/scopes/components/theme-params/static.js';

export * as viewport from '@/scopes/components/viewport/instance.js';
export {
bindCssVars as bindViewportCssVars,
expand as expandViewport,
height as viewportHeight,
isExpanded as isViewportExpanded,
isStable as isViewportStable,
isCssVarsBound as isViewportCssVarsBound,
isMounting as isViewportMounting,
isMounted as isViewportMounted,
mount as mountViewport,
mountError as viewportMountError,
state as viewportState,
stableHeight as viewportStableHeight,
unmount as unmountViewport,
width as viewportWidth,
} from '@/scopes/components/viewport/instance.js';
export * as Viewport from '@/scopes/components/viewport/static.js';

export {
openLink,
openTelegramLink,
shareURL,
type OpenLinkOptions,
} from '@/scopes/utilities/links/links.js';
export {
requestPhoneAccess,
requestWriteAccess,
requestContact,
type RequestedContact,
} from '@/scopes/utilities/privacy/privacy.js';
export {
readTextFromClipboard,
sendData,
switchInlineQuery,
shareStory,
} from '@/scopes/utilities/uncategorized/uncategorized.js';

export {
$postEvent,
$version,
$createRequestId,
} from '@/scopes/globals.js';
export * from '@/scopes/components/settings-button/exports.js';
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/links/exports.js';
export * from '@/scopes/utilities/privacy/exports.js';
export * from '@/scopes/utilities/uncategorized/exports.js';

export { $postEvent, $version, $createRequestId } from '@/scopes/globals.js';

export { isColorDark } from '@/utils/isColorDark.js';
export { isSSR } from '@/utils/isSSR.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { afterEach, vi, it, expect, beforeEach } from 'vitest';
// import { mockDocument } from 'test-utils';
import { createWindow, dispatchMiniAppsEvent, mockDocument } from 'test-utils';

import { resetPackageState } from '@test-utils/reset.js';
import { resetPackageState } from '@test-utils/reset/reset.js';
import { $postEvent, $version } from '@/scopes/globals.js';

import { init } from './init.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mockSessionStorageGetItem, mockPageReload, mockSessionStorageSetItem }
import { emitMiniAppsEvent } from '@telegram-apps/bridge';

import { mockPostEvent } from '@test-utils/mockPostEvent.js';
import { resetPackageState, resetSignal } from '@test-utils/reset.js';
import { resetPackageState } from '@test-utils/reset/reset.js';
import { $version } from '@/scopes/globals.js';

import {
Expand All @@ -20,7 +20,6 @@ import {

beforeEach(() => {
resetPackageState();
[isVisible, isMounted].forEach(resetSignal);
vi.restoreAllMocks();
mockPostEvent();
});
Expand Down Expand Up @@ -115,7 +114,12 @@ describe('isSupported', () => {
});

describe('mount', () => {
afterEach(unmount);
it('should call postEvent with "web_app_setup_back_button"', () => {
const spy = mockPostEvent();
mount();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('web_app_setup_back_button', { is_visible: false });
});

it('should set isMounted = true', () => {
expect(isMounted()).toBe(false);
Expand Down
10 changes: 6 additions & 4 deletions packages/sdk/src/scopes/components/back-button/back-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isPageReload } from '@telegram-apps/navigation';
import { signal } from '@telegram-apps/signals';

import { $version, postEvent } from '@/scopes/globals.js';
import { subAndCall } from '@/utils/subAndCall.js';


type StorageValue = boolean;
Expand Down Expand Up @@ -51,14 +52,15 @@ export const isMounted = signal(false);
export function mount(): void {
if (!isMounted()) {
isVisible.set(isPageReload() && getStorageValue<StorageValue>(STORAGE_KEY) || false);
isVisible.sub(onStateChanged);
subAndCall(isVisible, onStateChanged);
isMounted.set(true);
}
}

function onStateChanged(isVisible: boolean): void {
postEvent(MINI_APPS_METHOD, { is_visible: isVisible });
setStorageValue<StorageValue>(STORAGE_KEY, isVisible);
function onStateChanged(): void {
const value = isVisible();
postEvent(MINI_APPS_METHOD, { is_visible: value });
setStorageValue<StorageValue>(STORAGE_KEY, value);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/src/scopes/components/back-button/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export {
hide as hideBackButton,
isVisible as isBackButtonVisible,
isMounted as isBackButtonMounted,
isSupported as isBackButtonSupported,
mount as mountBackButton,
onClick as onBackButtonClick,
offClick as offBackButtonClick,
show as showBackButton,
unmount as unmountBackButton,
} from './exports.variable.js';
export * as backButton from './exports.variable.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './back-button.js';
23 changes: 23 additions & 0 deletions packages/sdk/src/scopes/components/biometry/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export {
authenticate as authenticateBiometry,
isMounting as isBiometryMounting,
isMounted as isBiometryMounted,
isSupported as isBiometrySupported,
mount as mountBiometry,
mountError as biometryMountError,
openSettings as openBiometrySettings,
requestAccess as requestBiometryAccess,
state as biometryState,
unmount as unmountBiometry,
updateToken as updateBiometryToken,
isAuthenticating as isAuthenticatingBiometry,
isRequestingAccess as isRequestingBiometryAccess,
} from './exports.variable.js';
export * as biometry from './exports.variable.js';
export * from './requestBiometry.js';
export type {
AuthenticateOptions as AuthenticateBiometryOptions,
State as BiometryState,
RequestAccessOptions as RequestBiometryAccessOptions,
UpdateTokenOptions as UpdateBiometryTokenOptions,
} from './types.js';
8 changes: 5 additions & 3 deletions packages/sdk/src/scopes/components/biometry/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { isPageReload } from '@telegram-apps/navigation';

import { $version, postEvent, request } from '@/scopes/globals.js';
import { createMountFn } from '@/scopes/createMountFn.js';
import { subAndCall } from '@/utils/subAndCall.js';
import { ERR_ALREADY_CALLED, ERR_NOT_AVAILABLE } from '@/errors.js';

import {
Expand All @@ -24,7 +25,7 @@ import {
isAuthenticating,
isMounting,
} from './signals.js';
import { request as requestBiometry } from './static.js';
import { requestBiometry } from './requestBiometry.js';
import { eventToState } from './eventToState.js';
import type {
State,
Expand Down Expand Up @@ -162,7 +163,7 @@ export const mount = createMountFn<State>(
},
result => {
on(BIOMETRY_INFO_RECEIVED_EVENT, onBiometryInfoReceived);
state.sub(onStateChanged);
subAndCall(state, onStateChanged);
state.set(result);
},
{ isMounted, mountError, isMounting },
Expand All @@ -172,7 +173,8 @@ const onBiometryInfoReceived: EventListener<'biometry_info_received'> = e => {
state.set(eventToState(e));
};

function onStateChanged(s: State | undefined): void {
function onStateChanged(): void {
const s = state();
s && setStorageValue<StorageValue>(STORAGE_KEY, s);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ExecuteWithOptions, CancelablePromise } from '@telegram-apps/bridge';

import { request as _request } from '@/scopes/globals.js';
import { request } from '@/scopes/globals.js';
import { withIsSupported } from '@/scopes/withIsSupported.js';

import { eventToState } from './eventToState.js';
Expand All @@ -12,10 +12,8 @@ const GET_INFO_METHOD = 'web_app_biometry_get_info';
* Requests biometry information.
* @param options - additional execution options.
*/
export const request = withIsSupported(
export const requestBiometry = withIsSupported(
(options?: ExecuteWithOptions): CancelablePromise<State> => {
return _request(GET_INFO_METHOD, 'biometry_info_received', options).then(eventToState);
return request(GET_INFO_METHOD, 'biometry_info_received', options).then(eventToState);
}, GET_INFO_METHOD,
);

export type * from './types.js';
Loading