@condorinst/mobile-bridge 0.19.2 → 0.21.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/AGENTS.md +100 -42
- package/CHANGELOG.md +106 -0
- package/README.md +64 -14
- package/contract/bridge-contract.json +70 -25
- package/dist/cli.cjs +436 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +433 -3
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +2110 -0
- package/dist/dev.cjs +1662 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +683 -0
- package/dist/dev.d.ts +683 -0
- package/dist/dev.js +1611 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.cjs +96 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +265 -19
- package/dist/index.d.ts +265 -19
- package/dist/index.js +94 -3
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +83 -25
- package/docs/bridge-contract.md +154 -25
- package/docs/client-guide.md +39 -28
- package/docs/device-testing.md +84 -0
- package/docs/pilot-onboarding.md +26 -12
- package/docs/troubleshooting.md +83 -18
- package/examples/minimal-home/AGENTS.md +9 -6
- package/examples/minimal-home/README.md +10 -5
- package/examples/minimal-home/build.mjs +2 -1
- package/examples/minimal-home/index.html +2 -0
- package/examples/minimal-home/package.json +3 -2
- package/examples/minimal-home/src/main.ts +151 -73
- package/llms-full.txt +2041 -0
- package/llms.txt +15 -13
- package/package.json +9 -3
package/AGENTS.md
CHANGED
|
@@ -6,19 +6,18 @@ owns login, settings and the Bluetooth connection to the devices; you own what
|
|
|
6
6
|
the person sees on the Home.
|
|
7
7
|
|
|
8
8
|
This file is the contract. Read it before writing code. Everything here is
|
|
9
|
-
enforced
|
|
9
|
+
enforced, either by a gate in the app or by the browser runtime on a real
|
|
10
10
|
device, where mistakes are expensive to diagnose.
|
|
11
11
|
|
|
12
12
|
## The one thing that makes this different
|
|
13
13
|
|
|
14
|
-
**Failures are silent.**
|
|
15
|
-
|
|
16
|
-
exception, no rejected promise, no console error.
|
|
17
|
-
moves on.
|
|
14
|
+
**Failures are silent.** A malformed command, an oversized payload, a command
|
|
15
|
+
sent before the app is ready, a request for an account with no device: nothing
|
|
16
|
+
happens. No exception, no rejected promise, no console error.
|
|
18
17
|
|
|
19
|
-
So
|
|
20
|
-
Drive
|
|
21
|
-
|
|
18
|
+
So never write code whose correctness you infer from the absence of an error.
|
|
19
|
+
Drive the UI from the events that arrive, give every wait a deadline, and
|
|
20
|
+
always have a screen for "it did not come".
|
|
22
21
|
|
|
23
22
|
## Hard rules
|
|
24
23
|
|
|
@@ -35,12 +34,14 @@ wait for.
|
|
|
35
34
|
whole bundle**.
|
|
36
35
|
5. **Build single-chunk.** No code splitting, no ES module imports between
|
|
37
36
|
chunks — `srcdoc` cannot resolve them. Vite: `vite-plugin-singlefile`.
|
|
38
|
-
6. **The `.zip` must be at most 2 MB.**
|
|
39
|
-
|
|
37
|
+
6. **The `.zip` must be at most 2 MB.** `data:` URIs inflate assets by about a
|
|
38
|
+
third.
|
|
40
39
|
7. **Use `BridgeClient`, never raw `postMessage`.** Hand-rolled messages fail
|
|
41
40
|
schema validation and are dropped without a trace.
|
|
42
41
|
8. **Never assume a capability.** Read `nativeVersion` from the handshake and
|
|
43
42
|
branch on it. See the capability matrix in `docs/bridge-contract.md`.
|
|
43
|
+
9. **Never ask once and wait.** Use `BridgeClient.requestUserDevices()` for the
|
|
44
|
+
device list; a single `send('userDevice')` can be lost (see below).
|
|
44
45
|
|
|
45
46
|
## What you cannot reach
|
|
46
47
|
|
|
@@ -49,38 +50,92 @@ wait for.
|
|
|
49
50
|
camera, microphone and geolocation (denied unconditionally) · the `dfu` key and
|
|
50
51
|
command (denied by policy).
|
|
51
52
|
|
|
52
|
-
## The
|
|
53
|
+
## The golden path
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
|
-
npm install @condorinst/mobile-bridge
|
|
56
|
+
npm install @condorinst/mobile-bridge@^0.21.0
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
```ts
|
|
59
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
BridgeClient, BRIDGE_KEYS, versionAtLeast, type UserDevicePayload,
|
|
62
|
+
} from '@condorinst/mobile-bridge';
|
|
60
63
|
|
|
61
|
-
// 1.
|
|
64
|
+
// 1. Subscribe before you ask for anything. Roster changes arrive here too.
|
|
65
|
+
BridgeClient.on(BRIDGE_KEYS.userDevice, (devices: UserDevicePayload) => renderDevices(devices));
|
|
66
|
+
BridgeClient.on(BRIDGE_KEYS.systemStatus, (s) => renderEnvironment(s)); // bluetooth, internet, permissions
|
|
67
|
+
|
|
68
|
+
// 2. Handshake. nativeVersion is the ONLY capability signal; ack.accepted is
|
|
69
|
+
// always true and carries no information — never branch on it.
|
|
62
70
|
const ack = await BridgeClient.handshake({ clientVersion: '1.0.0' });
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
const canAddressById = versionAtLeast(ack.nativeVersion, '0.17.0'); // null → false
|
|
72
|
+
|
|
73
|
+
// 3. Ask for the devices with the helper. It resends until they arrive.
|
|
74
|
+
const result = await BridgeClient.requestUserDevices();
|
|
75
|
+
if (result.status === 'timeout') {
|
|
76
|
+
// No reply in 30 s. hasDevices === false suggests an account with no device;
|
|
77
|
+
// otherwise the app could not load the list. Either way: show an empty state
|
|
78
|
+
// with a "Try again" button that calls requestUserDevices({ getFromServer: true }).
|
|
79
|
+
renderEmpty(result.hasDevices === false);
|
|
80
|
+
} else {
|
|
81
|
+
// 4. Subscribe to the connection state of each device (`add` pairs nothing).
|
|
82
|
+
for (const d of result.devices.devicesConfig) {
|
|
83
|
+
BridgeClient.send('deviceConnection', {
|
|
84
|
+
type: 'add', serialNo: d.serialNo, ...(canAddressById ? { deviceId: d.deviceId } : {}),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 5. History: one page per call; it resolves { success: false, error } on
|
|
90
|
+
// failure and only rejects on timeout.
|
|
91
|
+
const page = await BridgeClient.fetchSensorLog({ requestId: crypto.randomUUID(), serialNo });
|
|
92
|
+
```
|
|
65
93
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
**Why the helper.** Device data comes from a background service that the app
|
|
95
|
+
starts *after* your bundle is already running. Anything you send to it before it
|
|
96
|
+
is up is lost without a reply — while the handshake, `systemStatus` and
|
|
97
|
+
`language` already work, so the bridge *looks* ready. And an account with no
|
|
98
|
+
linked device gets no `userDevice` reply at all.
|
|
99
|
+
|
|
100
|
+
**Identify devices by `deviceId`.** `serialNo` can repeat: an ActLumus and an
|
|
101
|
+
ActLumus Plus may share one. Join `devicesConfig`, `devicesTelemetry` and
|
|
102
|
+
`devicesUserConfig` on `deviceId`; send `serialNo` in every command, plus
|
|
103
|
+
`deviceId` when `nativeVersion` ≥ 0.17.0 (`versionAtLeast` does the comparison).
|
|
104
|
+
The typed shapes (`DeviceConfigItem`, `DeviceTelemetryItem`, …) show every field
|
|
105
|
+
and its unit — `devicesConfig` has no name or battery; those are in the
|
|
106
|
+
telemetry.
|
|
107
|
+
|
|
108
|
+
**Timestamps are device-local.** `log_time` is `'YYYY-MM-DD HH:MM:SS'` in the
|
|
109
|
+
device's wall-clock time. Parse and build it with local `Date` getters, never
|
|
110
|
+
with `toISOString()` or a `'Z'` suffix.
|
|
111
|
+
|
|
112
|
+
Three commands open a confirmation dialog owned by Condor: `syncTime`,
|
|
113
|
+
`deleteMemory` and `logout`. If the person declines, you get nothing — the ack
|
|
114
|
+
never arrives. Do not block your UI on them.
|
|
69
115
|
|
|
70
|
-
|
|
71
|
-
BridgeClient.send('userDevice', { getFromServer: false });
|
|
116
|
+
## Develop in the harness, not in a bare browser tab
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Resolves with { success: false, error } on failure — it only REJECTS on timeout.
|
|
118
|
+
```bash
|
|
119
|
+
npx condor-mobile-bridge dev -i dist/index.html --scenario cold-start
|
|
76
120
|
```
|
|
77
121
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
122
|
+
It serves your bundle at `http://localhost:5173` inside the **same sandbox,
|
|
123
|
+
shell and policy as the app**, with a simulated native side. Opening
|
|
124
|
+
`dist/index.html` directly does not work: `BridgeClient.handshake()` rejects at
|
|
125
|
+
once outside the shell. Rebuild and reload; the harness re-reads the file.
|
|
81
126
|
|
|
82
|
-
|
|
83
|
-
|
|
127
|
+
| Scenario | What it reproduces |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `default` | two devices, realistic latency |
|
|
130
|
+
| `cold-start` | the background service takes 4 s; earlier `ping`/`userDevice` are lost |
|
|
131
|
+
| `no-devices` | `userDevice` never answers; `hasDevices: false` |
|
|
132
|
+
| `shared-serial` | an ActLumus and a Plus share serial 7047 |
|
|
133
|
+
| `offline` | no internet; `sensorLog` fails |
|
|
134
|
+
|
|
135
|
+
The harness console (`[condor dev]`) says what was sent (with its arguments),
|
|
136
|
+
answered and dropped. The **◀ OS back** button at the top presses the OS back
|
|
137
|
+
button. `--native-version null` (or `0.15.0`, …) makes the handshake report that
|
|
138
|
+
version, to test your capability branches.
|
|
84
139
|
|
|
85
140
|
## The validation loop — run this before you call the work done
|
|
86
141
|
|
|
@@ -91,22 +146,23 @@ npx condor-mobile-bridge validate -i dist/bundle.zip --strict
|
|
|
91
146
|
|
|
92
147
|
`validate` applies the same gates the app applies on download, plus a lint for
|
|
93
148
|
the APIs the sandbox blocks at runtime. Under `--strict` it exits non-zero on
|
|
94
|
-
warnings too. **Treat a non-zero exit as a build failure.**
|
|
95
|
-
|
|
96
|
-
It is a textual check, so it cannot see through minification: a clean run is
|
|
97
|
-
necessary, not sufficient. It will not catch a capability you assumed without
|
|
98
|
-
checking `nativeVersion`.
|
|
149
|
+
warnings too. **Treat a non-zero exit as a build failure.** It is a textual
|
|
150
|
+
check, so a clean run is necessary, not sufficient.
|
|
99
151
|
|
|
100
152
|
## Definition of done
|
|
101
153
|
|
|
102
154
|
- [ ] `validate --strict` exits 0.
|
|
103
|
-
- [ ] The
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- [ ]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
- [ ] The bundle works in **every** harness scenario: it leaves the loading
|
|
156
|
+
screen in `cold-start`, shows an empty state in `no-devices`, keeps two
|
|
157
|
+
cards apart in `shared-serial`, and explains the failure in `offline`.
|
|
158
|
+
- [ ] The UI renders before any event arrives — first paint must not depend on
|
|
159
|
+
the bridge.
|
|
160
|
+
- [ ] Every capability beyond the baseline is guarded by a `nativeVersion` check
|
|
161
|
+
(`versionAtLeast`), and the page still renders with
|
|
162
|
+
`--native-version null`.
|
|
163
|
+
- [ ] Nothing waits forever on a reply that may never come.
|
|
164
|
+
|
|
165
|
+
To put the bundle on a phone, read `docs/device-testing.md`.
|
|
110
166
|
|
|
111
167
|
## Where to read more
|
|
112
168
|
|
|
@@ -115,7 +171,9 @@ checking `nativeVersion`.
|
|
|
115
171
|
| Every key, command, error shape and capability | `docs/bridge-contract.md` |
|
|
116
172
|
| The same, as JSON you can parse | `contract/bridge-contract.json` |
|
|
117
173
|
| Step-by-step build of a Home | `docs/agent-guide.md` |
|
|
174
|
+
| "My command did nothing" / "the device list never came" | `docs/troubleshooting.md` |
|
|
175
|
+
| Getting the bundle onto a device, and seeing its errors | `docs/device-testing.md` |
|
|
118
176
|
| Every hard limit, with the code that enforces it | `docs/limits.md` |
|
|
119
|
-
| "My command did nothing" | `docs/troubleshooting.md` |
|
|
120
177
|
| Payload shapes, authoritative | `dist/index.d.ts` (your editor shows them) |
|
|
121
178
|
| A working client you can copy | `examples/minimal-home/` |
|
|
179
|
+
| All of the above in one file | `llms-full.txt` |
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,112 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.21.0] — 2026-10-20
|
|
11
|
+
|
|
12
|
+
A client built from the 0.20.0 docs worked in the browser and stalled on the
|
|
13
|
+
first real device: the device list never arrived. This release makes the
|
|
14
|
+
browser behave like the device, gives the boot sequence a helper, and types the
|
|
15
|
+
device payloads.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`condor-mobile-bridge dev`** — serves a bundle (`index.html`, `dist/` or
|
|
20
|
+
`.zip`) in a browser harness that reproduces the app: the bundle runs in
|
|
21
|
+
`<iframe sandbox="allow-scripts">` behind the real `BridgeShell`, with the
|
|
22
|
+
app's policy and CSP and a simulated native side. Scenarios: `default`,
|
|
23
|
+
`cold-start` (requests lost while the app's background service starts),
|
|
24
|
+
`no-devices` (`userDevice` never answers), `shared-serial` and `offline`.
|
|
25
|
+
The page logs every command, reply and dropped request.
|
|
26
|
+
- **`@condorinst/mobile-bridge/dev`** — the same harness as a library
|
|
27
|
+
(`createDevHarness`, `createSimulatedNative`, scenarios and fixtures) for a
|
|
28
|
+
host page on your own dev server.
|
|
29
|
+
- **`BridgeClient.requestUserDevices({ getFromServer })`** — requests the device
|
|
30
|
+
list and resends it with backoff (1.5 s, 3 s, 6 s, then every 10 s, and on
|
|
31
|
+
every `systemStatus`) until it arrives. Never rejects: resolves
|
|
32
|
+
`{ status: 'timeout', hasDevices }` after 30 s, with `hasDevices` from the
|
|
33
|
+
latest `systemStatus` received — also one that arrived before the call.
|
|
34
|
+
- **`BridgeClient.waitForService()`** — resends `ping` until the app's
|
|
35
|
+
background service answers.
|
|
36
|
+
- **Typed device payloads**: `DeviceConfigItem`, `DeviceTelemetryItem`,
|
|
37
|
+
`DeviceUserConfigItem`, `DeviceSyncErrorItem` and `SensorLogItem`, with units
|
|
38
|
+
(battery and memory in %, `bleConnectionPeriod` in minutes, epoch ms) and the
|
|
39
|
+
join rule across the three arrays of `userDevice` (`deviceId`).
|
|
40
|
+
- **`docs/device-testing.md`** — how a bundle reaches a phone, development vs
|
|
41
|
+
production, inspecting the WebView, and where errors go.
|
|
42
|
+
- **`llms-full.txt`** — `AGENTS.md`, the docs and the reference example in one
|
|
43
|
+
file, generated on build.
|
|
44
|
+
- Contract: `payloadItems`, `devTools`, `sensorLog` paging rules, and two
|
|
45
|
+
silent-drop paths for `userDevice` (request before the background service is
|
|
46
|
+
up; a list that failed to load, including an account with no device).
|
|
47
|
+
- `validate` fails a bundle that includes the dev harness
|
|
48
|
+
(`dev-harness-bundled`).
|
|
49
|
+
- `dev --native-version <v|null>` sets the `nativeVersion` the harness
|
|
50
|
+
reports; the harness page has an **◀ OS back** button (emits
|
|
51
|
+
`navigationBack` only after the bundle reported `canGoBack: true`), and its
|
|
52
|
+
log shows the arguments of every command.
|
|
53
|
+
- `versionAtLeast(version, min)` — numeric capability check; `null` → `false`.
|
|
54
|
+
- `SensorLogItem` declares the ActLumus Plus channels `light_sensor_x/_y/_z/_fd`
|
|
55
|
+
(`null` on gen1).
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **`BridgeClient.handshake()` rejects at once outside the shell** (a page
|
|
60
|
+
opened directly in a browser), with a message pointing to the harness. It
|
|
61
|
+
used to wait 5 s and time out.
|
|
62
|
+
- `UserDevicePayload` arrays and `SensorLogPayload.logs` are typed instead of
|
|
63
|
+
`Record<string, unknown>`. Code that cast items to `Record<string, unknown>`
|
|
64
|
+
still compiles.
|
|
65
|
+
- `AGENTS.md` is rewritten around the boot sequence that survives a cold start
|
|
66
|
+
and an account with no device; the docs no longer describe a browser mock for
|
|
67
|
+
`BridgeClient`, which never existed.
|
|
68
|
+
- `examples/minimal-home` uses the helpers and the harness, and reads the device
|
|
69
|
+
name from the telemetry (it read a field `devicesConfig` does not have).
|
|
70
|
+
`examples/client_demo` drops its private mock and reads `log_time` as
|
|
71
|
+
device-local time.
|
|
72
|
+
|
|
73
|
+
### Documented
|
|
74
|
+
|
|
75
|
+
- `userDevice` is not guaranteed to answer; `systemStatus.hasDevices` is a hint
|
|
76
|
+
(`false` also when the lookup failed); what `getFromServer` does.
|
|
77
|
+
- `deviceConnection` `type: 'add'` subscribes to a device's connection state and
|
|
78
|
+
pairs nothing.
|
|
79
|
+
- `sensorLog` error codes are matched by prefix; transport failures carry free
|
|
80
|
+
text. A full 7-day sweep recipe (keep the range on every page).
|
|
81
|
+
- The back button example keeps navigation in memory instead of `history`.
|
|
82
|
+
- `sensorLog`: without a range the first page is the most recent; with
|
|
83
|
+
`startTime` the oldest of the range. The log period is programmable per
|
|
84
|
+
device (default 60 s).
|
|
85
|
+
|
|
86
|
+
## [0.20.0] — 2026-10-13
|
|
87
|
+
|
|
88
|
+
### Added
|
|
89
|
+
|
|
90
|
+
- **Address one device by `deviceId` when two share a `serialNo`.** An ActLumus
|
|
91
|
+
and an ActLumus Plus have separate serial sequences, so one account can hold
|
|
92
|
+
both with the same `serialNo`. `realTime`, `syncTime`, `deleteMemory` and
|
|
93
|
+
`deviceConnection` accept an optional `deviceId` next to `serialNo`; keep
|
|
94
|
+
sending `serialNo` as well, because an older app drops in silence a command
|
|
95
|
+
that lacks it. Requires `nativeVersion` 0.17.0, which the contract now lists
|
|
96
|
+
as the `deviceIdAddressing` capability.
|
|
97
|
+
- **`deviceId` in the device events.** `deviceConnection`, `deviceNotification`
|
|
98
|
+
and `deviceLightSensors` carry an optional `deviceId`, present only when the
|
|
99
|
+
`serialNo` is shared by more than one device. The acks of `realTime`,
|
|
100
|
+
`syncTime` and `deleteMemory` echo the `deviceId` you sent and always keep
|
|
101
|
+
`serialNo`.
|
|
102
|
+
- **`systemStatus.deviceSyncErrorById`**, the same errors keyed by `deviceId`
|
|
103
|
+
for every device. `deviceSyncError` leaves out a `serialNo` that two devices
|
|
104
|
+
share, since no single device owns it.
|
|
105
|
+
- The contract and `docs/bridge-contract.md` document the rule in a new section,
|
|
106
|
+
and list `DEVICE_SERIAL_AMBIGUOUS` among the `sensorLog` error codes: the
|
|
107
|
+
backend answers it for a shared `serialNo`, and `sensorLog` takes no
|
|
108
|
+
`deviceId` yet.
|
|
109
|
+
|
|
110
|
+
### Notes
|
|
111
|
+
|
|
112
|
+
- Additive and backward-compatible: every new field is optional and older
|
|
113
|
+
clients ignore it. The model of a device is **not** carried by these events;
|
|
114
|
+
read `modelId` from the `userDevice` list.
|
|
115
|
+
|
|
10
116
|
## [0.19.2] — 2026-09-23
|
|
11
117
|
|
|
12
118
|
### Fixed
|
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ Over the network, every document is plain text at a pinned, unauthenticated
|
|
|
20
20
|
URL. `llms.txt` is the index:
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
23
|
+
https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.21.0/llms.txt
|
|
24
|
+
https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.21.0/llms-full.txt # every doc in one file
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
The machine-readable contract — keys, commands, limits, capability matrix — is
|
|
@@ -43,7 +44,15 @@ const ack = await BridgeClient.handshake({ clientVersion: '1.0.0' });
|
|
|
43
44
|
// ack.nativeVersion is your capability signal. ack.accepted is always true.
|
|
44
45
|
|
|
45
46
|
BridgeClient.on(BRIDGE_KEYS.userDevice, (devices) => render(devices));
|
|
46
|
-
BridgeClient.
|
|
47
|
+
const result = await BridgeClient.requestUserDevices(); // resends until the list arrives
|
|
48
|
+
if (result.status === 'timeout') renderEmptyState();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Develop it in the browser with the harness, which runs the bundle under the
|
|
52
|
+
same sandbox and shell as the app:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx condor-mobile-bridge dev -i dist/index.html --scenario cold-start
|
|
47
56
|
```
|
|
48
57
|
|
|
49
58
|
Then read the [Client Guide](docs/client-guide.md) — _"Build your White Label
|
|
@@ -63,11 +72,15 @@ The published package ships a prebuilt `dist/` (ESM + CJS + type
|
|
|
63
72
|
declarations) — **no build toolchain is required on your side**.
|
|
64
73
|
|
|
65
74
|
> **Versioning during the pilot.** The package is on `0.x` (beta). Pin
|
|
66
|
-
> `"@condorinst/mobile-bridge": "^0.
|
|
67
|
-
> caret range auto-updates **patches only** (`0.
|
|
75
|
+
> `"@condorinst/mobile-bridge": "^0.21.0"`. Under semver, while on `0.x` a
|
|
76
|
+
> caret range auto-updates **patches only** (`0.21.x`); moving to `0.22.0` is
|
|
68
77
|
> an intentional opt-in. `1.0.0` is reserved for production.
|
|
69
78
|
|
|
70
|
-
## Quickstart
|
|
79
|
+
## Quickstart (legacy, shell-side `Bridge`)
|
|
80
|
+
|
|
81
|
+
> **Not for client bundles.** This is the API the Condor shell uses to talk to
|
|
82
|
+
> the native layer. A client bundle cannot reach it from the sandbox — use
|
|
83
|
+
> `BridgeClient` (above, and in the next section).
|
|
71
84
|
|
|
72
85
|
```typescript
|
|
73
86
|
import { Bridge, BRIDGE_KEYS } from '@condorinst/mobile-bridge';
|
|
@@ -82,8 +95,8 @@ const unsubscribe = Bridge.on(BRIDGE_KEYS.deviceConnection, (payload) => {
|
|
|
82
95
|
console.log('Device:', payload);
|
|
83
96
|
});
|
|
84
97
|
|
|
85
|
-
// Command: enable real-time
|
|
86
|
-
Bridge.send('realTime', {
|
|
98
|
+
// Command: enable real-time for one device
|
|
99
|
+
Bridge.send('realTime', { serialNo: 1234, value: true });
|
|
87
100
|
|
|
88
101
|
// Native hook: open the app's native Settings screen
|
|
89
102
|
Bridge.openSettings();
|
|
@@ -134,8 +147,8 @@ skip it and the app just stays on Home when back is pressed. See
|
|
|
134
147
|
dev/mock APIs live only on the shell.
|
|
135
148
|
- `handshake` resolves with `{ shellVersion, nativeVersion, accepted }`. Since
|
|
136
149
|
0.17.0 the shell relays `nativeVersion` into the iframe — it is the only
|
|
137
|
-
capability signal you get, and it is `null`
|
|
138
|
-
older than 0.6.0. `accepted` is always `true`; never branch on it.
|
|
150
|
+
capability signal you get, and it is `null` when the shell could not reach
|
|
151
|
+
the native side and against shells older than 0.6.0. `accepted` is always `true`; never branch on it.
|
|
139
152
|
- Commands not allowed by the `ShellPolicy` are silently dropped (with a
|
|
140
153
|
`console.warn`). The external client has no visibility into which commands
|
|
141
154
|
do or do not exist.
|
|
@@ -198,6 +211,34 @@ if (!verify.ok) {
|
|
|
198
211
|
| **3** | (Native side) | Shell nonce, stamped on every outgoing message via `Bridge.setOutgoingStamp`, validated natively. **This is the guarantee.** |
|
|
199
212
|
| **4** | (Fallback if 1+2 fail) | Dual `InAppWebView` — shell and client in separate WebViews. |
|
|
200
213
|
|
|
214
|
+
## Dev harness — `condor-mobile-bridge dev`
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
npx condor-mobile-bridge dev -i dist/index.html [--scenario cold-start] [--port 5173]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Serves the bundle (an `index.html`, a `dist/` folder or a `.zip`) at
|
|
221
|
+
`http://localhost:5173` inside a sandboxed iframe, behind the real
|
|
222
|
+
`BridgeShell` with the app's policy and CSP, and a simulated native side. The
|
|
223
|
+
input is re-read on every reload.
|
|
224
|
+
|
|
225
|
+
Scenarios reproduce device behaviour a naive mock hides: `default`,
|
|
226
|
+
`cold-start` (requests lost while the app's background service starts),
|
|
227
|
+
`no-devices` (`userDevice` never answers), `shared-serial` (two devices, one
|
|
228
|
+
`serialNo`) and `offline`. The page's console (`[condor dev]`) logs what was
|
|
229
|
+
sent, answered and dropped.
|
|
230
|
+
|
|
231
|
+
With your own dev server, mount the same harness from the **host** page:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { createDevHarness } from '@condorinst/mobile-bridge/dev';
|
|
235
|
+
const html = await (await fetch('/bundle/index.html')).text();
|
|
236
|
+
await createDevHarness({ html, scenario: 'no-devices', mount: document.getElementById('device')! });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Never import `@condorinst/mobile-bridge/dev` into the bundle itself: `validate`
|
|
240
|
+
fails it with `dev-harness-bundled`.
|
|
241
|
+
|
|
201
242
|
## Build CLI — `condor-mobile-bridge build`
|
|
202
243
|
|
|
203
244
|
Generates the **self-contained `.zip` bundle** that the white-label app
|
|
@@ -280,10 +321,10 @@ The client passes `Record<string, unknown>`.
|
|
|
280
321
|
|
|
281
322
|
## Versioning policy (semver)
|
|
282
323
|
|
|
283
|
-
- **PATCH** (`0.
|
|
324
|
+
- **PATCH** (`0.21.x`): bugfix with no API change.
|
|
284
325
|
- **MINOR** (`0.x.0`): new observable key, new command, backward-compatible API.
|
|
285
326
|
> While on `0.x`, a minor bump may carry breaking changes (semver treats
|
|
286
|
-
> pre-1.0 minors as breaking). Pin `^0.
|
|
327
|
+
> pre-1.0 minors as breaking). Pin `^0.21.0` to stay on safe patches.
|
|
287
328
|
- **MAJOR** (`x.0.0`): incompatible schema change (key rename, payload shape
|
|
288
329
|
change, public API change). `1.0.0` marks the production-stable contract.
|
|
289
330
|
|
|
@@ -301,9 +342,15 @@ The package detects automatically:
|
|
|
301
342
|
JSON string. Takes precedence when present.
|
|
302
343
|
- **Android / iOS (legacy)** — `window.flutter_inappwebview` (via the `flutter_inappwebview`
|
|
303
344
|
package). Still supported for the transition; will be removed in a future minor.
|
|
304
|
-
- **Browser outside the app** (dev/test): **mock mode** activates automatically
|
|
345
|
+
- **Browser outside the app** (dev/test): **mock mode** activates automatically
|
|
346
|
+
for `Bridge`. A client bundle (`BridgeClient`) runs in the browser only
|
|
347
|
+
through the `dev` harness.
|
|
305
348
|
|
|
306
|
-
## Mock mode
|
|
349
|
+
## Mock mode (shell-side `Bridge`)
|
|
350
|
+
|
|
351
|
+
> For code that uses the legacy `Bridge` directly — the shell, tests,
|
|
352
|
+
> Storybook. A client bundle uses `BridgeClient`, which has no mock of its own:
|
|
353
|
+
> develop it with `condor-mobile-bridge dev` (above).
|
|
307
354
|
|
|
308
355
|
When the package runs outside the Flutter WebView (regular browser, Vite dev
|
|
309
356
|
server, Storybook), the adapter activates mock mode automatically. You can
|
|
@@ -361,7 +408,10 @@ console.log(state.subscriberKeys);
|
|
|
361
408
|
// → ['deviceConnection']
|
|
362
409
|
```
|
|
363
410
|
|
|
364
|
-
## Dev panel
|
|
411
|
+
## Dev panel (shell-side `Bridge`)
|
|
412
|
+
|
|
413
|
+
> The `dev` harness installs this panel for you. Call `installDevPanel` yourself
|
|
414
|
+
> only when hosting `Bridge` directly.
|
|
365
415
|
|
|
366
416
|
An opt-in HTML panel to inspect the bridge in the browser during development.
|
|
367
417
|
|