@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/dist/index.d.ts
CHANGED
|
@@ -225,23 +225,126 @@ interface HandshakeOptions {
|
|
|
225
225
|
interface PingPongPayload {
|
|
226
226
|
data: 'ping' | 'pong';
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* One linked device, as configured on the server. Mirrors
|
|
230
|
+
* `DeviceConfig.toMap()` in `condor_core`
|
|
231
|
+
* (`domain/entities/device_config_entity.dart`).
|
|
232
|
+
*
|
|
233
|
+
* `deviceId` is the server id and is unique. `serialNo` is printed on the
|
|
234
|
+
* device and **can repeat**: an ActLumus and an ActLumus Plus may share one.
|
|
235
|
+
*/
|
|
236
|
+
interface DeviceConfigItem {
|
|
237
|
+
/** Server id. Unique — the join key across the three arrays. */
|
|
238
|
+
deviceId: number;
|
|
239
|
+
/** Printed serial number. Not unique: an ActLumus and a Plus may share one. */
|
|
240
|
+
serialNo: number;
|
|
241
|
+
/** Where the device is worn. `null` when not set. */
|
|
242
|
+
deviceBodyPart: 'wrist' | 'necklace' | null;
|
|
243
|
+
/**
|
|
244
|
+
* Device model id. Absent when the server did not send one, which means an
|
|
245
|
+
* ActLumus (gen1); the Plus always carries it. Never a display name.
|
|
246
|
+
*/
|
|
247
|
+
modelId?: 'actlumus' | 'actlumus_plus' | (string & {});
|
|
248
|
+
/** BLE advertising period, in minutes, set by the Condor web system. */
|
|
249
|
+
bleConnectionPeriod: number;
|
|
250
|
+
autosync: boolean;
|
|
251
|
+
eraseDeviceMemory: boolean;
|
|
252
|
+
autoEraseDeviceMemory: boolean;
|
|
253
|
+
autoEraseDeviceMemoryThreshold: number;
|
|
254
|
+
readSensorsMemory: boolean;
|
|
255
|
+
startDevice: boolean;
|
|
256
|
+
readFullMemory: boolean;
|
|
257
|
+
allowFirmwareUpdate: boolean;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* One failed sync attempt. Mirrors `DeviceSyncError.toMap()`
|
|
261
|
+
* (`domain/entities/device_sync_error_entity.dart`).
|
|
262
|
+
*/
|
|
263
|
+
interface DeviceSyncErrorItem {
|
|
264
|
+
serialNo: number;
|
|
265
|
+
/** Epoch milliseconds. */
|
|
266
|
+
attemptDate: number;
|
|
267
|
+
/** Already localized by the app. Show it; do not branch on it. */
|
|
268
|
+
message: string;
|
|
269
|
+
/** Stable identifier to branch on. `null` for legacy errors. */
|
|
270
|
+
messageCode: string | null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Last known state of one device, as read over BLE. Mirrors
|
|
274
|
+
* `DeviceTelemetry.toMap()` (`domain/entities/device_telemetry_entity.dart`).
|
|
275
|
+
*
|
|
276
|
+
* Every reading is `null` until the device has synced at least once.
|
|
277
|
+
*/
|
|
278
|
+
interface DeviceTelemetryItem {
|
|
279
|
+
serialNo: number;
|
|
280
|
+
/**
|
|
281
|
+
* Server id. **Omitted** on legacy data the app could not attribute, so join
|
|
282
|
+
* by `deviceId` when present and fall back to `serialNo` only when that
|
|
283
|
+
* `serialNo` is unique in `devicesConfig`.
|
|
284
|
+
*/
|
|
285
|
+
deviceId?: number;
|
|
286
|
+
/**
|
|
287
|
+
* Last successful sync, epoch milliseconds. The misspelling (`updata`) is the
|
|
288
|
+
* real field name — do not "fix" it.
|
|
289
|
+
*/
|
|
290
|
+
updataDateTime: number | null;
|
|
291
|
+
/** Display name reported by the device. Not in `devicesConfig`. */
|
|
292
|
+
deviceName: string | null;
|
|
293
|
+
/** Firmware version. */
|
|
294
|
+
deviceVersion: string | null;
|
|
295
|
+
hardwareVersion: string | null;
|
|
296
|
+
/** Device clock at the last sync, epoch milliseconds. */
|
|
297
|
+
deviceDateTime: number | null;
|
|
298
|
+
/** Data memory used, percent 0–100. */
|
|
299
|
+
memoryDataUsage: number | null;
|
|
300
|
+
/** System memory used, percent 0–100. */
|
|
301
|
+
memorySystemUsage: number | null;
|
|
302
|
+
/** Battery charge, percent 0–100. */
|
|
303
|
+
batteryLevel: number | null;
|
|
304
|
+
/** Battery voltage, volts. */
|
|
305
|
+
batteryVoltage: number | null;
|
|
306
|
+
isCharging: boolean | null;
|
|
307
|
+
isOnDock: boolean | null;
|
|
308
|
+
/** Sync failures of this device. Empty when there are none. */
|
|
309
|
+
syncErrors: DeviceSyncErrorItem[];
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Per-device toggles of the user. Mirrors `DeviceUserConfig.toMap()`
|
|
313
|
+
* (`domain/entities/device_user_config_entity.dart`).
|
|
314
|
+
*/
|
|
315
|
+
interface DeviceUserConfigItem {
|
|
316
|
+
serialNo: number;
|
|
317
|
+
/** Server id. Omitted on legacy data — see `DeviceTelemetryItem.deviceId`. */
|
|
318
|
+
deviceId?: number;
|
|
319
|
+
syncTime: boolean;
|
|
320
|
+
realTime: boolean;
|
|
321
|
+
deleteMemory: boolean;
|
|
322
|
+
readAllMemory: boolean;
|
|
323
|
+
readAllSystemLog: boolean;
|
|
324
|
+
}
|
|
228
325
|
/**
|
|
229
326
|
* Snapshot of the user's device store. Mirrors `UserDevices.toMap()` in
|
|
230
327
|
* `condor_core`.
|
|
231
328
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
329
|
+
* **Joining the three arrays.** `devicesConfig` lists the devices; the other
|
|
330
|
+
* two describe them. Join on `deviceId`. When an item of `devicesTelemetry` or
|
|
331
|
+
* `devicesUserConfig` has no `deviceId` (legacy data), fall back to `serialNo`
|
|
332
|
+
* — but only when that `serialNo` appears once in `devicesConfig`. A device may
|
|
333
|
+
* have no telemetry yet: render it with empty readings, never drop it.
|
|
334
|
+
*
|
|
335
|
+
* **It is not guaranteed to answer.** Use `BridgeClient.requestUserDevices()`
|
|
336
|
+
* instead of `send('userDevice')`; see `docs/troubleshooting.md`.
|
|
235
337
|
*
|
|
236
338
|
* The app enforces no device cap. A commercial rule currently limits a user to
|
|
237
339
|
* two linked devices, but that can change with no signal in this contract — read
|
|
238
340
|
* the length of the array rather than hard-coding it.
|
|
239
341
|
*/
|
|
240
342
|
interface UserDevicePayload {
|
|
343
|
+
/** Epoch milliseconds of the snapshot. */
|
|
241
344
|
lastUpdate: number;
|
|
242
|
-
devicesConfig:
|
|
243
|
-
devicesTelemetry:
|
|
244
|
-
devicesUserConfig:
|
|
345
|
+
devicesConfig: DeviceConfigItem[];
|
|
346
|
+
devicesTelemetry: DeviceTelemetryItem[];
|
|
347
|
+
devicesUserConfig: DeviceUserConfigItem[];
|
|
245
348
|
}
|
|
246
349
|
/**
|
|
247
350
|
* Raw light-sensor channels, under the names the backend uses in
|
|
@@ -341,7 +444,12 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
341
444
|
};
|
|
342
445
|
/**
|
|
343
446
|
* Real-time light-sensor stream. Shape:
|
|
344
|
-
* `{ serialNo, type, data }`.
|
|
447
|
+
* `{ serialNo, deviceId?, type, data }`.
|
|
448
|
+
*
|
|
449
|
+
* `deviceId` is the server id of the device and is **optional**: the app only
|
|
450
|
+
* sends it when more than one device on the account shares that `serialNo` (an
|
|
451
|
+
* ActLumus and an ActLumus Plus can). With a unique `serialNo` it is absent, so
|
|
452
|
+
* keep matching on `serialNo` in that case. Present from `nativeVersion` 0.17.0.
|
|
345
453
|
*
|
|
346
454
|
* A union discriminated by `type`, which is the `DeviceType.name` on the Dart
|
|
347
455
|
* side. `data` carries **both the derived spectrum and the raw channels it came
|
|
@@ -353,27 +461,35 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
|
353
461
|
*/
|
|
354
462
|
type DeviceLightSensorsPayload = {
|
|
355
463
|
serialNo: number;
|
|
464
|
+
deviceId?: number;
|
|
356
465
|
type: 'actlumus';
|
|
357
466
|
data: LightSensorsActlumus1Data;
|
|
358
467
|
} | {
|
|
359
468
|
serialNo: number;
|
|
469
|
+
deviceId?: number;
|
|
360
470
|
type: 'actlumus2';
|
|
361
471
|
data: LightSensorsActlumus2Data;
|
|
362
472
|
};
|
|
363
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* Ack de comando `syncTime`. `deviceId` echoes the one you sent (see
|
|
475
|
+
* `DeviceLightSensorsPayload`); `serialNo` is always present.
|
|
476
|
+
*/
|
|
364
477
|
interface SyncTimeAckPayload {
|
|
365
478
|
serialNo: number;
|
|
479
|
+
deviceId?: number;
|
|
366
480
|
success: boolean;
|
|
367
481
|
}
|
|
368
|
-
/** Ack/state do toggle `realTime`. */
|
|
482
|
+
/** Ack/state do toggle `realTime`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
369
483
|
interface RealTimeAckPayload {
|
|
370
484
|
serialNo: number;
|
|
485
|
+
deviceId?: number;
|
|
371
486
|
success: boolean;
|
|
372
487
|
value: boolean;
|
|
373
488
|
}
|
|
374
|
-
/** Ack de comando `deleteMemory`. */
|
|
489
|
+
/** Ack de comando `deleteMemory`. `deviceId` as in `SyncTimeAckPayload`. */
|
|
375
490
|
interface DeleteMemoryAckPayload {
|
|
376
491
|
serialNo: number;
|
|
492
|
+
deviceId?: number;
|
|
377
493
|
success: boolean;
|
|
378
494
|
}
|
|
379
495
|
/**
|
|
@@ -382,6 +498,8 @@ interface DeleteMemoryAckPayload {
|
|
|
382
498
|
*/
|
|
383
499
|
interface DeviceConnectionPayload {
|
|
384
500
|
serialNo: number;
|
|
501
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
502
|
+
deviceId?: number;
|
|
385
503
|
isConnect: boolean;
|
|
386
504
|
}
|
|
387
505
|
/**
|
|
@@ -391,6 +509,8 @@ interface DeviceConnectionPayload {
|
|
|
391
509
|
*/
|
|
392
510
|
interface DeviceNotificationPayload {
|
|
393
511
|
serialNo: number;
|
|
512
|
+
/** Only when the `serialNo` is shared by more than one device. */
|
|
513
|
+
deviceId?: number;
|
|
394
514
|
isConnect: boolean;
|
|
395
515
|
message: string | null;
|
|
396
516
|
messageCode: string | null;
|
|
@@ -427,6 +547,63 @@ interface SensorLogQueryPayload {
|
|
|
427
547
|
/** Exclusive, `YYYY-MM-DD HH:MM:SS`, in device-local time. */
|
|
428
548
|
endTime?: string;
|
|
429
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* One sensor log, passed through from the backend `get_log` contract.
|
|
552
|
+
*
|
|
553
|
+
* **Cadence.** The device writes one log per period, which is programmable per
|
|
554
|
+
* device (default 60 s). Never assume one per minute: size pagination from the
|
|
555
|
+
* gaps between `log_time` values you actually receive.
|
|
556
|
+
*
|
|
557
|
+
* `null` on a channel means the hardware does not have it, never zero.
|
|
558
|
+
*/
|
|
559
|
+
interface SensorLogItem {
|
|
560
|
+
/** Stable id of the log. */
|
|
561
|
+
id: number;
|
|
562
|
+
/** `'YYYY-MM-DD HH:MM:SS'` in **device-local time**, not UTC. */
|
|
563
|
+
log_time: string;
|
|
564
|
+
log_type: number;
|
|
565
|
+
body_part: 'wrist' | 'necklace' | null;
|
|
566
|
+
/** Degrees Celsius, already converted by the backend. */
|
|
567
|
+
temperature: number | null;
|
|
568
|
+
/** Photopic illuminance, lux. */
|
|
569
|
+
lux_photopic: number | null;
|
|
570
|
+
/** Melanopic illuminance (melanopic EDI), lux. */
|
|
571
|
+
lux_melanopic: number | null;
|
|
572
|
+
company_id?: number;
|
|
573
|
+
user_id?: number;
|
|
574
|
+
device_id?: number;
|
|
575
|
+
orientation?: number | null;
|
|
576
|
+
light_sensor_f1?: number | null;
|
|
577
|
+
light_sensor_f2?: number | null;
|
|
578
|
+
light_sensor_f3?: number | null;
|
|
579
|
+
light_sensor_f4?: number | null;
|
|
580
|
+
light_sensor_f5?: number | null;
|
|
581
|
+
light_sensor_f6?: number | null;
|
|
582
|
+
light_sensor_f7?: number | null;
|
|
583
|
+
light_sensor_f8?: number | null;
|
|
584
|
+
light_sensor_clear?: number | null;
|
|
585
|
+
light_sensor_nir?: number | null;
|
|
586
|
+
/** ActLumus Plus only (`null` on gen1). Approximates CIE X; not an exact CIE value. */
|
|
587
|
+
light_sensor_x?: number | null;
|
|
588
|
+
/** ActLumus Plus only (`null` on gen1). */
|
|
589
|
+
light_sensor_y?: number | null;
|
|
590
|
+
/** ActLumus Plus only (`null` on gen1). */
|
|
591
|
+
light_sensor_z?: number | null;
|
|
592
|
+
/** ActLumus Plus only (`null` on gen1). Reserved by the firmware; do not interpret. */
|
|
593
|
+
light_sensor_fd?: number | null;
|
|
594
|
+
act_data_pim?: number | null;
|
|
595
|
+
act_data_tat?: number | null;
|
|
596
|
+
act_data_zcm?: number | null;
|
|
597
|
+
off_wrist?: number | null;
|
|
598
|
+
cap_sensor_1?: number | null;
|
|
599
|
+
cap_sensor_2?: number | null;
|
|
600
|
+
event?: number | null;
|
|
601
|
+
state?: number | null;
|
|
602
|
+
from_service?: boolean;
|
|
603
|
+
/** Batch markers of the upload, not per-log timestamps. Do not plot them. */
|
|
604
|
+
device_log_time?: string | null;
|
|
605
|
+
app_log_time?: string | null;
|
|
606
|
+
}
|
|
430
607
|
/**
|
|
431
608
|
* Reply to the `sensorLog` command.
|
|
432
609
|
*
|
|
@@ -447,7 +624,8 @@ interface SensorLogQueryPayload {
|
|
|
447
624
|
interface SensorLogPayload {
|
|
448
625
|
requestId: string;
|
|
449
626
|
success: boolean;
|
|
450
|
-
|
|
627
|
+
/** Chronological ascending. Without a cursor or range: the most recent page. */
|
|
628
|
+
logs?: SensorLogItem[];
|
|
451
629
|
startingAfter?: string | null;
|
|
452
630
|
endingBefore?: string | null;
|
|
453
631
|
hasMore?: boolean;
|
|
@@ -461,7 +639,9 @@ interface SensorLogPayload {
|
|
|
461
639
|
*
|
|
462
640
|
* Emitted on attach (before your bundle exists), again right after
|
|
463
641
|
* `handshake-ack`, and on every change. `deviceSyncError` maps `serialNo` to an
|
|
464
|
-
* error message the app has already localized.
|
|
642
|
+
* error message the app has already localized. A `serialNo` shared by two
|
|
643
|
+
* devices has no single owner, so it is **left out** of `deviceSyncError` and
|
|
644
|
+
* only appears in `deviceSyncErrorById`.
|
|
465
645
|
*/
|
|
466
646
|
interface SystemStatusPayload {
|
|
467
647
|
bluetooth: boolean;
|
|
@@ -485,6 +665,11 @@ interface SystemStatusPayload {
|
|
|
485
665
|
*/
|
|
486
666
|
missingEssentialPermissions: string[];
|
|
487
667
|
deviceSyncError: Record<number, string>;
|
|
668
|
+
/**
|
|
669
|
+
* Same errors keyed by the server `deviceId`, for **every** device, shared
|
|
670
|
+
* `serialNo` included. Absent below `nativeVersion` 0.17.0, so guard it.
|
|
671
|
+
*/
|
|
672
|
+
deviceSyncErrorById?: Record<number, string>;
|
|
488
673
|
}
|
|
489
674
|
/**
|
|
490
675
|
* Arguments of the `requestPermission` command (JS → native).
|
|
@@ -711,7 +896,9 @@ declare const Bridge: CondorBridge;
|
|
|
711
896
|
* deletes them before your bundle runs.
|
|
712
897
|
*
|
|
713
898
|
* It deliberately does **not** expose `installDevPanel`, `mockEmit`, `isMock`
|
|
714
|
-
* or `getMockState`: those belong to the shell (`BridgeShell`).
|
|
899
|
+
* or `getMockState`: those belong to the shell (`BridgeShell`). To run a bundle
|
|
900
|
+
* in a browser, use the dev harness (`npx condor-mobile-bridge dev`, or
|
|
901
|
+
* `createDevHarness` from `@condorinst/mobile-bridge/dev`).
|
|
715
902
|
*
|
|
716
903
|
* The module also answers the shell's `verify-sandbox` probe — the iframe
|
|
717
904
|
* attesting whether it can still see the native globals. If any is reachable,
|
|
@@ -723,14 +910,14 @@ interface BridgeClientHandshakeAck {
|
|
|
723
910
|
shellVersion: string;
|
|
724
911
|
/**
|
|
725
912
|
* Schema version of the **native** side, relayed by the shell. `null` when
|
|
726
|
-
* the native side has not answered,
|
|
727
|
-
* the shell is older than 0.6.0.
|
|
913
|
+
* the native side has not answered, or the shell is older than 0.6.0.
|
|
728
914
|
*
|
|
729
915
|
* **This number is the only capability signal a client gets.** There is no
|
|
730
916
|
* version negotiation, and a sandboxed client reaches the native side by no
|
|
731
917
|
* other path. For example, the raw light channels (`light_sensor_*` in
|
|
732
918
|
* `deviceLightSensors`) exist from `0.16.0`. Always keep a working branch
|
|
733
|
-
* for `null
|
|
919
|
+
* for `null`. The dev harness reports `0.17.0` unless a scenario says
|
|
920
|
+
* otherwise.
|
|
734
921
|
*/
|
|
735
922
|
nativeVersion: string | null;
|
|
736
923
|
/**
|
|
@@ -791,13 +978,62 @@ interface CondorBridgeClient {
|
|
|
791
978
|
* native enum is dropped in silence.
|
|
792
979
|
*/
|
|
793
980
|
requestPermission(permission: string): void;
|
|
981
|
+
/**
|
|
982
|
+
* Resolves when the app's **background service** answers a `ping`.
|
|
983
|
+
*
|
|
984
|
+
* Commands that need device data (`userDevice`, `ping`) travel to a service
|
|
985
|
+
* the app starts after your bundle is already running. A command sent before
|
|
986
|
+
* the service is up is **lost without a reply** — the handshake, `systemStatus`
|
|
987
|
+
* and `language` work earlier, which makes the bridge look ready when it is
|
|
988
|
+
* not. This helper resends `ping` with backoff (1.5 s, 3 s, 6 s, then every
|
|
989
|
+
* 10 s) until a `pingPong` arrives.
|
|
990
|
+
*
|
|
991
|
+
* Never rejects: resolves `{ status: 'timeout' }` after `timeoutMs`
|
|
992
|
+
* (default 30 s).
|
|
993
|
+
*/
|
|
994
|
+
waitForService(opts?: {
|
|
995
|
+
timeoutMs?: number;
|
|
996
|
+
}): Promise<{
|
|
997
|
+
status: 'ready' | 'timeout';
|
|
998
|
+
}>;
|
|
999
|
+
/**
|
|
1000
|
+
* Requests the user's devices and **keeps asking** until they arrive. Use
|
|
1001
|
+
* this instead of `send('userDevice')`, which may never be answered:
|
|
1002
|
+
*
|
|
1003
|
+
* - on a cold start the first request is lost while the background service
|
|
1004
|
+
* comes up;
|
|
1005
|
+
* - an account with no linked device gets no reply at all.
|
|
1006
|
+
*
|
|
1007
|
+
* Resends with backoff (1.5 s, 3 s, 6 s, then every 10 s) and on every
|
|
1008
|
+
* `systemStatus` while pending. Never rejects. Resolves with
|
|
1009
|
+
* `{ status: 'ok', devices }`, or `{ status: 'timeout', hasDevices }` after
|
|
1010
|
+
* `timeoutMs` (default 30 s), where `hasDevices` is the last
|
|
1011
|
+
* `systemStatus.hasDevices` seen (`null` if none). `false` there **suggests**
|
|
1012
|
+
* an account with no device — show an empty state with a retry button — but
|
|
1013
|
+
* the app also reports `false` when it could not reach the server.
|
|
1014
|
+
*
|
|
1015
|
+
* `getFromServer: true` asks the app to refresh from the server; leave it
|
|
1016
|
+
* `false` (default) at boot, where the cached list is enough.
|
|
1017
|
+
*/
|
|
1018
|
+
requestUserDevices(opts?: {
|
|
1019
|
+
timeoutMs?: number;
|
|
1020
|
+
getFromServer?: boolean;
|
|
1021
|
+
}): Promise<{
|
|
1022
|
+
status: 'ok';
|
|
1023
|
+
devices: UserDevicePayload;
|
|
1024
|
+
} | {
|
|
1025
|
+
status: 'timeout';
|
|
1026
|
+
hasDevices: boolean | null;
|
|
1027
|
+
}>;
|
|
794
1028
|
/**
|
|
795
1029
|
* Handshakes with the shell. Resolves with
|
|
796
1030
|
* `{ shellVersion, nativeVersion, accepted }` — read `nativeVersion` to learn
|
|
797
1031
|
* what this device can do, and ignore `accepted`, which is always `true`.
|
|
798
1032
|
*
|
|
799
|
-
* Rejects after 5 s if the shell does not answer
|
|
800
|
-
*
|
|
1033
|
+
* Rejects after 5 s if the shell does not answer, and **at once** when the
|
|
1034
|
+
* page is not inside the Condor shell (opened directly in a browser) — run
|
|
1035
|
+
* `npx condor-mobile-bridge dev` instead. Only one handshake may be in flight
|
|
1036
|
+
* at a time.
|
|
801
1037
|
*/
|
|
802
1038
|
handshake(opts: {
|
|
803
1039
|
clientVersion: string;
|
|
@@ -1072,4 +1308,14 @@ declare class BridgeShell {
|
|
|
1072
1308
|
private postToClient;
|
|
1073
1309
|
}
|
|
1074
1310
|
|
|
1075
|
-
|
|
1311
|
+
/**
|
|
1312
|
+
* `versionAtLeast(ack.nativeVersion, '0.17.0')` — the capability check every
|
|
1313
|
+
* client needs. Compares the three numeric segments; `null` (an unknown native
|
|
1314
|
+
* side) is never at least anything, so it always takes the baseline branch.
|
|
1315
|
+
*
|
|
1316
|
+
* Do not compare version strings directly: `'0.9.0' > '0.16.0'` is `true` as
|
|
1317
|
+
* strings and false as versions.
|
|
1318
|
+
*/
|
|
1319
|
+
declare function versionAtLeast(version: string | null | undefined, min: string): boolean;
|
|
1320
|
+
|
|
1321
|
+
export { type AutoRespondResponse, BRIDGE_KEYS, Bridge, BridgeClient, type BridgeClientHandshakeAck, type BridgeIncomingMessage, type BridgeKey, type BridgeOutgoingEvent, BridgeShell, type BridgeShellOptions, type ClientToShellMessage, type CondorBridge, type CondorBridgeClient, type ConfirmationRequest, type DeleteMemoryAckPayload, type DevPanelOptions, type DeviceConfigItem, type DeviceConnectionPayload, type DeviceLightSensorsPayload, type DeviceNotificationPayload, type DeviceSyncErrorItem, type DeviceTelemetryItem, type DeviceUserConfigItem, type HandshakeAck, type HandshakeOptions, type KeyPayloads, type LanguagePayload, type LightSensorsActlumus1Data, type LightSensorsActlumus2Data, type LightSensorsRawChannels, type MockMessage, type MockState, NATIVE_CHANNEL_NAME, type NavigationBackPayload, type NavigationStatePayload, type OutgoingStamp, type PayloadOf, type PingPongPayload, type RealTimeAckPayload, type RequestPermissionPayload, type SandboxVerifyResult, type SensorLogItem, type SensorLogPayload, type SensorLogQueryPayload, type ShellPolicy, type ShellToClientMessage, type SyncTimeAckPayload, type SystemStatusPayload, type UserDevicePayload, versionAtLeast };
|
package/dist/index.js
CHANGED
|
@@ -616,17 +616,27 @@ var Bridge = {
|
|
|
616
616
|
var PROTOCOL_VERSION2 = "0.5.0";
|
|
617
617
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
618
618
|
var SENSOR_LOG_TIMEOUT_MS = 1e4;
|
|
619
|
+
var SERVICE_TIMEOUT_MS = 3e4;
|
|
620
|
+
var RESEND_BACKOFF_MS = [1500, 3e3, 6e3, 1e4];
|
|
621
|
+
var NOT_IN_SHELL_HINT = 'Not running inside the Condor shell. To develop in a browser, run `npx condor-mobile-bridge dev -i dist/index.html`, or mount the page with createDevHarness() from "@condorinst/mobile-bridge/dev".';
|
|
622
|
+
function insideShell() {
|
|
623
|
+
return typeof window !== "undefined" && window.parent !== window;
|
|
624
|
+
}
|
|
619
625
|
var subscribers2 = /* @__PURE__ */ new Map();
|
|
626
|
+
var lastSystemStatus = null;
|
|
620
627
|
var pendingHandshake2 = null;
|
|
621
628
|
var messageListenerInstalled = false;
|
|
622
629
|
function postToShell(msg) {
|
|
623
|
-
if (
|
|
624
|
-
console.warn(
|
|
630
|
+
if (!insideShell()) {
|
|
631
|
+
console.warn(`[BridgeClient] message dropped (${msg.type}). ${NOT_IN_SHELL_HINT}`);
|
|
625
632
|
return;
|
|
626
633
|
}
|
|
627
634
|
window.parent.postMessage(msg, "*");
|
|
628
635
|
}
|
|
629
636
|
function dispatchEvent(key, payload) {
|
|
637
|
+
if (key === BRIDGE_KEYS.systemStatus && payload && typeof payload === "object") {
|
|
638
|
+
lastSystemStatus = payload;
|
|
639
|
+
}
|
|
630
640
|
const arr = subscribers2.get(key);
|
|
631
641
|
if (!arr || arr.length === 0) return;
|
|
632
642
|
for (const sub of arr) {
|
|
@@ -732,8 +742,44 @@ var BridgeClient = {
|
|
|
732
742
|
requestPermission(permission) {
|
|
733
743
|
this.send("requestPermission", { permission });
|
|
734
744
|
},
|
|
745
|
+
waitForService(opts) {
|
|
746
|
+
return retryUntilReply({
|
|
747
|
+
timeoutMs: opts?.timeoutMs ?? SERVICE_TIMEOUT_MS,
|
|
748
|
+
send: () => this.send("ping"),
|
|
749
|
+
listen: (done) => this.on(BRIDGE_KEYS.pingPong, (payload) => {
|
|
750
|
+
if (payload.data === "pong") done({ status: "ready" });
|
|
751
|
+
}),
|
|
752
|
+
onTimeout: () => ({ status: "timeout" })
|
|
753
|
+
});
|
|
754
|
+
},
|
|
755
|
+
requestUserDevices(opts) {
|
|
756
|
+
let hasDevices = typeof lastSystemStatus?.hasDevices === "boolean" ? lastSystemStatus.hasDevices : null;
|
|
757
|
+
const getFromServer = opts?.getFromServer ?? false;
|
|
758
|
+
return retryUntilReply({
|
|
759
|
+
timeoutMs: opts?.timeoutMs ?? SERVICE_TIMEOUT_MS,
|
|
760
|
+
send: () => this.send("userDevice", { getFromServer }),
|
|
761
|
+
listen: (done, resend) => {
|
|
762
|
+
const offDevices = this.on(
|
|
763
|
+
BRIDGE_KEYS.userDevice,
|
|
764
|
+
(devices) => done({ status: "ok", devices })
|
|
765
|
+
);
|
|
766
|
+
const offStatus = this.on(BRIDGE_KEYS.systemStatus, (status) => {
|
|
767
|
+
hasDevices = status.hasDevices;
|
|
768
|
+
resend();
|
|
769
|
+
});
|
|
770
|
+
return () => {
|
|
771
|
+
offDevices();
|
|
772
|
+
offStatus();
|
|
773
|
+
};
|
|
774
|
+
},
|
|
775
|
+
onTimeout: () => ({ status: "timeout", hasDevices })
|
|
776
|
+
});
|
|
777
|
+
},
|
|
735
778
|
handshake(opts) {
|
|
736
779
|
ensureMessageListener();
|
|
780
|
+
if (!insideShell()) {
|
|
781
|
+
return Promise.reject(new Error(`BridgeClient: handshake impossible. ${NOT_IN_SHELL_HINT}`));
|
|
782
|
+
}
|
|
737
783
|
if (pendingHandshake2) {
|
|
738
784
|
return Promise.reject(
|
|
739
785
|
new Error(
|
|
@@ -760,6 +806,37 @@ var BridgeClient = {
|
|
|
760
806
|
return this.on(BRIDGE_KEYS.navigationBack, () => cb());
|
|
761
807
|
}
|
|
762
808
|
};
|
|
809
|
+
function retryUntilReply(opts) {
|
|
810
|
+
return new Promise((resolve) => {
|
|
811
|
+
let settled = false;
|
|
812
|
+
let attempt = 0;
|
|
813
|
+
let lastSend = 0;
|
|
814
|
+
let resendTimer;
|
|
815
|
+
const finish = (result) => {
|
|
816
|
+
if (settled) return;
|
|
817
|
+
settled = true;
|
|
818
|
+
clearTimeout(deadline);
|
|
819
|
+
clearTimeout(resendTimer);
|
|
820
|
+
unlisten();
|
|
821
|
+
resolve(result);
|
|
822
|
+
};
|
|
823
|
+
const sendNow = () => {
|
|
824
|
+
if (settled) return;
|
|
825
|
+
lastSend = Date.now();
|
|
826
|
+
opts.send();
|
|
827
|
+
const delay = RESEND_BACKOFF_MS[Math.min(attempt, RESEND_BACKOFF_MS.length - 1)] ?? 1e4;
|
|
828
|
+
attempt += 1;
|
|
829
|
+
clearTimeout(resendTimer);
|
|
830
|
+
resendTimer = setTimeout(sendNow, delay);
|
|
831
|
+
};
|
|
832
|
+
const resend = () => {
|
|
833
|
+
if (Date.now() - lastSend >= 1e3) sendNow();
|
|
834
|
+
};
|
|
835
|
+
const unlisten = opts.listen(finish, resend);
|
|
836
|
+
const deadline = setTimeout(() => finish(opts.onTimeout()), opts.timeoutMs);
|
|
837
|
+
sendNow();
|
|
838
|
+
});
|
|
839
|
+
}
|
|
763
840
|
|
|
764
841
|
// src/bridge-shell.ts
|
|
765
842
|
import * as v2 from "valibot";
|
|
@@ -1137,11 +1214,25 @@ var BridgeShell = class {
|
|
|
1137
1214
|
this.iframe.contentWindow?.postMessage(msg, "*");
|
|
1138
1215
|
}
|
|
1139
1216
|
};
|
|
1217
|
+
|
|
1218
|
+
// src/version.ts
|
|
1219
|
+
function versionAtLeast(version, min) {
|
|
1220
|
+
if (!version) return false;
|
|
1221
|
+
const a = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1222
|
+
const b = min.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1223
|
+
for (let i = 0; i < 3; i++) {
|
|
1224
|
+
const x = a[i] ?? 0;
|
|
1225
|
+
const y = b[i] ?? 0;
|
|
1226
|
+
if (x !== y) return x > y;
|
|
1227
|
+
}
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1140
1230
|
export {
|
|
1141
1231
|
BRIDGE_KEYS,
|
|
1142
1232
|
Bridge,
|
|
1143
1233
|
BridgeClient,
|
|
1144
1234
|
BridgeShell,
|
|
1145
|
-
NATIVE_CHANNEL_NAME
|
|
1235
|
+
NATIVE_CHANNEL_NAME,
|
|
1236
|
+
versionAtLeast
|
|
1146
1237
|
};
|
|
1147
1238
|
//# sourceMappingURL=index.js.map
|