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.
 
 
 
 
 

271 lines
6.1 KiB

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