@condorinst/mobile-bridge 0.19.1 → 0.20.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 +3 -2
- package/CHANGELOG.md +51 -0
- package/contract/bridge-contract.json +19 -18
- package/dist/index.d.cts +29 -5
- package/dist/index.d.ts +29 -5
- package/docs/agent-guide.md +8 -1
- package/docs/bridge-contract.md +35 -7
- package/docs/troubleshooting.md +4 -0
- package/llms.txt +13 -13
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -104,8 +104,9 @@ checking `nativeVersion`.
|
|
|
104
104
|
depend on the bridge.
|
|
105
105
|
- [ ] Every capability beyond the baseline is guarded by a `nativeVersion` check.
|
|
106
106
|
- [ ] Nothing waits forever on an ack that may never come.
|
|
107
|
-
- [ ] Tested in the browser with the mock (`npm run
|
|
108
|
-
`nativeVersion` is `null` — your code must
|
|
107
|
+
- [ ] Tested in the browser with the mock (in the example: `npm run build`, then
|
|
108
|
+
open `dist/index.html`), where `nativeVersion` is `null` — your code must
|
|
109
|
+
survive that.
|
|
109
110
|
|
|
110
111
|
## Where to read more
|
|
111
112
|
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.20.0] — 2026-10-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Address one device by `deviceId` when two share a `serialNo`.** An ActLumus
|
|
15
|
+
and an ActLumus Plus have separate serial sequences, so one account can hold
|
|
16
|
+
both with the same `serialNo`. `realTime`, `syncTime`, `deleteMemory` and
|
|
17
|
+
`deviceConnection` accept an optional `deviceId` next to `serialNo`; keep
|
|
18
|
+
sending `serialNo` as well, because an older app drops in silence a command
|
|
19
|
+
that lacks it. Requires `nativeVersion` 0.17.0, which the contract now lists
|
|
20
|
+
as the `deviceIdAddressing` capability.
|
|
21
|
+
- **`deviceId` in the device events.** `deviceConnection`, `deviceNotification`
|
|
22
|
+
and `deviceLightSensors` carry an optional `deviceId`, present only when the
|
|
23
|
+
`serialNo` is shared by more than one device. The acks of `realTime`,
|
|
24
|
+
`syncTime` and `deleteMemory` echo the `deviceId` you sent and always keep
|
|
25
|
+
`serialNo`.
|
|
26
|
+
- **`systemStatus.deviceSyncErrorById`**, the same errors keyed by `deviceId`
|
|
27
|
+
for every device. `deviceSyncError` leaves out a `serialNo` that two devices
|
|
28
|
+
share, since no single device owns it.
|
|
29
|
+
- The contract and `docs/bridge-contract.md` document the rule in a new section,
|
|
30
|
+
and list `DEVICE_SERIAL_AMBIGUOUS` among the `sensorLog` error codes: the
|
|
31
|
+
backend answers it for a shared `serialNo`, and `sensorLog` takes no
|
|
32
|
+
`deviceId` yet.
|
|
33
|
+
|
|
34
|
+
### Notes
|
|
35
|
+
|
|
36
|
+
- Additive and backward-compatible: every new field is optional and older
|
|
37
|
+
clients ignore it. The model of a device is **not** carried by these events;
|
|
38
|
+
read `modelId` from the `userDevice` list.
|
|
39
|
+
|
|
40
|
+
## [0.19.2] — 2026-09-23
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **`AGENTS.md` told you to run a script that does not exist.** The definition
|
|
45
|
+
of done asked for `npm run dev` in the example, but `examples/minimal-home`
|
|
46
|
+
only defines `build`, `bundle` and `typecheck`. It now describes the loop the
|
|
47
|
+
example itself documents: `npm run build`, then open `dist/index.html`, where
|
|
48
|
+
the SDK auto-enables mock mode.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **The public repository no longer carries internal files.** The mirror step
|
|
53
|
+
used to publish everything it tracked except its own pipeline definition, so
|
|
54
|
+
the release runbook, the test suite and the lint/test configuration were all
|
|
55
|
+
public. It now excludes them by an explicit list, guarded so that renaming a
|
|
56
|
+
listed path fails the release instead of silently publishing it.
|
|
57
|
+
`tsconfig.json` and `tsup.config.ts` stay, because installing the package
|
|
58
|
+
straight from git builds it on `prepare` and cannot do so without them.
|
|
59
|
+
Nothing changes in the npm tarball, which never carried these files.
|
|
60
|
+
|
|
10
61
|
## [0.19.1] — 2026-09-22
|
|
11
62
|
|
|
12
63
|
### Changed
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"contractVersion": "0.
|
|
3
|
+
"contractVersion": "0.20.0",
|
|
4
4
|
"description": "Machine-readable contract of the Condor Canvas JS<->Native bridge. It states what the TypeScript declarations in dist/index.d.ts cannot express: which keys and commands a sandboxed client can actually reach, which ones need human confirmation, where messages are dropped in silence, the hard limits, and which capability each native version carries. The TypeScript types remain canonical for payload shapes.",
|
|
5
5
|
"audience": "Developers and AI agents building the client bundle that renders inside the Canvas Home.",
|
|
6
6
|
|
|
7
7
|
"versions": {
|
|
8
8
|
"note": "Four independent numbers coexist. Only nativeVersion tells you what the device can do.",
|
|
9
|
-
"package": "0.
|
|
9
|
+
"package": "0.20.0",
|
|
10
10
|
"shellVersion": "0.6.0",
|
|
11
11
|
"clientProtocolVersion": "0.5.0",
|
|
12
|
-
"nativeVersion": "0.
|
|
12
|
+
"nativeVersion": "0.17.0",
|
|
13
13
|
"capabilityDetection": {
|
|
14
14
|
"how": "await BridgeClient.handshake({ clientVersion }) resolves to { shellVersion, nativeVersion, accepted }. Compare ack.nativeVersion against the minNativeVersion in capabilities[].",
|
|
15
15
|
"acceptedIsAlwaysTrue": true,
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"entries": {
|
|
102
102
|
"ping": { "args": {}, "requiresConfirmation": false, "repliesOn": "pingPong" },
|
|
103
103
|
"userDevice": { "args": { "getFromServer": "boolean, default false" }, "requiresConfirmation": false, "repliesOn": "userDevice" },
|
|
104
|
-
"realTime": { "args": { "serialNo": "int", "value": "boolean" }, "requiresConfirmation": false, "repliesOn": "realTime", "note": "Off by default. The deviceLightSensors stream only flows while this is on." },
|
|
105
|
-
"deviceConnection": { "args": { "type": "'add' | 'remove'", "serialNo": "int" }, "requiresConfirmation": false, "repliesOn": "deviceConnection" },
|
|
104
|
+
"realTime": { "args": { "serialNo": "int", "deviceId": "int, optional, minNativeVersion 0.17.0", "value": "boolean" }, "requiresConfirmation": false, "repliesOn": "realTime", "note": "Off by default. The deviceLightSensors stream only flows while this is on. Always send serialNo; add deviceId to address one device when two share a serialNo." },
|
|
105
|
+
"deviceConnection": { "args": { "type": "'add' | 'remove'", "serialNo": "int", "deviceId": "int, optional, minNativeVersion 0.17.0" }, "requiresConfirmation": false, "repliesOn": "deviceConnection" },
|
|
106
106
|
"sensorLog": { "args": { "requestId": "string", "serialNo": "int", "bodyPart": "'wrist' | 'necklace', optional", "limit": "1..1000, optional", "startingAfter": "opaque cursor, optional", "endingBefore": "opaque cursor, optional", "startTime": "timestamp, optional, minNativeVersion 0.15.0", "endTime": "timestamp, optional, minNativeVersion 0.15.0" }, "requiresConfirmation": false, "repliesOn": "sensorLog", "prefer": "BridgeClient.fetchSensorLog(query), which correlates requestId and applies a timeout for you." },
|
|
107
107
|
"openSettings": { "args": {}, "requiresConfirmation": false, "repliesOn": null },
|
|
108
108
|
"requestPermission": { "args": { "permission": "string, must match a native AppPermission name" }, "requiresConfirmation": false, "repliesOn": null, "note": "There is no permission-result key. The OS shows its own prompt and the outcome reaches you as a fresh systemStatus emission." },
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
"navigationState": { "args": { "canGoBack": "boolean" }, "requiresConfirmation": false, "repliesOn": null, "prefer": "BridgeClient.reportNavigation(canGoBack)" },
|
|
111
111
|
"language": { "args": {}, "requiresConfirmation": false, "repliesOn": "language", "note": "Pull. The native push happens before window.CondorBridge exists, so a client that mounts later must ask.", "minNativeVersion": "0.14.0" },
|
|
112
112
|
"jsError": { "args": { "message": "string", "stack": "optional", "source": "optional", "lineno": "optional", "colno": "optional" }, "requiresConfirmation": false, "repliesOn": null, "note": "Rate limited: 20 per 10 s, fields truncated at 4 KB." },
|
|
113
|
-
"syncTime": { "args": { "serialNo": "int" }, "requiresConfirmation": true, "repliesOn": "syncTime" },
|
|
114
|
-
"deleteMemory": { "args": { "serialNo": "int" }, "requiresConfirmation": true, "repliesOn": "deleteMemory" },
|
|
113
|
+
"syncTime": { "args": { "serialNo": "int", "deviceId": "int, optional, minNativeVersion 0.17.0" }, "requiresConfirmation": true, "repliesOn": "syncTime" },
|
|
114
|
+
"deleteMemory": { "args": { "serialNo": "int", "deviceId": "int, optional, minNativeVersion 0.17.0" }, "requiresConfirmation": true, "repliesOn": "deleteMemory" },
|
|
115
115
|
"logout": { "args": {}, "requiresConfirmation": true, "repliesOn": null },
|
|
116
116
|
"dfu": { "args": {}, "denied": true, "repliesOn": null }
|
|
117
117
|
},
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"name": "sensorLog failure",
|
|
131
131
|
"channel": "sensorLog key",
|
|
132
132
|
"shape": { "requestId": "string", "success": false, "error": "string code" },
|
|
133
|
-
"codes": ["CONFLICTING_CURSORS", "INVALID_BODY_PART", "INVALID_LIMIT", "INVALID_START_TIME", "INVALID_END_TIME", "INVALID_TIME_RANGE", "MISSING_DEVICE_SERIAL_NO", "invalid session", "device not found for this user"],
|
|
133
|
+
"codes": ["CONFLICTING_CURSORS", "INVALID_BODY_PART", "INVALID_LIMIT", "INVALID_START_TIME", "INVALID_END_TIME", "INVALID_TIME_RANGE", "MISSING_DEVICE_SERIAL_NO", "DEVICE_SERIAL_AMBIGUOUS", "invalid session", "device not found for this user"],
|
|
134
134
|
"visibility": "fetchSensorLog RESOLVES with success false. It only rejects on timeout.",
|
|
135
135
|
"warning": "error is a diagnostic code, never user-facing text. Map it to your own copy."
|
|
136
136
|
},
|
|
@@ -155,6 +155,7 @@
|
|
|
155
155
|
{ "feature": "languageKey", "minNativeVersion": "0.14.0", "description": "The language key and the language pull command exist." },
|
|
156
156
|
{ "feature": "sensorLogTimeRange", "minNativeVersion": "0.15.0", "description": "sensorLog accepts startTime and endTime." },
|
|
157
157
|
{ "feature": "lightSensorRawChannels", "minNativeVersion": "0.16.0", "description": "deviceLightSensors carries the raw light_sensor_* channels and the actlumus2 variant." },
|
|
158
|
+
{ "feature": "deviceIdAddressing", "minNativeVersion": "0.17.0", "description": "realTime, syncTime, deleteMemory and deviceConnection accept an optional deviceId, so one device can be addressed when an ActLumus and a Plus share a serialNo; the acks echo it; deviceConnection, deviceNotification and deviceLightSensors carry it when the serialNo is shared; systemStatus adds deviceSyncErrorById." },
|
|
158
159
|
{ "feature": "nativeVersionRelay", "minShellVersion": "0.6.0", "description": "The shell relays nativeVersion into the sandboxed client. Below this, handshake-ack carried only shellVersion and capability detection was impossible from inside the iframe." }
|
|
159
160
|
],
|
|
160
161
|
|
|
@@ -179,23 +180,23 @@
|
|
|
179
180
|
"verifiedAgainst": {
|
|
180
181
|
"monorepo": "condorinstprogdev/mobile_applications",
|
|
181
182
|
"branch": "dev_canvas",
|
|
182
|
-
"commit": "
|
|
183
|
-
"verifiedOn": "2026-
|
|
183
|
+
"commit": "7a40a779d23d1ee49433982d37ebf4979444bf95",
|
|
184
|
+
"verifiedOn": "2026-10-13",
|
|
184
185
|
"sources": {
|
|
185
|
-
"nativeVersion": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
186
|
-
"jsToNativePayloadBytes": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
187
|
-
"jsErrorPerWindow": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
188
|
-
"jsErrorFieldBytes": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
189
|
-
"acceptedIsAlwaysTrue": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
190
|
-
"silentDropPaths": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:
|
|
186
|
+
"nativeVersion": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:38",
|
|
187
|
+
"jsToNativePayloadBytes": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:94",
|
|
188
|
+
"jsErrorPerWindow": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:99-100",
|
|
189
|
+
"jsErrorFieldBytes": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:103",
|
|
190
|
+
"acceptedIsAlwaysTrue": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:486-494",
|
|
191
|
+
"silentDropPaths": "apps/canvas/lib/ui/pages/home/bridge/condor_bridge.dart:246-266,319-323,521-536,606-613",
|
|
191
192
|
"bundleZipBytes": "apps/canvas/lib/core/config/whitelabel_bundle_limits.dart:14",
|
|
192
193
|
"bundleGuards": "apps/canvas/lib/data/services/whitelabel_bundle_service.dart:227-398",
|
|
193
194
|
"integrity": "apps/canvas/lib/data/services/whitelabel_bundle_service.dart:244-259",
|
|
194
195
|
"sensorLogValidation": "apps/canvas/lib/domain/entities/sensor_log_entity.dart:70-85",
|
|
195
196
|
"csp": "apps/canvas/web-mock-src/shell.template.html:28",
|
|
196
197
|
"shellPolicy": "apps/canvas/web-mock-src/src/shell.ts:48-98",
|
|
197
|
-
"webViewPermissionDeny": "apps/canvas/lib/ui/pages/home/home_page.dart:
|
|
198
|
-
"opaqueOrigin": "apps/canvas/lib/ui/pages/home/home_page.dart:
|
|
198
|
+
"webViewPermissionDeny": "apps/canvas/lib/ui/pages/home/home_page.dart:263-271",
|
|
199
|
+
"opaqueOrigin": "apps/canvas/lib/ui/pages/home/home_page.dart:299-301"
|
|
199
200
|
},
|
|
200
201
|
"maintenance": "Re-verify every entry in sources before cutting a release. A published contract that drifts from the app is worse than no contract."
|
|
201
202
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -341,7 +341,12 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
341
341
|
};
|
|
342
342
|
/**
|
|
343
343
|
* Real-time light-sensor stream. Shape:
|
|
344
|
-
* `{ serialNo, type, data }`.
|
|
344
|
+
* `{ serialNo, deviceId?, type, data }`.
|
|
345
|
+
*
|
|
346
|
+
* `deviceId` is the server id of the device and is **optional**: the app only
|
|
347
|
+
* sends it when more than one device on the account shares that `serialNo` (an
|
|
348
|
+
* ActLumus and an ActLumus Plus can). With a unique `serialNo` it is absent, so
|
|
349
|
+
* keep matching on `serialNo` in that case. Present from `nativeVersion` 0.17.0.
|
|
345
350
|
*
|
|
346
351
|
* A union discriminated by `type`, which is the `DeviceType.name` on the Dart
|
|
347
352
|
* side. `data` carries **both the derived spectrum and the raw channels it came
|
|
@@ -353,27 +358,35 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
353
358
|
*/
|
|
354
359
|
type DeviceLightSensorsPayload = {
|
|
355
360
|
serialNo: number;
|
|
361
|
+
deviceId?: number;
|
|
356
362
|
type: 'actlumus';
|
|
357
363
|
data: LightSensorsActlumus1Data;
|
|
358
364
|
} | {
|
|
359
365
|
serialNo: number;
|
|
366
|
+
deviceId?: number;
|
|
360
367
|
type: 'actlumus2';
|
|
361
368
|
data: LightSensorsActlumus2Data;
|
|
362
369
|
};
|
|
363
|
-
/**
|
|
370
|
+
/**
|
|
371
|
+
* Ack de comando `syncTime`. `deviceId` echoes the one you sent (see
|
|
372
|
+
* `DeviceLightSensorsPayload`); `serialNo` is always present.
|
|
373
|
+
*/
|
|
364
374
|
interface SyncTimeAckPayload {
|
|
365
375
|
serialNo: number;
|
|
376
|
+
deviceId?: number;
|
|
366
377
|
success: boolean;
|
|
367
378
|
}
|
|
368
|
-
/** Ack/state do toggle `realTime`. */
|
|
379
|
+
/** Ack/state do toggle `realTime`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
369
380
|
interface RealTimeAckPayload {
|
|
370
381
|
serialNo: number;
|
|
382
|
+
deviceId?: number;
|
|
371
383
|
success: boolean;
|
|
372
384
|
value: boolean;
|
|
373
385
|
}
|
|
374
|
-
/** Ack de comando `deleteMemory`. */
|
|
386
|
+
/** Ack de comando `deleteMemory`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
375
387
|
interface DeleteMemoryAckPayload {
|
|
376
388
|
serialNo: number;
|
|
389
|
+
deviceId?: number;
|
|
377
390
|
success: boolean;
|
|
378
391
|
}
|
|
379
392
|
/**
|
|
@@ -382,6 +395,8 @@ interface DeleteMemoryAckPayload {
|
|
|
382
395
|
*/
|
|
383
396
|
interface DeviceConnectionPayload {
|
|
384
397
|
serialNo: number;
|
|
398
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
399
|
+
deviceId?: number;
|
|
385
400
|
isConnect: boolean;
|
|
386
401
|
}
|
|
387
402
|
/**
|
|
@@ -391,6 +406,8 @@ interface DeviceConnectionPayload {
|
|
|
391
406
|
*/
|
|
392
407
|
interface DeviceNotificationPayload {
|
|
393
408
|
serialNo: number;
|
|
409
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
410
|
+
deviceId?: number;
|
|
394
411
|
isConnect: boolean;
|
|
395
412
|
message: string | null;
|
|
396
413
|
messageCode: string | null;
|
|
@@ -461,7 +478,9 @@ interface SensorLogPayload {
|
|
|
461
478
|
*
|
|
462
479
|
* Emitted on attach (before your bundle exists), again right after
|
|
463
480
|
* `handshake-ack`, and on every change. `deviceSyncError` maps `serialNo` to an
|
|
464
|
-
* error message the app has already localized.
|
|
481
|
+
* error message the app has already localized. A `serialNo` shared by two
|
|
482
|
+
* devices has no single owner, so it is **left out** of `deviceSyncError` and
|
|
483
|
+
* only appears in `deviceSyncErrorById`.
|
|
465
484
|
*/
|
|
466
485
|
interface SystemStatusPayload {
|
|
467
486
|
bluetooth: boolean;
|
|
@@ -485,6 +504,11 @@ interface SystemStatusPayload {
|
|
|
485
504
|
*/
|
|
486
505
|
missingEssentialPermissions: string[];
|
|
487
506
|
deviceSyncError: Record<number, string>;
|
|
507
|
+
/**
|
|
508
|
+
* Same errors keyed by the server `deviceId`, for **every** device, shared
|
|
509
|
+
* `serialNo` included. Absent below `nativeVersion` 0.17.0, so guard it.
|
|
510
|
+
*/
|
|
511
|
+
deviceSyncErrorById?: Record<number, string>;
|
|
488
512
|
}
|
|
489
513
|
/**
|
|
490
514
|
* Arguments of the `requestPermission` command (JS → native).
|
package/dist/index.d.ts
CHANGED
|
@@ -341,7 +341,12 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
341
341
|
};
|
|
342
342
|
/**
|
|
343
343
|
* Real-time light-sensor stream. Shape:
|
|
344
|
-
* `{ serialNo, type, data }`.
|
|
344
|
+
* `{ serialNo, deviceId?, type, data }`.
|
|
345
|
+
*
|
|
346
|
+
* `deviceId` is the server id of the device and is **optional**: the app only
|
|
347
|
+
* sends it when more than one device on the account shares that `serialNo` (an
|
|
348
|
+
* ActLumus and an ActLumus Plus can). With a unique `serialNo` it is absent, so
|
|
349
|
+
* keep matching on `serialNo` in that case. Present from `nativeVersion` 0.17.0.
|
|
345
350
|
*
|
|
346
351
|
* A union discriminated by `type`, which is the `DeviceType.name` on the Dart
|
|
347
352
|
* side. `data` carries **both the derived spectrum and the raw channels it came
|
|
@@ -353,27 +358,35 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
353
358
|
*/
|
|
354
359
|
type DeviceLightSensorsPayload = {
|
|
355
360
|
serialNo: number;
|
|
361
|
+
deviceId?: number;
|
|
356
362
|
type: 'actlumus';
|
|
357
363
|
data: LightSensorsActlumus1Data;
|
|
358
364
|
} | {
|
|
359
365
|
serialNo: number;
|
|
366
|
+
deviceId?: number;
|
|
360
367
|
type: 'actlumus2';
|
|
361
368
|
data: LightSensorsActlumus2Data;
|
|
362
369
|
};
|
|
363
|
-
/**
|
|
370
|
+
/**
|
|
371
|
+
* Ack de comando `syncTime`. `deviceId` echoes the one you sent (see
|
|
372
|
+
* `DeviceLightSensorsPayload`); `serialNo` is always present.
|
|
373
|
+
*/
|
|
364
374
|
interface SyncTimeAckPayload {
|
|
365
375
|
serialNo: number;
|
|
376
|
+
deviceId?: number;
|
|
366
377
|
success: boolean;
|
|
367
378
|
}
|
|
368
|
-
/** Ack/state do toggle `realTime`. */
|
|
379
|
+
/** Ack/state do toggle `realTime`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
369
380
|
interface RealTimeAckPayload {
|
|
370
381
|
serialNo: number;
|
|
382
|
+
deviceId?: number;
|
|
371
383
|
success: boolean;
|
|
372
384
|
value: boolean;
|
|
373
385
|
}
|
|
374
|
-
/** Ack de comando `deleteMemory`. */
|
|
386
|
+
/** Ack de comando `deleteMemory`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
375
387
|
interface DeleteMemoryAckPayload {
|
|
376
388
|
serialNo: number;
|
|
389
|
+
deviceId?: number;
|
|
377
390
|
success: boolean;
|
|
378
391
|
}
|
|
379
392
|
/**
|
|
@@ -382,6 +395,8 @@ interface DeleteMemoryAckPayload {
|
|
|
382
395
|
*/
|
|
383
396
|
interface DeviceConnectionPayload {
|
|
384
397
|
serialNo: number;
|
|
398
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
399
|
+
deviceId?: number;
|
|
385
400
|
isConnect: boolean;
|
|
386
401
|
}
|
|
387
402
|
/**
|
|
@@ -391,6 +406,8 @@ interface DeviceConnectionPayload {
|
|
|
391
406
|
*/
|
|
392
407
|
interface DeviceNotificationPayload {
|
|
393
408
|
serialNo: number;
|
|
409
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
410
|
+
deviceId?: number;
|
|
394
411
|
isConnect: boolean;
|
|
395
412
|
message: string | null;
|
|
396
413
|
messageCode: string | null;
|
|
@@ -461,7 +478,9 @@ interface SensorLogPayload {
|
|
|
461
478
|
*
|
|
462
479
|
* Emitted on attach (before your bundle exists), again right after
|
|
463
480
|
* `handshake-ack`, and on every change. `deviceSyncError` maps `serialNo` to an
|
|
464
|
-
* error message the app has already localized.
|
|
481
|
+
* error message the app has already localized. A `serialNo` shared by two
|
|
482
|
+
* devices has no single owner, so it is **left out** of `deviceSyncError` and
|
|
483
|
+
* only appears in `deviceSyncErrorById`.
|
|
465
484
|
*/
|
|
466
485
|
interface SystemStatusPayload {
|
|
467
486
|
bluetooth: boolean;
|
|
@@ -485,6 +504,11 @@ interface SystemStatusPayload {
|
|
|
485
504
|
*/
|
|
486
505
|
missingEssentialPermissions: string[];
|
|
487
506
|
deviceSyncError: Record<number, string>;
|
|
507
|
+
/**
|
|
508
|
+
* Same errors keyed by the server `deviceId`, for **every** device, shared
|
|
509
|
+
* `serialNo` included. Absent below `nativeVersion` 0.17.0, so guard it.
|
|
510
|
+
*/
|
|
511
|
+
deviceSyncErrorById?: Record<number, string>;
|
|
488
512
|
}
|
|
489
513
|
/**
|
|
490
514
|
* Arguments of the `requestPermission` command (JS → native).
|
package/docs/agent-guide.md
CHANGED
|
@@ -130,6 +130,12 @@ BridgeClient.send('realTime', { serialNo, value: true });
|
|
|
130
130
|
|
|
131
131
|
`serialNo` is a **number**. A string is dropped in silence.
|
|
132
132
|
|
|
133
|
+
Two devices can share a `serialNo` (an ActLumus and an ActLumus Plus). To act on
|
|
134
|
+
one of them, send its `deviceId` **together with** `serialNo`, taken from the
|
|
135
|
+
`userDevice` list: `BridgeClient.send('realTime', { serialNo, deviceId, value })`.
|
|
136
|
+
A command with only the shared `serialNo` is ignored on purpose. Gate this on
|
|
137
|
+
`nativeVersion` ≥ 0.17.0; details in `docs/bridge-contract.md`.
|
|
138
|
+
|
|
133
139
|
In a raw channel, `null` means the channel does not exist on that hardware
|
|
134
140
|
generation. It does not mean zero — `0.0` is a valid reading. Coercing `null`
|
|
135
141
|
to `0` invents data, and has previously led to a confident, wrong conclusion
|
|
@@ -191,7 +197,8 @@ necessary, not sufficient.
|
|
|
191
197
|
(`syncTime`, `deleteMemory`, `logout`) may never ack, because the person
|
|
192
198
|
can decline.
|
|
193
199
|
- [ ] No `localStorage`, no `fetch`, no `eval`. Not even behind a `try`.
|
|
194
|
-
- [ ] `serialNo` is a number everywhere
|
|
200
|
+
- [ ] `serialNo` is a number everywhere, and a command for a device whose `serialNo`
|
|
201
|
+
repeats on the account also carries its `deviceId` (`nativeVersion` ≥ 0.17.0).
|
|
195
202
|
- [ ] Errors are reported through `BridgeClient.send('jsError', …)`, the only
|
|
196
203
|
channel out.
|
|
197
204
|
- [ ] Tested in the browser mock with `nativeVersion === null`.
|
package/docs/bridge-contract.md
CHANGED
|
@@ -29,10 +29,10 @@ Four numbers coexist. Only one tells you what the device can do.
|
|
|
29
29
|
|
|
30
30
|
| Number | Current | What it means |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| `nativeVersion` | `0.
|
|
32
|
+
| `nativeVersion` | `0.17.0` | **The capability signal.** The bridge schema the app on this device implements. |
|
|
33
33
|
| `shellVersion` | `0.6.0` | The Condor shell wrapping your iframe. |
|
|
34
34
|
| client protocol | `0.5.0` | The postMessage protocol `BridgeClient` speaks. |
|
|
35
|
-
| package | `0.
|
|
35
|
+
| package | `0.20.0` | The npm version you compiled against. |
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
38
|
const ack = await BridgeClient.handshake({ clientVersion: '1.0.0' });
|
|
@@ -58,6 +58,7 @@ the one you see most.
|
|
|
58
58
|
| `language` key and the `language` pull command | `nativeVersion` ≥ 0.14.0 |
|
|
59
59
|
| `sensorLog` accepts `startTime` / `endTime` | `nativeVersion` ≥ 0.15.0 |
|
|
60
60
|
| `deviceLightSensors` carries raw `light_sensor_*` channels, and the `actlumus2` variant | `nativeVersion` ≥ 0.16.0 |
|
|
61
|
+
| Address one device by `deviceId` when two share a `serialNo` (`realTime`, `syncTime`, `deleteMemory`, `deviceConnection`); `deviceId` in the device events; `systemStatus.deviceSyncErrorById` | `nativeVersion` ≥ 0.17.0 |
|
|
61
62
|
| `nativeVersion` reaches the client at all | `shellVersion` ≥ 0.6.0 |
|
|
62
63
|
|
|
63
64
|
`CHANGELOG.md` is the precise record of when each of these landed.
|
|
@@ -115,7 +116,8 @@ conclusions before.
|
|
|
115
116
|
bluetooth: boolean; internet: boolean; permission: boolean; hasDevices: boolean;
|
|
116
117
|
missingEssentialPermissions: string[]; // e.g. ["bluetoothScan", "bluetoothConnect"]
|
|
117
118
|
missingOptionalPermissions: string[]; // e.g. ["notification", "backgroundLocation"]
|
|
118
|
-
deviceSyncError: Record<number, string>; // serialNo → message, already localized
|
|
119
|
+
deviceSyncError: Record<number, string>; // serialNo → message, already localized; a shared serialNo is left out
|
|
120
|
+
deviceSyncErrorById?: Record<number, string>; // deviceId → message, every device (nativeVersion ≥ 0.17.0)
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
@@ -133,8 +135,8 @@ column.
|
|
|
133
135
|
| --- | --- | --- | --- |
|
|
134
136
|
| `ping` | — | no | `pingPong` |
|
|
135
137
|
| `userDevice` | `{ getFromServer?: boolean }` | no | `userDevice` |
|
|
136
|
-
| `realTime` | `{ serialNo: number, value: boolean }` | no | `realTime` |
|
|
137
|
-
| `deviceConnection` | `{ type: 'add' \| 'remove', serialNo: number }` | no | `deviceConnection` |
|
|
138
|
+
| `realTime` | `{ serialNo: number, deviceId?: number, value: boolean }` | no | `realTime` |
|
|
139
|
+
| `deviceConnection` | `{ type: 'add' \| 'remove', serialNo: number, deviceId?: number }` | no | `deviceConnection` |
|
|
138
140
|
| `sensorLog` | see below | no | `sensorLog` |
|
|
139
141
|
| `openSettings` | — | no | — |
|
|
140
142
|
| `requestPermission` | `{ permission: string }` | no | — (re-emits `systemStatus`) |
|
|
@@ -142,14 +144,40 @@ column.
|
|
|
142
144
|
| `navigationState` | `{ canGoBack: boolean }` | no | — |
|
|
143
145
|
| `language` | — | no | `language` |
|
|
144
146
|
| `jsError` | `{ message, stack?, source?, lineno?, colno? }` | no | — |
|
|
145
|
-
| `syncTime` | `{ serialNo: number }` | **yes** | `syncTime` |
|
|
146
|
-
| `deleteMemory` | `{ serialNo: number }` | **yes** | `deleteMemory` |
|
|
147
|
+
| `syncTime` | `{ serialNo: number, deviceId?: number }` | **yes** | `syncTime` |
|
|
148
|
+
| `deleteMemory` | `{ serialNo: number, deviceId?: number }` | **yes** | `deleteMemory` |
|
|
147
149
|
| `logout` | — | **yes** | — |
|
|
148
150
|
| `dfu` | — | denied | — |
|
|
149
151
|
|
|
150
152
|
`serialNo` is an integer. Passing a string makes the native side drop the
|
|
151
153
|
message in silence.
|
|
152
154
|
|
|
155
|
+
### Two devices with the same `serialNo`
|
|
156
|
+
|
|
157
|
+
An ActLumus and an ActLumus Plus have separate serial sequences, so one account
|
|
158
|
+
can hold both with the **same** `serialNo` (the server `deviceId` is always
|
|
159
|
+
unique). A command that carries only `serialNo` cannot say which one you mean:
|
|
160
|
+
the app **ignores it** instead of guessing, because guessing wrong would act on
|
|
161
|
+
the wrong device (erasing the wrong memory, for instance).
|
|
162
|
+
|
|
163
|
+
- **Send `deviceId` together with `serialNo`**, taken from the `userDevice`
|
|
164
|
+
list (`devicesConfig[].deviceId`). Always keep `serialNo` in the message: an
|
|
165
|
+
app older than 0.17.0 drops in silence a command that lacks it.
|
|
166
|
+
- **Read `deviceId` when it is there.** `deviceConnection`,
|
|
167
|
+
`deviceNotification` and `deviceLightSensors` carry it **only when the
|
|
168
|
+
`serialNo` is shared**; with a unique `serialNo` it is absent and `serialNo`
|
|
169
|
+
alone identifies the device. Never treat a missing `deviceId` as "unknown
|
|
170
|
+
device".
|
|
171
|
+
- **Acks always carry `serialNo`**, plus the `deviceId` you sent.
|
|
172
|
+
- **`systemStatus.deviceSyncError` leaves a shared `serialNo` out**, because no
|
|
173
|
+
single device owns it. Read `deviceSyncErrorById` for every device.
|
|
174
|
+
- **Gate on `nativeVersion` ≥ 0.17.0** before relying on any of this, and
|
|
175
|
+
degrade to `serialNo` only below it (where a shared `serialNo` was already
|
|
176
|
+
unaddressable).
|
|
177
|
+
- `sensorLog` takes no `deviceId` yet: for a shared `serialNo` the backend
|
|
178
|
+
answers with the error code `DEVICE_SERIAL_AMBIGUOUS`. Show your own copy for
|
|
179
|
+
it; do not retry.
|
|
180
|
+
|
|
153
181
|
### Commands that need human confirmation
|
|
154
182
|
|
|
155
183
|
`syncTime`, `deleteMemory` and `logout` open a modal owned by the Condor shell.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -30,6 +30,10 @@ in the field can be several versions behind your development device.
|
|
|
30
30
|
payload natively and drop it on mismatch. `serialNo` is an **integer**, not a
|
|
31
31
|
string; `value` and `canGoBack` are **booleans**, not strings.
|
|
32
32
|
|
|
33
|
+
**Also check:** if the account holds an ActLumus and an ActLumus Plus with the same
|
|
34
|
+
`serialNo`, a command without `deviceId` is ignored (never guessed). Send both
|
|
35
|
+
(`nativeVersion` ≥ 0.17.0).
|
|
36
|
+
|
|
33
37
|
**Check:** `typeof` at the call site. `serialNo: "1234"` fails; `serialNo: 1234`
|
|
34
38
|
works.
|
|
35
39
|
|
package/llms.txt
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> pagination, permissions, system status and language.
|
|
8
8
|
|
|
9
9
|
Every URL below is plain text over HTTPS, pinned to a released version, and
|
|
10
|
-
needs no authentication. Replace `0.
|
|
10
|
+
needs no authentication. Replace `0.20.0` with the version you depend on; the
|
|
11
11
|
contract changes between minors while the package is below 1.0.0.
|
|
12
12
|
|
|
13
13
|
The same files ship inside the npm tarball, so if you have run
|
|
@@ -16,27 +16,27 @@ The same files ship inside the npm tarball, so if you have run
|
|
|
16
16
|
|
|
17
17
|
## Start here
|
|
18
18
|
|
|
19
|
-
- [AGENTS.md](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
20
|
-
- [Agent guide](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
19
|
+
- [AGENTS.md](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/AGENTS.md): The contract in one screen — hard rules, what is unreachable, and the validation loop. Read before writing code.
|
|
20
|
+
- [Agent guide](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/agent-guide.md): Step-by-step construction of a client Home, with the checks to run at each stage.
|
|
21
21
|
|
|
22
22
|
## Reference
|
|
23
23
|
|
|
24
|
-
- [Bridge contract](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
25
|
-
- [Machine-readable contract](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
26
|
-
- [Limits](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
27
|
-
- [Troubleshooting](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
28
|
-
- [TypeScript declarations](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
24
|
+
- [Bridge contract](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/bridge-contract.md): Every observable key, every command, the three error shapes, and the capability matrix by native version.
|
|
25
|
+
- [Machine-readable contract](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/contract/bridge-contract.json): The same contract as JSON. Also importable as `@condorinst/mobile-bridge/contract`.
|
|
26
|
+
- [Limits](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/limits.md): Every hard limit — payload size, rate limits, bundle size, timeouts — with what happens when you cross it.
|
|
27
|
+
- [Troubleshooting](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/troubleshooting.md): Why a command did nothing. Silence is the platform's dominant failure mode.
|
|
28
|
+
- [TypeScript declarations](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/dist/index.d.ts): Authoritative payload shapes, with documentation on every member.
|
|
29
29
|
|
|
30
30
|
## Guides
|
|
31
31
|
|
|
32
|
-
- [Client guide](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
33
|
-
- [Pilot onboarding](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
34
|
-
- [README](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
35
|
-
- [CHANGELOG](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
32
|
+
- [Client guide](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/client-guide.md): Narrative walkthrough for a developer building the Home by hand.
|
|
33
|
+
- [Pilot onboarding](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/docs/pilot-onboarding.md): Checklist from zero to a bundle delivered to Condor.
|
|
34
|
+
- [README](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/README.md): Package overview, install, quickstart.
|
|
35
|
+
- [CHANGELOG](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/CHANGELOG.md): When each capability was introduced. The most precise record of what a given native version supports.
|
|
36
36
|
|
|
37
37
|
## Examples
|
|
38
38
|
|
|
39
|
-
- [Minimal home](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.
|
|
39
|
+
- [Minimal home](https://cdn.jsdelivr.net/npm/@condorinst/mobile-bridge@0.20.0/examples/minimal-home/src/main.ts): A complete, working client in ~160 lines. Copy this as your starting point.
|
|
40
40
|
|
|
41
41
|
## Optional
|
|
42
42
|
|
package/package.json
CHANGED