105 lines
5.6 KiB
Markdown
105 lines
5.6 KiB
Markdown
# 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.
|