Root cause: Auth middleware was commented out on game detail page ([id].vue), causing SSR to render without checking authentication. Safari's client-side auth check wasn't reaching the backend due to caching behavior, resulting in "Auth: Failed" display. Changes: - Re-enabled middleware: ['auth'] in pages/games/[id].vue - Added /api/auth/ws-token endpoint for Safari WebSocket fallback - Added expires_minutes param to create_token() for short-lived tokens - Added token query param support to WebSocket handlers - Updated SAFARI_WEBSOCKET_ISSUE.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
3.3 KiB
3.3 KiB
Safari/iPad WebSocket Connection Issue
Problem Summary
WebSocket connections intermittently fail on iPad Safari while working reliably on desktop browsers.
Pattern:
- Works initially after login
- Randomly fails (minutes to hours later)
- "Fixes itself" during debugging (suggesting cache/timing issues)
- Desktop browsers never have this problem
Root Causes Identified
1. Cookie SameSite Policy (Fixed 2025-11-28)
Safari's ITP treats SameSite=Lax differently than Chrome/Firefox. Safari may not send cookies with fetch/XHR even on same-origin.
Fix: Changed to SameSite=None; Secure=true in backend/app/utils/cookies.py
2. Nuxt Composable Context Issue (Fixed 2025-11-28)
useRuntimeConfig() was being called inside connect() function, which is invoked from:
onMountedhooks (valid context)setIntervalcallbacks (INVALID context)
In Nuxt 4, composables MUST be called during component setup, not inside callbacks.
Fix: Moved useRuntimeConfig() to composable setup phase in useWebSocket.ts
3. Auth Middleware Disabled on Game Page (Fixed 2025-11-28)
The game detail page (pages/games/[id].vue) had middleware: ['auth'] commented out for WebSocket testing. This caused:
- SSR rendered page without checking auth
- Page showed "Auth: Failed" immediately from SSR
- Safari's client-side auth check wasn't reaching the backend (likely cached)
Fix: Re-enabled middleware: ['auth'] in definePageMeta on the game page.
4. Potential Safari-Specific Issues (Not Yet Fixed)
- Safari ITP: Can expire cookies unpredictably
- iOS Memory Management: Safari may clear JS state when backgrounded
- Cached Old Code: Safari may aggressively cache outdated JS bundles
Files Modified
backend/app/utils/cookies.py- Cookie SameSite policyfrontend-sba/composables/useWebSocket.ts- Composable context fixbackend/app/api/routes/auth.py- Added GET /logout endpoint, added /ws-token endpointfrontend-sba/pages/games/[id].vue- Re-enabled auth middlewarebackend/app/websocket/handlers.py- Added token query param support for Safari fallback
Prevention Strategies
Short-term (Implemented)
- Stuck state detector every 5 seconds
- Multiple retry mechanisms in websocket.client.ts plugin
- Error logging in connect() function
Long-term (Consider Implementing)
- Token in WebSocket URL: Pass short-lived auth token as query param (fallback if cookies fail)
- Connection health indicator: Visible UI showing connection state, not just loading spinner
- Automatic logout/re-login flow: If connection fails repeatedly, redirect to login
Debugging Steps
- Check backend logs:
tail -f logs/backend.log | grep -E "auth|Cookie|WebSocket" - Check browser console for
[WebSocket]prefixed logs - Verify cookies exist: Safari Settings > Advanced > Website Data > gameplay-demo.manticorum.com
- Force cache clear: Close Safari completely, reopen
History
| Date | Issue | Fix | Result |
|---|---|---|---|
| 2025-11-28 | SameSite=Lax not working on Safari | Changed to SameSite=None | Worked initially |
| 2025-11-28 | useRuntimeConfig in callback | Moved to setup phase | Connected |
| 2025-11-28 | Auth middleware disabled on game page | Re-enabled middleware: ['auth'] | Auth: OK, WebSocket connected |