Files
familysync/.planning/phases/05-web-push-notifications/05-02-SUMMARY.md
T

97 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 05-web-push-notifications
plan: 02
subsystem: api/push-dispatcher
tags: [web-push, vapid, push-dispatcher, tdd, red-green]
dependency_graph:
requires: [05-01]
provides: [dispatchPush helper, buildPushBody helper, 410/404 prune logic]
affects: [apps/api/src/lib/pushDispatcher.ts]
tech_stack:
added: []
patterns: [default-import-cjs (web-push Pitfall 7), dual-format push payload (iOS 18.4+ declarative + legacy), 410/404 DB prune pattern]
key_files:
created:
- apps/api/src/lib/pushDispatcher.ts
modified: []
decisions:
- "dispatchPush uses sub.id (not a separate dbRowId argument) — test calls with 2 args; signature matches test"
- "buildPushBody emits both web_push:8030+notification{} (iOS 18.4+) and top-level title/body/tag/data (iOS 16.418.3 + Android)"
- "setVapidDetails is NOT called at module scope — deferred to index.ts startup (Plan 05-04)"
- "dispatchPush never throws — resolves after logging transient errors; safe for fan-out loops"
metrics:
duration: 5
completed_date: "2026-06-10"
tasks_completed: 1
files_changed: 1
---
# 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.418.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 === 410` or `statusCode === 404`: deletes the row via `db.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:**
- [x] 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)
## Self-Check: PASSED