feat(01-04): wire broker + routes into bootstrap, add SSE endpoint, EventProof
- Mount /api/events, /api/sse in index.ts behind oidcAuthMiddleware; /callback + /health before guard - Call startBrokerPoller() on boot (5-min ctag-poll background schedule) - Add sseRouter with GET /heartbeat (streamSSE, 10s interval) for Pangolin SSE smoke test (D-08, T-04-01) - Add CAL-08 spike script (broker/spike.ts): createFastmailClient → fetchCalendars → print calendar URLs - Add fetchEvents() to pwa/api/client.ts with typed CalendarEvent/EventsResponse shapes - Add EventProof.tsx: React Query ['events'], renders first event title+date or empty-state (CAL-01 broker proof) - Update App.tsx to render MemberBadge + EventProof on landing page - Add ical.js@2.2.1 to PWA dependencies for VEVENT summary parsing in EventProof - All 24 API unit tests green; tsc --noEmit clean in both apps/api and apps/pwa
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* GET /api/sse/heartbeat — Pangolin SSE pass-through smoke test endpoint.
|
||||
*
|
||||
* Emits a `heartbeat` event every 10 seconds with { ts, id } payload.
|
||||
* Runs until the client disconnects (stream.aborted).
|
||||
*
|
||||
* Mounted under /api/sse in index.ts, so it sits behind oidcAuthMiddleware (T-04-01).
|
||||
* Heartbeat payload carries only timestamps — no user data or secrets (T-04-02).
|
||||
*
|
||||
* Smoke test procedure (D-08):
|
||||
* curl -N https://familysync.<domain>/api/sse/heartbeat
|
||||
* Keep open 5+ min — confirm no proxy timeout. PASS → SSE viable for Phase 4.
|
||||
*
|
||||
* Source: https://hono.dev/docs/helpers/streaming
|
||||
* RESEARCH Pattern 5: Pangolin SSE Smoke Test
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono'
|
||||
import { streamSSE } from 'hono/streaming'
|
||||
|
||||
export const sseRouter = new Hono()
|
||||
|
||||
/**
|
||||
* GET /heartbeat
|
||||
* Streams SSE heartbeat events every 10 seconds until client disconnects.
|
||||
* Response: text/event-stream with events of type "heartbeat".
|
||||
*/
|
||||
sseRouter.get('/heartbeat', (c) => {
|
||||
return streamSSE(c, async (stream) => {
|
||||
let id = 0
|
||||
while (!stream.aborted) {
|
||||
await stream.writeSSE({
|
||||
data: JSON.stringify({ ts: new Date().toISOString(), id }),
|
||||
event: 'heartbeat',
|
||||
id: String(id++),
|
||||
})
|
||||
await stream.sleep(10_000)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user