BangSo/apps/mobile/lib/session.ts

91 lines
2.8 KiB
TypeScript

import * as SecureStore from "expo-secure-store";
import { stopLiveNotifications } from "./live-notifications";
const SESSION_KEY = "rakazo.session_token";
/** In-memory gate so a failed SecureStore wipe cannot keep sending the old bearer. */
let sessionInvalidated = false;
let sessionFallback: string | undefined;
export async function loadSessionToken() {
const snapshot = await snapshotSessionToken();
return snapshot.ok ? snapshot.value : "";
}
export async function saveSessionToken(token: string) {
await SecureStore.setItemAsync(SESSION_KEY, token);
sessionInvalidated = false;
sessionFallback = undefined;
}
/** Clears the session. Returns false only when SecureStore could neither delete nor overwrite. */
export async function clearSessionToken(): Promise<boolean> {
await stopLiveNotifications(true).catch(() => undefined);
try {
await SecureStore.deleteItemAsync(SESSION_KEY);
sessionInvalidated = false;
sessionFallback = undefined;
return true;
} catch {
try {
await SecureStore.setItemAsync(SESSION_KEY, "");
sessionInvalidated = false;
sessionFallback = undefined;
return true;
} catch {
sessionInvalidated = true;
sessionFallback = undefined;
return false;
}
}
}
/** Restores the current-server session in memory even when persistence is unavailable. */
export async function restoreSessionToken(token: string) {
if (!token) {
sessionInvalidated = false;
sessionFallback = undefined;
return;
}
try {
await saveSessionToken(token);
} catch {
sessionInvalidated = false;
sessionFallback = token;
}
}
/** Snapshots the active token without treating an unreadable store as an empty session. */
export async function snapshotSessionToken(): Promise<{ ok: true; value: string } | { ok: false }> {
if (sessionFallback !== undefined) return { ok: true, value: sessionFallback };
if (sessionInvalidated) return { ok: true, value: "" };
try {
return { ok: true, value: (await SecureStore.getItemAsync(SESSION_KEY)) ?? "" };
} catch {
return { ok: false };
}
}
export function tokenFromAuthResponse(res: Response, body: unknown) {
const fromJson = jsonToken(body);
if (fromJson) return fromJson;
const cookies = res.headers.get("set-cookie") ?? "";
const match = cookies.match(/better-auth\.session_token=([^;]+)/);
return match?.[1] ? decodeURIComponent(match[1]) : "";
}
function jsonToken(body: unknown): string {
if (!body || typeof body !== "object") return "";
const record = body as Record<string, unknown>;
if (typeof record.token === "string" && record.token) return record.token;
const session = record.session;
if (
session &&
typeof session === "object" &&
typeof (session as { token?: string }).token === "string"
) {
return (session as { token: string }).token;
}
return "";
}