feat(07-01): grow TelegramWebAppLike with 8.0-optional fullscreen members + capability wrapper
- TelegramWebAppLike gains requestFullscreen/exitFullscreen/isFullscreen/lockOrientation/unlockOrientation as OPTIONAL members only (no existing member reshaped)
- new web/src/telegram/fullscreen.ts: requestGuidanceFullscreen/exitGuidanceFullscreen/watchFullscreenEvents behind the BOTH-halves guard (method presence AND isVersionAtLeast("8.0"))
- every platform call in try/catch — a refused request is a supported degradation, never an error surface (T-07-06)
- no DOM Fullscreen API, no generic orientation lock, zero logging (T-07-10, T-07-09)
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* fullscreen.ts — PX-04 (D-03): the Telegram-native fullscreen capability
|
||||||
|
* layer, guarded by BOTH halves of the Bot API 8.0 capability check.
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
* The affordance is `Telegram.WebApp.requestFullscreen()` / `exitFullscreen()`
|
||||||
|
* ONLY — Bot API 8.0+ methods on the narrow `TelegramWebAppLike` contract
|
||||||
|
* (the same phase-scoped law as homescreen.ts: guard on the optional member,
|
||||||
|
* a total-function handler, no new UI furniture).
|
||||||
|
*
|
||||||
|
* The capability guard is BOTH halves: the method may be absent on older
|
||||||
|
* clients, AND the platform documents the API as Bot API 8.0+ — a no-op on
|
||||||
|
* an unsupported client either way (the homescreen.ts precedent).
|
||||||
|
*
|
||||||
|
* A refused request is a SUPPORTED DEGRADATION, never an error surface: every
|
||||||
|
* platform call rides inside a try/catch, and the boolean returns are
|
||||||
|
* best-effort signals the caller deliberately ignores. The orientation lock
|
||||||
|
* locks the CURRENT orientation only — never force landscape — and the DOM
|
||||||
|
* Fullscreen API plus the generic screen-level orientation lock are
|
||||||
|
* hard-banned: the Telegram-native path is the
|
||||||
|
* only primary path (T-07-10, pinned by the FS9 source-scan ban).
|
||||||
|
*
|
||||||
|
* D-14 category-only law: this path logs NOTHING — no coordinates, no user
|
||||||
|
* ids, no route payloads. A `fullscreenFailed` is a category event.
|
||||||
|
*/
|
||||||
|
import type { TelegramWebAppLike } from "./webapp.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Best-effort request for the Mini App's native fullscreen at the
|
||||||
|
* guidance-start edge. Returns true only when the platform call did not
|
||||||
|
* throw; false is the honest fallback on null WebApp, absent method, an
|
||||||
|
* old client, or a refusal — never an error surface.
|
||||||
|
*/
|
||||||
|
export function requestGuidanceFullscreen(wa: TelegramWebAppLike | null): boolean {
|
||||||
|
if (wa === null) return false;
|
||||||
|
if (typeof wa.requestFullscreen !== "function") return false;
|
||||||
|
if (typeof wa.isVersionAtLeast !== "function" || !wa.isVersionAtLeast("8.0")) return false;
|
||||||
|
try {
|
||||||
|
wa.requestFullscreen();
|
||||||
|
try {
|
||||||
|
// Telegram's lock pins the CURRENT orientation — never force landscape,
|
||||||
|
// and never the DOM's screen-level orientation lock (T-07-10).
|
||||||
|
wa.lockOrientation?.();
|
||||||
|
} catch {
|
||||||
|
/* best-effort: an orientation refusal is not a guidance failure */
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// the platform refused (FS4) — a supported degradation, never an error
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mirror of requestGuidanceFullscreen: releases fullscreen and the
|
||||||
|
* orientation lock at the trip-end / walk-back teardown edges, same
|
||||||
|
* both-halves guard, same try/catch law.
|
||||||
|
*/
|
||||||
|
export function exitGuidanceFullscreen(wa: TelegramWebAppLike | null): boolean {
|
||||||
|
if (wa === null) return false;
|
||||||
|
if (typeof wa.exitFullscreen !== "function") return false;
|
||||||
|
if (typeof wa.isVersionAtLeast !== "function" || !wa.isVersionAtLeast("8.0")) return false;
|
||||||
|
try {
|
||||||
|
wa.exitFullscreen?.();
|
||||||
|
try {
|
||||||
|
wa.unlockOrientation?.();
|
||||||
|
} catch {
|
||||||
|
/* best-effort: the lock release is not a guidance failure */
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the fullscreen observability listeners (Bot API 8.0+ clients
|
||||||
|
* fire `fullscreenChanged` / `fullscreenFailed`). The onChange callback is
|
||||||
|
* wrapped in try/catch so a throwing client callback can never break the
|
||||||
|
* guidance pump; the events are observability, never correctness, and this
|
||||||
|
* path logs NOTHING (the D-14 category-only law, T-07-09).
|
||||||
|
*/
|
||||||
|
export function watchFullscreenEvents(
|
||||||
|
wa: TelegramWebAppLike | null,
|
||||||
|
onChange: (isFullscreen: boolean) => void,
|
||||||
|
): void {
|
||||||
|
if (wa === null || typeof wa.onEvent !== "function") return;
|
||||||
|
wa.onEvent("fullscreenChanged", () => {
|
||||||
|
try {
|
||||||
|
onChange(true);
|
||||||
|
} catch {
|
||||||
|
/* a throwing observer must never break the guidance pump */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
wa.onEvent("fullscreenFailed", () => {
|
||||||
|
try {
|
||||||
|
onChange(false);
|
||||||
|
} catch {
|
||||||
|
/* a throwing observer must never break the guidance pump */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -23,8 +23,12 @@
|
|||||||
* `enableClosingConfirmation`/`disableClosingConfirmation` (6.2+) and
|
* `enableClosingConfirmation`/`disableClosingConfirmation` (6.2+) and
|
||||||
* `isActive` (8.0+) — the contract stays the contract, no existing member
|
* `isActive` (8.0+) — the contract stays the contract, no existing member
|
||||||
* changes shape, every test fake and the harness mock satisfy exactly
|
* changes shape, every test fake and the harness mock satisfy exactly
|
||||||
* these members. `@types/telegram-web-app` remains deliberately
|
* these members. Phase 7 (PX-04/D-03) adds the native-fullscreen members as
|
||||||
* NOT a dependency.
|
* OPTIONAL only: requestFullscreen/exitFullscreen/isFullscreen/
|
||||||
|
* lockOrientation/unlockOrientation (8.0+) — the contract stays the contract,
|
||||||
|
* no existing member changes shape, every test fake and the harness mock
|
||||||
|
* satisfy exactly these members. `@types/telegram-web-app` remains
|
||||||
|
* deliberately NOT a dependency.
|
||||||
*/
|
*/
|
||||||
export type HomeScreenStatus =
|
export type HomeScreenStatus =
|
||||||
| "unsupported" // the feature is not supported here
|
| "unsupported" // the feature is not supported here
|
||||||
@@ -71,6 +75,17 @@ export interface TelegramWebAppLike {
|
|||||||
enableClosingConfirmation?: () => void;
|
enableClosingConfirmation?: () => void;
|
||||||
/** Bot API 6.2+ — release the native close net (at trip end). */
|
/** Bot API 6.2+ — release the native close net (at trip end). */
|
||||||
disableClosingConfirmation?: () => void;
|
disableClosingConfirmation?: () => void;
|
||||||
|
/** Bot API 8.0+ — request the Mini App's native fullscreen. Optional:
|
||||||
|
* absent on older clients; the 8.0 guard runs before every call. */
|
||||||
|
requestFullscreen?(): void;
|
||||||
|
/** Bot API 8.0+ — leave the Mini App's native fullscreen. Optional. */
|
||||||
|
exitFullscreen?(): void;
|
||||||
|
/** Bot API 8.0+ — whether the Mini App is currently fullscreen. Optional. */
|
||||||
|
isFullscreen?: boolean;
|
||||||
|
/** Bot API 8.0+ — lock the CURRENT orientation (never forces landscape). */
|
||||||
|
lockOrientation?(): void;
|
||||||
|
/** Bot API 8.0+ — release the orientation lock. */
|
||||||
|
unlockOrientation?: () => void;
|
||||||
onEvent?(event: string, cb: () => void): void;
|
onEvent?(event: string, cb: () => void): void;
|
||||||
offEvent?(event: string, cb: () => void): void;
|
offEvent?(event: string, cb: () => void): void;
|
||||||
BackButton?: { show(): void; hide(): void; onClick(cb: () => void): void; offClick(cb: () => void): void };
|
BackButton?: { show(): void; hide(): void; onClick(cb: () => void): void; offClick(cb: () => void): void };
|
||||||
|
|||||||
Reference in New Issue
Block a user