You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

219 lines
5.0 KiB

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 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<string | null>((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();