Files
Kordant/tasks/shieldai-unified-restructure/25-realtime-alerts.md
2026-05-25 12:23:23 -04:00

5.6 KiB

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.