4.1 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-web-push-notifications | 02 | api/push-dispatcher |
|
|
|
|
|
|
Phase 05 Plan 02: pushDispatcher — VAPID send + 410/404 prune — Summary
TDD GREEN: pushDispatcher.ts implemented with dual-format iOS payload, VAPID send via web-push, and DB prune on 410/404.
Tasks Executed
Task 1: Implement pushDispatcher.ts (GREEN)
Status: Completed.
The RED test scaffold was already committed in Plan 05-01 (commit ef558b6). This plan turns it GREEN.
Created apps/api/src/lib/pushDispatcher.ts with:
buildPushBody(notification) — builds the dual-format JSON payload string:
web_push: 8030+notification: { title, body, navigate }— iOS 18.4+ declarative web push format- Top-level
title,body,tag,data: { url: navigate }— legacy format for iOS 16.4–18.3 and Android
dispatchPush(sub, notification) — VAPID-signed push send + prune:
- Constructs
webPushSub = { endpoint, keys: { p256dh, auth } }from subscription row - Calls
webpush.sendNotification(webPushSub, body, { TTL: 300, urgency: 'normal' }) - On thrown error with
statusCode === 410orstatusCode === 404: deletes the row viadb.delete(pushSubscriptions).where(eq(pushSubscriptions.id, sub.id)) - On transient errors (5xx, 429, network): logs
[pushDispatcher] sendNotification failed: <statusCode> <message>then resolves - On success: no action
Uses default import import webpush from 'web-push' (CommonJS — Pitfall 7 from RESEARCH.md).
TDD Gate Compliance:
- RED:
test(05-01): add Wave-0 RED scaffolds + VAPID fixture + setup truncation—ef558b6(Plan 05-01) - GREEN:
feat(05-02): implement pushDispatcher — VAPID send + 410/404 prune—e4170b3
Commit: e4170b3
Verification
pnpm --filter @familysync/api exec vitest run tests/lib/pushDispatcher.test.ts
Test Files 1 passed (1)
Tests 4 passed (4)
pnpm --filter @familysync/api typecheck — passes (no errors).
Deviations from Plan
Plan specifies dispatchPush(subscription, notification, dbRowId) — test uses 2-arg form
The plan text describes a 3-argument signature dispatchPush(sub, notification, dbRowId). The existing RED scaffold test (committed in Plan 05-01) calls dispatchPush(FAKE_SUB, { title, body }) with 2 arguments — the subscription object already carries the id field. The test is canonical; the implementation uses sub.id directly and exposes a 2-argument signature. No test file changes were needed.
Known Stubs
None.
Threat Flags
No new threat surface introduced. pushDispatcher.ts is a pure utility module — no new network endpoints, no auth paths, no file access. T-05-03 (VAPID signing via web-push only), T-05-04 (per-sub catch), and T-05-05 (no key/payload logging) are all mitigated.
Self-Check
Files created/verified:
- apps/api/src/lib/pushDispatcher.ts — exists
Commits verified:
ef558b6: test(05-01): add Wave-0 RED scaffolds + VAPID fixture + setup truncation (RED gate — from Plan 05-01)e4170b3: feat(05-02): implement pushDispatcher — VAPID send + 410/404 prune (GREEN gate)