@condorinst/mobile-bridge 0.8.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,350 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.8.0] — 2026-06-23
11
+
12
+ First **public** release. Runtime is identical to `0.7.0`; this version
13
+ packages the SDK for external White Label partners.
14
+
15
+ ### Changed
16
+
17
+ - **Package renamed `condor-mobile-bridge` → `@condorinst/mobile-bridge`**
18
+ (scoped, published to the public npm registry). Update imports accordingly.
19
+ This is the breaking change behind the minor bump (pre-1.0 semver).
20
+ - `README.md` and `CHANGELOG.md` rewritten in English; internal monorepo
21
+ references removed.
22
+ - `license` field set to `SEE LICENSE IN LICENSE` (proprietary EULA, unchanged
23
+ in spirit). `publishConfig.access: public`, `prepublishOnly` (build + test).
24
+
25
+ ### Added
26
+
27
+ - `docs/client-guide.md` — _"Build your White Label Home"_ onboarding guide.
28
+ - `examples/minimal-home/` — runnable standalone client example.
29
+ - `RELEASING.md` — dev-private → npm + public-repo release flow.
30
+
31
+ ## [0.7.0] — 2026-06-23
32
+
33
+ ### Added
34
+
35
+ - **`sensorLog` key** in the `BRIDGE_KEYS` catalog (10th key) — read command
36
+ for the sensor log history. Since `subscribe`/`event` are validated against
37
+ `picklist(Object.values(BRIDGE_KEYS))`, the key must exist here for the shell
38
+ to relay the response to the sandboxed iframe (otherwise the message is
39
+ dropped at `safeParse`).
40
+ - **`SensorLogPayload`** in `payloads.ts` + an entry in `KeyPayloads` →
41
+ typed `PayloadOf<'sensorLog'>`. Envelope with an echoed `requestId`
42
+ (correlation of concurrent keyset pagination) + `success`/`error`; on success
43
+ it carries `logs[]` (ASC, °C, naive UTC timestamps) + opaque cursors +
44
+ `hasMore`/`hasPrevious`; on error, an `error` with the contract code (e.g.
45
+ `CONFLICTING_CURSORS`).
46
+ - `tests/messages.test.ts` — catalog updated (9→10), `sensorLog` wire string
47
+ and `PayloadOf<'sensorLog'>`. Total: **116/116**.
48
+
49
+ ### Notes
50
+
51
+ - `sensorLog` is **read-only** for the client (it only receives the page/error);
52
+ sending the command uses the permissive `send` path (`command: v.string()`).
53
+
54
+ ## [0.6.0] — 2026-06-22
55
+
56
+ ### Added
57
+
58
+ - **Build CLI `condor-mobile-bridge build`** (`bin`) — generates the
59
+ self-contained white-label `.zip` bundle from the client's `dist/`. Inlines
60
+ `<script src>` (escaping `</script>`) and `<link rel=stylesheet>`, resolves
61
+ CSS `url()` and local `<img>`/icons to `data:` URIs, and packs a single
62
+ `index.html` at the root. Prints `bundleSize` (bytes) and `bundleHash`
63
+ (`sha256-<hex>`, **deterministic** via fixed mtime) for backend manifest
64
+ registration; **warns** (does not block) above the 2 MB limit. Flags:
65
+ `-i/--html`, `-o/--out`, `--max-bytes`.
66
+ - Production deps `node-html-parser` (a real HTML parser, not regex) and
67
+ `fflate` (100% JS zip, cross-platform — no system `zip` binary). Used **only**
68
+ by the CLI; they do not enter the iframe runtime bundle.
69
+ - `tests/cli.test.ts` — 18 tests (inliner, recursive `url()`, escaping,
70
+ preserved external refs, root zip, deterministic hash). Total: **115/115**.
71
+
72
+ ### Notes
73
+
74
+ - The post-build inliner assumes a **single-chunk** `dist` (one JS + one CSS +
75
+ assets). Builds with code-splitting / ES modules require a single-file config
76
+ on the client side (Vite: `vite-plugin-singlefile`; CRA: no split). See README.
77
+
78
+ ## [0.5.1] — 2026-06-09
79
+
80
+ ### Added
81
+
82
+ - **`BridgeShell.bootstrapClientIframeWithHtml(clientHtml: string)`** — new
83
+ public method that accepts an HTML string directly instead of fetching it.
84
+ Required where `fetch` does not work (asset bundled at `file://` in Android
85
+ WebView, build-time inlined bundle via `esbuild.define`, etc.).
86
+ - 4 new tests covering the new API + the legacy `bootstrapClientIframe` error
87
+ flow. Total: **98/98 green**.
88
+
89
+ ### Changed
90
+
91
+ - **`OVERRIDE_PREFIX` fixed** — `value/writable: false` replaced by a **no-op
92
+ getter/setter** + `configurable: false`. Reason: modern ESM bundles run in
93
+ strict mode; any `window.X = …` on a non-writable property **throws**,
94
+ breaking the bootstrap of legacy modules (including this package's own
95
+ `Bridge`, which sets `window.CondorBridge = {emit}` as a module-load
96
+ side effect). With a getter/setter, assignment passes silently; the getter
97
+ always returns undefined; native bridges stay unreachable in the iframe
98
+ without breaking legitimate JS flows. Validated empirically via an offensive
99
+ pen test on a physical device.
100
+ - `BridgeShell.bootstrapClientIframe(clientPath)` refactored to delegate
101
+ internally to `bootstrapClientIframeWithHtml` after the `fetch`. **No
102
+ observable behavior change**; the legacy API is preserved.
103
+ - `OVERRIDE_PREFIX` extracted as a module constant (was inline in the method).
104
+
105
+ ### Notes
106
+
107
+ - **No breaking change.** `bootstrapClientIframe(clientPath)` keeps the same
108
+ signature and behavior as v0.5.0 (modulo the override-prefix fix). A client
109
+ importing only `BridgeClient` or other APIs is unaffected.
110
+ - The shell version (`shellVersion` in the handshake-ack) now reports `0.5.1`.
111
+
112
+ ## [0.5.0] — 2026-06-09
113
+
114
+ ### Added
115
+
116
+ - **`BridgeClient`** — API for external client code running inside an
117
+ `<iframe sandbox="allow-scripts">`. Surface identical to the legacy `Bridge`
118
+ (`on`/`send`/`openSettings`/`handshake`); internally every call becomes a
119
+ `window.parent.postMessage`. The client imports `BridgeClient` instead of
120
+ `Bridge` and never notices it is sandboxed.
121
+ - **`BridgeShell`** — API for the Condor-controlled shell that wraps the
122
+ iframe. Responsibilities: iframe bootstrap with the override prefix
123
+ (`bootstrapClientIframe`), iframe attestation (`verifySandbox`),
124
+ `postMessage` ↔ legacy `Bridge` routing with a default-deny policy, and
125
+ gating of sensitive commands via the `onConfirmationRequest` callback.
126
+ - **Valibot schemas** (`ClientToShellMessage` / `ShellToClientMessage`) —
127
+ strict validation of every `postMessage`. Malformed messages are dropped
128
+ silently. Types: `subscribe`, `unsubscribe`, `send`, `handshake`, `jsError`,
129
+ `sandbox-verify-response` (client→shell) + `event`, `handshake-ack`, `nack`,
130
+ `verify-sandbox` (shell→client).
131
+ - **`ShellPolicy`** — declarative interface for gating: `readOnlyKeys`,
132
+ `allowedCommands`, `requiresConfirmation`, `deniedCommands`. The policy is
133
+ data provided by the shell (not embedded in the package).
134
+ - 33 new tests (`tests/bridge-client.test.ts` 14 + `tests/bridge-shell.test.ts` 19).
135
+ Total: **94/94 green** (61 from v0.4.0 + 33 new).
136
+ - New dependency: `valibot` (~3 KB minified, tree-shakeable).
137
+
138
+ ### Layered defense against `window.flutter_inappwebview` propagation to the iframe
139
+
140
+ Implemented in this release:
141
+
142
+ - **Layer 1** — `BridgeShell.bootstrapClientIframe(clientPath)`: fetches the
143
+ client HTML, prepends an override script that `delete`s
144
+ `window.flutter_inappwebview` and `window.CondorBridge` +
145
+ `Object.defineProperty(..., configurable: false)`, and sets it as `srcdoc`.
146
+ The override runs at parse-time, before any client script.
147
+ - **Layer 2** — `BridgeShell.verifySandbox()`: a `verify-sandbox` →
148
+ `sandbox-verify-response` `postMessage` round-trip. `BridgeClient` answers
149
+ authoritatively about the state of the bridges in the iframe. The shell
150
+ aborts the boot if it receives `ok: false`.
151
+
152
+ Layers 3 (native-validated shell nonce) and 4 (dual-WebView fallback) live on
153
+ the native Flutter side.
154
+
155
+ ### Notes
156
+
157
+ - The legacy `Bridge` is unchanged — v0.4.0 clients importing `Bridge` keep
158
+ working identically. Migrating to the sandbox model is optional (but
159
+ recommended for an external-client audience).
160
+ - `BridgeClient` does **not** expose `installDevPanel`, `mockEmit`, `isMock`,
161
+ `getMockState` — those APIs are the shell's responsibility.
162
+ - Bundle: `dist/index.js` 29 KB (was 19 KB in v0.4.0; +10 KB for Valibot +
163
+ BridgeClient + BridgeShell + schemas).
164
+ - No runtime or build-time breaking changes for legacy `Bridge` users.
165
+
166
+ ## [0.4.0] — 2026-06-04
167
+
168
+ ### Added
169
+
170
+ - **Per-key strict types** in `PayloadOf<K>`. `Bridge.on<K>` now infers the
171
+ typed payload for a cataloged key. Unknown keys fall back to the permissive
172
+ `Record<string, unknown>`.
173
+ - **8 per-key DTOs**, derived authoritatively from the native source (services
174
+ + entities):
175
+ - `PingPongPayload = { data: 'ping' | 'pong' }`
176
+ - `UserDevicePayload = { lastUpdate, devicesConfig[], devicesTelemetry[], devicesUserConfig[] }`
177
+ - `DeviceLightSensorsPayload = { serialNo, type, data: Record<string, unknown> }`
178
+ - `SyncTimeAckPayload = { serialNo, success }`
179
+ - `RealTimeAckPayload = { serialNo, success, value }`
180
+ - `DeleteMemoryAckPayload = { serialNo, success }`
181
+ - `DeviceNotificationPayload = { serialNo, isConnect, message: string | null, messageCode: string | null, isError }`
182
+ - `DeviceConnectionPayload = { serialNo, isConnect }`
183
+ - **`KeyPayloads`** mapped type discriminated by key. `PayloadOf<K>` is now
184
+ `K extends keyof KeyPayloads ? KeyPayloads[K] : Record<string, unknown>`.
185
+ - 9 new tests in `tests/messages.test.ts` using `expectTypeOf` (Vitest) to
186
+ validate compile-time inference + runtime sanity per key. Total: **61/61
187
+ green** (52 from v0.3.0 + 9 new).
188
+
189
+ ### Notes
190
+
191
+ - `dfu` keeps the permissive fallback — reserved.
192
+ - `Bridge.send(type, payload?)` stays permissive in v0.4.0 (JS→BG commands are
193
+ heterogeneous: `realTime` `{serialNo, value}`, `userDevice` `{getFromServer}`,
194
+ `deviceConnection` `{type, serialNo}`, etc.). Typing a `send` overload is left
195
+ for a future release.
196
+ - **Runtime-compatible**, but technically a build-time break for v0.3.0 clients
197
+ that relied on an `unknown` payload in `Bridge.on`. A client that needs the
198
+ permissive shape can declare `(payload: Record<string, unknown>) => ...`
199
+ explicitly.
200
+ - `dist/` grew from ~17 KB to ~17.7 KB (minimal type overhead).
201
+
202
+ ### Migration from v0.3.0 → v0.4.0 (client)
203
+
204
+ A client that had:
205
+ ```typescript
206
+ Bridge.on(BRIDGE_KEYS.realTime, (payload) => {
207
+ // payload: Record<string, unknown> in v0.3.0
208
+ });
209
+ ```
210
+
211
+ Still compiles. The gain: `payload` is now `RealTimeAckPayload` — accessing
212
+ `payload.serialNo`/`payload.success`/`payload.value` is type-safe without a cast.
213
+
214
+ ## [0.3.0] — 2026-06-04
215
+
216
+ ### Added
217
+
218
+ - **Auto-respond toggle** in the dev panel — an optional checkbox that, when
219
+ on, programmatically fires `mockEmit` in response to each `Bridge.send`
220
+ (closed loop in mock mode without manual intervention). Conditional render:
221
+ appears only when the client provides the policy in
222
+ `DevPanelOptions.onSendForAutoRespond`.
223
+ - **`DevPanelOptions.onSendForAutoRespond`** — synchronous callback
224
+ `(msg: MockMessage) => { key, payload } | null`. The client provides the
225
+ policy (send → emit mapping); the package provides the mechanism.
226
+ - **`DevPanelOptions.startAutoRespondEnabled`** — initial toggle state.
227
+ Default: `false` (manual stays default; v0.2.0 semantics preserved). The
228
+ client can override to `true` to auto-loop from boot.
229
+ - **`DevPanelOptions.autoRespondDelayMs`** — delay between the `send` and the
230
+ auto-response `mockEmit`. Default: `50` (approximates real native-bridge IPC
231
+ latency).
232
+ - **`AutoRespondResponse`** type exported: `{ key: string; payload: unknown }`.
233
+ - **`MockAdapter.onCallHandler(listener)`** — registers a listener invoked on
234
+ each `callHandler`. Returns an unsubscribe function. Errors in listeners are
235
+ caught + logged (they do not affect other listeners or the bridge pipeline).
236
+ Useful for runtime-inspection integrations (client tests, dev tooling).
237
+
238
+ ### Behavior
239
+
240
+ - The "Auto-respond" toggle persists in memory for the panel's lifetime.
241
+ Re-installing the panel resets it to the declared `startAutoRespondEnabled`.
242
+ - The listener registered by the dev panel is cleaned up automatically on
243
+ `uninstall()` (no leak between re-installs).
244
+ - Errors in the client policy are caught via try/catch + `console.error` —
245
+ they do not destabilize `callHandler`.
246
+
247
+ ### Tests
248
+
249
+ - 52/52 green (40 from v0.2.0 + 12 new: 4 `onCallHandler` + 8 auto-respond).
250
+
251
+ ### Notes
252
+
253
+ - The Layer 1 API (`handshake`, `on`, `send`, `openSettings`) and the initial
254
+ Layer 2 API (`isMock`, `mockEmit`, `getMockState`, `installDevPanel`) are
255
+ **unchanged**. Auto-respond is purely opt-in: a client without a policy sees
256
+ no change in the panel.
257
+ - `dist/` grew from ~14 KB (v0.2.0) to ~17 KB (v0.3.0) — acceptable overhead.
258
+ - Minor bump (`0.2.0 → 0.3.0`): backward-compatible feature, no breaking
259
+ changes in API or behavior.
260
+
261
+ ## [0.2.0] — 2026-06-03
262
+
263
+ ### Added
264
+
265
+ - **Mock mode** — when `window.flutter_inappwebview` is not detected (regular
266
+ browser, Storybook, Vite dev server), the adapter activates automatically.
267
+ Unblocks client development in the browser without building the APK.
268
+ - **`Bridge.isMock`** (readonly getter) — `true` when the mock adapter is active.
269
+ - **`Bridge.mockEmit(key, payload?)`** — dispatches `window.CondorBridge.emit`
270
+ programmatically to simulate native events. Throws outside mock mode.
271
+ - **`Bridge.getMockState()`** — returns `{ isMock, messages, subscriberKeys }`.
272
+ A snapshot of the mock adapter state for asserts in client tests. Throws
273
+ outside mock mode.
274
+ - **`Bridge.installDevPanel(options?)`** — injects a floating HTML panel into
275
+ `document.body` to inspect the bridge in the browser:
276
+ - Header with a "MOCK" badge, sent counter, minimize/maximize button.
277
+ - Sent log — last N (default 20) messages (timestamp, type, payload).
278
+ - Trigger emit — dropdown of `BRIDGE_KEYS` + `handshake-ack` + JSON payload
279
+ textarea + "Emit" button + "Clear sent" button.
280
+ - Auto-refresh via `setInterval(500ms)`.
281
+ - Idempotent — re-install replaces the previous one.
282
+ - Inline styles + `condor-mock-panel-*` classes (reduces CSS conflicts).
283
+ - Options: `position`/`maxMessages`/`theme`/`startCollapsed`.
284
+
285
+ ### Behavior
286
+
287
+ - **Handshake in mock mode is EXPLICIT.** `Bridge.handshake()` does NOT
288
+ auto-resolve in mock. The client/test must call
289
+ `Bridge.mockEmit('handshake-ack', {nativeVersion, accepted})` to unblock the
290
+ Promise. A design decision to force intentional tests.
291
+ - **`Bridge.send` in mock** records `{handlerName, payload, timestamp}` in a
292
+ FIFO buffer (cap 100) + `console.debug`. The buffer is accessible via
293
+ `getMockState().messages`.
294
+
295
+ ### Refactor (non-breaking)
296
+
297
+ - `src/internal/native.ts` refactored to an adapter pattern. `NativeAdapter` is
298
+ a `RealAdapter | MockAdapter` union discriminated by `readonly isMock`.
299
+ `getActiveAdapter()` caches detection; `__resetAdapterForTests()` resets it.
300
+
301
+ ### Tests
302
+
303
+ - 40/40 green (21 from v0.1.0 + 19 new):
304
+ - `tests/mock.test.ts`: 12 tests (isMock, send, mockEmit, explicit handshake,
305
+ getMockState, buffer cap).
306
+ - `tests/dev-panel.test.ts`: 7 tests (DOM injection, interaction, idempotency).
307
+
308
+ ### Notes
309
+
310
+ - The Layer 1 API (`handshake`, `on`, `send`, `openSettings`) is **unchanged**.
311
+ - The `v0.1.0 → v0.2.0` migration is **automatic** — nothing to adjust in client
312
+ code; mock mode only activates when running outside the WebView.
313
+ - `dist/` grew from ~3 KB to ~14 KB (ESM) because of the inline dev panel.
314
+ Considered acceptable for the feature.
315
+
316
+ ## [0.1.0] — 2026-06-03
317
+
318
+ ### Added
319
+
320
+ - **Layer 1 of the bridge schema/runtime:**
321
+ - `Bridge.handshake({clientVersion})` — versioned handshake with native.
322
+ Returns `Promise<HandshakeAck>`. Rejects parallel calls.
323
+ - `Bridge.on(key, cb)` — typed subscribe to observable keys. Returns
324
+ `unsubscribe`. An error in one subscriber does not affect others.
325
+ - `Bridge.send(type, payload?)` — generic command to native via
326
+ `window.flutter_inappwebview.callHandler`.
327
+ - `Bridge.openSettings()` — sugar over `send('openSettings')`.
328
+ - **`BRIDGE_KEYS` catalog** — 9 keys in camelCase JS API mirroring the native
329
+ foreground com keys: `pingPong`, `userDevice`, `deviceLightSensors`,
330
+ `syncTime`, `realTime`, `deleteMemory`, `deviceNotification`,
331
+ `deviceConnection`, `dfu`.
332
+ - **Types:** `BridgeKey`, `BridgeIncomingMessage`, `BridgeOutgoingEvent`,
333
+ `HandshakeAck`, `HandshakeOptions`, `PayloadOf<K>` (permissive in the MVP).
334
+ - **Automatic bootstrap** of `window.CondorBridge.emit` at module load.
335
+ - **Native host detection** — `flutter_inappwebview` cross-platform (Android +
336
+ iOS). Clear error outside a Flutter WebView (mock mode in v0.2.0).
337
+ - **Tooling:** TypeScript strict + Biome (lint+format) + tsup (dual ESM+CJS
338
+ build + types) + Vitest (jsdom env) + CI.
339
+ - **21 tests** covering handshake, on/emit, send, openSettings, missing host,
340
+ and the key catalog.
341
+
342
+ ### Notes
343
+
344
+ - Payloads use `Record<string, unknown>` in the MVP. Per-key strict types
345
+ landed once the native shapes crystallized.
346
+ - Only `pingPong` was actually emitted by native in the initial release. The
347
+ other 8 keys were reserved as a forward-looking contract.
348
+ - Protocol version (`PROTOCOL_VERSION`) = `'0.1.0'`, sent in the `version` of
349
+ each `BridgeIncomingMessage`. Native accepts any `clientVersion` in the MVP —
350
+ real semver validation lands once there are 2+ versions in the field.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Proprietary — Condor Instruments
2
+
3
+ Copyright (c) 2026 Condor Instruments. All rights reserved.
4
+
5
+ This package is the canonical TypeScript SDK of the JS↔Native bridge of the
6
+ Condor white-label mobile app (`sleep_whitelabel`). It is distributed under a
7
+ proprietary license to authorized partners of Condor Instruments.
8
+
9
+ Unauthorized copying, redistribution, modification, reverse engineering, or
10
+ use of this package or any portion of it, by any medium, is strictly
11
+ prohibited without prior written consent from Condor Instruments.
12
+
13
+ For licensing inquiries, contact Condor Instruments through the official
14
+ support channels.