# 25. Real-Time Alerts — WebSocket Push Notifications

meta:
  id: shieldai-unified-restructure-25
  feature: shieldai-unified-restructure
  priority: P1
  depends_on: [shieldai-unified-restructure-23]
  tags: [backend, frontend, websocket, realtime, notifications]

objective:
- Implement real-time alert push from server to client using WebSockets. When a new alert is generated (exposure detected, synthetic voice found, spam blocked, property changed, broker listing found), the user should receive an immediate notification in the web app.

deliverables:
- `web/src/server/websocket.ts` — WebSocket server:
  - Integrates with SolidStart's Nitro server or runs alongside it
  - Authenticates connections using JWT from query param or cookie
  - Maintains userId → socket mapping
  - Broadcasts alerts to connected users
- `web/src/lib/websocket.ts` — WebSocket client:
  - Connects to `/ws/alerts` endpoint
   - Reconnects with exponential backoff on disconnect
   - Authenticates with JWT token
   - Exposes `alerts` signal that emits incoming alert objects
   - Heartbeat/ping-pong to keep connection alive
- `web/src/hooks/useRealtimeAlerts.ts` — Hook:
   - Uses websocket client
   - Triggers toast notification on new alert
   - Increments unread badge count in Navbar
   - Plays subtle notification sound (optional, respects reduced motion)
- `web/src/server/services/alert.publisher.ts` — Alert publisher:
   - `publishAlert(userId, alert)` — sends alert to user's connected sockets
   - Called from each service's alert pipeline (DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers)
   - Falls back to push notification (task 14) if user is not connected

steps:
1. Install `ws` npm package in `web/` (server-side WebSocket library).
2. Create `web/src/server/websocket.ts`:
   - If using Nitro (SolidStart's server), create a custom Nitro plugin or API route that upgrades to WebSocket
   - If standalone, create a separate WebSocket server on a different port (e.g., 3001)
   - On connection:
     - Parse `token` from query string or cookie
     - Verify JWT, extract `userId`
     - Store socket in `userSockets` Map
   - On message: handle ping with pong, ignore other messages (server pushes only)
   - On disconnect: remove socket from map
   - `broadcastToUser(userId, data)`: find all sockets for user, send JSON
3. Create `web/src/lib/websocket.ts`:
   - `connect()` — create WebSocket, set up event handlers
   - `disconnect()` — close connection
   - `onMessage` callback or signal for incoming data
   - Reconnect logic: 1s, 2s, 5s, 10s, 30s backoff, max 10 attempts
   - Heartbeat: send ping every 30s, expect pong within 10s
4. Create `web/src/hooks/useRealtimeAlerts.ts`:
   - Call `connect()` on mount if user is authenticated
   - On alert received: show toast via `useToast()`, increment unread count
   - On disconnect: show "Reconnecting..." indicator
   - Cleanup on unmount
5. Create `web/src/server/services/alert.publisher.ts`:
   - `publishAlert(userId, alert)`:
     - Try WebSocket broadcast first
     - If no active sockets, queue push notification (FCM/APNs) via task 14
     - If push fails, queue email notification
   - `publishToGroup(userIds, alert)` — for family group alerts
6. Integrate with service alert pipelines:
   - In DarkWatch alert pipeline (task 15): call `alertPublisher.publishAlert(userId, alert)`
   - In VoicePrint analysis result handler (task 16): same
   - In SpamShield classification (task 17): same
   - In HomeTitle change detector (task 18): same
   - In RemoveBrokers listing scanner (task 19): same
7. Update Navbar:
   - Add a pulsing dot indicator when WebSocket is connected
   - Show "Offline" badge when disconnected
8. Write integration tests for WebSocket flow.

steps:
- Unit: WebSocket server authenticates connections with valid JWT
- Unit: WebSocket server rejects connections with invalid JWT
- Unit: Client reconnects with exponential backoff on disconnect
- Integration: Triggering an alert in DarkWatch service sends real-time notification to connected client
- E2E: User receives toast notification within 2 seconds of alert generation

acceptance_criteria:
- [ ] WebSocket server authenticates connections and maps them to users
- [ ] Alerts are broadcast to connected users within 1 second of generation
- [ ] Client reconnects automatically after network interruption
- [ ] Toast notifications appear for real-time alerts
- [ ] Unread badge count increments immediately on new alert
- [ ] If user is offline, alert falls back to push notification
- [ ] Heartbeat keeps connections alive without timeout
- [ ] WebSocket does not leak memory on disconnect/reconnect cycles

validation:
- Open dashboard in two browser tabs, trigger an alert in one, verify both receive notification
- Disconnect network, reconnect, verify client re-establishes connection
- Check server memory usage after 100 connect/disconnect cycles
- Run `cd web && pnpm test` for WebSocket integration tests

notes:
- Reference legacy: `server/alerts/` (WebSocket alert server), `packages/api/src/routes/websocket.routes.ts`
- Nitro (SolidStart's server) has experimental WebSocket support. If unstable, run a separate `ws` server on a different port and proxy through nginx in production.
- For production scaling, consider using Redis Pub/Sub to broadcast alerts across multiple server instances.
- The WebSocket connection should be lazy: only connect when user is on a protected route and authenticated.
- Respect `prefers-reduced-motion` for notification sounds and aggressive toast animations.
- Consider adding a "Do Not Disturb" mode where real-time toasts are suppressed but badge count still updates.
