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; 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"; 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(); if (AUTH_COOKIE_DEBUG) { console.info("[auth-cookie] using client cookie", { storagePrefix, length: cookie.length, names: cookieNames(cookie), }); } return cookie; } const fromPrefix = readStoredCookie(storagePrefix); if (fromPrefix) { if (AUTH_COOKIE_DEBUG) { console.info("[auth-cookie] 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); if (AUTH_COOKIE_DEBUG) { console.info("[auth-cookie] 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 { const cookie = getAuthCookie(authClient, storagePrefix); if (!cookie) { if (AUTH_COOKIE_DEBUG) { console.info("[auth-cookie] no tRPC auth cookie", { storagePrefix }); } return {}; } const sessionToken = cookie.match(SESSION_TOKEN_COOKIE_PART)?.[1]; if (AUTH_COOKIE_DEBUG) { console.info("[auth-cookie] 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 } : {}), }; }