/** * detector.ts — route + overlay observer. * * - Watches the document for SPA route changes (Next.js pushState) * - Watches for .bb-room-overlay appearing (in-game state) * - Emits `bbp:phase` on the document with `phase` ∈ "home" | "lobby-list" | "lobby" | "in-game" | "login" */ import { isHome, isLogin, isInGame, isLobbyList } from './util/selectors'; export type Phase = 'home' | 'lobby-list' | 'login' | 'in-game' | 'unknown'; let lastPhase: Phase = 'unknown'; function detectPhase(): Phase { if (isLogin()) return 'login'; if (isInGame()) return 'in-game'; if (isLobbyList()) return 'lobby-list'; if (isHome()) return 'home'; return 'unknown'; } export function currentPhase(): Phase { return lastPhase; } function emit(newPhase: Phase): void { if (newPhase === lastPhase) return; lastPhase = newPhase; document.dispatchEvent(new CustomEvent('bbp:phase', { detail: { phase: newPhase } })); } /** Patch history.pushState / replaceState to detect SPA route changes. */ function patchHistory(): void { const origPush = history.pushState.bind(history); const origReplace = history.replaceState.bind(history); history.pushState = function (...args: Parameters) { const r = origPush(...args); queueMicrotask(() => emit(detectPhase())); return r; }; history.replaceState = function (...args: Parameters) { const r = origReplace(...args); queueMicrotask(() => emit(detectPhase())); return r; }; window.addEventListener('popstate', () => emit(detectPhase())); } /** Observe DOM for overlay appearing / disappearing. */ function observeOverlay(): void { const obs = new MutationObserver(() => emit(detectPhase())); obs.observe(document.documentElement, { childList: true, subtree: true }); } /** * Wait until any of the candidate selectors exist, then resolve. */ export function waitFor(selector: string | string[], timeoutMs = 8000): Promise { const sels = Array.isArray(selector) ? selector : [selector]; return new Promise((resolve) => { for (const s of sels) { const el = document.querySelector(s); if (el) return resolve(el); } const obs = new MutationObserver(() => { for (const s of sels) { const el = document.querySelector(s); if (el) { obs.disconnect(); return resolve(el); } } }); obs.observe(document.documentElement, { childList: true, subtree: true }); setTimeout(() => { obs.disconnect(); resolve(null); }, timeoutMs); }); } export function startDetector(): Phase { patchHistory(); observeOverlay(); // Run once now emit(detectPhase()); return lastPhase; }