@condorinst/mobile-bridge 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dev.d.cts CHANGED
@@ -105,20 +105,167 @@ interface DeviceUserConfigItem {
105
105
  * — but only when that `serialNo` appears once in `devicesConfig`. A device may
106
106
  * have no telemetry yet: render it with empty readings, never drop it.
107
107
  *
108
- * **It is not guaranteed to answer.** Use `BridgeClient.requestUserDevices()`
109
- * instead of `send('userDevice')`; see `docs/troubleshooting.md`.
108
+ * **Below `nativeVersion` 0.18.0 it is not guaranteed to answer**: a request
109
+ * sent while the app's background service starts is lost, and a failed lookup
110
+ * gets no reply. From 0.18.0 the app re-sends the lost request itself and
111
+ * answers a failure with empty arrays and `error`. Either way, use
112
+ * `BridgeClient.requestUserDevices()` instead of `send('userDevice')`; see
113
+ * `docs/troubleshooting.md`.
110
114
  *
111
115
  * The app enforces no device cap. A commercial rule currently limits a user to
112
116
  * two linked devices, but that can change with no signal in this contract — read
113
117
  * the length of the array rather than hard-coding it.
114
118
  */
119
+ /**
120
+ * Why the device list could not be loaded (`UserDevicePayload.error`, from
121
+ * `nativeVersion` 0.18.0):
122
+ * - `NO_DEVICES` — the server says the account has no linked device;
123
+ * - `NETWORK` — no answer from the server (offline, timeout) or a 5xx;
124
+ * - `SESSION` — no session, or the server ended it;
125
+ * - `UNKNOWN` — anything else. Treat an unrecognised value like `UNKNOWN`.
126
+ */
127
+ type UserDeviceErrorCode = 'NO_DEVICES' | 'NETWORK' | 'SESSION' | 'UNKNOWN' | (string & {});
115
128
  interface UserDevicePayload {
116
129
  /** Epoch milliseconds of the snapshot. */
117
130
  lastUpdate: number;
131
+ /**
132
+ * Present only when the list could not be loaded; the three arrays are then
133
+ * empty. From `nativeVersion` 0.18.0 — older apps send no reply at all in
134
+ * that case. `BridgeClient.requestUserDevices()` turns it into a status.
135
+ */
136
+ error?: UserDeviceErrorCode;
118
137
  devicesConfig: DeviceConfigItem[];
119
138
  devicesTelemetry: DeviceTelemetryItem[];
120
139
  devicesUserConfig: DeviceUserConfigItem[];
121
140
  }
141
+ /**
142
+ * Raw light-sensor channels, under the names the backend uses in
143
+ * `user_cmd=log` — the same vocabulary for history and for real time.
144
+ * Relative values with no physical unit (as are the `wavelength*` channels);
145
+ * only `photopic` / `melanopic` (and `lux_*` in the history) are lux.
146
+ *
147
+ * Present from **nativeVersion 0.16.0**. A bundle compiled against this SDK can
148
+ * run on an older native app that does not emit them: check `nativeVersion`
149
+ * from the handshake ack before depending on them, or read defensively. They
150
+ * are deliberately **not** optional in the type — optional would hide the
151
+ * version skew instead of making it explicit.
152
+ *
153
+ * ⚠️ `nativeVersion` only reaches the client from **SDK 0.17.0**: up to
154
+ * `0.16.x` the shell answered the client handshake with `shellVersion` alone,
155
+ * so this check was impossible to satisfy from inside the iframe.
156
+ *
157
+ * `null` means **the channel does not exist on this hardware generation**,
158
+ * never "the reading was zero" — `0.0` is a valid reading. Coercing `null` to
159
+ * `0` fabricates data.
160
+ */
161
+ type LightSensorsRawChannels = {
162
+ light_sensor_f1: number;
163
+ light_sensor_f2: number;
164
+ light_sensor_f3: number;
165
+ light_sensor_f4: number;
166
+ light_sensor_f5: number;
167
+ light_sensor_f6: number;
168
+ light_sensor_f7: number;
169
+ light_sensor_f8: number;
170
+ /** Total light (`visible` on the device wire format). */
171
+ light_sensor_clear: number;
172
+ /** Near infrared. */
173
+ light_sensor_nir: number;
174
+ };
175
+ /**
176
+ * A real-time reading from an ActLumus gen1 (`type: 'actlumus'`).
177
+ *
178
+ * Mirrors `LightSensorsActlumus1GraphicData.toMap()` in `condor_interactors`.
179
+ * Gen1 has neither the tristimulus channels nor flicker detect — those four
180
+ * keys arrive as `null`, present in the schema so that its shape does not
181
+ * change between hardware generations.
182
+ */
183
+ type LightSensorsActlumus1Data = LightSensorsRawChannels & {
184
+ /** When the phone received the reading: epoch ms (UTC), phone clock. */
185
+ time: number;
186
+ wavelength390: number;
187
+ wavelength415: number;
188
+ wavelength445: number;
189
+ wavelength480: number;
190
+ wavelength515: number;
191
+ wavelength555: number;
192
+ wavelength590: number;
193
+ wavelength630: number;
194
+ wavelength680: number;
195
+ wavelength750: number;
196
+ melanopic: number;
197
+ photopic: number;
198
+ light_sensor_z: null;
199
+ light_sensor_y: null;
200
+ light_sensor_x: null;
201
+ light_sensor_fd: null;
202
+ };
203
+ /**
204
+ * A real-time reading from an ActLumus Plus / gen2 (`type: 'actlumus2'`).
205
+ *
206
+ * Mirrors `LightSensorsActlumus2GraphicData.toMap()` in `condor_interactors`.
207
+ *
208
+ * ⚠️ The wavelengths are **not** the gen1 ones: 14 of them (395…800) against
209
+ * 10 (390…750), with no value in common. A chart that reads the gen1 list on a
210
+ * gen2 device finds `undefined` in every series and renders empty. Always
211
+ * discriminate on `type`.
212
+ *
213
+ * `light_sensor_z/_y/_x` are the CIE 1931 tristimulus values — Z in the blue,
214
+ * Y in the green, X in the red.
215
+ */
216
+ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
217
+ /** When the phone received the reading: epoch ms (UTC), phone clock. */
218
+ time: number;
219
+ wavelength395: number;
220
+ wavelength426: number;
221
+ wavelength457: number;
222
+ wavelength488: number;
223
+ wavelength520: number;
224
+ wavelength551: number;
225
+ wavelength582: number;
226
+ wavelength613: number;
227
+ wavelength644: number;
228
+ wavelength675: number;
229
+ wavelength707: number;
230
+ wavelength738: number;
231
+ wavelength769: number;
232
+ wavelength800: number;
233
+ melanopic: number;
234
+ photopic: number;
235
+ light_sensor_z: number;
236
+ light_sensor_y: number;
237
+ light_sensor_x: number;
238
+ /** Flicker detect. Not part of the spectrum matrix. */
239
+ light_sensor_fd: number;
240
+ };
241
+ /**
242
+ * Real-time light-sensor stream. Shape:
243
+ * `{ serialNo, deviceId?, type, data }`.
244
+ *
245
+ * `deviceId` is the server id of the device and is **optional**: the app only
246
+ * sends it when more than one device on the account shares that `serialNo` (an
247
+ * ActLumus and an ActLumus Plus can). With a unique `serialNo` it is absent, so
248
+ * keep matching on `serialNo` in that case. Present from `nativeVersion` 0.17.0.
249
+ *
250
+ * A union discriminated by `type`, which is the `DeviceType.name` on the Dart
251
+ * side. `data` carries **both the derived spectrum and the raw channels it came
252
+ * from** — before 0.16.0 only the spectrum crossed the bridge, and a probe
253
+ * looking up a raw channel by name read `undefined`.
254
+ *
255
+ * This stream only flows while real-time is on for that device; it is off by
256
+ * default. See the `realTime` command.
257
+ */
258
+ type DeviceLightSensorsPayload = {
259
+ serialNo: number;
260
+ deviceId?: number;
261
+ type: 'actlumus';
262
+ data: LightSensorsActlumus1Data;
263
+ } | {
264
+ serialNo: number;
265
+ deviceId?: number;
266
+ type: 'actlumus2';
267
+ data: LightSensorsActlumus2Data;
268
+ };
122
269
  /**
123
270
  * Parameters of the `sensorLog` command.
124
271
  *
@@ -139,8 +286,8 @@ interface SensorLogQueryPayload {
139
286
  /** Echoed back in the reply — correlates concurrent requests. */
