docs: record WS push completion + browser-can't-auth-WS-handshake caveat

This commit is contained in:
Bernd Rodler
2026-08-04 14:21:03 +02:00
parent 0f15132ec0
commit b15098a6eb
+22 -4
View File
@@ -82,7 +82,7 @@ by direct research/verification or by explicit user sign-off. Dates are when eac
| 2026-08-04 | Does an upstream React Native app already solve push/pairing? | **Yes — `bulwarkmail/native`** has multi-account JMAP auth, full QR cross-device pairing, and Android FCM push via `bulwarkmail/relay`. Forked to `vncmail-native`. | Duplicating working auth/pairing/push code in a fresh Capacitor wrapper has no upside. **This flipped the entire mobile strategy** — see §6. |
| 2026-08-04 | Self-host the push relay, or depend on upstream's shared hosted instance? | **Self-host.** Forked `bulwarkmail/relay``vncmail-relay`. | Keeps push metadata (FCM tokens, timing) on VNC infrastructure rather than a third party's. |
| 2026-08-04 | Which SQLite library for the eventual SQLCipher-backed local index, and what Expo workflow? | **`expo-sqlite`'s official `useSQLCipher` config-plugin option** (verified via web search — Android/iOS/macOS support, [docs](https://docs.expo.dev/versions/latest/sdk/sqlite/)), not a third-party binding. **Stay Continuous Native Generation** (don't commit `ios`/`android`, let `expo prebuild` regenerate them) rather than going fully bare. | The official plugin already covers this. SQLCipher is unusable in Expo Go — day-to-day development necessarily moves to a custom dev client, which the user explicitly accepted. Going bare would turn every future merge from upstream `bulwarkmail/native` into a native-project merge conflict, for no offsetting benefit. |
| 2026-08-04 | Electron's background/foreground notification strategy: JMAP WebSocket push, polling, or quit-to-tray? | **JMAP WebSocket push.** Confirmed live and available: the Stalwart sandbox's JMAP session resource advertises `urn:ietf:params:jmap:websocket` with `supportsPush: true` (`wss://stalwart.sandbox.vnc.de/jmap/ws`), independently confirmed by two separate build agents. | Lower latency, no polling/backoff logic to write, and it's not hypothetical — it's live on the server this build already targets. |
| 2026-08-04 | Electron's background/foreground notification strategy: JMAP WebSocket push, polling, or quit-to-tray? | **JMAP WebSocket push, implemented with automatic SSE fallback — see caveat below.** Confirmed live and available: the Stalwart sandbox's JMAP session resource advertises `urn:ietf:params:jmap:websocket` with `supportsPush: true` (`wss://stalwart.sandbox.vnc.de/jmap/ws`), independently confirmed by two separate build agents. | Lower latency, no polling/backoff logic to write, and it's not hypothetical — it's live on the server this build already targets. |
| 2026-08-04 | Code-signing: Apple Developer ID? Windows cert? | **Apple: yes** (also unblocks Phase 2's iOS push) — **human must actually enroll**, no agent can create the account or pay the ~$99/yr fee. **Windows: yes, eventually** — but ship unsigned for now during this build phase. | Signing is a purchase/account-creation action, categorically outside what an agent can do. |
## 5. Phase 1 — Electron desktop client
@@ -136,9 +136,27 @@ npm run test:electron # the smoke-test regression gate
### Still open
- **JMAP WebSocket push implementation** (skill steps 6-7) — decision resolved (§4), build not
yet done as of this manual's last update; check the skill's status log or task tracker for
current state.
- **JMAP WebSocket push implementation** (skill steps 6-7) — **DONE.** `getWebSocketUrl()`
discovers the endpoint from the session's own capability object (never hardcoded), with
exponential-jitter reconnect (200ms base / 5s cap / 3-attempt circuit breaker) and a 30s
heartbeat, falling back to the existing SSE/polling chain on failure. A real end-to-end
integration test (`integration/tests/11-electron-notification.spec.ts`) logs into the actual
Stalwart docker fixture, injects mail over real SMTP, and asserts the native notification
fires — not a mocked path. Two real bugs were found and fixed building this: production CSP
blocked `wss:` outright (the feature was completely inert in any production build until
fixed), and the original backoff timing had a window where a real delivery could be silently
missed during a retry cycle.
**Caveat, found empirically against the real sandbox server:** `stalwart.sandbox.vnc.de`'s
`/jmap/ws` endpoint requires the same HTTP `Authorization` header as every other JMAP endpoint
*on the WebSocket handshake itself* — which the browser `WebSocket` API cannot attach (browsers
don't allow custom headers on the handshake request). Against this specific server, the client
will therefore always fail the WS handshake and fall back to SSE — correctly, by design, but it
means "live WebSocket push" is currently unreachable in practice from a browser/Electron
client, not just theoretically available. Fixing this for real would need a server-side
accommodation (e.g. a short-lived token passed as a WS subprotocol or query parameter) — that's
a Stalwart-side change, out of scope for this client work. Functionally nothing is broken (SSE
fallback works), but don't expect WS to actually engage against this sandbox until that's
addressed.
- **Code signing** — blocked on the human actually enrolling in the Apple Developer Program
(§4). Once done, wiring the signing identity + notarization into `electron-builder` and CI
secrets is a config change, not a rewrite — the current config is structured for it.