Initial scaffold: Beat Pocket MV3 extension
- manifest.json with MV3 config, host permissions for beat-battle.net - content/detector.ts: route + overlay observer, emits bbp:phase events - content/layout.ts: defensive mobile-first layout reflow - content/chat-drawer.ts: bottom-sheet drawer, dispatches synthetic events through site's own React-managed textarea + button (preserves auth, rate-limit, mute, profanity guards) - content/util/selectors.ts: text- and role-based fallback chain - content/util/ws-proxy.ts: lightweight WebSocket instrumentation, no payload capture - content/inject.css: mobile overrides using site's existing --bb-* vars - background.js: MV3 service worker stub - package.json + tsconfig.json + esbuild.config.mjs: build pipeline - icons/: 16/32/48/128 placeholder PNGs (chili-red disc) - docs/PLAN.md: full V1 architecture, risks, scope - README.md: scope, dev workflow (load unpacked), tech
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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<typeof history.pushState>) {
|
||||
const r = origPush(...args);
|
||||
queueMicrotask(() => emit(detectPhase()));
|
||||
return r;
|
||||
};
|
||||
history.replaceState = function (...args: Parameters<typeof history.replaceState>) {
|
||||
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<Element | null> {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user