remove the remainder of PWA functionality that wasn't being used, and fix the web "share target"

This commit is contained in:
2026-10-12 22:08:25 -06:00
parent 6fb4314b3a
commit d14843622d
38 changed files with 407 additions and 9837 deletions
+167
View File
@@ -0,0 +1,167 @@
# Plan: Web Push on the Web Build
## Goal
A user of the web build (browser tab or installed web app) can turn on the
Daily Reminder and New Activity notifications and receive them, matching what
native builds deliver.
Requirements:
1. A `DIRECT_NOTIFICATION` push shows its message as sent.
2. A `DAILY_CHECK` push shows the count of new activity for the active
identity, using the same Endorser query the native fetcher uses.
3. The service worker never holds a long-lived credential: it holds only
day-scoped JWTs, the same pool native builds hand to their background
fetcher.
4. The Notifications section of the Account view works on the web build.
## Can FCM replace Web Push?
No. Firebase Cloud Messaging on the web (the `firebase/messaging` SDK) is a
layer over the browser Push API: `getToken()` requires a registered service
worker (`firebase-messaging-sw.js` by default), and that worker must display the
notification, the same as a plain Web Push worker. FCM changes only the
sending side — the push server would call the FCM HTTP v1 API instead of
sending VAPID-signed Web Push requests — and it adds a Google dependency and
Firebase config to the web build. It removes none of the work below.
## Current state
- `public/sw.js` is registered by `src/main.web.ts` on the web platform. It
handles only the Web Share Target POST; it has no `push` or
`notificationclick` handler.
- The Notifications section of the Account view renders only on native
(`v-if="isNativePlatform"`, `src/views/AccountViewView.vue:81`), and the
"Notification Push Server" setting is hidden with `v-if="false"`
(`src/views/AccountViewView.vue:719`). Web users cannot subscribe.
- `src/components/PushNotificationPermission.vue` contains the web
subscription flow:
- GET `{pushUrl}/web-push/vapid` for the VAPID key (`:242`);
- `pushManager.subscribe({ userVisibleOnly: true, ... })` (`:538`);
- POST of the subscription to the relative URL `/web-push/subscribe`
(`:583`), so the web host must proxy that path to the push server;
- `askPermission()` sends the worker the placeholder secret
`"temporary-secret"` via `SEND_LOCAL_DATA`, which nothing reads.
- The push server sends a JSON payload whose `title` is `DAILY_CHECK` (run the
new-activity check) or `DIRECT_NOTIFICATION` (show `message` as-is).
- Account data lives in SQLite through absurd-sql, which a service worker
cannot open. The worker therefore cannot decrypt the identity and sign its
own JWTs.
- Native builds solve the same problem with `mintBackgroundJwtTokenPool()`
(`src/libs/crypto/backgroundJwtPool.ts`): 100 JWTs, each valid for one UTC
day, stored at index `epochDay % 100`, handed to the background fetcher by
`configureNativeFetcherIfReady()`
(`src/services/notifications/nativeFetcherConfig.ts`). Passkey (`did:peer`)
identities cannot mint the pool and raise
`BackgroundJwtUnsupportedIdentityError`.
## Work
### 1. Service worker push handling (`public/sw.js`)
- `push`: parse the JSON payload.
- `DIRECT_NOTIFICATION`: `showNotification("Direct Message", { body: message })`.
- `DAILY_CHECK` (and any other title): take the token for today's slot from the
pool, GET `{apiServer}/api/v2/report/claims` with it, count claims newer
than the stored `lastNotifiedClaimId`, store the newest ID, and show "There
is 1 new activity" / "There are N new activities".
- `notificationclick`: close the notification, focus an open app window or open
`/`.
- Worker state (token pool, `apiServer`, `activeDid`, `lastNotifiedClaimId`)
lives in the worker's own IndexedDB database, never in the app's SQLite
database.
### 2. Hand the token pool to the worker
- On app start, identity change, and pool expiry, mint the pool with
`mintBackgroundJwtTokenPool()` and `postMessage` it to the worker together
with `apiServer` and `activeDid`. Reuse the trigger points in
`src/services/notifications/notificationAuthLifecycle.ts`
(`onNotificationAuthMayBeReady`).
- Delete the `SEND_LOCAL_DATA` / placeholder-secret path in
`PushNotificationPermission.vue`.
- Passkey identities: do not offer New Activity notifications (the Daily
Reminder needs no token).
### 3. Empty daily checks
Chrome requires every push received with `userVisibleOnly: true` to show a
notification. A `DAILY_CHECK` that finds nothing new must show one anyway, or
Chrome eventually shows its own "This site has been updated in the background"
notice. Choose one (see Open questions):
- show a quiet "No new activity" notification and close it after a short delay;
- have the push server skip pushes when there is nothing new, which requires
the server to query Endorser on the user's behalf.
### 4. Re-enable the UI on the web build
- Render the Notifications section on web when `"PushManager" in window` and a
service worker is registered, instead of only on native.
- Restore the "Notification Push Server" setting for web.
- iOS Safari delivers Web Push only to web apps added to the Home Screen
(iOS 16.4+). `public/manifest.webmanifest` is linked from `index.html` and
makes the app installable; the UI must explain the Home Screen requirement on
iOS.
### 5. Hosting
- Confirm each web host (test, production, Docker `docker/*.conf`) proxies
`/web-push/subscribe` to the push server, or change the client to POST to
`{pushUrl}/web-push/subscribe`.
- Serve `sw.js` with `Cache-Control: no-cache` so worker updates reach users
promptly.
### 6. Tests
- Playwright: register the worker, then deliver pushes with the Chrome DevTools
Protocol `ServiceWorker.deliverPushMessage` and assert on the shown
notification (`registration.getNotifications()`), for both payload types.
- Unit-test the claim-counting function, extracted so it runs outside the
worker.
## Estimate
Two to four days of work, plus push-server and nginx checks (low confidence:
the push server's current behavior and routing are not visible from this
repository).
## Open questions
- Empty daily checks: silent-notification workaround, or server-side filtering?
- Does the push server serve `/web-push/vapid` and accept
`/web-push/subscribe`, and does it send `DAILY_CHECK` daily?
- Should New Activity on web use the same Endorser query as the native fetcher
(starred-plan changes) rather than `/api/v2/report/claims`, so both platforms
report the same thing?
## Rejected
- **FCM for web:** still needs a service worker; adds a Google dependency to the
web build and a server-side switch to the FCM API. See "Can FCM replace Web
Push?".
- **Reviving the old worker scripts (`sw_scripts/safari-notifications.js`):**
they read the Dexie `TimeSafari` IndexedDB database, which no longer holds
accounts, and decrypted the identity inside the worker using a secret passed
from the page, shipping NaCl and noble crypto libraries into the worker. The
day-scoped token pool needs neither.
## History
- 2025-02-18 (`2746b8fe`): the Vite config reorganization switched the web build
to a Workbox-generated worker, so the custom push, notification-click, and
share-target handlers stopped shipping.
- 2025-07-18 (`8bc1c521`): VitePWA and the generated worker were removed; the
web manifest stopped being generated and linked.
- The leftover `sw_scripts/` files and PWA config were deleted, and
`public/sw.js` was added for the Web Share Target only.
## Checklist
- [ ] Decide the empty-daily-check behavior
- [ ] Confirm push-server endpoints and routing per environment
- [ ] `push` and `notificationclick` handlers in `public/sw.js`
- [ ] Token-pool handoff to the worker; remove the placeholder secret
- [ ] Re-enable the web Notifications UI, with the iOS Home Screen note
- [ ] Playwright push-delivery tests