Resolved WebSocket connection issues and games list loading on iPad: - cookies.py: Added is_secure_context() to set Secure flag when accessed via HTTPS even in development mode (Safari requires this) - useWebSocket.ts: Changed auto-connect from immediate watcher to onMounted hook for safer SSR hydration - games/index.vue: Replaced onMounted + fetchGames() with useAsyncData for SSR data fetching with proper cookie forwarding - Updated COOKIE_AUTH_IMPLEMENTATION.md with new issues and solutions - Updated composables/CLAUDE.md with auto-connect pattern documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
100 lines
3.2 KiB
Markdown
100 lines
3.2 KiB
Markdown
# Composables Directory
|
|
|
|
Vue 3 composables for shared logic. These handle WebSocket communication and game actions.
|
|
|
|
## Available Composables
|
|
|
|
### useWebSocket.ts
|
|
**Purpose**: Manages Socket.io connection with authentication and auto-reconnection.
|
|
|
|
**Key Exports**:
|
|
```typescript
|
|
const {
|
|
socket, // Computed<TypedSocket | null>
|
|
isConnected, // Readonly<Ref<boolean>>
|
|
isConnecting, // Readonly<Ref<boolean>>
|
|
connectionError, // Readonly<Ref<string | null>>
|
|
connect, // () => void
|
|
disconnect, // () => void
|
|
} = useWebSocket()
|
|
```
|
|
|
|
**Event Flow** (Backend → Store):
|
|
```
|
|
socketInstance.on('game_state_update') → gameStore.setGameState()
|
|
socketInstance.on('lineup_data') → gameStore.updateLineup()
|
|
socketInstance.on('decision_required') → gameStore.setDecisionPrompt()
|
|
socketInstance.on('play_completed') → gameStore.addPlayToHistory()
|
|
socketInstance.on('dice_rolled') → gameStore.setPendingRoll()
|
|
```
|
|
|
|
**Singleton Pattern**: Socket instance is module-level, shared across all `useWebSocket()` calls.
|
|
|
|
**Auto-Connect Pattern**: Uses `onMounted` hook (not `immediate: true` watcher) to connect when already authenticated. This is safer for SSR hydration:
|
|
```typescript
|
|
// Watch for auth changes (not immediate)
|
|
watch(() => authStore.isAuthenticated, (authenticated) => {
|
|
if (authenticated && !isConnected.value) {
|
|
connect()
|
|
} else if (!authenticated && isConnected.value) {
|
|
disconnect()
|
|
}
|
|
})
|
|
|
|
// Initial connection on client-side mount
|
|
onMounted(() => {
|
|
if (authStore.isAuthenticated && !isConnected.value) {
|
|
connect()
|
|
}
|
|
})
|
|
```
|
|
|
|
**Why not `immediate: true`?** Using `immediate: true` with `import.meta.client` guard can cause SSR hydration issues. The `onMounted` hook only runs on client-side, making it safer.
|
|
|
|
### useGameActions.ts
|
|
**Purpose**: Wraps WebSocket emits with type safety and validation.
|
|
|
|
**Key Exports**:
|
|
```typescript
|
|
const {
|
|
joinGame, // (gameId: string) => void
|
|
requestGameState, // (gameId: string) => void
|
|
setDefense, // (gameId: string, decision: DefensiveDecision) => void
|
|
setOffense, // (gameId: string, decision: OffensiveDecision) => void
|
|
rollDice, // (gameId: string) => void
|
|
submitOutcome, // (gameId: string, outcome: PlayOutcome) => void
|
|
// ... substitution methods
|
|
} = useGameActions()
|
|
```
|
|
|
|
**Usage Pattern**:
|
|
```typescript
|
|
// In component
|
|
const { setDefense } = useGameActions()
|
|
|
|
const handleSubmit = (decision: DefensiveDecision) => {
|
|
setDefense(gameId, decision)
|
|
}
|
|
```
|
|
|
|
## Data Flow Architecture
|
|
|
|
```
|
|
User Action → useGameActions → socket.emit() → Backend
|
|
↓
|
|
Component ← gameStore ← useWebSocket.on() ← socket event
|
|
```
|
|
|
|
**Why this separation?**
|
|
- `useWebSocket`: Low-level connection management, event routing
|
|
- `useGameActions`: High-level game operations, business logic validation
|
|
- `gameStore`: Centralized state, computed getters
|
|
|
|
## Common Issues
|
|
|
|
| Issue | Cause | Solution |
|
|
|-------|-------|----------|
|
|
| "Not connected" error | Socket disconnected | Check `isConnected` before actions |
|
|
| Events not firing | Listeners not set up | Ensure `useWebSocket()` called in component |
|
|
| Stale data | Missed reconnection | Call `requestGameState()` after reconnect |
|