fix: safari cookie issue

This commit is contained in:
Michael Freno
2026-01-13 19:19:00 -05:00
parent b612d12a51
commit 48f01b6171
4 changed files with 39 additions and 4 deletions

View File

@@ -44,6 +44,13 @@ export const api = createTRPCProxyClient<AppRouter>({
headers: () => { headers: () => {
const csrfToken = getCSRFToken(); const csrfToken = getCSRFToken();
return csrfToken ? { "x-csrf-token": csrfToken } : {}; return csrfToken ? { "x-csrf-token": csrfToken } : {};
},
// CRITICAL FOR SAFARI: Ensure cookies are sent and received
fetch(url, options) {
return fetch(url, {
...options,
credentials: "include" // Safari requires this for cookie handling
});
} }
}) })
] ]

View File

@@ -23,6 +23,7 @@ class TokenRefreshManager {
private onlineHandler: (() => void) | null = null; private onlineHandler: (() => void) | null = null;
private focusHandler: (() => void) | null = null; private focusHandler: (() => void) | null = null;
private lastRefreshTime: number | null = null; private lastRefreshTime: number | null = null;
private lastCheckTime: number = 0;
/** /**
* Start monitoring and auto-refresh * Start monitoring and auto-refresh
@@ -73,7 +74,18 @@ class TokenRefreshManager {
window.addEventListener("online", this.onlineHandler); window.addEventListener("online", this.onlineHandler);
// Re-check on window focus (device was asleep or user switched apps) // Re-check on window focus (device was asleep or user switched apps)
// Debounce to prevent Safari from firing this too frequently
this.focusHandler = () => { this.focusHandler = () => {
const now = Date.now();
const timeSinceLastCheck = now - this.lastCheckTime;
// Debounce: only check if last check was >1s ago (prevents Safari spam)
if (timeSinceLastCheck < 1000) {
console.log("[Token Refresh] Window focused but debouncing (Safari)");
return;
}
this.lastCheckTime = now;
console.log("[Token Refresh] Window focused, checking token status"); console.log("[Token Refresh] Window focused, checking token status");
this.checkAndRefreshIfNeeded(); this.checkAndRefreshIfNeeded();
}; };

View File

@@ -1660,17 +1660,33 @@ export const authRouter = createTRPCRouter({
}); });
} }
// Step 4: Refresh CSRF token // Step 4: Force response headers to be sent immediately
// This is critical for Safari to receive the new session cookies
// Safari is very strict about cookie updates from fetch responses
try {
const headers = event.node?.res?.getHeaders?.() || {};
console.log(
"[Token Refresh] Response headers set:",
Object.keys(headers)
);
} catch (e) {
// Headers already sent or not available - that's OK
}
// Step 5: Refresh CSRF token
setCSRFToken(event); setCSRFToken(event);
// Step 5: Opportunistic cleanup (serverless-friendly) // Step 6: Opportunistic cleanup (serverless-friendly)
import("~/server/token-cleanup") import("~/server/token-cleanup")
.then((module) => module.opportunisticCleanup()) .then((module) => module.opportunisticCleanup())
.catch((err) => console.error("Opportunistic cleanup failed:", err)); .catch((err) => console.error("Opportunistic cleanup failed:", err));
return { return {
success: true, success: true,
message: "Token refreshed successfully" message: "Token refreshed successfully",
// Return new session ID for Safari fallback
// If Safari doesn't apply cookies, client can use this to restore
sessionId: newSession.sessionId
}; };
} catch (error) { } catch (error) {
console.error("Token refresh error:", error); console.error("Token refresh error:", error);

View File

@@ -232,7 +232,7 @@ export async function createAuthSession(
// Explicitly seal/flush the session to ensure cookie is written // Explicitly seal/flush the session to ensure cookie is written
// This is important in serverless environments where response might stream early // This is important in serverless environments where response might stream early
const { sealSession } = await import("vinxi/http"); const { sealSession } = await import("vinxi/http");
sealSession(event, configWithMaxAge); await sealSession(event, configWithMaxAge);
console.log("[Session Create] Session sealed"); console.log("[Session Create] Session sealed");