feat(03-02): Fix type + three source adapters (telegram/webview/chat-pin) — GEO-01 wrappers

- types.ts: the shared Fix shape + LocationState machine (type-only, D-04/D-05/D-07)
- telegram.ts: version-gated LocationManager init/getLocation, hard 8s timeout (Pitfall 3),
  null-safe telegramLocationSupported (plain browser/Desktop = false, not TypeError),
  looksStale + haversine + the Android nudge (issue #56 navbar read DISCARDED, then retry)
- webview.ts: one-shot geolocation read, maximumAge: 0 (freshness axis), 8s timeout
- chat-pin.ts: bounded 60s poll of GET /api/location (Bearer token) + POST /api/location-fallback
  throw-on-fail so the ASK VIA CHAT handler can revalidate (D-03)
- telegram/webapp.ts created early (Rule 3: task 1 imports TelegramWebAppLike from task 3's
  file; declared the narrow hand-rolled interface now, verbatim per plan)
This commit is contained in:
araharah-executor
2026-10-22 23:37:58 +08:00
parent 3fd0694ee6
commit 9552e7e4ab
5 changed files with 307 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
/*
* webapp.ts — the ONE narrow typed accessor for window.Telegram.WebApp
* (D-11, RESEARCH §Standard Stack note).
* -------------------------------------------------------------------
* `@types/telegram-web-app` is deliberately NOT a dependency (plan 03-01
* dropped it): this hand-rolled `TelegramWebAppLike` IS the contract —
* every test fake, the D-11 harness mock, and the production clients
* satisfy exactly these ~12 members. The official 500-member `WebApp`
* type would force each fake to stub ~50 members.
*
* getWebApp() may honestly return NULL (plain browser, no Telegram) —
* that null is the contract the location chain must handle. All
* `Telegram.WebApp.*` calls must originate from the app's own document
* (Bot API 10.2 origin restriction).
*/
export interface TelegramWebAppLike {
initData: string;
colorScheme?: "light" | "dark";
themeParams?: { header_bg_color?: string };
version?: string;
platform?: string;
safeAreaInset?: { top: number; bottom: number; left: number; right: number };
contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number };
isVersionAtLeast(version: string): boolean;
close(): void;
onEvent?(event: string, cb: () => void): void;
offEvent?(event: string, cb: () => void): void;
BackButton?: { show(): void; hide(): void; onClick(cb: () => void): void; offClick(cb: () => void): void };
LocationManager?: {
isInited: boolean;
isLocationAvailable?: boolean;
isAccessRequested?: boolean;
isAccessGranted?: boolean;
init(cb: () => void): void;
getLocation(cb: (data: unknown | null) => void): void;
openSettings?(): void;
};
}
export function getWebApp(): TelegramWebAppLike | null {
const w = window as unknown as { Telegram?: { WebApp?: TelegramWebAppLike } };
return w.Telegram?.WebApp ?? null;
}