feat(ui): refine Telegram invite & close UX
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m2s

- Shared invite links open the Mini App fullscreen (mode=fullscreen), so a
  shared link matches the bot's own fullscreen launch.
- A used or expired invite deep-link now lands the visitor in the lobby with a
  gentle notice pointing at the right bot (@<username>, by service language),
  instead of a red "code invalid/expired" error on the Friends screen.
- The in-game close confirmation is armed only on Telegram mobile clients; on
  desktop (tdesktop/macOS/web) it is skipped, where the "changes may not be
  saved" dialog is just noise (drafts auto-save).
This commit is contained in:
Ilia Denisov
2026-06-15 17:22:09 +02:00
parent 01d2d1f368
commit 853730823b
9 changed files with 200 additions and 19 deletions
+35 -8
View File
@@ -8,6 +8,7 @@ import type { TelegramThemeParams } from './theme';
interface TelegramWebApp {
initData: string;
initDataUnsafe?: { start_param?: string };
platform?: string;
themeParams?: TelegramThemeParams;
colorScheme?: 'light' | 'dark';
isFullscreen?: boolean;
@@ -50,6 +51,19 @@ export function insideTelegram(): boolean {
return !!w && typeof w.initData === 'string' && w.initData.length > 0;
}
/**
* telegramOpenLink opens a t.me link through the Mini App SDK, so Telegram navigates to
* it natively (e.g. a bot chat) rather than spawning an in-app browser tab. Returns false
* outside Telegram or when the SDK lacks the method, so the caller can fall back to a plain
* window.open.
*/
export function telegramOpenLink(url: string): boolean {
const w = webApp();
if (!w?.openTelegramLink) return false;
w.openTelegramLink(url);
return true;
}
/**
* shareTelegramLink opens Telegram's native "share to a chat" picker for url with a
* caption, through the Mini App SDK (https://t.me/share/url). Returns false outside
@@ -57,10 +71,7 @@ export function insideTelegram(): boolean {
* Share API. Works consistently on iOS and Android within Telegram.
*/
export function shareTelegramLink(url: string, text: string): boolean {
const w = webApp();
if (!w?.openTelegramLink) return false;
w.openTelegramLink(`https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`);
return true;
return telegramOpenLink(`https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`);
}
/** TelegramLaunch is the data a Mini App launch carries. */
@@ -156,13 +167,29 @@ export function telegramHaptic(kind: Haptic): void {
}
/**
* telegramClosingConfirmation toggles the confirmation Telegram shows when the user
* swipes the Mini App closed — enabled during an active game so it is not lost by accident.
* isMobilePlatform reports whether the Mini App runs on a Telegram mobile client (iOS or
* Android), where a stray swipe-down can drop the app. Desktop clients (tdesktop, macOS,
* web) report other values and close only on a deliberate window action.
*/
function isMobilePlatform(): boolean {
const p = webApp()?.platform;
return p === 'ios' || p === 'android';
}
/**
* telegramClosingConfirmation toggles the confirmation Telegram shows when the user swipes
* the Mini App closed — enabled during an active game so it is not lost by accident. The
* guard is only armed on mobile clients: on desktop, closing a window is deliberate and
* Telegram surfaces a "changes may not be saved" dialog that is just noise here (drafts
* auto-save), so the confirmation is skipped there.
*/
export function telegramClosingConfirmation(on: boolean): void {
const w = webApp();
if (on) w?.enableClosingConfirmation?.();
else w?.disableClosingConfirmation?.();
if (on) {
if (isMobilePlatform()) w?.enableClosingConfirmation?.();
} else {
w?.disableClosingConfirmation?.();
}
}
let backHandler: (() => void) | null = null;