|
|
|
@ -2,18 +2,37 @@ 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<string, unknown>) { |
|
|
|
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 hydrateFromWindow() { |
|
|
|
private syncFromStorage() { |
|
|
|
if (typeof window === "undefined") { |
|
|
|
return false; |
|
|
|
} |
|
|
|
@ -28,67 +47,164 @@ class AuthBridge { |
|
|
|
getClientCookie(TOKEN_COOKIE_NAME) ?? |
|
|
|
getClientCookie("habib_token"); |
|
|
|
const coinsValue = win.HABIB_COINS; |
|
|
|
const coinsCookie = getClientCookie(COINS_COOKIE_NAME) ?? getClientCookie("habib_coins"); |
|
|
|
const coinsCookie = |
|
|
|
getClientCookie(COINS_COOKIE_NAME) ?? getClientCookie("habib_coins"); |
|
|
|
|
|
|
|
if (token) { |
|
|
|
this.token = token; |
|
|
|
this.coins = coinsValue ?? Number(coinsCookie ?? 0); |
|
|
|
return true; |
|
|
|
if (!token) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
this.token = token; |
|
|
|
this.coins = coinsValue ?? Number(coinsCookie ?? 0); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
private init() { |
|
|
|
if (typeof window === "undefined") { |
|
|
|
private setupFlutterResponseListener() { |
|
|
|
if (typeof window === "undefined" || this.flutterResponseUnsubscribe) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.hydrateFromWindow()) { |
|
|
|
this.isReady = true; |
|
|
|
this.notifyReady(); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
this.waitForInjection(); |
|
|
|
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 waitForInjection() { |
|
|
|
let attempts = 0; |
|
|
|
const maxAttempts = 50; |
|
|
|
private handleLoginResponse(success: boolean) { |
|
|
|
if (success && this.syncFromStorage()) { |
|
|
|
this.loginRequested = false; |
|
|
|
this.markReady(this.token); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const checkInterval = window.setInterval(() => { |
|
|
|
if (this.hydrateFromWindow()) { |
|
|
|
this.isReady = true; |
|
|
|
window.clearInterval(checkInterval); |
|
|
|
this.notifyReady(); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
if (++attempts >= maxAttempts) { |
|
|
|
window.clearInterval(checkInterval); |
|
|
|
console.warn("⚠️ Token not received from Flutter"); |
|
|
|
this.isReady = true; |
|
|
|
this.notifyReady(); |
|
|
|
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); |
|
|
|
} |
|
|
|
}, 100); |
|
|
|
}, 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() { |
|
|
|
this.readyCallbacks.forEach((cb) => cb()); |
|
|
|
this.readyCallbacks = []; |
|
|
|
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(); |
|
|
|
} else { |
|
|
|
this.readyCallbacks.push(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<string | null>((resolve) => { |
|
|
|
this.pendingResolvers.push(resolve); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public getToken(): string | null { |
|
|
|
return this.token ?? getClientCookie(TOKEN_COOKIE_NAME) ?? getClientCookie("habib_token"); |
|
|
|
if (this.token) { |
|
|
|
return this.token; |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
getClientCookie(TOKEN_COOKIE_NAME) ?? getClientCookie("habib_token") |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
public getCoins(): number { |
|
|
|
|