feat(03-04): Mock-Telegram runner page + Playwright capture harness — 7/7 captures + GEO-03 probe
- runner page: ?mock/?scheme/?pin/?locale profile block wholly inside if (__ARAHARAH_HARNESS__) — installer + /api/session + /api/location + /api/location-fallback fetch stubs dead-code-eliminated from production; main.ts import stays outside the guard
- harness-only ?sessiondelay/?fixdelay boot-stage photograph knobs (localhost resolves the chain in ms — staged BOOT-01/BOOT-02 cards need a hold)
- capture.mjs extends theme/scripts/screenshot.mjs: MIN_BYTES, WR-05 docroot guard, READY-signal race, dist-harness docroot, DENIED_LEG_TIMEOUT_MS=90000 for LINK 3's 60s pre-denial poll, timeout = hard failure with a signal diagnostic
- 7 captures: boot-1/boot-2 stages, live light+dark (theme-slot attribute asserted), Desktop denied card w/ ASK VIA CHAT, chat-pin relay LIVE, DSGN-02 side-by-side vs navigate_map_overview
- GEO-03 runtime probe: localStorage/sessionStorage/cookies/IndexedDB asserted empty after boot-to-live
- mock fixes (Rule 1): LocationManager delivery now uses the Telegram wire shape {latitude,longitude,horizontal_accuracy,course} (authoring shape mapped to undefined → NaN LngLat pageerror); languageCode→initData so localeFromInitData parses the real wire format
CAPTURE OK — 7/7 non-blank captures; GEO-03 probe PASSED
This commit is contained in:
@@ -26,6 +26,17 @@ export interface MockOptions {
|
||||
platform?: "android" | "ios" | "tdesktop" | "macos" | "windows" | "linux" | "web";
|
||||
version?: string;
|
||||
colorScheme?: "light" | "dark";
|
||||
/** Telegram language_code carried in the fake initData's user JSON —
|
||||
* lets main.ts's localeFromInitData exercise its real parsing path
|
||||
* and lets the runner page switch locales via ?locale=. */
|
||||
languageCode?: string;
|
||||
/** Artificial delivery delay (ms) for LocationManager.init()/getLocation()
|
||||
* callbacks — the runner page's ?fixdelay= knob. At localhost speeds the
|
||||
* whole three-source chain resolves in a few ms, so the staged boot cards
|
||||
* (BOOT-01 validating / BOOT-02 acquiring) cannot otherwise be photographed;
|
||||
* a delayed fix delivery holds the earlier stage on screen (03-04 capture
|
||||
* harness only — defaults to 0, the fallback dev install is unchanged). */
|
||||
deliveryDelayMs?: number;
|
||||
telegramFix?: { lat: number; lng: number; accuracyM?: number | null; headingDeg?: number | null } | null;
|
||||
webviewFix?: { lat: number; lng: number; accuracyM?: number } | null;
|
||||
grantLocation?: boolean;
|
||||
@@ -112,9 +123,26 @@ function versionAtLeast(have: string, want: string): boolean {
|
||||
|
||||
let installed: MockWebApp | null = null;
|
||||
|
||||
/**
|
||||
* Harness initData: a non-empty, correctly-SHAPED Telegram wire string
|
||||
* (`user=<url-encoded JSON>&auth_date=1&query_id=h&hash=harness`). The
|
||||
* capture runner stubs POST /api/session OUTSIDE any dead-code strip, so
|
||||
* this never reaches an HMAC validator — it exists so main.ts's
|
||||
* localeFromInitData parses the real wire format instead of falling
|
||||
* back to "en" (locale = options.languageCode, defaulting to "en").
|
||||
* Untouched when no languageCode is given (the plain-dev fallback mock
|
||||
* keeps initData "" exactly as before).
|
||||
*/
|
||||
function buildMockInitData(languageCode?: string): string {
|
||||
if (languageCode === undefined) return "";
|
||||
const user = JSON.stringify({ language_code: languageCode, id: 1, first_name: "Harness" });
|
||||
return `user=${encodeURIComponent(user)}&auth_date=1&query_id=h&hash=harness`;
|
||||
}
|
||||
|
||||
export function installMockWebApp(options: MockOptions = {}): TelegramWebAppLike {
|
||||
const version = options.version ?? "8.0";
|
||||
const telegramFix = options.telegramFix ?? null;
|
||||
const delayMs = options.deliveryDelayMs ?? 0;
|
||||
const lm: MockLocationManager = {
|
||||
isInited: false,
|
||||
isLocationAvailable: options.grantLocation !== false,
|
||||
@@ -124,13 +152,25 @@ export function installMockWebApp(options: MockOptions = {}): TelegramWebAppLike
|
||||
setTimeout(() => {
|
||||
lm.isInited = true;
|
||||
cb();
|
||||
}, 0);
|
||||
}, delayMs);
|
||||
},
|
||||
getLocation(cb: (data: unknown | null) => void) {
|
||||
// grantLocation true → delivers the configured fix on a macrotask so the
|
||||
// callback is asynchronous (a wedged client is cb(null) or silence).
|
||||
// The DELIVERY SHAPE is the Telegram LocationManager wire format —
|
||||
// { latitude, longitude, horizontal_accuracy, course } — because the
|
||||
// consumer is requestTelegramFix, which maps exactly those keys. The
|
||||
// MockOptions.telegramFix keys (lat/lng/accuracyM/headingDeg) are the
|
||||
// authoring shape; delivering them verbatim maps to undefined → a NaN
|
||||
// LngLat pageerror in marker.update (found by the 03-04 capture harness,
|
||||
// the first end-to-end consumer of this callback).
|
||||
if (options.grantLocation !== false && telegramFix !== null) {
|
||||
setTimeout(() => cb(telegramFix), 0);
|
||||
setTimeout(() => cb({
|
||||
latitude: telegramFix.lat,
|
||||
longitude: telegramFix.lng,
|
||||
horizontal_accuracy: telegramFix.accuracyM ?? undefined,
|
||||
course: telegramFix.headingDeg ?? undefined,
|
||||
}), delayMs);
|
||||
} else {
|
||||
setTimeout(() => cb(null), 0); // the denial signal — synchronous null would hide async bugs
|
||||
}
|
||||
@@ -139,7 +179,7 @@ export function installMockWebApp(options: MockOptions = {}): TelegramWebAppLike
|
||||
const events = new Map<string, Array<() => void>>();
|
||||
const backButtonClicks: Array<() => void> = [];
|
||||
const fake: MockWebApp = {
|
||||
initData: "",
|
||||
initData: buildMockInitData(options.languageCode),
|
||||
colorScheme: options.colorScheme ?? "light",
|
||||
themeParams: {},
|
||||
version,
|
||||
|
||||
Reference in New Issue
Block a user