import { getClientCookie } from "./cookies"; const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; const COINS_COOKIE_NAME = "HABIB_COINS"; const REDIRECT_SESSION_KEY = "redirect"; 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; 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 || token === "NO_TOKEN" || token.trim() === "") { this.token = null; 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; }; const attach = () => { if (typeof win.addFlutterResponseListener !== "function") { return false; } this.flutterResponseUnsubscribe = win.addFlutterResponseListener( (event) => { if (event.action === "login") { this.handleLoginResponse(event.success); } }, ); return true; }; if (attach()) { return; } const fallbackInterval = window.setInterval(() => { if (attach()) { window.clearInterval(fallbackInterval); } }, 50); } private handleLoginResponse(success: boolean) { if (success) { this.loginRequested = false; if (this.syncFromStorage()) { this.markReady(this.token); return; } console.warn( "⚠️ Flutter login succeeded, but token is not available yet. Set cookie before emitting success.", ); this.resolvePending(null); this.markReady(null); return; } this.loginRequested = false; console.warn("⚠️ Flutter login failed"); this.resolvePending(null); this.markReady(null); } private requestLogin() { if (this.loginRequested || this.token) { return true; } if (typeof window !== "undefined") { try { window.sessionStorage.setItem(REDIRECT_SESSION_KEY, "true"); } catch (e) { console.warn("sessionStorage is not accessible:", e); } } this.loginRequested = true; const sent = postFlutterMessage({ action: "login" }); if (!sent) { this.loginRequested = false; return false; } return true; } private markReady(token: string | null) { this.isReady = true; this.resolvePending(token); this.notifyReady(); if (typeof window !== "undefined" && (window as any).__queryClient) { (window as any).__queryClient.invalidateQueries(); } } 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; } this.setupFlutterResponseListener(); if (this.syncFromStorage()) { this.markReady(this.token); return; } this.isReady = true; this.notifyReady(); } 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 === "NO_TOKEN" ? null : this.token; } this.setupFlutterResponseListener(); return await new Promise((resolve) => { this.pendingResolvers.push(resolve); if (!this.requestLogin()) { this.resolvePending(null); } }); } public getToken(): string | null { if (this.token === "NO_TOKEN") { return null; } let sessionToken: string | null = null; if (typeof window !== "undefined") { try { sessionToken = sessionStorage.getItem(TOKEN_COOKIE_NAME); } catch (e) { console.warn("sessionStorage is not accessible:", e); } } const cookieToken = getClientCookie(TOKEN_COOKIE_NAME) ?? getClientCookie("habib_token") ?? sessionToken; const effectiveCookie = cookieToken && cookieToken !== "NO_TOKEN" && cookieToken.trim() !== "" ? cookieToken : null; if (this.token && this.token !== "NO_TOKEN" && this.token.trim() !== "") { return this.token; } if (effectiveCookie) { return effectiveCookie; } const isDevelopment = process.env.NODE_ENV !== "production"; if ( isDevelopment && process.env.NEXT_PUBLIC_DEFAULT_TOKEN && process.env.NEXT_PUBLIC_DEFAULT_TOKEN !== "NO_TOKEN" ) { return process.env.NEXT_PUBLIC_DEFAULT_TOKEN; } return null; } public getCoins(): number { return this.coins; } public isAuthenticated(): boolean { const token = this.getToken(); return !!token && token !== "NO_TOKEN" && token.trim() !== ""; } } export const authBridge = new AuthBridge();