forked from c-fu/arah2
- 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)
45 lines
1.9 KiB
TypeScript
45 lines
1.9 KiB
TypeScript
/*
|
|
* webview.ts — source 2: navigator.geolocation inside the WebView, and the
|
|
* Android freshness nudge's reading hand (D-04, D-05).
|
|
* -------------------------------------------------------------------
|
|
* maximumAge: 0 is load-bearing — D-05 requires the freshest fix, and a
|
|
* cached position would silently defeat the freshness axis (the whole
|
|
* point of the nudge is to force a NEW read, not to re-read a cache).
|
|
* Success AND timeout both resolve; the error callback resolves null so
|
|
* the chain advances instead of rejecting. Timeout present for the same
|
|
* reason as the telegram wrapper: wedging ACQUIRING is never acceptable.
|
|
*/
|
|
import type { Fix } from "./types.ts";
|
|
|
|
export function webviewLocationSupported(): boolean {
|
|
return typeof navigator !== "undefined" && typeof navigator.geolocation?.getCurrentPosition === "function";
|
|
}
|
|
|
|
export function readWebviewFix(options: { timeoutMs?: number; nowMs?: () => number } = {}): Promise<Fix | null> {
|
|
const timeoutMs = options.timeoutMs ?? 8000;
|
|
if (!webviewLocationSupported()) return Promise.resolve(null); // Desktop Telegram, plain browsers without permission
|
|
return new Promise((resolve) => {
|
|
let settled = false;
|
|
const done = (fix: Fix | null): void => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timer);
|
|
resolve(fix);
|
|
};
|
|
const timer = setTimeout(() => done(null), timeoutMs);
|
|
navigator.geolocation.getCurrentPosition(
|
|
(pos) =>
|
|
done({
|
|
lat: pos.coords.latitude,
|
|
lng: pos.coords.longitude,
|
|
accuracyM: pos.coords.accuracy ?? null,
|
|
headingDeg: pos.coords.heading ?? null,
|
|
ts: (options.nowMs ?? Date.now)(),
|
|
source: "webview",
|
|
}),
|
|
() => done(null), // denial / unavailability = null; the chain's normal signal (no error object escapes)
|
|
{ enableHighAccuracy: true, timeout: timeoutMs, maximumAge: 0 },
|
|
);
|
|
});
|
|
}
|