@condorinst/mobile-bridge 0.22.0 → 0.23.1
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 +187 -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 +402 -55
- package/dist/dev.cjs +403 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +245 -9
- package/dist/dev.d.ts +245 -9
- package/dist/dev.js +402 -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 +317 -26
- package/docs/client-guide.md +34 -400
- package/docs/device-testing.md +235 -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 +769 -865
- package/llms.txt +13 -15
- package/package.json +1 -1
package/docs/client-guide.md
CHANGED
|
@@ -1,400 +1,34 @@
|
|
|
1
|
-
# Build your
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
a
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
│ ┌──────────────────────┐ │
|
|
36
|
-
│ │ iframe (your bundle) │ │ ← your index.html runs here
|
|
37
|
-
│ │ BridgeClient ◄────────────► native bridge (devices, BLE, …)
|
|
38
|
-
│ └──────────────────────┘ │
|
|
39
|
-
└────────────────────────────┘
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Two hard rules** that shape everything below:
|
|
43
|
-
- The bundle must be **one `index.html`**, fully **self-contained** (JS/CSS
|
|
44
|
-
inline, media as `data:` URIs). Relative paths and external URLs do **not**
|
|
45
|
-
load inside the sandbox.
|
|
46
|
-
- The bundle must be **≤ 2 MB** (zipped).
|
|
47
|
-
|
|
48
|
-
The CLI in this SDK turns your normal build into a compliant bundle for you
|
|
49
|
-
(see §6) — you don't have to inline by hand.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## 2. Install the SDK
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
npm install @condorinst/mobile-bridge
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Pin the version with a caret:
|
|
60
|
-
|
|
61
|
-
```jsonc
|
|
62
|
-
// package.json
|
|
63
|
-
"dependencies": {
|
|
64
|
-
"@condorinst/mobile-bridge": "^0.22.0"
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Updates.** The SDK is on `0.x` during the pilot. With `^0.22.0`, running
|
|
69
|
-
`npm install` (or your CI) auto-picks **patch** releases (`0.22.x`) — bug fixes,
|
|
70
|
-
no action needed. A `0.23.0` (a larger change during beta) is an intentional
|
|
71
|
-
opt-in: you bump the range when you're ready. `1.0.0` will mark the
|
|
72
|
-
production-stable contract.
|
|
73
|
-
|
|
74
|
-
The published package ships prebuilt JS + TypeScript types — no build tooling
|
|
75
|
-
of ours required on your side.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## 3. Talk to the app — the bridge
|
|
80
|
-
|
|
81
|
-
Import **`BridgeClient`** (the model for code running inside the app's iframe):
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
import { BridgeClient, BRIDGE_KEYS } from '@condorinst/mobile-bridge';
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Handshake
|
|
88
|
-
|
|
89
|
-
Announce yourself once at boot. The ack tells you the bridge is live.
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
const ack = await BridgeClient.handshake({ clientVersion: '1.0.0' });
|
|
93
|
-
// ack.accepted: boolean
|
|
94
|
-
// ack.shellVersion: string
|
|
95
|
-
// ack.nativeVersion: string | null — the native's schema version, relayed by
|
|
96
|
-
// the shell (0.17.0+). `null` means the native has not answered or is absent
|
|
97
|
-
// (browser mock). There is no version negotiation, so this is the only signal
|
|
98
|
-
// telling you whether a capability exists — e.g. the raw `light_sensor_*`
|
|
99
|
-
// channels on `deviceLightSensors` require native >= 0.16.0.
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Listen for events (native → you)
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
const unsubscribe = BridgeClient.on(BRIDGE_KEYS.deviceConnection, (payload) => {
|
|
106
|
-
// payload is typed: { serialNo: number, isConnect: boolean }
|
|
107
|
-
console.log(payload.serialNo, payload.isConnect);
|
|
108
|
-
});
|
|
109
|
-
// later: unsubscribe();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Send commands (you → native)
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
const devices = await BridgeClient.requestUserDevices(); // device list, resent until it arrives
|
|
116
|
-
BridgeClient.send('realTime', { serialNo: 1234, value: true }); // start real-time
|
|
117
|
-
BridgeClient.openSettings(); // open native Settings
|
|
118
|
-
BridgeClient.requestPermission('bluetoothScan'); // in-app permission dialog
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
> Commands are gated by the app's policy. A command that isn't allowed is
|
|
122
|
-
> silently ignored — design your UI to not depend on a response you won't get.
|
|
123
|
-
>
|
|
124
|
-
> Ask for the device list with `requestUserDevices()`, not with
|
|
125
|
-
> `send('userDevice')`: the first request after the app starts is often lost,
|
|
126
|
-
> and an account with no device gets no reply at all. The helper resends with
|
|
127
|
-
> backoff and resolves `{ status: 'timeout', hasDevices }` after 30 s, so you can
|
|
128
|
-
> show an empty state. See [Troubleshooting](./troubleshooting.md).
|
|
129
|
-
|
|
130
|
-
### Handle the system back button
|
|
131
|
-
|
|
132
|
-
Your page runs inside the app's Home screen. On Android, the OS back
|
|
133
|
-
button/gesture would otherwise leave the Home — so the app delegates it to you
|
|
134
|
-
when you opt in, letting the back button walk **your** internal navigation
|
|
135
|
-
(e.g. a device detail page → back to the list) before it does anything else.
|
|
136
|
-
|
|
137
|
-
Two calls wire it up:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
// 1. Tell the app whether you have an internal page to go back to.
|
|
141
|
-
// Call this on EVERY route change (and once at boot).
|
|
142
|
-
BridgeClient.reportNavigation(currentView !== 'home'); // true = you can go back
|
|
143
|
-
|
|
144
|
-
// 2. React to the OS back button. Called only when you last reported `true`.
|
|
145
|
-
BridgeClient.onBack(() => {
|
|
146
|
-
goToPreviousPage(); // pop one internal page, then reportNavigation(...) again
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
How it behaves:
|
|
151
|
-
|
|
152
|
-
- You reported `true` → the OS back button fires your `onBack` (go back one
|
|
153
|
-
internal page). Nothing else happens — the app stays on Home.
|
|
154
|
-
- You reported `false` (or never adopted this API) → the OS back button is
|
|
155
|
-
ignored and the user stays on Home. **No regression if you skip it.**
|
|
156
|
-
|
|
157
|
-
Keep `reportNavigation` in sync with your router: the app caches the last value
|
|
158
|
-
and reads it the instant back is pressed. A single choke point (call it at the
|
|
159
|
-
end of your `render`/route function) is the simplest way to never miss a change.
|
|
160
|
-
See `examples/client_demo/src/main.ts` for a worked home↔device example.
|
|
161
|
-
|
|
162
|
-
### Key catalog
|
|
163
|
-
|
|
164
|
-
These are the keys you can subscribe to / send. Payloads are fully typed in
|
|
165
|
-
TypeScript; the table is the quick reference (see the
|
|
166
|
-
[README](../README.md#key-catalog-strictly-typed) for exact shapes).
|
|
167
|
-
|
|
168
|
-
| Key | Payload (typed) | Direction |
|
|
169
|
-
|---|---|---|
|
|
170
|
-
| `userDevice` | device config / telemetry / user config arrays | native → you (also send to refresh) |
|
|
171
|
-
| `deviceConnection` | `{ serialNo, isConnect }` | native → you (status) |
|
|
172
|
-
| `deviceNotification` | `{ serialNo, isConnect, message, messageCode, isError }` | native → you |
|
|
173
|
-
| `deviceLightSensors` | `{ serialNo, type, data }` — union on `type` (`'actlumus'` \| `'actlumus2'`); `data` has the spectrum plus the raw `light_sensor_*` channels | native → you |
|
|
174
|
-
| `realTime` | `{ serialNo, success, value }` | both (command + state) |
|
|
175
|
-
| `syncTime` | `{ serialNo, success }` | both |
|
|
176
|
-
| `sensorLog` | paginated sensor log envelope | native → you |
|
|
177
|
-
| `systemStatus` | `{ bluetooth, internet, permission, hasDevices, missingOptionalPermissions, missingEssentialPermissions, deviceSyncError }` | native → you (env status) |
|
|
178
|
-
| `deleteMemory` | `{ serialNo, success }` | both |
|
|
179
|
-
| `pingPong` | `{ data: 'ping' \| 'pong' }` | both |
|
|
180
|
-
| `navigationBack` | `{}` (OS back pressed — via `onBack`) | native → you |
|
|
181
|
-
| `language` | `{ deviceLocale, appLanguage, resolvedLanguage }` | native → you (language state) |
|
|
182
|
-
|
|
183
|
-
> **System back button.** Use the `reportNavigation(canGoBack)` /
|
|
184
|
-
> `onBack(cb)` helpers (see [Handle the system back button](#handle-the-system-back-button))
|
|
185
|
-
> rather than subscribing to `navigationBack` directly — `reportNavigation`
|
|
186
|
-
> sends the `navigationState` command the app needs to route the back button.
|
|
187
|
-
|
|
188
|
-
**Permissions on `systemStatus`.** Two arrays of `AppPermission` names let you
|
|
189
|
-
render permission prompts from your own UI:
|
|
190
|
-
|
|
191
|
-
- `missingEssentialPermissions` — **required** for the app to work; background
|
|
192
|
-
collection stays off until granted. Render a **blocking "Required permissions"
|
|
193
|
-
card** (e.g. `["bluetoothScan", "bluetoothConnect"]`).
|
|
194
|
-
- `missingOptionalPermissions` — **recommended** (help background reliability);
|
|
195
|
-
render a **dismissible "Recommended permissions" nudge** (e.g.
|
|
196
|
-
`["notification", "backgroundLocation"]`).
|
|
197
|
-
|
|
198
|
-
Both are empty when nothing is missing. For the **required** card, wire the
|
|
199
|
-
"Grant" button to `BridgeClient.requestPermission(name)` — it fires the **native
|
|
200
|
-
in-app permission dialog** for that `AppPermission` (falls back to Settings only
|
|
201
|
-
when the OS refuses the prompt). Pass a name straight from
|
|
202
|
-
`missingEssentialPermissions` (e.g. `'bluetoothScan'`). There is no dedicated
|
|
203
|
-
result key: the native side re-emits `systemStatus` once granted, so the card
|
|
204
|
-
disappears when `missingEssentialPermissions` empties. `openSettings` remains
|
|
205
|
-
available for the optional nudge (permissions not grantable by prompt on modern
|
|
206
|
-
Android). See `examples/client_demo/src/render-home.ts` for a worked render.
|
|
207
|
-
|
|
208
|
-
### Translate your bundle — the `language` key
|
|
209
|
-
|
|
210
|
-
The app tells you **two** languages, and the distinction between them is the
|
|
211
|
-
whole point of the channel:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
BridgeClient.on(BRIDGE_KEYS.language, ({ deviceLocale, appLanguage, resolvedLanguage }) => {
|
|
215
|
-
// Your precedence rule. This one honours an explicit choice, else the device.
|
|
216
|
-
const lang = appLanguage ?? pickSupported(deviceLocale) ?? 'en';
|
|
217
|
-
renderIn(lang);
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
- **`deviceLocale`** — the raw device locale as a BCP-47 tag (`"pt-BR"`,
|
|
222
|
-
`"es-419"`, `"ja-JP"`), exactly as the OS reports it, region included.
|
|
223
|
-
- **`appLanguage`** — the language the user explicitly picked in the app
|
|
224
|
-
Settings (`"en" | "pt" | "de" | "fr"`), or **`null`** when they never picked
|
|
225
|
-
one.
|
|
226
|
-
- **`resolvedLanguage`** — the language the native chrome (splash, Settings,
|
|
227
|
-
loading/error screens) is rendering right now.
|
|
228
|
-
|
|
229
|
-
**Why two raw values instead of one answer.** The app itself ships four
|
|
230
|
-
languages and collapses everything else to English. Your bundle has no such
|
|
231
|
-
limit — it may ship Spanish, Italian, Japanese. If we sent you only the resolved
|
|
232
|
-
language, a device set to Spanish would reach you as `en` and you would render
|
|
233
|
-
English while holding a Spanish bundle. The information would have been
|
|
234
|
-
destroyed on our side before reaching you. So we send both and let you decide:
|
|
235
|
-
|
|
236
|
-
| `deviceLocale` | `appLanguage` | a reasonable decision |
|
|
237
|
-
|---|---|---|
|
|
238
|
-
| `es-ES` | `null` | render **Spanish** — you have it, we don't. Our chrome stays English while your content is Spanish, which is better, not worse |
|
|
239
|
-
| `es-ES` | `"pt"` | render **Portuguese** — an explicit choice outranks the device |
|
|
240
|
-
| `pt-BR` | `null` | render Portuguese |
|
|
241
|
-
| `ja-JP` | `"de"` | render German (the explicit choice), or Japanese if you'd rather honour the device — your call, with both values in hand |
|
|
242
|
-
|
|
243
|
-
`appLanguage` is `null` when — and only when — the user never chose. It is never
|
|
244
|
-
a fallback in disguise, so `null` is a real signal you can branch on. Use
|
|
245
|
-
`resolvedLanguage` when you'd rather stay visually consistent with the native
|
|
246
|
-
frame around your page than follow the device.
|
|
247
|
-
|
|
248
|
-
**When it arrives — and how to ask for it.** The app pushes this key on attach,
|
|
249
|
-
again right after the handshake ack, and on every change in the app Settings, so
|
|
250
|
-
a page already loaded re-renders without a restart. But the first push happens
|
|
251
|
-
while the WebView is still being created, before your bundle exists, so **send
|
|
252
|
-
the `language` command once after your handshake** to pull the current state:
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
BridgeClient.send('language', {}); // replies with a `language` event
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Without that pull, a first launch would show your default language until the
|
|
259
|
-
user changed it in Settings. Subscribe first, then pull.
|
|
260
|
-
|
|
261
|
-
Do not read `navigator.language` instead: it follows the system, not the in-app
|
|
262
|
-
choice, and you would end up in Spanish while the native chrome around you is in
|
|
263
|
-
Portuguese.
|
|
264
|
-
|
|
265
|
-
**Older app versions.** There is no version negotiation. An app older than
|
|
266
|
-
native `0.14.0` never emits this key, so the event simply never arrives — keep a
|
|
267
|
-
default language for that case, and read `nativeVersion` from the handshake ack
|
|
268
|
-
if you need to branch on it explicitly.
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## 4. Develop in your browser (no app needed)
|
|
273
|
-
|
|
274
|
-
Build your page, then serve it with the harness:
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
npx condor-mobile-bridge dev -i dist/index.html
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
Open `http://localhost:5173`. Your page runs inside the same sandboxed iframe,
|
|
281
|
-
shell, policy and CSP as in the app, and a simulated app answers your commands.
|
|
282
|
-
Rebuild and reload — the harness re-reads the file.
|
|
283
|
-
|
|
284
|
-
Pick a **scenario** in the selector at the top (or with `--scenario`) to see
|
|
285
|
-
what the real app does to your page:
|
|
286
|
-
|
|
287
|
-
- `cold-start` — the first device requests are lost while the app starts;
|
|
288
|
-
- `no-devices` — an account with no device: the device list never comes;
|
|
289
|
-
- `shared-serial` — two devices with the same serial number;
|
|
290
|
-
- `offline` — no internet, history unavailable.
|
|
291
|
-
|
|
292
|
-
Your page is ready for a phone when it behaves in all of them. The page's
|
|
293
|
-
console (`[condor dev]` lines) shows each command, reply and dropped request,
|
|
294
|
-
and the dev panel in the corner lets you emit any event by hand.
|
|
295
|
-
|
|
296
|
-
> Opening `dist/index.html` directly in a browser does not work: there is no
|
|
297
|
-
> shell around it, so `BridgeClient.handshake()` rejects at once with a message
|
|
298
|
-
> pointing to the harness.
|
|
299
|
-
|
|
300
|
-
The [`examples/minimal-home`](../examples/minimal-home) example runs this way
|
|
301
|
-
out of the box.
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
## 5. Bundle format & constraints
|
|
306
|
-
|
|
307
|
-
Your final artifact is a `.zip` containing exactly **one self-contained
|
|
308
|
-
`index.html`** at the root. Why the constraints:
|
|
309
|
-
|
|
310
|
-
- The app injects your HTML via `iframe.srcdoc` with origin `null` and no base
|
|
311
|
-
URL → **relative references don't resolve** (`<script src="app.js">`,
|
|
312
|
-
`<img src="logo.png">` would break).
|
|
313
|
-
- The app's Content-Security-Policy **blocks external fetches** → CDN scripts,
|
|
314
|
-
Google Fonts, remote APIs called at load time won't work.
|
|
315
|
-
|
|
316
|
-
So the bundle must have:
|
|
317
|
-
- ✅ One `index.html` at the zip root.
|
|
318
|
-
- ✅ JS and CSS **inlined** into the HTML.
|
|
319
|
-
- ✅ Images/icons/fonts as **`data:` URIs**.
|
|
320
|
-
- ✅ Total size **≤ 2 MB** (zipped). Note base64 inflates media ~33%.
|
|
321
|
-
- ❌ No external `https://` script/style/font references loaded at runtime.
|
|
322
|
-
- ❌ No code-splitting / dynamic `import()` across chunks (see below).
|
|
323
|
-
|
|
324
|
-
### Single-chunk build
|
|
325
|
-
|
|
326
|
-
The bundler CLI is post-build and assumes **one JS + one CSS + assets**. If
|
|
327
|
-
your framework code-splits, the split chunks won't resolve in the sandbox.
|
|
328
|
-
Configure a single-file build:
|
|
329
|
-
|
|
330
|
-
- **Vite:** add [`vite-plugin-singlefile`](https://github.com/richardtallent/vite-plugin-singlefile),
|
|
331
|
-
or set `build.rollupOptions.output.inlineDynamicImports: true` +
|
|
332
|
-
`manualChunks: undefined`.
|
|
333
|
-
- **Create React App:** avoid `React.lazy` / dynamic `import()` in routes so the
|
|
334
|
-
build emits a single bundle.
|
|
335
|
-
- **esbuild:** bundle to a single IIFE/ESM file (see the example's `build.mjs`).
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## 6. Build the bundle
|
|
340
|
-
|
|
341
|
-
Once your build produces a `dist/index.html` (referencing a local `main.js`,
|
|
342
|
-
CSS, images), run the CLI to inline everything and pack the zip:
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
npx condor-mobile-bridge build -i dist/index.html -o dist/bundle.zip
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
It inlines `<script src>`, `<link rel=stylesheet>`, CSS `url()`, and local
|
|
349
|
-
`<img>`/icons into a single `index.html`, then zips it. Output:
|
|
350
|
-
|
|
351
|
-
```
|
|
352
|
-
Built dist/bundle.zip
|
|
353
|
-
entry: index.html (123.4 KB self-contained)
|
|
354
|
-
bundleSize (zip): 45678 bytes
|
|
355
|
-
bundleHash (zip): sha256-<hex>
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
| Option | Description |
|
|
359
|
-
|---|---|
|
|
360
|
-
| `-i, --html <path>` | Input HTML. Required. |
|
|
361
|
-
| `-o, --out <path>` | Output `.zip` (default `dist/whitelabel-bundle.zip`). |
|
|
362
|
-
| `--max-bytes <n>` | Warning threshold (default 2 MB). |
|
|
363
|
-
|
|
364
|
-
The hash is deterministic — the same input always yields the same
|
|
365
|
-
`bundleHash`. The CLI **warns** above 2 MB but the **app rejects** oversized
|
|
366
|
-
bundles and falls back to a default screen, so keep under the limit.
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## 7. Deliver
|
|
371
|
-
|
|
372
|
-
Publish it yourself with a manager user of your company —
|
|
373
|
-
`npx condor-mobile-bridge canvas publish dist/index.html --version 1.0.0` — or
|
|
374
|
-
send the resulting `dist/bundle.zip` through the publication channel Condor
|
|
375
|
-
gave you (the admin upload). The backend validates structure, self-containment,
|
|
376
|
-
and size on upload, and records the `bundleSize`/`bundleHash`. Once published,
|
|
377
|
-
the app downloads, verifies, and renders your Home — for **every user of your
|
|
378
|
-
company**, so test on a development test company first. See
|
|
379
|
-
[Testing on a device](./device-testing.md).
|
|
380
|
-
|
|
381
|
-
---
|
|
382
|
-
|
|
383
|
-
## 8. Troubleshooting
|
|
384
|
-
|
|
385
|
-
| Symptom | Likely cause |
|
|
386
|
-
|---|---|
|
|
387
|
-
| Blank screen / nothing loads | Relative `<script src>` or external URL — must be inlined. Run the build CLI. |
|
|
388
|
-
| Images missing | Referenced by path instead of `data:` URI — the CLI inlines local images; remote images are blocked. |
|
|
389
|
-
| Works in browser, not in app | You relied on a network call or external asset at load time (CSP blocks it), or a code-split chunk didn't resolve. Go single-chunk + self-contained. |
|
|
390
|
-
| Bundle rejected on upload / falls back | Over 2 MB, multiple files at root, or not self-contained. |
|
|
391
|
-
| `handshake` rejects in the browser | The page is not inside the shell. Serve it with `npx condor-mobile-bridge dev`. |
|
|
392
|
-
| Stuck on "loading devices" on the phone | The device request was lost or the account has no device. Use `requestUserDevices()` and test the `cold-start` and `no-devices` scenarios. |
|
|
393
|
-
| A command does nothing | The command may not be allowed by the app policy; it's dropped silently. |
|
|
394
|
-
|
|
395
|
-
---
|
|
396
|
-
|
|
397
|
-
## Next steps
|
|
398
|
-
|
|
399
|
-
- Copy [`examples/minimal-home`](../examples/minimal-home) and start from there.
|
|
400
|
-
- Full API reference: the [README](../README.md).
|
|
1
|
+
# Build your Canvas Home — start here
|
|
2
|
+
|
|
3
|
+
You build the Home of the Condor Canvas app as an ordinary web page: one
|
|
4
|
+
self-contained `index.html`, at most 2 MB zipped. The app downloads it, checks
|
|
5
|
+
it, and shows it inside a sandboxed iframe after the person logs in. Your page
|
|
6
|
+
talks to the app — device list, live light readings, history, permissions,
|
|
7
|
+
language — through `BridgeClient` from this SDK. No Flutter, Android or iOS
|
|
8
|
+
knowledge is needed.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌─────────────────────────────┐
|
|
12
|
+
│ Condor Canvas app (native) │
|
|
13
|
+
│ ┌──────────────────────┐ │
|
|
14
|
+
│ │ iframe: your bundle │ │
|
|
15
|
+
│ │ BridgeClient ◄────────────► devices, Bluetooth, server
|
|
16
|
+
│ └──────────────────────┘ │
|
|
17
|
+
└─────────────────────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The documentation is written once, for people and for AI agents alike:
|
|
21
|
+
|
|
22
|
+
1. [`AGENTS.md`](../AGENTS.md) — **read this first**: the rules, the boot
|
|
23
|
+
sequence to copy, the browser harness and what "done" means.
|
|
24
|
+
2. [`docs/bridge-contract.md`](./bridge-contract.md) — what each key and command
|
|
25
|
+
does, including the `language` key and the back button.
|
|
26
|
+
3. [`docs/device-testing.md`](./device-testing.md) — testing in the browser and
|
|
27
|
+
on a phone, and publishing from the command line.
|
|
28
|
+
4. [`docs/pilot-onboarding.md`](./pilot-onboarding.md) — the steps from zero to
|
|
29
|
+
a bundle live for your company.
|
|
30
|
+
5. [`docs/troubleshooting.md`](./troubleshooting.md) when something does not
|
|
31
|
+
work, and [`docs/limits.md`](./limits.md) for every hard number.
|
|
32
|
+
|
|
33
|
+
A complete, working Home to start from ships in the package:
|
|
34
|
+
`examples/minimal-home/`.
|