126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { getCookie as serializeStoredCookies } from "@better-auth/expo/client";
|
|
import * as SecureStore from "expo-secure-store";
|
|
import type { createAuthClient } from "better-auth/react";
|
|
|
|
import { GUEST_AUTH_STORAGE_PREFIX } from "@/lib/auth-storage";
|
|
import { normalizeSecureStoreKey } from "@/lib/secure-store-keys";
|
|
|
|
type AuthClient = ReturnType<typeof createAuthClient>;
|
|
|
|
const CHUNK_MARKER = "\u0001ba-chunks:";
|
|
const SESSION_TOKEN_COOKIE_PART =
|
|
/(?:^|;\s*)(?:__Secure-)?[^=]*session_token=([^;]+)/;
|
|
const AUTH_COOKIE_DEBUG = process.env.EXPO_PUBLIC_AUTH_COOKIE_DEBUG === "1";
|
|
const lastAuthCookieDebugState = new Map<string, string>();
|
|
|
|
function debugAuthCookie(event: string, storagePrefix: string, details: Record<string, unknown>) {
|
|
if (!AUTH_COOKIE_DEBUG) return;
|
|
|
|
const key = `${event}:${storagePrefix}`;
|
|
const state = JSON.stringify(details);
|
|
if (lastAuthCookieDebugState.get(key) === state) return;
|
|
|
|
lastAuthCookieDebugState.set(key, state);
|
|
console.info(`[auth-cookie] ${event}`, { storagePrefix, ...details });
|
|
}
|
|
|
|
function readSecureStoreValueSync(key: string): string | null {
|
|
const value = SecureStore.getItem(key);
|
|
if (value == null) return null;
|
|
if (!value.startsWith(CHUNK_MARKER)) return value;
|
|
|
|
const count = Number(value.slice(CHUNK_MARKER.length));
|
|
if (!Number.isInteger(count) || count < 1) return null;
|
|
|
|
let assembled = "";
|
|
for (let index = 0; index < count; index += 1) {
|
|
const chunk = SecureStore.getItem(`${key}.${index}`);
|
|
if (chunk == null) return null;
|
|
assembled += chunk;
|
|
}
|
|
return assembled;
|
|
}
|
|
|
|
function readStoredCookie(storagePrefix: string): string | null {
|
|
const raw = readSecureStoreValueSync(
|
|
normalizeSecureStoreKey(`${storagePrefix}_cookie`),
|
|
);
|
|
if (!raw || raw === "{}") return null;
|
|
|
|
const cookie = serializeStoredCookies(raw);
|
|
return cookie.trim() || null;
|
|
}
|
|
|
|
function cookieNames(cookie: string): string[] {
|
|
return cookie
|
|
.split(";")
|
|
.map((part) => part.trim().split("=", 1)[0])
|
|
.filter(Boolean);
|
|
}
|
|
|
|
/** Read session cookie string for tRPC requests (Expo client plugin + SecureStore fallback). */
|
|
export function getAuthCookie(
|
|
authClient: AuthClient,
|
|
storagePrefix: string,
|
|
): string | null {
|
|
const fromClient = (
|
|
authClient as AuthClient & { getCookie?: () => string }
|
|
).getCookie?.();
|
|
if (fromClient?.trim()) {
|
|
const cookie = fromClient.trim();
|
|
debugAuthCookie("using client cookie", storagePrefix, {
|
|
length: cookie.length,
|
|
names: cookieNames(cookie),
|
|
});
|
|
return cookie;
|
|
}
|
|
|
|
const fromPrefix = readStoredCookie(storagePrefix);
|
|
if (fromPrefix) {
|
|
debugAuthCookie("using stored cookie", storagePrefix, {
|
|
length: fromPrefix.length,
|
|
names: cookieNames(fromPrefix),
|
|
});
|
|
return fromPrefix;
|
|
}
|
|
|
|
const fromGuest =
|
|
storagePrefix === GUEST_AUTH_STORAGE_PREFIX
|
|
? null
|
|
: readStoredCookie(GUEST_AUTH_STORAGE_PREFIX);
|
|
debugAuthCookie("resolved tRPC cookie", storagePrefix, {
|
|
fallbackPrefix:
|
|
fromGuest && storagePrefix !== GUEST_AUTH_STORAGE_PREFIX
|
|
? GUEST_AUTH_STORAGE_PREFIX
|
|
: null,
|
|
hasCookie: Boolean(fromGuest),
|
|
length: fromGuest?.length ?? 0,
|
|
names: fromGuest ? cookieNames(fromGuest) : [],
|
|
});
|
|
return fromGuest;
|
|
}
|
|
|
|
export function getAuthCookieHeaders(
|
|
authClient: AuthClient,
|
|
storagePrefix: string,
|
|
): Record<string, string> {
|
|
const cookie = getAuthCookie(authClient, storagePrefix);
|
|
if (!cookie) {
|
|
debugAuthCookie("no tRPC auth cookie", storagePrefix, {});
|
|
return {};
|
|
}
|
|
|
|
const sessionToken = cookie.match(SESSION_TOKEN_COOKIE_PART)?.[1];
|
|
debugAuthCookie("sending tRPC auth headers", storagePrefix, {
|
|
cookieLength: cookie.length,
|
|
cookieNames: cookieNames(cookie),
|
|
hasSessionTokenHeader: Boolean(sessionToken),
|
|
});
|
|
return {
|
|
cookie,
|
|
Cookie: cookie,
|
|
"x-beenvoice-auth-cookie": cookie,
|
|
...(sessionToken ? { "x-beenvoice-session-token": sessionToken } : {}),
|
|
};
|
|
}
|