@openchambery/web 1.18.7-beta.1 → 1.18.7-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentAvatar-CVTgW_rx.js +1 -0
- package/dist/assets/AssistantView-CvQ91w2s.js +1 -0
- package/dist/assets/{ChatContainer-Dnxdg6He.js → ChatContainer-CERrtaB-.js} +41 -43
- package/dist/assets/ContextPanel-CLz4r1G-.js +285 -0
- package/dist/assets/{DiagramView-BplbsR8l.js → DiagramView-DhV4Zthj.js} +1 -1
- package/dist/assets/{FadeInOnReveal-By2F9vcI.js → FadeInOnReveal-Cy3sf2M5.js} +1 -1
- package/dist/assets/{Icon-DpjYnXxn.js → Icon-BLWVqBee.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-EP8W7j6_.js → MarkdownRendererImpl-B4qwUu4q.js} +2 -2
- package/dist/assets/{MobileDetailNavigation-CYq_8ZAq.js → MobileDetailNavigation-CuWgEThi.js} +1 -1
- package/dist/assets/{MobileShareBridge-VmA8Qdh0.js → MobileShareBridge-C3nay5Sq.js} +2 -2
- package/dist/assets/MobileSurface-D7v_ZkYO.js +2 -0
- package/dist/assets/{MultiRunWindow-F6NGMY5U.js → MultiRunWindow-Cq8yyiqm.js} +1 -1
- package/dist/assets/{OnboardingScreen-BKmuT7AL.js → OnboardingScreen-CTNi2zuA.js} +1 -1
- package/dist/assets/OverlayScrollbar-GQgJusKL.js +2 -0
- package/dist/assets/SettingsView-CViKCsxJ.js +25 -0
- package/dist/assets/{SettingsWindow-Z4sr2ZBF.js → SettingsWindow-C6vwy4hH.js} +1 -1
- package/dist/assets/{TerminalView-DnOowqR4.js → TerminalView-C1aX22ka.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BKJJN6ZR.js → ToolOutputDialog-BrOqj1sB.js} +4 -4
- package/dist/assets/appThemeRegistry-BbqImxSo.js +2 -0
- package/dist/assets/{button-C7OXr4ae.js → button-CeCkEpvn.js} +1 -1
- package/dist/assets/{clipboard-DWuULPzo.js → clipboard-BXC1vZwE.js} +1 -1
- package/dist/assets/{collapsible-DaEya6b6.js → collapsible-B-aagADh.js} +1 -1
- package/dist/assets/{desktopHosts-Txp4xOVQ.js → desktopHosts-BrrYPqKq.js} +1 -1
- package/dist/assets/{desktopRelayRestore-C_FSEHKM.js → desktopRelayRestore-DR60DATT.js} +1 -1
- package/dist/assets/{diagram-CHfginjS.js → diagram-Cxma_IKI.js} +1 -1
- package/dist/assets/dialog-wraJHZQ2.js +1 -0
- package/dist/assets/es-DQqFAaWT.js +15 -0
- package/dist/assets/{fileQueries-BrUKKgPW.js → fileQueries-BJKLdYos.js} +1 -1
- package/dist/assets/fr-CNfGRpc1.js +13 -0
- package/dist/assets/{gitApi-CtHhG2UX.js → gitApi-B4z4eSZV.js} +1 -1
- package/dist/assets/{input-BzYA603C.js → input-CyG05w-V.js} +1 -1
- package/dist/assets/insertionBoundaries-DCYAK5W1.js +4 -0
- package/dist/assets/ja-CQqehnVn.js +15 -0
- package/dist/assets/ko-kmwi8UJD.js +15 -0
- package/dist/assets/main-A8oe9r34.js +29 -0
- package/dist/assets/{main-YMWxpyqS.js → main-BJBKpmw3.js} +2 -2
- package/dist/assets/{markdown-worker-C3uVXiOS.js → markdown-worker-DDCUZkz4.js} +1 -1
- package/dist/assets/{markdownTheme-B89dbj3N.js → markdownTheme-H7_7lbuh.js} +1 -1
- package/dist/assets/miniChat-BObOyFSV.js +2 -0
- package/dist/assets/mobile-Ba390Cmc.js +2 -0
- package/dist/assets/multirun-CMDYOrmD.js +4 -0
- package/dist/assets/{persistence-6sclr7B6.js → persistence-DE9RnVcA.js} +2 -2
- package/dist/assets/pl-Blv5o2QA.js +15 -0
- package/dist/assets/platform-MjT8iqKB.js +2 -0
- package/dist/assets/projectMeta-uWBWFG5A.js +3 -0
- package/dist/assets/pt-BR-BK74t-Bd.js +15 -0
- package/dist/assets/radio-CEXUd4RC.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-Bpgb2E78.js +2 -0
- package/dist/assets/renderMobileApp-BMwRozEC.js +23 -0
- package/dist/assets/runtimeConfig-hHuyauKZ.js +2 -0
- package/dist/assets/runtimeEndpointReset-CmMI7Xp1.js +4 -0
- package/dist/assets/shikiHighlight-C-kTQvqd.js +1 -0
- package/dist/assets/{sortable-tabs-strip-nLsPC06f.js → sortable-tabs-strip-BdbKDDAP.js} +1 -1
- package/dist/assets/src-CgFGBE_A.css +2 -0
- package/dist/assets/store-UweBeN2y.js +15 -0
- package/dist/assets/{terminalApi-BpPeXWIu.js → terminalApi-Bnx0KvaV.js} +1 -1
- package/dist/assets/{tooltip-D_orJMYd.js → tooltip-B__x-HDJ.js} +1 -1
- package/dist/assets/uk-6I-KXy3R.js +15 -0
- package/dist/assets/{url-BsaBG5-1.js → url-CTuw7Rjf.js} +1 -1
- package/dist/assets/{useAppFontEffects-DPE9SuwC.js → useAppFontEffects-8iAE0Yrj.js} +5 -5
- package/dist/assets/{useAssistantUIStore-BHsHXCKE.js → useAssistantUIStore-D2K7ksGj.js} +1 -1
- package/dist/assets/{useDesktopWindowControlsLayout-evJk53fe.js → useDesktopWindowControlsLayout--oz1VAPt.js} +1 -1
- package/dist/assets/useEffectiveDirectory-DmoqXaa4.js +1 -0
- package/dist/assets/{useFontPreferences-ckzpXQaJ.js → useFontPreferences-CWZnNRjz.js} +1 -1
- package/dist/assets/useMobileNavigationStore-BxbEX-es.js +412 -0
- package/dist/assets/useSessionAutoCleanup-BwlJlhY7.js +1 -0
- package/dist/assets/{useUIStore-DzwCfRqr.js → useUIStore-DOxLVeGb.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-ClEzCydD.js → useWorkerHighlightedLines-B21D9MOD.js} +1 -1
- package/dist/assets/{utils-B1EK5zKa.js → utils-C2R0YoC5.js} +1 -1
- package/dist/assets/vendor-legendapp-list-WE32lwNk.js +1 -0
- package/dist/assets/zh-CN-DwlzS4yK.js +15 -0
- package/dist/assets/zh-TW-p0VriyfT.js +15 -0
- package/dist/index.html +9 -9
- package/dist/mini-chat.html +9 -9
- package/dist/mobile.html +9 -9
- package/package.json +3 -2
- package/server/index.js +23 -20
- package/server/lib/config-sync/DOCUMENTATION.md +6 -7
- package/server/lib/config-sync/engine.js +0 -8
- package/server/lib/config-sync/index.js +0 -7
- package/server/lib/config-sync/receiver.js +0 -35
- package/server/lib/config-sync/routes.js +8 -41
- package/server/lib/config-sync/routes.test.js +1 -38
- package/server/lib/notifications/APNS.md +88 -38
- package/server/lib/notifications/DOCUMENTATION.md +11 -4
- package/server/lib/notifications/apns-runtime.js +793 -78
- package/server/lib/notifications/apns-runtime.test.js +922 -0
- package/server/lib/notifications/apns-titles.js +212 -0
- package/server/lib/notifications/apns-titles.test.js +55 -0
- package/server/lib/notifications/routes.js +57 -1
- package/server/lib/notifications/runtime.js +20 -17
- package/server/lib/notifications/runtime.test.js +108 -0
- package/server/lib/opencode/DOCUMENTATION.md +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +6 -0
- package/server/lib/opencode/bootstrap-runtime.test.js +21 -0
- package/server/lib/opencode/openchamber-routes.js +1 -7
- package/server/lib/opencode/routes.js +24 -5
- package/server/lib/opencode/routes.test.js +78 -1
- package/server/lib/opencode/settings-helpers.js +0 -3
- package/server/lib/opencode/shutdown-runtime.js +0 -2
- package/server/lib/relay/DOCUMENTATION.md +2 -2
- package/server/lib/relay/service.js +45 -3
- package/server/lib/relay/service.test.js +117 -0
- package/server/lib/relay/signing-key.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +27 -2
- package/server/lib/scheduled-tasks/runtime.js +271 -3
- package/server/lib/scheduled-tasks/runtime.test.js +308 -0
- package/server/lib/session-goal/DOCUMENTATION.md +3 -3
- package/server/lib/session-goal/runtime.js +3 -3
- package/server/lib/session-title/DOCUMENTATION.md +1 -2
- package/server/lib/small-model/DOCUMENTATION.md +2 -2
- package/dist/assets/AgentAvatar-DDcjamcJ.js +0 -1
- package/dist/assets/AssistantView-BsQRWYlz.js +0 -1
- package/dist/assets/MobileSurface-D7aKyOLV.js +0 -2
- package/dist/assets/OverlayScrollbar-Ch3J6ASR.js +0 -2
- package/dist/assets/SettingsView-5b0tAHTL.js +0 -25
- package/dist/assets/appThemeRegistry-Dfucen00.js +0 -2
- package/dist/assets/context-menu-in4KRVok.js +0 -1
- package/dist/assets/dialog-DPS0W9eS.js +0 -1
- package/dist/assets/es-BX0Vm3Ac.js +0 -15
- package/dist/assets/fr-CuPzXTYx.js +0 -13
- package/dist/assets/insertionBoundaries--0yrL837.js +0 -1
- package/dist/assets/ja-DGtzjrFO.js +0 -15
- package/dist/assets/ko-DZ9Di0je.js +0 -15
- package/dist/assets/main-B9klnJvc.js +0 -172
- package/dist/assets/miniChat-D6inDYgY.js +0 -2
- package/dist/assets/mobile-DRu5smYb.js +0 -2
- package/dist/assets/multirun-CVRJ-r1z.js +0 -4
- package/dist/assets/pl-BuCO3qb1.js +0 -15
- package/dist/assets/platform-CbeLpKPk.js +0 -2
- package/dist/assets/projectMeta-Cr4ghXIQ.js +0 -3
- package/dist/assets/pt-BR-Brf4RMsw.js +0 -15
- package/dist/assets/radio-C9Qe2mW1.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-CCpXytpP.js +0 -2
- package/dist/assets/renderMobileApp-BmAEbSuq.js +0 -23
- package/dist/assets/runtimeConfig-JC8_XlMh.js +0 -2
- package/dist/assets/runtimeEndpointReset-Cgefx6dv.js +0 -147
- package/dist/assets/src-CpetTO35.css +0 -2
- package/dist/assets/store-DQY8Fkb0.js +0 -15
- package/dist/assets/uk-C-wmaPTi.js +0 -15
- package/dist/assets/useEffectiveDirectory-Bdc1_8wI.js +0 -1
- package/dist/assets/useMobileNavigationStore-BNyyqZch.js +0 -475
- package/dist/assets/useSessionAutoCleanup-C-NCLDvZ.js +0 -1
- package/dist/assets/zh-CN-DueRrIql.js +0 -15
- package/dist/assets/zh-TW-6875X80V.js +0 -15
- package/server/lib/config-sync/credential-auth.js +0 -39
- package/server/lib/session-assist/DOCUMENTATION.md +0 -62
- package/server/lib/session-assist/runtime.js +0 -376
- package/server/lib/session-assist/runtime.test.js +0 -114
|
@@ -13,19 +13,57 @@ registered them — so a leaked device token alone can't be used to push.
|
|
|
13
13
|
`POST /v1/push/register-token`, signed with its auto-generated ECDSA P-256 key
|
|
14
14
|
(`getOrCreateRelayKeypair`, persisted in settings like the VAPID keys). The relay records
|
|
15
15
|
`token → serverId` where `serverId = SHA-256(publicKey)`.
|
|
16
|
-
3. On a trigger (ready/error/question/permission), the server composes **generic,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
3. On a trigger (ready/error/question/permission/goal_*), the server composes **generic,
|
|
17
|
+
content-free** text — a **locale-specific** scenario title (from `apns-titles.js`, keyed off
|
|
18
|
+
the locale stored with each device token at `POST /api/push/apns-token`) + the **session name**
|
|
19
|
+
as the body, no model/project/message content — plus a **`badge`** count (see below). Tokens
|
|
20
|
+
are grouped by locale (relay signatures cover `title`, so mixed locales cannot share one
|
|
21
|
+
batch) and each group POSTs `{ tokens, title, body, badge, env, data:{sessionId},
|
|
22
|
+
publicKeyJwk, ts, sig }` to `POST /v1/push/send` (`apns-runtime.js` → `sendViaRelay`). It does
|
|
23
|
+
**not** gate on UI visibility (see below). Legacy tokens without a locale fall back to English.
|
|
24
|
+
4. The **Push Relay** (`packages/relay-server/src/push/`, process `openchamber-push-relay`)
|
|
25
|
+
verifies the signature + `ts` freshness, derives `serverId`, and only delivers to tokens bound
|
|
26
|
+
to that server. It holds the project APNs `.p8` key, signs an ES256 JWT with Node crypto, and
|
|
27
|
+
sends each token to APNs over HTTP/2, returning per-token results; the server drops tokens
|
|
28
|
+
flagged `drop` (410 / BadDeviceToken). The Push database stores `token → serverId` bindings,
|
|
29
|
+
not application plaintext. Layer 1 (`openchamber-relay`) never sees these secrets.
|
|
27
30
|
5. Tapping a push deep-links to its session via the forwarded `sessionId`.
|
|
28
31
|
|
|
32
|
+
## Live Activity (update / end)
|
|
33
|
+
|
|
34
|
+
ActivityKit has issued **update/end push tokens** for an Activity that is
|
|
35
|
+
**already started on-device** since **iOS 16.1**. **Push-to-start** (creating an
|
|
36
|
+
Activity from APNs while the app is not running) is **iOS 17.2+** and is not
|
|
37
|
+
part of this Host path.
|
|
38
|
+
|
|
39
|
+
The native app registers a Live Activity token with **its own server**
|
|
40
|
+
(`POST /api/push/live-activity-token`: `token`, `activityId`, `sessionId`).
|
|
41
|
+
Those tokens are stored separately from alert APNs device tokens (same
|
|
42
|
+
`apns-tokens.json` file, `version` 2 `liveActivityTokensBySession`; v1 alert
|
|
43
|
+
data remains readable) and are never used for banner push. The Host binds them
|
|
44
|
+
on the relay at `POST /v1/push/register-live-activity-token` with
|
|
45
|
+
`{ token, platform: "ios", kind: "liveactivity", publicKeyJwk, ts, sig }` signed
|
|
46
|
+
over `${ts}.${token}.${platform}.${kind}`. Unregister uses
|
|
47
|
+
`/v1/push/unregister-live-activity-token` with the same signature, and only the
|
|
48
|
+
owning UI session may delete a local token.
|
|
49
|
+
|
|
50
|
+
On top-level session **completion** or **error** (child sessions and small-model
|
|
51
|
+
system sessions suppressed; independent of ordinary notification settings and UI
|
|
52
|
+
visibility) the Host calls `sendLiveActivityEnd`. Relay send is
|
|
53
|
+
`POST /v1/push/live-activity` with `{ tokens, event, contentState,
|
|
54
|
+
dismissalDate?, staleDate?, publicKeyJwk, ts, sig }` signed over
|
|
55
|
+
`${ts}.${sortedTokens}.${event}.${status}.${eventVersion}.${updatedAt}.${endedAt}.${dismissalDate}.${staleDate}`.
|
|
56
|
+
Direct mode uses HTTP/2 token auth, `apns-topic: ${bundleId}.push-type.liveactivity`,
|
|
57
|
+
`apns-push-type: liveactivity`, and no alert.
|
|
58
|
+
|
|
59
|
+
The APNs / relay payload **must not** carry `sessionId` or user/session content.
|
|
60
|
+
`contentState` is only `{ status, eventVersion, updatedAt, endedAt }`.
|
|
61
|
+
`complete` uses a 15-minute dismissal and `error` a 60-minute dismissal (same as
|
|
62
|
+
native). `staleDate = updatedAt + 20min` is for **update** events only. After a
|
|
63
|
+
successfully accepted `end`, the Host clears the local Activity tokens for those
|
|
64
|
+
devices. A Live Activity failure does not drop alert tokens or block ordinary
|
|
65
|
+
push. Do not log Live Activity tokens or session ids.
|
|
66
|
+
|
|
29
67
|
## Foreground suppression
|
|
30
68
|
|
|
31
69
|
APNs is **not** gated on UI visibility. A backgrounded WKWebView can't reliably report "hidden"
|
|
@@ -34,8 +72,8 @@ responses. Instead the server always sends, and **iOS** suppresses the foregroun
|
|
|
34
72
|
(`PushNotifications.presentationOptions: []` in `capacitor.config`) — so there is no notification
|
|
35
73
|
while the app is active, with no race. APNs is the native app's **only** channel; local
|
|
36
74
|
notifications were removed (a WKWebView can't tell foreground from background — `document.hasFocus()`
|
|
37
|
-
is unreliable — so they leaked while the app was open).
|
|
38
|
-
app with notifications on has a registered token and a trigger fires.
|
|
75
|
+
is unreliable — so they leaked while the app was open). The Push Relay is contacted only when a
|
|
76
|
+
native app with notifications on has a registered token and a trigger fires.
|
|
39
77
|
|
|
40
78
|
## App-icon badge
|
|
41
79
|
|
|
@@ -61,36 +99,45 @@ mirrors the device zeroing its icon badge on `sceneDidBecomeActive` (`AppDelegat
|
|
|
61
99
|
server and device in sync.
|
|
62
100
|
|
|
63
101
|
The value flows `runtime.js` (`toApnsGenericPayload`) → `apns-runtime.js` (`sendViaRelay` body /
|
|
64
|
-
direct-mode `aps.badge`) →
|
|
65
|
-
`body`/`data`); the relay still only delivers to bound tokens. The set
|
|
66
|
-
device token of a server sees the same badge.
|
|
102
|
+
direct-mode `aps.badge`) → Push Relay (`packages/relay-server/src/push/schema.js` → `aps.badge`).
|
|
103
|
+
It is **not** signed (like `body`/`data`); the relay still only delivers to bound tokens. The set
|
|
104
|
+
is server-global, so every device token of a server sees the same badge.
|
|
67
105
|
|
|
68
106
|
## Modes
|
|
69
107
|
|
|
70
|
-
- **Relay (default):**
|
|
71
|
-
`
|
|
108
|
+
- **Relay (default):** Host has no Apple key. Unless `OPENCHAMBER_PUSH_RELAY_URL` is set, the
|
|
109
|
+
Host maps the effective Relay `wss://`/`ws://` URL to the same host as `https://`/`http://`
|
|
110
|
+
`/v1/push/send` (register URL is `/v1/push/register-token`). A Relay switch re-registers
|
|
111
|
+
persisted tokens and binds them again before the first send.
|
|
72
112
|
- **Direct (fallback):** set `OPENCHAMBER_PUSH_RELAY_DISABLED=true` + `OPENCHAMBER_APNS_KEY_ID/
|
|
73
113
|
TEAM_ID/P8` to sign+send from the server itself (HTTP/2 + ES256 JWT); no relay binding needed.
|
|
74
114
|
|
|
75
115
|
## Config
|
|
76
116
|
|
|
77
|
-
|
|
78
|
-
- `OPENCHAMBER_PUSH_RELAY_URL` (
|
|
79
|
-
(`sandbox` default / `production`). The signing keypair is
|
|
117
|
+
Host (`apns-runtime.js`):
|
|
118
|
+
- `OPENCHAMBER_PUSH_RELAY_URL` (explicit send-URL override), `OPENCHAMBER_APNS_ENVIRONMENT`
|
|
119
|
+
(`sandbox` default / `production` for TestFlight and App Store). The signing keypair is
|
|
120
|
+
auto-generated — nothing to set.
|
|
80
121
|
- Direct fallback: `OPENCHAMBER_APNS_KEY_ID`, `OPENCHAMBER_APNS_TEAM_ID`, `OPENCHAMBER_APNS_P8`
|
|
81
122
|
(or `_P8_PATH`), `OPENCHAMBER_APNS_BUNDLE_ID`, `OPENCHAMBER_PUSH_RELAY_DISABLED=true`.
|
|
82
123
|
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
124
|
+
Push process (`packages/relay-server/src/push/`, env prefix `OPENCHAMBER_PUSH_RELAY_`):
|
|
125
|
+
`HOST` (default `127.0.0.1`), `PORT` (default `8788`), `TRUST_PROXY`, `DATABASE_PATH`,
|
|
126
|
+
timestamp/replay/rate-limit/token/`MAX_IN_FLIGHT` limits, plus APNs `APNS_KEY_ID`,
|
|
127
|
+
`APNS_TEAM_ID`, `APNS_BUNDLE_ID`, and `APNS_P8` or `APNS_P8_PATH`. See
|
|
128
|
+
`packages/relay-server/README.md`. SQLite is single-instance. Do not log `.p8` material,
|
|
129
|
+
device tokens, or signatures.
|
|
86
130
|
|
|
87
131
|
## Apple setup (one-time)
|
|
88
132
|
|
|
89
133
|
1. Apple **Keys** (not Certificates) → create an **APNs Auth Key** (`.p8`) → Key ID + Team ID;
|
|
90
|
-
enable **Push Notifications** on App ID
|
|
91
|
-
|
|
92
|
-
|
|
134
|
+
enable **Push Notifications** on the App ID. TestFlight / App Store builds of this product
|
|
135
|
+
use bundle ID `com.yee94.openchamber`.
|
|
136
|
+
2. Give those values only to the Push process (environment or Docker secret). Do not put the
|
|
137
|
+
`.p8` on Layer 1 or on each OpenChamber Host when using Relay mode.
|
|
93
138
|
3. Xcode: confirm the Push Notifications capability; Clean Build Folder; run on device.
|
|
139
|
+
Development builds use APNs `sandbox`. TestFlight and App Store Hosts set
|
|
140
|
+
`OPENCHAMBER_APNS_ENVIRONMENT=production`.
|
|
94
141
|
|
|
95
142
|
## Security posture
|
|
96
143
|
|
|
@@ -98,10 +145,12 @@ binding table is created by `migrations/0002_push_tokens.sql` (applied on deploy
|
|
|
98
145
|
is signed by the server's private key, and the relay only delivers to a token from its bound
|
|
99
146
|
`serverId`. A leaked token alone is useless — an attacker has neither the private key nor a
|
|
100
147
|
matching binding.
|
|
101
|
-
- `serverId` self-certifies (`SHA-256(publicKey)`)
|
|
102
|
-
|
|
148
|
+
- `serverId` self-certifies (`SHA-256(publicKey)`). A SQLite leak exposes `token → serverId`
|
|
149
|
+
bindings, not Host private keys or application plaintext. The signed `ts` (±5 min window)
|
|
150
|
+
blocks replay. Signature verification lives in `packages/relay-server/src/push/crypto.js`
|
|
151
|
+
and must stay aligned with `packages/web/server/lib/relay/signing-key.js`.
|
|
103
152
|
- Residual: trust-on-first-bind (whoever registers a token first owns it) — acceptable, since
|
|
104
|
-
registering already requires possessing the token.
|
|
153
|
+
registering already requires possessing the token. Process rate limits are defence-in-depth.
|
|
105
154
|
|
|
106
155
|
## Data confidentiality (what the relay / Apple can see)
|
|
107
156
|
|
|
@@ -113,17 +162,18 @@ to APNs). The request **signature is authentication, not encryption** — the re
|
|
|
113
162
|
Who can read the alert text:
|
|
114
163
|
|
|
115
164
|
- **Network hops:** nothing (TLS).
|
|
116
|
-
- **The
|
|
117
|
-
`sessionId`. It stores
|
|
165
|
+
- **The Push Relay process:** the generic title + body (session name), the device token, and
|
|
166
|
+
`sessionId`. It stores `token → serverId` bindings (no alert text, no payload).
|
|
118
167
|
- **Apple APNs:** the alert text too — APNs always reads the alert payload of an `alert` push.
|
|
119
168
|
- **The device:** displays it.
|
|
120
169
|
|
|
121
|
-
This is acceptable **because the text is deliberately content-free**: a fixed scenario title
|
|
122
|
-
the session name only — no model, project, or message content
|
|
123
|
-
`toApnsGenericPayload`). The session name is the single
|
|
124
|
-
relay/Apple. To hide even that from Apple would require an
|
|
125
|
-
(`mutable-content` + a Notification Service Extension that
|
|
126
|
-
sent to the relay) — not implemented, and unnecessary for
|
|
170
|
+
This is acceptable **because the text is deliberately content-free**: a fixed scenario title
|
|
171
|
+
(localized per device) + the session name only — no model, project, or message content
|
|
172
|
+
(`runtime.js` → `toApnsGenericPayload` → `localizeApnsPayload`). The session name is the single
|
|
173
|
+
semi-personal field that crosses the relay/Apple. To hide even that from Apple would require an
|
|
174
|
+
end-to-end **encrypted payload** (`mutable-content` + a Notification Service Extension that
|
|
175
|
+
decrypts on-device with a key never sent to the relay) — not implemented, and unnecessary for
|
|
176
|
+
generic text.
|
|
127
177
|
|
|
128
178
|
## Android (FCM) note
|
|
129
179
|
|
|
@@ -7,7 +7,7 @@ This module provides notification message preparation utilities for the web serv
|
|
|
7
7
|
- `packages/web/server/lib/notifications/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
|
8
8
|
- `packages/web/server/lib/notifications/routes.js`: route registration for push, visibility, and session status/attention endpoints.
|
|
9
9
|
- `packages/web/server/lib/notifications/push-runtime.js`: push subscription persistence, VAPID initialization, and UI visibility runtime.
|
|
10
|
-
- `packages/web/server/lib/notifications/apns-runtime.js`: native iOS APNs device-token persistence + delivery. Two modes: **relay** (default — sign + POST tokens + generic text to the
|
|
10
|
+
- `packages/web/server/lib/notifications/apns-runtime.js`: native iOS APNs device-token persistence + delivery, plus a separate Live Activity token store and `end` send path. Two modes: **relay** (default — sign + POST tokens + generic text to the isolated Push Relay in `packages/relay-server/src/push/`, which holds the project APNs key) and **direct** (fallback — sign ES256 JWT with Node crypto + HTTP/2, when `OPENCHAMBER_PUSH_RELAY_DISABLED=true`). Unless `OPENCHAMBER_PUSH_RELAY_URL` is set, the Host maps the effective Relay `wss`/`ws` URL to the same host as `https`/`http` `/v1/push/send`. Before each relay send, the runtime checks an in-memory registration success cache (keyed by current register URL + token + platform) and only POSTs `/v1/push/register-token` for cache misses; only successfully bound tokens are sent. Switching Relay/Push URL clears that cache so persisted tokens re-bind to the new origin on the next send or `reRegisterAllTokens()`. Each server has an auto-generated ECDSA P-256 keypair (`getOrCreateRelayKeypair`, persisted in settings); it binds tokens on the relay (`/v1/push/register-token`) and signs every relay request, so the relay only delivers to tokens bound to that server. APNs is the native app's sole notification channel (no local notifications) and is NOT gated on UI visibility — iOS suppresses the foreground banner instead. Mobile push carries only generic text (scenario title + session name) — see `APNS.md`. Live Activity tokens are persisted beside alert tokens in the same file (`version` 2, `liveActivityTokensBySession`) without mixing the two token types; v1 files remain readable. Live Activity relay bind/send uses `/v1/push/register-live-activity-token`, `/v1/push/unregister-live-activity-token`, and `/v1/push/live-activity`.
|
|
11
11
|
- `packages/web/server/lib/notifications/emitter-runtime.js`: desktop/stdout + UI SSE notification emission runtime.
|
|
12
12
|
- `packages/web/server/lib/notifications/runtime.js`: trigger runtime for OpenCode event-driven notification fanout.
|
|
13
13
|
- `packages/web/server/lib/notifications/template-runtime.js`: notification template variables and session text/title enrichment runtime. Zen-model helpers are retained as compatibility stubs only.
|
|
@@ -27,6 +27,8 @@ This module provides notification message preparation utilities for the web serv
|
|
|
27
27
|
- `DELETE /api/push/subscribe`
|
|
28
28
|
- `POST /api/push/apns-token` (native iOS APNs device-token registration)
|
|
29
29
|
- `DELETE /api/push/apns-token`
|
|
30
|
+
- `POST /api/push/live-activity-token` (native iOS Live Activity token registration; UI-auth, length-limited `token` / `activityId` / `sessionId`)
|
|
31
|
+
- `DELETE /api/push/live-activity-token` (owning UI session only)
|
|
30
32
|
- `POST /api/push/visibility`
|
|
31
33
|
- `GET /api/push/visibility`
|
|
32
34
|
- `GET /api/notifications/stream`
|
|
@@ -46,6 +48,7 @@ This module provides notification message preparation utilities for the web serv
|
|
|
46
48
|
- `maybeSendPushForTrigger(payload)`
|
|
47
49
|
- Owns:
|
|
48
50
|
- top-level completion/question/permission trigger routing; completion is the sole task-status notification and child sessions plus small-model system sessions (`metadata.openchamber.smallModel.purpose`) are suppressed
|
|
51
|
+
- top-level completion/error Live Activity `end` (`sendLiveActivityEnd`), independent of ordinary push settings and UI visibility, with the same child/small-model suppression; duplicate terminal events are idempotent at the token store
|
|
49
52
|
- session meta cache for child-session and small-model suppression
|
|
50
53
|
- template resolution and fallback behavior
|
|
51
54
|
- native notification fanout and web push payload fanout
|
|
@@ -67,12 +70,16 @@ This module provides notification message preparation utilities for the web serv
|
|
|
67
70
|
### APNs runtime API (apns-runtime.js)
|
|
68
71
|
- `createApnsRuntime(dependencies)`: creates runtime for native iOS APNs push and device-token state. Dependencies: `fsPromises`, `path`, `crypto`, `http2`, `APNS_TOKENS_FILE_PATH`, `readSettingsFromDiskMigrated`, `writeSettingsToDisk` (persists the auto-generated relay signing keypair).
|
|
69
72
|
- Returned API:
|
|
70
|
-
- `addOrUpdateApnsToken(uiSessionToken, deviceToken, userAgent)` — also binds a newly-seen token on the relay (signed `/v1/push/register-token`).
|
|
73
|
+
- `addOrUpdateApnsToken(uiSessionToken, deviceToken, userAgent, platform?, locale?)` — also binds a newly-seen token on the relay (signed `/v1/push/register-token`). Persists app UI `locale` for localized push titles.
|
|
71
74
|
- `removeApnsToken(uiSessionToken, deviceToken)`
|
|
72
75
|
- `removeApnsTokenFromAllSessions(deviceToken)`
|
|
73
|
-
- `
|
|
76
|
+
- `addOrUpdateLiveActivityToken(uiSessionToken, token, activityId, sessionId)` — idempotent upsert bound to the UI session + OpenCode `sessionId` + ActivityKit `activityId`. Token refresh replaces that activity's previous token. Caps per UI session and lazily expires stale entries. Binds on the relay (`/v1/push/register-live-activity-token`, `kind: "liveactivity"`). Does not log tokens or session ids.
|
|
77
|
+
- `removeLiveActivityToken(uiSessionToken, token)` — owning UI session only; unregisters from the relay when no local copies remain.
|
|
78
|
+
- `sendLiveActivityEnd({ sessionId, status, eventVersion?, endedAt? })` — looks up Live Activity tokens for that OpenCode session and sends `event: "end"` with `contentState` `{ status, eventVersion, updatedAt, endedAt }` only. `complete` dismisses after 15 minutes, `error` after 60 minutes (same as native). `staleDate` (`updatedAt + 20min`) is update-only and omitted on end. `eventVersion` is monotonic per session using current milliseconds. Relay body follows the signed live-activity contract; direct mode uses token auth, topic `${bundleId}.push-type.liveactivity`, `apns-push-type: liveactivity`, and no alert. APNs payloads must not carry `sessionId` or user content. Successfully accepted tokens are cleared locally; failures do not drop alert tokens or block ordinary push.
|
|
79
|
+
- `sendApnsToAllUiSessions(payload)` — localizes scenario titles per stored token locale (`apns-titles.js`), groups tokens by locale, then signs + sends (no UI-visibility gate; iOS suppresses the foreground banner). Before a relay send, each token is checked against the in-memory registration success cache for the current register URL; cache hits skip `/v1/push/register-token`, misses are registered with bounded concurrency, and only successfully bound tokens are sent. Switching Relay/Push URL clears that cache so the next send re-binds persisted tokens to the new origin. No-ops with a single warning when APNs is unconfigured. Drops tokens on `410` / `BadDeviceToken` / `Unregistered`. Never sends Live Activity tokens.
|
|
80
|
+
- `reRegisterAllTokens()` — binds every persisted device token to the current Push origin (same success cache and URL-switch invalidation as send).
|
|
74
81
|
- `resolveApnsConfig()`
|
|
75
|
-
- Configuration (env first, then `settings.apnsConfig`): `OPENCHAMBER_APNS_KEY_ID`, `OPENCHAMBER_APNS_TEAM_ID`, `OPENCHAMBER_APNS_P8` (PEM contents; literal `\n` accepted) or `OPENCHAMBER_APNS_P8_PATH`, `OPENCHAMBER_APNS_BUNDLE_ID` (default `com.openchamber
|
|
82
|
+
- Configuration (env first, then `settings.apnsConfig`): `OPENCHAMBER_PUSH_RELAY_URL` (optional send-URL override), `OPENCHAMBER_PUSH_RELAY_DISABLED`, `OPENCHAMBER_APNS_KEY_ID`, `OPENCHAMBER_APNS_TEAM_ID`, `OPENCHAMBER_APNS_P8` (PEM contents; literal `\n` accepted) or `OPENCHAMBER_APNS_P8_PATH`, `OPENCHAMBER_APNS_BUNDLE_ID` (default `com.yee94.openchamber`), `OPENCHAMBER_APNS_ENVIRONMENT` (`sandbox` default, or `production` for TestFlight and App Store).
|
|
76
83
|
|
|
77
84
|
### Emitter runtime API (emitter-runtime.js)
|
|
78
85
|
- `createNotificationEmitterRuntime(dependencies)`: creates runtime for unified notification emission channels.
|