@condorinst/mobile-bridge 0.20.0 → 0.22.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/dist/index.d.cts 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
- * The inner arrays are `Record<string, unknown>`: each item has its own
233
- * `toMap()` on the Dart side (device config, telemetry, user config), and
234
- * strict typing for them is deferred until a client needs it.
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: Array<Record<string, unknown>>;
243
- devicesTelemetry: Array<Record<string, unknown>>;
244
- devicesUserConfig: Array<Record<string, unknown>>;
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
@@ -444,6 +547,63 @@ interface SensorLogQueryPayload {
444
547
  /** Exclusive, `YYYY-MM-DD HH:MM:SS`, in device-local time. */
445
548
  endTime?: string;
446
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
+ }
447
607
  /**
448
608
  * Reply to the `sensorLog` command.
449
609
  *
@@ -464,7 +624,8 @@ interface SensorLogQueryPayload {
464
624
  interface SensorLogPayload {
465
625
  requestId: string;
466
626
  success: boolean;
467
- logs?: Array<Record<string, unknown>>;
627
+ /** Chronological ascending. Without a cursor or range: the most recent page. */
628
+ logs?: SensorLogItem[];
468
629
  startingAfter?: string | null;
469
630
  endingBefore?: string | null;
470
631
  hasMore?: boolean;
@@ -735,7 +896,9 @@ declare const Bridge: CondorBridge;
735
896
  * deletes them before your bundle runs.
736
897
  *
737
898
  * It deliberately does **not** expose `installDevPanel`, `mockEmit`, `isMock`
738
- * 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`).
739
902
  *
740
903
  * The module also answers the shell's `verify-sandbox` probe — the iframe
741
904
  * attesting whether it can still see the native globals. If any is reachable,
@@ -747,14 +910,14 @@ interface BridgeClientHandshakeAck {
747
910
  shellVersion: string;
748
911
  /**
749
912
  * Schema version of the **native** side, relayed by the shell. `null` when
750
- * the native side has not answered, does not exist (browser mock mode), or
751
- * the shell is older than 0.6.0.
913
+ * the native side has not answered, or the shell is older than 0.6.0.
752
914
  *
753
915
  * **This number is the only capability signal a client gets.** There is no
754
916
  * version negotiation, and a sandboxed client reaches the native side by no
755
917
  * other path. For example, the raw light channels (`light_sensor_*` in
756
918
  * `deviceLightSensors`) exist from `0.16.0`. Always keep a working branch
757
- * for `null`: your browser development environment always hits it.
919
+ * for `null`. The dev harness reports `0.17.0` unless a scenario says
920
+ * otherwise.
758
921
  */
759
922
  nativeVersion: string | null;
760
923
  /**
@@ -815,13 +978,62 @@ interface CondorBridgeClient {
815
978
  * native enum is dropped in silence.
816
979
  */
817
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
+ }>;
818
1028
  /**
819
1029
  * Handshakes with the shell. Resolves with
820
1030
  * `{ shellVersion, nativeVersion, accepted }` — read `nativeVersion` to learn
821
1031
  * what this device can do, and ignore `accepted`, which is always `true`.
822
1032
  *
823
- * Rejects after 5 s if the shell does not answer. Only one handshake may be
824
- * in flight at a time.
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.
825
1037
  */
826
1038
  handshake(opts: {
827
1039
  clientVersion: string;
@@ -1096,4 +1308,14 @@ declare class BridgeShell {
1096
1308
  private postToClient;
1097
1309
  }
1098
1310
 
1099
- 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 DeviceConnectionPayload, type DeviceLightSensorsPayload, type DeviceNotificationPayload, 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 SensorLogPayload, type SensorLogQueryPayload, type ShellPolicy, type ShellToClientMessage, type SyncTimeAckPayload, type SystemStatusPayload, type UserDevicePayload };
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.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
- * The inner arrays are `Record<string, unknown>`: each item has its own
233
- * `toMap()` on the Dart side (device config, telemetry, user config), and
234
- * strict typing for them is deferred until a client needs it.
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: Array<Record<string, unknown>>;
243
- devicesTelemetry: Array<Record<string, unknown>>;
244
- devicesUserConfig: Array<Record<string, unknown>>;
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
@@ -444,6 +547,63 @@ interface SensorLogQueryPayload {
444
547
  /** Exclusive, `YYYY-MM-DD HH:MM:SS`, in device-local time. */
445
548
  endTime?: string;
446
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
+ }
447
607
  /**
448
608
  * Reply to the `sensorLog` command.
449
609
  *
@@ -464,7 +624,8 @@ interface SensorLogQueryPayload {
464
624
  interface SensorLogPayload {
465
625
  requestId: string;
466
626
  success: boolean;
467
- logs?: Array<Record<string, unknown>>;
627
+ /** Chronological ascending. Without a cursor or range: the most recent page. */
628
+ logs?: SensorLogItem[];
468
629
  startingAfter?: string | null;
469
630
  endingBefore?: string | null;
470
631
  hasMore?: boolean;
@@ -735,7 +896,9 @@ declare const Bridge: CondorBridge;
735
896
  * deletes them before your bundle runs.
736
897
  *
737
898
  * It deliberately does **not** expose `installDevPanel`, `mockEmit`, `isMock`
738
- * 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`).
739
902
  *
740
903
  * The module also answers the shell's `verify-sandbox` probe — the iframe
741
904
  * attesting whether it can still see the native globals. If any is reachable,
@@ -747,14 +910,14 @@ interface BridgeClientHandshakeAck {
747
910
  shellVersion: string;
748
911
  /**
749
912
  * Schema version of the **native** side, relayed by the shell. `null` when
750
- * the native side has not answered, does not exist (browser mock mode), or
751
- * the shell is older than 0.6.0.
913
+ * the native side has not answered, or the shell is older than 0.6.0.
752
914
  *
753
915
  * **This number is the only capability signal a client gets.** There is no
754
916
  * version negotiation, and a sandboxed client reaches the native side by no
755
917
  * other path. For example, the raw light channels (`light_sensor_*` in
756
918
  * `deviceLightSensors`) exist from `0.16.0`. Always keep a working branch
757
- * for `null`: your browser development environment always hits it.
919
+ * for `null`. The dev harness reports `0.17.0` unless a scenario says
920
+ * otherwise.
758
921
  */
759
922
  nativeVersion: string | null;
760
923
  /**
@@ -815,13 +978,62 @@ interface CondorBridgeClient {
815
978
  * native enum is dropped in silence.
816
979
  */
817
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
+ }>;
818
1028
  /**
819
1029
  * Handshakes with the shell. Resolves with
820
1030
  * `{ shellVersion, nativeVersion, accepted }` — read `nativeVersion` to learn
821
1031
  * what this device can do, and ignore `accepted`, which is always `true`.
822
1032
  *
823
- * Rejects after 5 s if the shell does not answer. Only one handshake may be
824
- * in flight at a time.
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.
825
1037
  */
826
1038
  handshake(opts: {
827
1039
  clientVersion: string;
@@ -1096,4 +1308,14 @@ declare class BridgeShell {
1096
1308
  private postToClient;
1097
1309
  }
1098
1310
 
1099
- 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 DeviceConnectionPayload, type DeviceLightSensorsPayload, type DeviceNotificationPayload, 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 SensorLogPayload, type SensorLogQueryPayload, type ShellPolicy, type ShellToClientMessage, type SyncTimeAckPayload, type SystemStatusPayload, type UserDevicePayload };
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 };