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