@condorinst/mobile-bridge 0.22.0 → 0.23.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 +133 -34
- package/CHANGELOG.md +175 -0
- package/README.md +30 -481
- package/contract/bridge-contract.json +32 -21
- package/dist/cli.cjs +126 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +126 -29
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +388 -55
- package/dist/dev.cjs +389 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +239 -9
- package/dist/dev.d.ts +239 -9
- package/dist/dev.js +388 -55
- package/dist/dev.js.map +1 -1
- package/dist/index.cjs +30 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -26
- package/dist/index.d.ts +154 -26
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +9 -265
- package/docs/bridge-contract.md +313 -26
- package/docs/client-guide.md +34 -400
- package/docs/device-testing.md +234 -0
- package/docs/pilot-onboarding.md +41 -115
- package/docs/shell-integration.md +292 -0
- package/docs/troubleshooting.md +19 -11
- package/examples/minimal-home/README.md +2 -2
- package/examples/minimal-home/package.json +1 -1
- package/examples/minimal-home/src/main.ts +23 -3
- package/llms-full.txt +764 -865
- package/llms.txt +13 -15
- package/package.json +1 -1
package/docs/device-testing.md
CHANGED
|
@@ -74,6 +74,26 @@ You need a **manager** user of the company. The command needs a terminal (it
|
|
|
74
74
|
prompts), so it does not run in CI; and an AI agent must not run it on its own
|
|
75
75
|
— it publishes to real people and asks for your password.
|
|
76
76
|
|
|
77
|
+
## Loading the bundle from your machine (development builds)
|
|
78
|
+
|
|
79
|
+
From `nativeVersion` 0.18.0 a **development** build of the app can skip the
|
|
80
|
+
published bundle and load yours straight from your computer, on every launch
|
|
81
|
+
and every return to the foreground — no publish, no other user affected.
|
|
82
|
+
|
|
83
|
+
The address is fixed when Condor builds the app, so use one that is the same on
|
|
84
|
+
every machine: the harness on `localhost`, forwarded over USB (Android).
|
|
85
|
+
|
|
86
|
+
1. Ask Condor for a development build made with
|
|
87
|
+
`--dart-define=IS_DEV=true --dart-define=WL_BUNDLE_DEV_URL=http://localhost:5173/__condor/client.html`.
|
|
88
|
+
2. Serve your bundle with the harness — it inlines it like `build` does:
|
|
89
|
+
`npx condor-mobile-bridge dev -i dist/index.html`.
|
|
90
|
+
3. Forward the port to the phone: `adb reverse tcp:5173 tcp:5173`.
|
|
91
|
+
4. Rebuild your bundle, bring the app to the foreground: the new bundle loads.
|
|
92
|
+
|
|
93
|
+
The app still applies the 2 MB and self-contained checks; it never caches this
|
|
94
|
+
bundle, so a normal build shows the published one again. Store builds refuse
|
|
95
|
+
the flag.
|
|
96
|
+
|
|
77
97
|
## Seeing what the bundle does on the phone
|
|
78
98
|
|
|
79
99
|
**Release builds of the app cannot be inspected.** Ask Condor for a
|
|
@@ -96,6 +116,220 @@ by the app (wrong argument type, unknown command, a request lost while the
|
|
|
96
116
|
background service starts) leaves no trace in the console — reproduce it in the
|
|
97
117
|
harness instead, whose console says what was dropped and why.
|
|
98
118
|
|
|
119
|
+
## What `build` does
|
|
120
|
+
|
|
121
|
+
Generates the **self-contained `.zip` bundle** that the white-label app
|
|
122
|
+
consumes. The app injects the client HTML via `iframe.srcdoc` (origin `null`,
|
|
123
|
+
no base URL) → **relative references do not resolve** and the CSP blocks
|
|
124
|
+
external fetches; therefore the `.zip` must contain **a single `index.html`**
|
|
125
|
+
with inline JS/CSS and media as `data:` URIs.
|
|
126
|
+
|
|
127
|
+
The CLI does this from your build's `dist/`: it reads `index.html`, **inlines**
|
|
128
|
+
`<script src>` (escaping `</script>`) and `<link rel=stylesheet>`, resolves CSS
|
|
129
|
+
`url()` and local `<img>`/icons to `data:` URIs, and packs a `.zip` with
|
|
130
|
+
`index.html` at the root. External refs (`https://…`) are **preserved** (and
|
|
131
|
+
blocked by the app's CSP at runtime).
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx condor-mobile-bridge build -i dist/index.html -o dist/bundle.zip
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Option | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `-i, --html <path>` | Input HTML (e.g. `dist/index.html`). Required. |
|
|
140
|
+
| `-o, --out <path>` | Output `.zip` (default: `dist/whitelabel-bundle.zip`). |
|
|
141
|
+
| `--max-bytes <n>` | **Warning** threshold in bytes (default: `2097152` = 2 MB). |
|
|
142
|
+
|
|
143
|
+
Output (for registering the bundle with the backend — `bundleSize`/`bundleHash`):
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Built dist/bundle.zip
|
|
147
|
+
entry: index.html (123.4 KB self-contained)
|
|
148
|
+
bundleSize (zip): 45678 bytes
|
|
149
|
+
bundleHash (zip): sha256-<hex>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The hash is **deterministic** (fixed mtime on the zip entry) — the same input
|
|
153
|
+
produces the same `bundleHash`. Above the threshold, the CLI **warns** (does
|
|
154
|
+
not block): remember that **the app rejects** a `.zip` larger than 2 MB and
|
|
155
|
+
falls back to the mock.
|
|
156
|
+
|
|
157
|
+
> ⚠️ **Constraint: single-chunk build.** The inliner is post-build and assumes
|
|
158
|
+
> a `dist` with **one JS + one CSS + assets**. Builds with **code-splitting /
|
|
159
|
+
> ES modules** that `import` across chunks **break** in `srcdoc` (the imports
|
|
160
|
+
> do not resolve). Configure your build to emit a single file:
|
|
161
|
+
> - **Vite:** `vite-plugin-singlefile` (or `build.rollupOptions.output.inlineDynamicImports: true` + `manualChunks: undefined`).
|
|
162
|
+
> - **CRA:** build without code-splitting (no `React.lazy`/dynamic `import()` in routes), single bundle.
|
|
163
|
+
>
|
|
164
|
+
> Inlining inflates `data:` media by ~33% (base64) — size against the 2 MB limit.
|
|
165
|
+
|
|
166
|
+
## Scripted tests
|
|
167
|
+
|
|
168
|
+
`npx condor-mobile-bridge dev` is also a test target. On the harness page,
|
|
169
|
+
`window.condorDev` drives the simulated app; your bundle runs in the iframe
|
|
170
|
+
under it, as on the phone.
|
|
171
|
+
|
|
172
|
+
| `window.condorDev.…` | Does |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| `ready` | `true` once the bundle is mounted **and** its handshake was answered — wait for it |
|
|
175
|
+
| `scenario` | the scenario in use, after every override |
|
|
176
|
+
| `log` | everything sent, answered and dropped: `{ at, kind, command?, key?, detail, args?, payload? }[]`. `detail` is a shortened line; `args` (sent) and `payload` (reply) are complete |
|
|
177
|
+
| `setStatus({ internet: false })` | changes `systemStatus` and emits it, as the app does; without internet `sensorLog` fails, and it recovers with `{ internet: true }` |
|
|
178
|
+
| `setConnected(deviceId, true \| false)` | starts or ends a device's BLE session: `deviceConnection` (if you sent `add`), the `deviceLightSensors` stream (if `realTime` is on) and the sync's `deviceNotification` steps, 200 ms apart, ending in `pipeline_done_notification`. As on the phone, the session then **ends by itself** — unless `realTime` is on for that device, which keeps it open until `realTime` goes off |
|
|
179
|
+
| `emit(key, payload)` | any native event, typed as in `docs/bridge-contract.md` — e.g. `emit('language', { deviceLocale: 'pt-BR', appLanguage: 'pt', resolvedLanguage: 'pt' })` |
|
|
180
|
+
| `setConfirmations('accept' \| 'decline' \| 'ask')` | answers the app's confirmation dialog for you |
|
|
181
|
+
| `pressBack()` | the OS back button |
|
|
182
|
+
|
|
183
|
+
URL parameters set the same things before the bundle loads:
|
|
184
|
+
`?scenario=offline`, `?nativeVersion=0.17.0`, `?confirm=accept`,
|
|
185
|
+
`?latency=0`, `?panel=0`. A `?scenario=` in the URL **replaces** a
|
|
186
|
+
`--scenario file.json` given to the CLI (the file is not loaded at all), so
|
|
187
|
+
with a file leave `scenario` out of the URL. The CLI equivalents are `--latency <ms>` (`0` makes
|
|
188
|
+
a test suite several times faster) and `--no-panel` (the floating MOCK panel
|
|
189
|
+
covers the bottom of your page and can swallow clicks).
|
|
190
|
+
|
|
191
|
+
**Your own scenario.** `--scenario edge.json` takes a file that extends a named
|
|
192
|
+
scenario; every field is optional:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"extends": "default",
|
|
197
|
+
"nativeVersion": "0.17.0",
|
|
198
|
+
"latencyMs": 0,
|
|
199
|
+
"serviceStartMs": 4000,
|
|
200
|
+
"logPeriodS": 300,
|
|
201
|
+
"firstSessionAfterMs": null,
|
|
202
|
+
"sessionFailAt": "get_data_log",
|
|
203
|
+
"history": { "days": 14, "dockHours": [7], "gapHours": [2, 3], "gapDates": ["2026-10-18"] },
|
|
204
|
+
"sensorLogFailOnRequest": 3,
|
|
205
|
+
"userDeviceError": "NETWORK",
|
|
206
|
+
"sensorLogError": "INVALID_TIME_RANGE",
|
|
207
|
+
"systemStatus": { "bluetooth": false, "missingEssentialPermissions": ["bluetoothScan"] },
|
|
208
|
+
"devices": { "lastUpdate": 0, "devicesConfig": [], "devicesTelemetry": [], "devicesUserConfig": [] }
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`devices` is the `userDevice` payload to answer (`UserDevicePayload`).
|
|
213
|
+
`firstSessionAfterMs` is when the first device starts a BLE session: `3000` in
|
|
214
|
+
`default`, absent in every other scenario; `null` turns it off.
|
|
215
|
+
`systemStatus` is merged into the base scenario's. `sessionFailAt` makes every
|
|
216
|
+
simulated sync fail at that stage (`<stage>_error`, no
|
|
217
|
+
`pipeline_done_notification`); without internet syncs fail at
|
|
218
|
+
`send_data_log` anyway. A pending `syncTime`/`deleteMemory` shows up as its step
|
|
219
|
+
in the next session. A failed sync also puts an English text in
|
|
220
|
+
`systemStatus.deviceSyncErrorById` (and `deviceSyncError`) and emits it; the
|
|
221
|
+
next successful sync of that device clears it.
|
|
222
|
+
|
|
223
|
+
`history` shapes the simulated `sensorLog` (`HistoryOptions`): `days` before
|
|
224
|
+
now, `dockHours` (local hours with `off_wrist: 0` and no light), `gapHours`
|
|
225
|
+
(local hours with no log, every day) and `gapDates` (whole local dates with no
|
|
226
|
+
log). `sensorLogFailOnRequest: N` makes the Nth `sensorLog` request fail once
|
|
227
|
+
with `Connection failed` — a failure in the middle of a sweep.
|
|
228
|
+
|
|
229
|
+
**The data the harness serves.** Two devices (`twoDevices()`: ActLumus 4534 /
|
|
230
|
+
id 9001 set to the wrist, ActLumus Plus 5120 / id 9002 set to the necklace).
|
|
231
|
+
Their `sensorLog`: `history.days` (14 by default; set it to 30 for a 30-day
|
|
232
|
+
report) × 24 h back from **now** (not from midnight), one
|
|
233
|
+
log per `logPeriodS` (60 s by default); `body_part` is the device's
|
|
234
|
+
`deviceBodyPart`; worn (`off_wrist: 1`) all day except the dock hour 07:00–08:00
|
|
235
|
+
local (`off_wrist: 0`); light follows a daylight curve on the browser's local
|
|
236
|
+
clock — deterministic for a given timestamp, so a test can recompute what a
|
|
237
|
+
chart should show. The `bodyPart` filter of a query is not simulated.
|
|
238
|
+
`deviceLightSensors` arrives once per second.
|
|
239
|
+
|
|
240
|
+
**Other harness behaviour worth knowing in a test:**
|
|
241
|
+
- `language` is answered from the **browser's** `navigator.language`, with
|
|
242
|
+
`appLanguage: null`. Fix it in a test with
|
|
243
|
+
`condorDev.emit('language', …)` before asserting on text.
|
|
244
|
+
- A declined confirmation (`?confirm=decline`) produces nothing: no ack, no
|
|
245
|
+
event — as on the phone.
|
|
246
|
+
- With `nativeVersion` null the handshake resolves only after the shell's 2 s
|
|
247
|
+
relay timeout, so such a test starts 2 s later.
|
|
248
|
+
- The harness has no fake clock: a time-out of yours (45 s for a declined
|
|
249
|
+
confirmation, minutes for a lost sync) belongs in a unit test with your test
|
|
250
|
+
runner's fake timers, not in a browser test.
|
|
251
|
+
- Event order is not the phone's: no order between events is guaranteed there
|
|
252
|
+
either (`docs/bridge-contract.md`, "Emission cadence"), so don't let a test
|
|
253
|
+
depend on it.
|
|
254
|
+
- `condorDev.log` keeps the **last 500** entries: read it as you go in a long
|
|
255
|
+
test. The
|
|
256
|
+
fixtures are exported from `@condorinst/mobile-bridge/dev` (`twoDevices`,
|
|
257
|
+
`sharedSerial`, `systemStatus`, `sensorLogPage`, `lightSensorsReading`,
|
|
258
|
+
`formatLocalTimestamp`) for unit tests without a browser. Pass `sensorLogPage`
|
|
259
|
+
the device's `deviceId` (`{ deviceId }`); without it `device_id` is `0`.
|
|
260
|
+
|
|
261
|
+
**Like the phone, not like `localhost`.** The harness removes
|
|
262
|
+
`crypto.randomUUID` from your iframe: on `localhost` it would exist, and on the
|
|
263
|
+
phone it probably does not.
|
|
264
|
+
|
|
265
|
+
A test with `puppeteer-core` and the Chrome already installed (`puppeteer`
|
|
266
|
+
works the same and downloads its own browser):
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
import puppeteer from 'puppeteer-core';
|
|
270
|
+
import { spawn } from 'node:child_process';
|
|
271
|
+
|
|
272
|
+
const port = 5180;
|
|
273
|
+
const cli = 'node_modules/@condorinst/mobile-bridge/dist/cli.js';
|
|
274
|
+
const dev = spawn('node', [cli, 'dev', '-i', 'dist/index.html',
|
|
275
|
+
'--port', String(port), '--latency', '0', '--no-panel'], { stdio: 'inherit' });
|
|
276
|
+
for (let i = 0; i < 50; i++) { // wait for the port, not a fixed sleep
|
|
277
|
+
try { await fetch(`http://localhost:${port}/`); break; } catch { await new Promise((r) => setTimeout(r, 100)); }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const browser = await puppeteer.launch({
|
|
281
|
+
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // your Chrome
|
|
282
|
+
});
|
|
283
|
+
const page = await browser.newPage();
|
|
284
|
+
await page.emulateTimezone('Pacific/Kiritimati'); // log_time is local: test far from UTC
|
|
285
|
+
await page.goto(`http://localhost:${port}/?confirm=accept`); // the CLI's --scenario applies
|
|
286
|
+
await page.waitForFunction(() => window.condorDev?.ready);
|
|
287
|
+
|
|
288
|
+
const bundle = page.frames().find((f) => f !== page.mainFrame()); // your page
|
|
289
|
+
await bundle.waitForSelector('[data-device-id]'); // assert on your markup, not on text
|
|
290
|
+
|
|
291
|
+
await page.evaluate(() => window.condorDev.setStatus({ internet: false }));
|
|
292
|
+
await bundle.waitForSelector('[data-offline]');
|
|
293
|
+
|
|
294
|
+
const queries = await page.evaluate(() =>
|
|
295
|
+
window.condorDev.log.filter((e) => e.kind === 'sent' && e.command === 'sensorLog').map((e) => e.args));
|
|
296
|
+
|
|
297
|
+
await browser.close();
|
|
298
|
+
dev.kill(); // node, not npx: no orphan process
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The harness does nothing on its own after load, except one session (the first
|
|
302
|
+
device of the `default` scenario connects after 3 s, syncs, and disconnects
|
|
303
|
+
about 4 s later unless `realTime` is on). Internet loss, a
|
|
304
|
+
device disconnecting, or a reading arriving are things your test does through
|
|
305
|
+
`window.condorDev`.
|
|
306
|
+
|
|
307
|
+
### What `validate` checks
|
|
308
|
+
|
|
309
|
+
Errors make the app reject the bundle; warnings are failures with `--strict`.
|
|
310
|
+
The warnings are **textual**: they match strings and comments too, so do not
|
|
311
|
+
leave these names in your code even unused.
|
|
312
|
+
|
|
313
|
+
| Rule | Severity | Matches |
|
|
314
|
+
| --- | --- | --- |
|
|
315
|
+
| `not-self-contained` | error | `<script src>` or `<link rel="stylesheet">` that is not inline |
|
|
316
|
+
| `too-large` | error | a `.zip` over 2 MB |
|
|
317
|
+
| `unreadable-zip` | error | a file that is not a valid `.zip` |
|
|
318
|
+
| `entry-missing` | error | no HTML entry in the `.zip` |
|
|
319
|
+
| `entry-fallback` | warning | no `index.html` at the root of the `.zip` |
|
|
320
|
+
| `dev-harness-bundled` | error | the dev harness compiled into the bundle |
|
|
321
|
+
| `relative-asset` | warning | a relative `<img src>` (use a `data:` URI) |
|
|
322
|
+
| `web-storage` | warning | `localStorage`, `sessionStorage` |
|
|
323
|
+
| `indexed-db` | warning | `indexedDB` |
|
|
324
|
+
| `cookies` | warning | `document.cookie` |
|
|
325
|
+
| `dynamic-eval` | warning | `eval(`, `new Function(` |
|
|
326
|
+
| `service-worker` | warning | `serviceWorker` |
|
|
327
|
+
| `send-beacon` | warning | `sendBeacon` |
|
|
328
|
+
| `popups` | warning | `window.open(` |
|
|
329
|
+
| `crypto-randomuuid` | warning | `crypto.randomUUID` |
|
|
330
|
+
| `top-navigation` | warning | `window.top` |
|
|
331
|
+
| `external-network` | warning | `fetch(`, `XMLHttpRequest`, `WebSocket(` |
|
|
332
|
+
|
|
99
333
|
## Errors after the fact
|
|
100
334
|
|
|
101
335
|
`BridgeClient.send('jsError', { message, stack })` sends an error to Condor's
|
package/docs/pilot-onboarding.md
CHANGED
|
@@ -1,121 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Canvas — pilot onboarding
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
we package and render it. No Flutter / Android / iOS needed.
|
|
6
|
-
|
|
7
|
-
For the full reference, see the [Client Guide](./client-guide.md). This page is
|
|
8
|
-
the quick checklist to get a pilot through the whole loop.
|
|
9
|
-
|
|
10
|
-
## What you'll end up with
|
|
11
|
-
|
|
12
|
-
A self-contained `bundle.zip` that Condor publishes to your tenant — your Home
|
|
13
|
-
renders inside the app, talking to the device through the Condor bridge.
|
|
3
|
+
The steps from zero to your Home live for your company, and who does what. The
|
|
4
|
+
how of each step lives in one place, linked from it.
|
|
14
5
|
|
|
15
6
|
## Who does what
|
|
16
7
|
|
|
17
|
-
| Condor provides | You build |
|
|
8
|
+
| Condor provides | You build and run |
|
|
18
9
|
|---|---|
|
|
19
|
-
| The SDK (`@condorinst/mobile-bridge`),
|
|
20
|
-
| The app that
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
bridge
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
### 4. Test in your browser (no app needed)
|
|
56
|
-
```bash
|
|
57
|
-
npx condor-mobile-bridge dev -i dist/index.html
|
|
58
|
-
```
|
|
59
|
-
Serves your page inside the same sandbox, shell and policy as the app, with a
|
|
60
|
-
simulated app behind it. Check every scenario in the selector at the top:
|
|
61
|
-
`default`, `cold-start`, `no-devices`, `shared-serial`, `offline`.
|
|
62
|
-
|
|
63
|
-
### 5. Build the bundle
|
|
64
|
-
```bash
|
|
65
|
-
npx condor-mobile-bridge build -i dist/index.html -o dist/bundle.zip
|
|
66
|
-
```
|
|
67
|
-
This inlines everything into a single self-contained `index.html` and zips it.
|
|
68
|
-
It prints `bundleSize` + `bundleHash`.
|
|
69
|
-
|
|
70
|
-
### 5b. Validate before you deliver
|
|
71
|
-
```bash
|
|
72
|
-
npx condor-mobile-bridge validate -i dist/bundle.zip --strict
|
|
73
|
-
```
|
|
74
|
-
Applies the same gates the app applies on download, plus a lint for the APIs the
|
|
75
|
-
sandbox and the CSP block at runtime. **A non-zero exit means do not deliver.**
|
|
76
|
-
Errors are outright rejections by the app; warnings install fine and then
|
|
77
|
-
misbehave on the device, which is harder to diagnose.
|
|
78
|
-
|
|
79
|
-
### 6. Publish
|
|
80
|
-
With a manager user of your company:
|
|
81
|
-
```bash
|
|
82
|
-
npx condor-mobile-bridge canvas publish dist/index.html --version 1.0.0
|
|
83
|
-
```
|
|
84
|
-
It validates, shows what goes where, asks for confirmation and your password,
|
|
85
|
-
and publishes. Publish to your **test company** first: a publish reaches every
|
|
86
|
-
user of the company at once. (Without a manager user, send `dist/bundle.zip`
|
|
87
|
-
with its `bundleSize` and `bundleHash` to your Condor contact.)
|
|
88
|
-
|
|
89
|
-
### 7. Verify on a device
|
|
90
|
-
Condor publishes → the app downloads, validates and renders your Home the next
|
|
91
|
-
time it comes to the foreground. [Testing on a device](./device-testing.md)
|
|
92
|
-
explains the environments, how to inspect the WebView, and where errors go.
|
|
93
|
-
|
|
94
|
-
## Hard rules (or the app rejects the bundle)
|
|
95
|
-
|
|
96
|
-
- ✅ Exactly **one `index.html`** at the zip root
|
|
97
|
-
- ✅ JS/CSS **inline**, media as **`data:` URIs**, no external/relative refs loaded at runtime
|
|
98
|
-
- ✅ **≤ 2 MB** (zipped)
|
|
99
|
-
- ✅ **Single-chunk build** (no code-splitting): Vite → `vite-plugin-singlefile`; CRA → no `React.lazy` / dynamic `import()`
|
|
100
|
-
|
|
101
|
-
The CLI handles the inlining/zipping for you — just keep your build single-file.
|
|
102
|
-
|
|
103
|
-
## Reference
|
|
104
|
-
|
|
105
|
-
- [Client Guide](./client-guide.md) — full walkthrough (bridge API, key catalog, dev harness)
|
|
106
|
-
- [Testing on a device](./device-testing.md) — from a published bundle to an inspected WebView
|
|
107
|
-
- [Troubleshooting](./troubleshooting.md) — starts from the symptom
|
|
108
|
-
- [`examples/minimal-home`](../examples/minimal-home) — runnable minimal client
|
|
109
|
-
- [README](../README.md) — API reference
|
|
110
|
-
|
|
111
|
-
## Pilot checklist
|
|
112
|
-
|
|
113
|
-
- [ ] SDK installed (`npm i @condorinst/mobile-bridge`)
|
|
114
|
-
- [ ] Example runs in the harness (`npx condor-mobile-bridge dev`), in every scenario
|
|
115
|
-
- [ ] Home built with `BridgeClient` (handshake + at least one key + one command)
|
|
116
|
-
- [ ] Constraints respected (1 index.html, ≤2 MB, self-contained, single-chunk)
|
|
117
|
-
- [ ] `bundle.zip` generated by the CLI, and `validate --strict` exits 0
|
|
118
|
-
- [ ] `.zip` delivered to Condor
|
|
119
|
-
- [ ] Home verified on a device
|
|
10
|
+
| The SDK (`@condorinst/mobile-bridge`), its documentation and example | Your Home: one self-contained web page |
|
|
11
|
+
| The Canvas app that shows your page, and the device bridge | The `.zip` bundle, built and checked with the SDK CLI |
|
|
12
|
+
| A **test company** in production, and a manager user for it | Publishing to the test company, then to your real one |
|
|
13
|
+
|
|
14
|
+
You need Node.js 18+. The package is public on npm: no credentials.
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
1. **Start from the example and the procedure.** Follow
|
|
19
|
+
[`AGENTS.md`](../AGENTS.md), "The golden path": copy
|
|
20
|
+
`examples/minimal-home`, install, build.
|
|
21
|
+
2. **Build your Home in the harness.**
|
|
22
|
+
`npx condor-mobile-bridge dev -i dist/index.html` runs your page as the app
|
|
23
|
+
does. It must work in every scenario — `AGENTS.md`, "Develop in the harness".
|
|
24
|
+
Remember that your page is the whole screen: navigation, logout and the way
|
|
25
|
+
to the app settings are yours (`AGENTS.md`).
|
|
26
|
+
3. **Build and validate.** `build` writes the `.zip` and prints its size and
|
|
27
|
+
hash; `validate --strict` must exit 0 — `AGENTS.md`, "The validation loop".
|
|
28
|
+
4. **Publish to your test company.**
|
|
29
|
+
`npx condor-mobile-bridge canvas publish dist/index.html --version 1.0.0`
|
|
30
|
+
with the manager user of the **test** company. A publish reaches every user
|
|
31
|
+
of that company at once — [`docs/device-testing.md`](./device-testing.md),
|
|
32
|
+
"Publishing from the command line".
|
|
33
|
+
5. **Check it on a phone** logged in with a test user —
|
|
34
|
+
[`docs/device-testing.md`](./device-testing.md).
|
|
35
|
+
6. **Publish to your company** with a new version number, the same way.
|
|
36
|
+
|
|
37
|
+
## Checklist
|
|
38
|
+
|
|
39
|
+
- [ ] The example runs in the harness, in every scenario
|
|
40
|
+
- [ ] Your Home works in every scenario, with `--native-version null` too
|
|
41
|
+
- [ ] Back navigation, logout and the way to the app settings are in your screens
|
|
42
|
+
- [ ] `validate --strict` exits 0 on the `.zip`
|
|
43
|
+
- [ ] Published to the test company and checked on a phone
|
|
44
|
+
- [ ] Published to your company
|
|
120
45
|
- [ ] Feedback shared with Condor
|
|
121
|
-
|
|
46
|
+
|
|
47
|
+
When something does not work: [`docs/troubleshooting.md`](./troubleshooting.md).
|