140
287
  requestId: string;
141
288
  serialNo: number;
142
- /** `wrist` or `necklace`. Omit for no filter. */
143
- bodyPart?: string;
289
+ /** Omit for no filter. */
290
+ bodyPart?: 'wrist' | 'necklace';
144
291
  /** 1..1000 (backend default is 10). Outside the range: `INVALID_LIMIT`. */
145
292
  limit?: number;
146
293
  startingAfter?: string;
@@ -165,6 +312,11 @@ interface SensorLogItem {
165
312
  /** `'YYYY-MM-DD HH:MM:SS'` in **device-local time**, not UTC. */
166
313
  log_time: string;
167
314
  log_type: number;
315
+ /**
316
+ * Where the device was set to be worn (its `deviceBodyPart`) when this log was
317
+ * uploaded — a setting, not a detection. Whether it was actually worn is
318
+ * `off_wrist`.
319
+ */
168
320
  body_part: 'wrist' | 'necklace' | null;
169
321
  /** Degrees Celsius, already converted by the backend. */
170
322
  temperature: number | null;
@@ -197,6 +349,11 @@ interface SensorLogItem {
197
349
  act_data_pim?: number | null;
198
350
  act_data_tat?: number | null;
199
351
  act_data_zcm?: number | null;
352
+ /**
353
+ * The device's raw wear flag, and **the name misleads**: `1` = worn, `0` = not
354
+ * worn (on its dock). Treat any value other than `1` as not worn, and `null`
355
+ * as unknown.
356
+ */
200
357
  off_wrist?: number | null;
201
358
  cap_sensor_1?: number | null;
202
359
  cap_sensor_2?: number | null;
@@ -251,6 +408,12 @@ interface SystemStatusPayload {
251
408
  internet: boolean;
252
409
  permission: boolean;
253
410
  hasDevices: boolean;
411
+ /**
412
+ * `true` when `hasDevices` is the server's answer; `false` when the app could
413
+ * not ask (no internet, session) — then `hasDevices: false` does **not** mean
414
+ * "no device". Absent below `nativeVersion` 0.18.0: treat absent as unknown.
415
+ */
416
+ hasDevicesKnown?: boolean;
254
417
  /**
255
418
  * Optional permissions not yet granted, as `AppPermission` names
256
419
  * (e.g. `["notification", "backgroundLocation"]`). Additive and
@@ -299,6 +462,28 @@ declare function twoDevices(now?: number): UserDevicePayload;
299
462
  */
300
463
  declare function sharedSerial(now?: number): UserDevicePayload;
301
464
  declare function systemStatus(overrides?: Partial<SystemStatusPayload>): SystemStatusPayload;
465
+ /**
466
+ * One `deviceLightSensors` reading for a device, shaped by its generation
467
+ * (`modelId` `'actlumus_plus'` → `type: 'actlumus2'`). The harness emits one
468
+ * per second while `realTime` is on for that device. Values are plausible,
469
+ * not measured.
470
+ */
471
+ declare function lightSensorsReading(device: Pick<DeviceConfigItem, 'serialNo' | 'deviceId' | 'modelId'>, epochMs?: number, withDeviceId?: boolean): DeviceLightSensorsPayload;
472
+ /**
473
+ * What the simulated history contains. The default: 14 days up to now, one log
474
+ * per `logPeriodS`, worn all day except the dock hour 07:00–08:00 (local), no
475
+ * gaps.
476
+ */
477
+ interface HistoryOptions {
478
+ /** Days of history before now. Default 14. */
479
+ days?: number;
480
+ /** Local hours (0–23) the device sits on its dock: `off_wrist` 0, no light. Default `[7]`. */
481
+ dockHours?: number[];
482
+ /** Local hours (0–23) with no log at all, every day (device off, or memory full). */
483
+ gapHours?: number[];
484
+ /** Local dates (`YYYY-MM-DD`) with no log at all. */
485
+ gapDates?: string[];
486
+ }
302
487
  /**
303
488
  * Answers a `sensorLog` query with the backend's paging semantics:
304
489
  * - no range: the most recent `limit` logs; older pages through `endingBefore`;
@@ -311,6 +496,9 @@ declare function sensorLogPage(query: SensorLogQueryPayload, opts?: {
311
496
  now?: number;
312
497
  periodS?: number;
313
498
  deviceId?: number;
499
+ /** `body_part` of every log: the device's `deviceBodyPart`. Default `'wrist'`. */
500
+ bodyPart?: SensorLogItem['body_part'];
501
+ history?: HistoryOptions;
314
502
  }): SensorLogPayload;
315
503
 
316
504
  /**
@@ -547,7 +735,7 @@ interface MockAdapter {
547
735
  */
548
736
 
549
737
  /** Native schema version the harness reports by default. */
550
- declare const HARNESS_NATIVE_VERSION = "0.17.0";
738
+ declare const HARNESS_NATIVE_VERSION = "0.18.0";
551
739
  interface Scenario {
552
740
  /** Shown in the harness toolbar. */
553
741
  description: string;
@@ -566,12 +754,30 @@ interface Scenario {
566
754
  */
567
755
  serviceStartMs: number;
568
756
  /**
569
- * `userDevice` never answers. Today the app replies only when the device
570
- * list loads, so an account with no linked device gets silence.
757
+ * The device lookup fails with this code (`NO_DEVICES`, `NETWORK`, …). From
758
+ * nativeVersion 0.18.0 the app answers `userDevice` with empty arrays and
759
+ * `error`; below it, the bundle gets no reply at all.
571
760
  */
572
- userDeviceSilent: boolean;
761
+ userDeviceError?: string;
573
762
  /** When set, every `sensorLog` reply fails with this `error`. */
574
763
  sensorLogError?: string;
764
+ /** The Nth `sensorLog` request (1-based) fails once with `Connection failed`. */
765
+ sensorLogFailOnRequest?: number;
766
+ /** What the simulated history contains — see `HistoryOptions`. */
767
+ history?: HistoryOptions;
768
+ /**
769
+ * After this many ms the first device starts a BLE session (a
770
+ * `deviceConnection` with `isConnect: true`). Only `default` sets it; absent,
771
+ * no device connects by itself (a scenario file can pass `null`).
772
+ */
773
+ firstSessionAfterMs?: number | null;
774
+ /**
775
+ * The pipeline stage at which every simulated BLE session fails, e.g.
776
+ * `'get_data_log'`: `deviceNotification` then ends with `<stage>_error`
777
+ * instead of `pipeline_done_notification`. Without internet the sessions
778
+ * fail at `'send_data_log'` anyway.
779
+ */
780
+ sessionFailAt?: string;
575
781
  /** Log period of the devices, in seconds (programmable; default 60). */
576
782
  logPeriodS: number;
577
783
  systemStatus: SystemStatusPayload;
@@ -613,7 +819,12 @@ interface HarnessLogEntry {
613
819
  kind: 'sent' | 'reply' | 'dropped' | 'info';
614
820
  command?: string;
615
821
  key?: string;
822
+ /** One line for humans; arguments are shortened. */
616
823
  detail: string;
824
+ /** `sent`: the command's arguments, complete. */
825
+ args?: Record<string, unknown>;
826
+ /** `reply`: the payload the bundle received, complete. */
827
+ payload?: unknown;
617
828
  }
618
829
  interface DevHarnessOptions {
619
830
  /** The bundle's `index.html`, self-contained, as a string. */
@@ -639,6 +850,23 @@ interface DevHarness {
639
850
  readonly log: readonly HarnessLogEntry[];
640
851
  /** Emits a native event to the bundle, as the app would. */
641
852
  emit(key: string, payload: unknown): void;
853
+ /**
854
+ * Merges into the simulated `systemStatus` and emits it, as the app does on
855
+ * every change — e.g. `setStatus({ internet: false })`.
856
+ */
857
+ setStatus(patch: Partial<SystemStatusPayload>): void;
858
+ /**
859
+ * Starts or ends the BLE session of one device: emits `deviceConnection`
860
+ * (when the bundle subscribed to it with `add`) and starts or stops its
861
+ * `deviceLightSensors` stream if `realTime` is on.
862
+ */
863
+ setConnected(deviceId: number, connected: boolean): void;
864
+ /**
865
+ * How the app's confirmation dialog (`syncTime`, `deleteMemory`, `logout`)
866
+ * is answered: `'ask'` (an in-page dialog, the default), `'accept'` or
867
+ * `'decline'` — the last two for scripted tests.
868
+ */
869
+ setConfirmations(mode: 'ask' | 'accept' | 'decline'): void;
642
870
  /**
643
871
  * Presses the OS back button. Emits `navigationBack` only when the bundle
644
872
  * last reported `canGoBack: true`, as the app does; otherwise the app keeps
@@ -658,6 +886,8 @@ declare function createSimulatedNative(adapter: MockAdapter, scenario: Scenario,
658
886
  }): {
659
887
  readonly log: readonly HarnessLogEntry[];
660
888
  pressBack(): void;
889
+ setStatus(patch: Partial<SystemStatusPayload>): void;
890
+ setConnected(deviceId: number, connected: boolean): void;
661
891
  dispose(): void;
662
892
  };
663
893
  /**
@@ -680,4 +910,4 @@ declare const CANVAS_POLICY: ShellPolicy;
680
910
  /** CSP of the app shell, inherited by the client's `srcdoc` iframe. */
681
911
  declare const CANVAS_CSP: string;
682
912
 
683
- export { CANVAS_CSP, CANVAS_POLICY, DEFAULT_LOG_PERIOD_S, DEV_HARNESS_MARKER, type DevHarness, type DevHarnessOptions, HARNESS_NATIVE_VERSION, type HarnessLogEntry, SCENARIOS, type Scenario, type ScenarioName, createDevHarness, createSimulatedNative, formatLocalTimestamp, parseLocalTimestamp, resolveScenario, sensorLogPage, sharedSerial, systemStatus, twoDevices };
913
+ export { CANVAS_CSP, CANVAS_POLICY, DEFAULT_LOG_PERIOD_S, DEV_HARNESS_MARKER, type DevHarness, type DevHarnessOptions, HARNESS_NATIVE_VERSION, type HarnessLogEntry, type HistoryOptions, SCENARIOS, type Scenario, type ScenarioName, createDevHarness, createSimulatedNative, formatLocalTimestamp, lightSensorsReading, parseLocalTimestamp, resolveScenario, sensorLogPage, sharedSerial, systemStatus, twoDevices };