remove the remainder of PWA functionality that wasn't being used, and fix the web "share target"
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user