import { getClientCookie } from "./cookies"; const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; const COINS_COOKIE_NAME = "HABIB_COINS"; const LOGIN_TIMEOUT_MS = 30_000; function postFlutterMessage(payload: Record) { if (typeof window === "undefined") { return false; } const app = window.HabibApp; if (!app?.postMessage) { return false; } app.postMessage(JSON.stringify(payload)); return true; } class AuthBridge { private token: string | null = null; private coins = 0; private isReady = false; private loginRequested = false; private readyCallbacks: Array<() => void> = []; private pendingResolvers: Array<(token: string | null) => void> = []; private flutterResponseUnsubscribe?: () => void; private loginTimeoutId?: number; constructor() { this.init(); } private syncFromStorage() { if (typeof window === "undefined") { return false; } const win = window as Window & { HABIB_TOKEN?: string; HABIB_COINS?: number; }; const token = win.HABIB_TOKEN ?? getClientCookie(TOKEN_COOKIE_NAME) ?? getClientCookie("habib_token"); const coinsValue = win.HABIB_COINS; const coinsCookie = getClientCookie(COINS_COOKIE_NAME) ?? getClientCookie("habib_coins"); if (!token) { return false; } this.token = token; this.coins = coinsValue ?? Number(coinsCookie ?? 0); return true; } private setupFlutterResponseListener() { if (typeof window === "undefined" || this.flutterResponseUnsubscribe) { return; } const win = window as Window & { addFlutterResponseListener?: ( listener: (event: FlutterResponseEvent) => void, ) => () => void; }; if (typeof win.addFlutterResponseListener === "function") { this.flutterResponseUnsubscribe = win.addFlutterResponseListener((event) => { if (event.action === "login") { this.handleLoginResponse(event.success); } }); return; } const fallbackInterval = window.setInterval(() => { if (typeof win.addFlutterResponseListener === "function") { window.clearInterval(fallbackInterval); this.flutterResponseUnsubscribe = win.addFlutterResponseListener((event) => { if (event.action === "login") { this.handleLoginResponse(event.success); } }); } }, 50); } private handleLoginResponse(success: boolean) { if (success && this.syncFromStorage()) { this.loginRequested = false; this.markReady(this.token); return; } if (!success) { this.loginRequested = false; console.warn("⚠️ Flutter login failed"); this.resolvePending(null); this.markReady(null); } } private requestLogin() { if (this.loginRequested || this.token) { return; } this.loginRequested = true; const sent = postFlutterMessage({ action: "login" }); if (!sent) { this.loginRequested = false; return; } if (this.loginTimeoutId) { window.clearTimeout(this.loginTimeoutId); } this.loginTimeoutId = window.setTimeout(() => { if (!this.token) { console.warn("⚠️ Flutter login timeout"); this.loginRequested = false; this.resolvePending(null); } }, LOGIN_TIMEOUT_MS); } private markReady(token: string | null) { if (this.loginTimeoutId) { window.clearTimeout(this.loginTimeoutId); this.loginTimeoutId = undefined; } this.isReady = true; this.resolvePending(token); this.notifyReady(); } private resolvePending(token: string | null) { const resolvers = this.pendingResolvers.splice(0, this.pendingResolvers.length); for (const resolve of resolvers) { resolve(token); } } private notifyReady() { const callbacks = this.readyCallbacks.splice(0, this.readyCallbacks.length); for (const callback of callbacks) { callback(); } } private init() { if (typeof window === "undefined") { return; } if (this.syncFromStorage()) { this.markReady(this.token); return; } this.setupFlutterResponseListener(); this.requestLogin(); } public onReady(callback: () => void) { if (this.isReady) { callback(); return; } this.readyCallbacks.push(callback); } public async ensureToken() { if (typeof window === "undefined") { return null; } if (this.syncFromStorage()) { return this.token; } this.setupFlutterResponseListener(); this.requestLogin(); return await new Promise((resolve) => { this.pendingResolvers.push(resolve); }); } public getToken(): string | null { if (this.token) { return this.token; } return ( getClientCookie(TOKEN_COOKIE_NAME) ?? getClientCookie("habib_token") ); } public getCoins(): number { return this.coins; } public isAuthenticated(): boolean { return !!this.getToken(); } } export const authBridge = new AuthBridge();