feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral in-memory. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes so over-sized notes don't kill delivery. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (account switch on shared device) so the previous user's notifications can't leak. - Three new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` (auth-gated, Zod-validated). - Push hooked into the 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick handlers only (no asset caching). Focuses an existing tab or opens a new one at the payload URL. - SW registration in `main.tsx` scoped to `BASE_URL`. - `use-push-subscription` hook (enable/disable/refresh + status: unsupported / denied / default / subscribed). - New `PushToggleRow` in `NotificationSettingsContent` with ar/en strings (notifSettings.push.*). iPad copy explains that the user must add to Home Screen first for iOS to allow Web Push. Plumbing - OpenAPI: 3 new operations under `notifications` tag; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the generated key; subscribe endpoint 401s without auth as expected. - Architect review surfaced two HIGH issues (account-switch leak, payload size); both fixed before completion.
This commit is contained in:
@@ -972,6 +972,83 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessResponse"
|
||||
|
||||
/push/vapid-public-key:
|
||||
get:
|
||||
operationId: getPushVapidPublicKey
|
||||
tags: [notifications]
|
||||
summary: Get the server's VAPID public key for Web Push subscriptions
|
||||
responses:
|
||||
"200":
|
||||
description: VAPID public key
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
publicKey:
|
||||
type: string
|
||||
required: [publicKey]
|
||||
"503":
|
||||
description: Web Push not configured on this server
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/push/subscribe:
|
||||
post:
|
||||
operationId: subscribePush
|
||||
tags: [notifications]
|
||||
summary: Register a Web Push subscription for the current user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
endpoint:
|
||||
type: string
|
||||
keys:
|
||||
type: object
|
||||
properties:
|
||||
p256dh:
|
||||
type: string
|
||||
auth:
|
||||
type: string
|
||||
required: [p256dh, auth]
|
||||
required: [endpoint, keys]
|
||||
responses:
|
||||
"200":
|
||||
description: Subscribed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessResponse"
|
||||
|
||||
/push/unsubscribe:
|
||||
post:
|
||||
operationId: unsubscribePush
|
||||
tags: [notifications]
|
||||
summary: Remove a Web Push subscription for the current user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
endpoint:
|
||||
type: string
|
||||
required: [endpoint]
|
||||
responses:
|
||||
"200":
|
||||
description: Unsubscribed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessResponse"
|
||||
|
||||
# Users (admin)
|
||||
/users:
|
||||
get:
|
||||
|
||||
Reference in New Issue
Block a user