@condorinst/mobile-bridge 0.17.0 → 0.19.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.ts CHANGED
@@ -1,21 +1,21 @@
1
1
  import * as v from 'valibot';
2
2
 
3
3
  /**
4
- * Mock adapter — ativo quando `window.flutter_inappwebview` não é
5
- * detectado (browser dev, jsdom sem host instalado, Storybook etc.).
6
- *
7
- * Comportamento:
8
- * - `callHandler` grava `{handlerName, payload, timestamp}` em buffer
9
- * interno (cap 100, FIFO), loga via `console.debug` e dispatcha
10
- * listeners registrados via `onCallHandler`.
11
- * - `triggerEmit` dispatcha via `window.CondorBridge.emit` — bridge real
12
- * trata como se viesse do nativo (handshake-ack resolve handshake;
13
- * demais chaves vão pros subscribers).
14
- * - `getMessages` retorna snapshot read-only do buffer.
15
- * - `clearMessages` esvazia o buffer (útil em testes).
16
- * - `onCallHandler(listener)` — registra listener invocado a cada send.
17
- * Retorna função de unsubscribe. Útil para dev panel reagir a sends
18
- * (v0.3.0+ auto-respond toggle) ou análise em testes.
4
+ * Mock adapter — active when no native host is detected: a plain browser,
5
+ * jsdom without an installed host, Storybook, and so on.
6
+ *
7
+ * Behaviour:
8
+ * - `callHandler` records `{ handlerName, payload, timestamp }` in an internal
9
+ * buffer (capped at 100, FIFO), logs through `console.debug`, and dispatches
10
+ * to listeners registered with `onCallHandler`.
11
+ * - `triggerEmit` dispatches through `window.CondorBridge.emit`, so the real
12
+ * bridge treats it as if it came from the native side: `handshake-ack`
13
+ * resolves the handshake, every other key reaches its subscribers.
14
+ * - `getMessages` returns a read-only snapshot of the buffer.
15
+ * - `clearMessages` empties it, which is useful in tests.
16
+ * - `onCallHandler(listener)` registers a listener invoked on every send and
17
+ * returns an unsubscribe function — used by the dev panel's auto-respond
18
+ * toggle, and for assertions in tests.
19
19
  */
20
20
  interface MockMessage {
21
21
  handlerName: string;
@@ -24,27 +24,26 @@ interface MockMessage {
24
24
  }
25
25
 
26
26
  /**
27
- * Dev panel — UI flutuante para inspecionar o bridge em mock mode.
27
+ * Dev panel — a floating UI for inspecting the bridge in mock mode.
28
28
  *
29
- * **Opt-in:** chamar `Bridge.installDevPanel(opts?)` quando quiser ver
30
- * o painel. Só funciona em mock mode (throws em modo real). Injeta no
31
- * `document.body`; idempotente (re-install remove o anterior).
29
+ * **Opt-in:** call `Bridge.installDevPanel(opts?)` when you want it. It only
30
+ * works in mock mode and throws in real mode. It injects into `document.body`
31
+ * and is idempotent — re-installing removes the previous panel.
32
32
  *
33
- * Conteúdo:
34
- * - Header com badge "MOCK", contador de sends, botão minimizar/maximizar.
35
- * - Sent log — últimas N mensagens (timestamp, type, payload truncado).
36
- * - Trigger emit — dropdown com `BRIDGE_KEYS` + textarea payload + botão.
33
+ * Contents:
34
+ * - a header with a "MOCK" badge, a send counter, and a minimise button;
35
+ * - a sent log with the last N messages (timestamp, type, truncated payload);
36
+ * - a trigger-emit control: a `BRIDGE_KEYS` dropdown, a payload textarea, and
37
+ * a button.
37
38
  *
38
- * Estilos inline + classes prefixadas `condor-mock-panel-*` para reduzir
39
- * conflito com CSS do cliente.
39
+ * Styles are inline and classes are prefixed `condor-mock-panel-*`, to reduce
40
+ * collisions with the client's own CSS.
40
41
  */
41
42
 
42
43
  /**
43
- * Resposta auto-emitida em reação a um `Bridge.send`, retornada pela
44
- * policy `onSendForAutoRespond` do cliente. `null` significa "não
45
- * responder a este send".
46
- *
47
- * @since v0.3.0
44
+ * A reply emitted automatically in reaction to a `Bridge.send`, returned by the
45
+ * client's `onSendForAutoRespond` policy. `null` means "do not answer this
46
+ * send".
48
47
  */
49
48
  interface AutoRespondResponse {
50
49
  key: string;
@@ -56,43 +55,81 @@ interface DevPanelOptions {
56
55
  theme?: 'dark' | 'light';
57
56
  startCollapsed?: boolean;
58
57
  /**
59
- * Policy de auto-respond: invocada a cada `Bridge.send` quando o
60
- * toggle "Auto-respond" está ligado no painel. Retorna
61
- * `{key, payload}` para disparar `Bridge.mockEmit(key, payload)`
62
- * após delay, ou `null` para não responder.
58
+ * Auto-respond policy: invoked on every `Bridge.send` while the panel's
59
+ * "Auto-respond" toggle is on. Return `{ key, payload }` to fire
60
+ * `Bridge.mockEmit(key, payload)` after the delay, or `null` to stay silent.
63
61
  *
64
- * Sem esta callback, o checkbox "Auto-respond" não aparece no painel.
65
- *
66
- * @since v0.3.0
62
+ * Without this callback the "Auto-respond" checkbox does not appear.
67
63
  */
68
64
  onSendForAutoRespond?: (msg: MockMessage) => AutoRespondResponse | null;
69
65
  /**
70
- * Estado inicial do toggle "Auto-respond". Default: `false`
71
- * (manual permanece padrão; cliente decide quando ligar).
72
- *
73
- * @since v0.3.0
66
+ * Initial state of the "Auto-respond" toggle. Defaults to `false` — manual
67
+ * stays the default, and the client decides when to turn it on.
74
68
  */
75
69
  startAutoRespondEnabled?: boolean;
76
70
  /**
77
- * Delay em ms entre o `send` e o `mockEmit` da auto-resposta.
78
- * Default: `50` — aproxima latência de IPC real de native bridge.
79
- *
80
- * @since v0.3.0
71
+ * Delay in milliseconds between the `send` and the auto-response `mockEmit`.
72
+ * Defaults to `50`, which approximates the IPC latency of the real native
73
+ * bridge.
81
74
  */
82
75
  autoRespondDelayMs?: number;
83
76
  }
84
77
 
85
78
  /**
86
- * Catálogo das chaves do bridge JS↔Native.
79
+ * Native host detection, behind an adapter.
80
+ *
81
+ * Detection order: the `window.CondorNative` JavaScript channel
82
+ * (`webview_flutter`) first, then the legacy `window.flutter_inappwebview`,
83
+ * then the mock. The channel only accepts strings, so the message travels as
84
+ * JSON. `setOutgoingStamp` lets the shell stamp every outgoing message — the
85
+ * nonce — without monkey-patching the host.
86
+ *
87
+ * With no host at all the mock adapter activates, which is what makes browser
88
+ * development possible without building an APK.
87
89
  *
88
- * Espelha o lado JS-facing de `ForegroundComCoreKeys` do `condor_core`
89
- * (monorepo `mobile_applications`). As strings aqui são o que o JS recebe
90
- * via `window.CondorBridge.emit(key, payload)`.
90
+ * The active adapter is cached on the first `getActiveAdapter()` call. Tests
91
+ * should call `__resetAdapterForTests()` in `beforeEach` to force
92
+ * re-detection, for instance after installing or removing
93
+ * `window.CondorNative`.
94
+ */
95
+
96
+ /**
97
+ * Name of the JavaScript channel the native side registers with
98
+ * `WebViewController.addJavaScriptChannel`.
91
99
  *
92
- * Camada 1 (v0.1.0): catálogo declarativo. Apenas `pingPong` está
93
- * efetivamente emitida pelo lado Dart no Trio B; as outras 8 são
94
- * reservadas para o Trio C (`sleep-whitelabel-bridge-keys`) e ficam
95
- * aqui como contrato forward-looking.
100
+ * It cannot be `CondorBridge`: the plugin publishes the channel as
101
+ * `window.<name>`, which would collide with the `window.CondorBridge` emitter
102
+ * this SDK installs.
103
+ */
104
+ declare const NATIVE_CHANNEL_NAME = "CondorNative";
105
+ interface FlutterInAppWebView {
106
+ callHandler(name: string, ...args: unknown[]): Promise<unknown>;
107
+ }
108
+ interface NativeChannel {
109
+ postMessage(message: string): void;
110
+ }
111
+ declare global {
112
+ interface Window {
113
+ flutter_inappwebview?: FlutterInAppWebView;
114
+ CondorNative?: NativeChannel;
115
+ }
116
+ }
117
+ /**
118
+ * Transforma toda mensagem JS → Native imediatamente antes do envio.
119
+ * Recebe e devolve o envelope `{type, version, payload}`.
120
+ */
121
+ type OutgoingStamp = (message: Record<string, unknown>) => Record<string, unknown>;
122
+
123
+ /**
124
+ * Catalogue of the JS↔Native bridge keys.
125
+ *
126
+ * Mirrors the JS-facing side of `ForegroundComCoreKeys` in `condor_core`.
127
+ * The strings here are what JavaScript receives through
128
+ * `window.CondorBridge.emit(key, payload)`.
129
+ *
130
+ * Thirteen keys exist. A sandboxed client can subscribe to twelve of them:
131
+ * `dfu` is denied by the shell policy and skipped natively. See
132
+ * `docs/bridge-contract.md` for what each one carries and when it is emitted.
96
133
  */
97
134
  declare const BRIDGE_KEYS: {
98
135
  readonly pingPong: "pingPong";
@@ -107,10 +144,10 @@ declare const BRIDGE_KEYS: {
107
144
  readonly dfu: "dfu";
108
145
  readonly systemStatus: "systemStatus";
109
146
  /**
110
- * Native → client: o back do SO foi pressionado e o cliente reportou
111
- * ter histórico interno (`reportNavigation(true)`). O cliente deve voltar
112
- * UMA página interna. Emitido só quando o nativo decidiu delegar o back
113
- * ao cliente. Vide `BridgeClient.onBack`.
147
+ * Native → client: the OS back button was pressed and the client had
148
+ * reported internal history via `reportNavigation(true)`. Go back exactly
149
+ * ONE internal page. Emitted only when the native side decided to delegate
150
+ * the back gesture. See `BridgeClient.onBack`.
114
151
  */
115
152
  readonly navigationBack: "navigationBack";
116
153
  /**
@@ -118,21 +155,24 @@ declare const BRIDGE_KEYS: {
118
155
  * AND the language explicitly chosen in the app Settings, so the client can
119
156
  * apply its own precedence rule — including rendering a language the native
120
157
  * chrome does not support. Emitted on attach, after `handshake-ack`, and on
121
- * every change in Settings. Vide `LanguagePayload`.
158
+ * every change in Settings. See `LanguagePayload`.
122
159
  */
123
160
  readonly language: "language";
124
161
  };
125
162
  type BridgeKey = (typeof BRIDGE_KEYS)[keyof typeof BRIDGE_KEYS];
126
163
 
127
164
  /**
128
- * Mensagens do protocolo do bridge.
165
+ * Wire messages of the bridge protocol.
129
166
  *
130
- * Espelha o lado Dart (`BridgeIncomingMessage`/`BridgeOutgoingEvent` em
131
- * `apps/sleep_whitelabel/lib/ui/pages/home/bridge/bridge_message.dart`).
167
+ * Mirrors the Dart side (`BridgeIncomingMessage` / `BridgeOutgoingEvent` in
168
+ * the Canvas app's `bridge_message.dart`).
132
169
  */
133
170
  /**
134
- * Mensagem JS → Native, enviada via
135
- * `window.flutter_inappwebview.callHandler('CondorBridge', msg)`.
171
+ * JS → native message, sent over
172
+ * `window.CondorNative.postMessage(JSON.stringify(msg))` (0.18.0+) or, on a
173
+ * legacy host, `window.flutter_inappwebview.callHandler('CondorBridge', msg)`.
174
+ *
175
+ * Anything above 64 KB is discarded by the native side without a reply.
136
176
  */
137
177
  interface BridgeIncomingMessage {
138
178
  type: string;
@@ -140,56 +180,60 @@ interface BridgeIncomingMessage {
140
180
  payload?: Record<string, unknown>;
141
181
  }
142
182
  /**
143
- * Evento Native → JS, recebido via
144
- * `window.CondorBridge.emit(key, payload)`.
183
+ * Native → JS event, delivered through `window.CondorBridge.emit(key, payload)`.
145
184
  */
146
185
  interface BridgeOutgoingEvent {
147
186
  key: string;
148
187
  payload: unknown;
149
188
  }
150
189
  /**
151
- * Resposta do handshake (`handshake-ack`).
190
+ * Handshake reply (`handshake-ack`).
152
191
  *
153
- * `nativeVersion`: versão semver do schema declarada pelo nativo.
154
- * `accepted`: nativo aceita o `clientVersion` enviado. v0.1.0 não impõe
155
- * validação semver dura — nativo retorna `accepted: true` sempre.
192
+ * - `nativeVersion`: the semver schema version the native side declares. This
193
+ * is the ONLY capability signal available; compare it against the minimum
194
+ * version of the feature you want to use.
195
+ * - `accepted`: whether the native side accepted the `clientVersion` you sent.
196
+ * There is no hard semver validation — it is **always** `true`, so it carries
197
+ * no information. Never branch on it.
156
198
  */
157
199
  interface HandshakeAck {
158
200
  nativeVersion: string;
159
201
  accepted: boolean;
160
202
  }
203
+ /** Options for {@link CondorBridge.handshake}. */
161
204
  interface HandshakeOptions {
205
+ /** Your own client version, for the native logs. Not validated. */
162
206
  clientVersion: string;
163
207
  }
164
208
 
165
209
  /**
166
- * Catálogo de payloads tipados por chave (v0.4.0+).
210
+ * Per-key typed payload catalogue.
167
211
  *
168
- * Shapes derivadas autoritativamente do source code do `condor_core` no
169
- * monorepo `mobile_applications` (ver §"Shapes derivadas" no execution
170
- * log do step2 do trio `2026-06-04_sleep-whitelabel-bridge-keys`).
212
+ * Shapes are derived from the `condor_core` source in the
213
+ * `mobile_applications` monorepo, and are authoritative for what arrives on
214
+ * each key.
171
215
  *
172
- * **Direção:** BG → JS (observáveis). Os comandos JS → BG (`Bridge.send`)
173
- * continuam permissivos (`Record<string, unknown>`) na v0.4.0 — sua
174
- * tipagem estrita é trabalho separado e fora do MVP atual.
216
+ * **Direction:** native → JS (observable events). JS → native commands
217
+ * (`send`) stay permissive (`Record<string, unknown>`); the typed helpers on
218
+ * `BridgeClient` are the ergonomic path for the ones that matter.
175
219
  *
176
- * **Migração da v0.3.0:** o tipo `PayloadOf<K>` era `Record<string, unknown>`
177
- * para qualquer K. Agora é mapeado pelas chaves do catálogo, com
178
- * fallback permissivo para chaves não-catalogadas (e.g., `dfu` continua
179
- * `Record<string, unknown>` — hub-only, reservado).
220
+ * `PayloadOf<K>` maps a catalogued key to its payload, with a permissive
221
+ * fallback for uncatalogued keys (`dfu` stays `Record<string, unknown>` — it
222
+ * is hub-only and a sandboxed client never receives it).
180
223
  */
181
- /** Loop heartbeat boot/keepalive. */
224
+ /** Boot / keepalive heartbeat. */
182
225
  interface PingPongPayload {
183
226
  data: 'ping' | 'pong';
184
227
  }
185
228
  /**
186
- * Snapshot do device store do usuário. Espelha `UserDevices.toMap()`
187
- * (`packages/condor_core/lib/src/domain/entities/user_devices_entity.dart:144-151`).
229
+ * Snapshot of the user's device store. Mirrors `UserDevices.toMap()` in
230
+ * `condor_core`.
231
+ *
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.
188
235
  *
189
- * Os arrays internos são `Record<string, unknown>` no MVP — cada item
190
- * tem seu próprio `toMap()` (DeviceConfig, DeviceTelemetry,
191
- * DeviceUserConfig) cujo strict typing fica para release futura quando
192
- * consumidor JS precisar.
236
+ * There is no device cap in the app. Do not assume this list is bounded.
193
237
  */
194
238
  interface UserDevicePayload {
195
239
  lastUpdate: number;
@@ -198,23 +242,22 @@ interface UserDevicePayload {
198
242
  devicesUserConfig: Array<Record<string, unknown>>;
199
243
  }
200
244
  /**
201
- * Canais crus do sensor de luz, com os nomes que o backend usa no
202
- * `user_cmd=log` — mesmo vocabulário para histórico e tempo real.
245
+ * Raw light-sensor channels, under the names the backend uses in
246
+ * `user_cmd=log` — the same vocabulary for history and for real time.
203
247
  *
204
- * Presentes a partir da **nativeVersion 0.16.0**. Um bundle compilado contra
205
- * este SDK pode rodar num app nativo mais velho, que não os emite: cheque o
206
- * `nativeVersion` do `handshake-ack` antes de depender deles, ou leia
207
- * defensivamente. Não são opcionais no tipo de propósito — opcional
208
- * esconderia o skew de versão em vez de deixá-lo explícito.
248
+ * Present from **nativeVersion 0.16.0**. A bundle compiled against this SDK can
249
+ * run on an older native app that does not emit them: check `nativeVersion`
250
+ * from the handshake ack before depending on them, or read defensively. They
251
+ * are deliberately **not** optional in the type — optional would hide the
252
+ * version skew instead of making it explicit.
209
253
  *
210
- * ⚠️ O `nativeVersion` só chega ao cliente a partir do **SDK 0.17.0**: até a
211
- * `0.16.x` o shell respondia o handshake do cliente apenas com o
212
- * `shellVersion`, então essa checagem era impossível de cumprir de dentro do
213
- * iframe.
254
+ * ⚠️ `nativeVersion` only reaches the client from **SDK 0.17.0**: up to
255
+ * `0.16.x` the shell answered the client handshake with `shellVersion` alone,
256
+ * so this check was impossible to satisfy from inside the iframe.
214
257
  *
215
- * `null` significa **canal ausente nesta geração de hardware**, nunca "leitura
216
- * zero": `0.0` é leitura válida. Mesma distinção que o backend faz com
217
- * `float | null`.
258
+ * `null` means **the channel does not exist on this hardware generation**,
259
+ * never "the reading was zero" — `0.0` is a valid reading. Coercing `null` to
260
+ * `0` fabricates data.
218
261
  */
219
262
  type LightSensorsRawChannels = {
220
263
  light_sensor_f1: number;
@@ -225,18 +268,18 @@ type LightSensorsRawChannels = {
225
268
  light_sensor_f6: number;
226
269
  light_sensor_f7: number;
227
270
  light_sensor_f8: number;
228
- /** Luz total (`visible` no wire do device). */
271
+ /** Total light (`visible` on the device wire format). */
229
272
  light_sensor_clear: number;
230
- /** Infravermelho próximo. */
273
+ /** Near infrared. */
231
274
  light_sensor_nir: number;
232
275
  };
233
276
  /**
234
- * Leitura de tempo real de um ActLumus gen1 (`type: 'actlumus'`).
277
+ * A real-time reading from an ActLumus gen1 (`type: 'actlumus'`).
235
278
  *
236
- * Espelha `LightSensorsActlumus1GraphicData.toMap()`
237
- * (`packages/condor_interactors/lib/src/domain/entities/actlumus/1/light_sensors1_graphic_data_entity.dart`).
238
- * O gen1 não tem as tristimulus nem o flicker detect — as quatro chaves vêm
239
- * como `null`, presentes no schema para que ele não mude entre gerações.
279
+ * Mirrors `LightSensorsActlumus1GraphicData.toMap()` in `condor_interactors`.
280
+ * Gen1 has neither the tristimulus channels nor flicker detect — those four
281
+ * keys arrive as `null`, present in the schema so that its shape does not
282
+ * change between hardware generations.
240
283
  */
241
284
  type LightSensorsActlumus1Data = LightSensorsRawChannels & {
242
285
  time: number;
@@ -258,17 +301,17 @@ type LightSensorsActlumus1Data = LightSensorsRawChannels & {
258
301
  light_sensor_fd: null;
259
302
  };
260
303
  /**
261
- * Leitura de tempo real de um ActLumus Plus / gen2 (`type: 'actlumus2'`).
304
+ * A real-time reading from an ActLumus Plus / gen2 (`type: 'actlumus2'`).
262
305
  *
263
- * Espelha `LightSensorsActlumus2GraphicData.toMap()`
264
- * (`packages/condor_interactors/lib/src/domain/entities/actlumus/2/light_sensors2_graphic_data_entity.dart`).
265
- * ⚠️ Os comprimentos de onda **não** são os mesmos do gen1 — são 14 (395…800)
266
- * contra 10 (390…750), sem nenhum valor em comum. Um gráfico que leia a lista
267
- * do gen1 num device gen2 encontra `undefined` em todas as séries e renderiza
268
- * vazio; discrimine sempre pelo `type`.
306
+ * Mirrors `LightSensorsActlumus2GraphicData.toMap()` in `condor_interactors`.
269
307
  *
270
- * `light_sensor_z/_y/_x` são as tristimulus CIE 1931 — Z no azul, Y no verde,
271
- * X no vermelho.
308
+ * ⚠️ The wavelengths are **not** the gen1 ones: 14 of them (395…800) against
309
+ * 10 (390…750), with no value in common. A chart that reads the gen1 list on a
310
+ * gen2 device finds `undefined` in every series and renders empty. Always
311
+ * discriminate on `type`.
312
+ *
313
+ * `light_sensor_z/_y/_x` are the CIE 1931 tristimulus values — Z in the blue,
314
+ * Y in the green, X in the red.
272
315
  */
273
316
  type LightSensorsActlumus2Data = LightSensorsRawChannels & {
274
317
  time: number;
@@ -291,17 +334,20 @@ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
291
334
  light_sensor_z: number;
292
335
  light_sensor_y: number;
293
336
  light_sensor_x: number;
294
- /** Flicker detect. Não entra na matriz do espectro. */
337
+ /** Flicker detect. Not part of the spectrum matrix. */
295
338
  light_sensor_fd: number;
296
339
  };
297
340
  /**
298
- * Stream real-time de sensores de luz (FEST). Shape:
299
- * `{serialNo, type, data: <lightSensors.toMap()>}`.
341
+ * Real-time light-sensor stream. Shape:
342
+ * `{ serialNo, type, data }`.
343
+ *
344
+ * A union discriminated by `type`, which is the `DeviceType.name` on the Dart
345
+ * side. `data` carries **both the derived spectrum and the raw channels it came
346
+ * from** — before 0.16.0 only the spectrum crossed the bridge, and a probe
347
+ * looking up a raw channel by name read `undefined`.
300
348
  *
301
- * União discriminada pelo `type`, que é o `DeviceType.name` do lado Dart. O
302
- * `data` carrega **o espectro derivado e os canais crus que o originaram** —
303
- * até a `0.16.0` só o espectro atravessava, e uma sonda que procurasse um canal
304
- * cru pelo nome lia `undefined`.
349
+ * This stream only flows while real-time is on for that device; it is off by
350
+ * default. See the `realTime` command.
305
351
  */
306
352
  type DeviceLightSensorsPayload = {
307
353
  serialNo: number;
@@ -329,16 +375,17 @@ interface DeleteMemoryAckPayload {
329
375
  success: boolean;
330
376
  }
331
377
  /**
332
- * Status de conexão BLE. Espelha `_userDevicesService.openConnection`
333
- * subscriber (`devices_notification_service.dart:38-44`).
378
+ * BLE connection status. Mirrors the `openConnection` subscriber in the app's
379
+ * device notification service.
334
380
  */
335
381
  interface DeviceConnectionPayload {
336
382
  serialNo: number;
337
383
  isConnect: boolean;
338
384
  }
339
385
  /**
340
- * Evento/status de device (logs). Espelha `DevicesNotification.toMap()`
341
- * (`devices_notification_entity.dart:33-41`).
386
+ * Device event or status notice. Mirrors `DevicesNotification.toMap()`.
387
+ * `message` is already localized by the app; `messageCode` is the stable
388
+ * identifier to branch on.
342
389
  */
343
390
  interface DeviceNotificationPayload {
344
391
  serialNo: number;
@@ -348,46 +395,52 @@ interface DeviceNotificationPayload {
348
395
  isError: boolean;
349
396
  }
350
397
  /**
351
- * Parâmetros do comando `sensorLog` (GET `user_cmd=log` do Mobile Lambda).
398
+ * Parameters of the `sensorLog` command.
352
399
  *
353
- * `startTime`/`endTime` delimitam um período — `startTime` é **inclusivo** e
354
- * `endTime` **exclusivo**, ambos no formato estrito `YYYY-MM-DD HH:MM:SS`
355
- * (sem `T`, sem offset, sem date-only).
400
+ * `startTime` / `endTime` delimit a period — `startTime` is **inclusive**,
401
+ * `endTime` **exclusive**, both in the strict format `YYYY-MM-DD HH:MM:SS`
402
+ * (no `T`, no offset, no date-only form). Requires nativeVersion >= 0.15.0.
356
403
  *
357
- * ⚠️ Os timestamps do contrato são **hora local do dispositivo**, não UTC:
358
- * `log_time` é passthrough do RTC do aparelho. Monte o range com os getters
359
- * locais do `Date` (`getFullYear()`, `getHours()`, …) e **nunca** com
360
- * `toISOString()` — converter para UTC desloca as séries pelo offset do fuso,
361
- * com dados plausíveis e nenhum erro visível.
404
+ * ⚠️ These timestamps are **device-local time, not UTC**: `log_time` passes
405
+ * through from the device RTC. Build the range with local `Date` getters
406
+ * (`getFullYear()`, `getHours()`, …) and **never** with `toISOString()` —
407
+ * converting to UTC shifts every series by the timezone offset, producing
408
+ * plausible data and no visible error.
362
409
  *
363
- * Os cursores `startingAfter`/`endingBefore` são **opacos** e mutuamente
364
- * exclusivos: ecoe o valor recebido, nunca construa um.
410
+ * The `startingAfter` / `endingBefore` cursors are **opaque** and mutually
411
+ * exclusive: echo back the value you received, never construct one.
365
412
  */
366
413
  interface SensorLogQueryPayload {
367
- /** Ecoado na resposta — correlaciona requisições concorrentes. */
414
+ /** Echoed back in the reply — correlates concurrent requests. */
368
415
  requestId: string;
369
416
  serialNo: number;
370
- /** `wrist` ou `necklace`. Omita para não filtrar. */
417
+ /** `wrist` or `necklace`. Omit for no filter. */
371
418
  bodyPart?: string;
372
- /** 1..1000 (default do backend = 10). */
419
+ /** 1..1000 (backend default is 10). Outside the range: `INVALID_LIMIT`. */
373
420
  limit?: number;
374
421
  startingAfter?: string;
375
422
  endingBefore?: string;
376
- /** Inclusivo, `YYYY-MM-DD HH:MM:SS` em hora local do dispositivo. */
423
+ /** Inclusive, `YYYY-MM-DD HH:MM:SS`, in device-local time. */
377
424
  startTime?: string;
378
- /** Exclusivo, `YYYY-MM-DD HH:MM:SS` em hora local do dispositivo. */
425
+ /** Exclusive, `YYYY-MM-DD HH:MM:SS`, in device-local time. */
379
426
  endTime?: string;
380
427
  }
381
428
  /**
382
- * Resposta do comando `sensorLog` (GET `user_cmd=log` do Mobile Lambda).
383
- * Envelope com `requestId` ecoado para correlação JS (suporta paginação
384
- * keyset concorrente) + `success`/`error`. No sucesso traz a página de logs
385
- * (ordem cronológica ASC, temperatura em °C, timestamps naive em **hora local
386
- * do dispositivo**) e os cursores opacos. Com range, `hasMore`/`hasPrevious`
387
- * são calculados **dentro do período**, então `hasMore: false` significa
388
- * "período completo". No erro, `error` carrega o código do contrato (ex.:
389
- * `CONFLICTING_CURSORS`, `INVALID_TIME_RANGE`, `invalid session`) — código de
390
- * diagnóstico, **nunca** texto para exibir ao usuário.
429
+ * Reply to the `sensorLog` command.
430
+ *
431
+ * An envelope carrying the echoed `requestId` for correlation (so concurrent
432
+ * keyset pagination works) plus `success` / `error`. On success it brings the
433
+ * page of logs — chronological ascending, temperature in °C, naive timestamps
434
+ * in **device-local time** — and the opaque cursors.
435
+ *
436
+ * With a time range, `hasMore` / `hasPrevious` are computed **within the
437
+ * period**, so `hasMore: false` means "this period is complete", not "no more
438
+ * data exists".
439
+ *
440
+ * On failure, `error` carries the contract code (`CONFLICTING_CURSORS`,
441
+ * `INVALID_TIME_RANGE`, `invalid session`, …) — a **diagnostic code, never
442
+ * text to show a user**. Note that `fetchSensorLog` RESOLVES with
443
+ * `success: false`; it only rejects on timeout.
391
444
  */
392
445
  interface SensorLogPayload {
393
446
  requestId: string;
@@ -400,13 +453,13 @@ interface SensorLogPayload {
400
453
  error?: string;
401
454
  }
402
455
  /**
403
- * Status agregado do sistema (BG → JS). Espelha `SysNotification` do app
404
- * (`apps/sleep_whitelabel/lib/domain/entities/sys_notification_entity.dart`):
405
- * flags de ambiente que o cliente pode usar para renderizar avisos na home
406
- * (ex.: Bluetooth desligado impede o scan/conexão dos devices).
456
+ * Aggregate environment status (native → JS). Mirrors `SysNotification` in the
457
+ * app: the flags a client uses to explain why things are not working — for
458
+ * instance, Bluetooth being off prevents scanning and connecting to devices.
407
459
  *
408
- * Emitido na mudança + snapshot no handshake. `deviceSyncError` mapeia
409
- * `serialNo` → mensagem de erro já localizada pelo app.
460
+ * Emitted on attach (before your bundle exists), again right after
461
+ * `handshake-ack`, and on every change. `deviceSyncError` maps `serialNo` to an
462
+ * error message the app has already localized.
410
463
  */
411
464
  interface SystemStatusPayload {
412
465
  bluetooth: boolean;
@@ -432,29 +485,38 @@ interface SystemStatusPayload {
432
485
  deviceSyncError: Record<number, string>;
433
486
  }
434
487
  /**
435
- * Args do comando `requestPermission` (JS → nativo).
436
- *
437
- * Dispara o **diálogo de permissão nativo in-app** para UMA permissão
438
- * (`AppPermission`), diferente de `openSettings` (que só abre as Configurações
439
- * do sistema). Use para o botão "Conceder" do card de permissões Necessárias,
440
- * passando um dos nomes vindos de `systemStatus.missingEssentialPermissions`
441
- * (ex.: `'bluetoothScan'`).
488
+ * Arguments of the `requestPermission` command (JS → native).
442
489
  *
443
- * Não há chave observável de resultado: o nativo re-emite `systemStatus` após a
444
- * concessão, então o card some quando `missingEssentialPermissions` esvazia.
490
+ * Triggers the **in-app native permission dialog** for ONE permission, unlike
491
+ * `openSettings`, which only opens the system Settings. Use it for the "Grant"
492
+ * button of your required-permissions card, passing a name taken from
493
+ * `systemStatus.missingEssentialPermissions` (for example `'bluetoothScan'`).
445
494
  *
446
- * NOTA: comandos JS → nativo seguem permissivos em `send(command, args)`; este
447
- * tipo é oferecido para DX/tipagem do helper `requestPermission`.
495
+ * **There is no result key.** The native side re-emits `systemStatus` after the
496
+ * person answers, so your card disappears when the array empties. Waiting for a
497
+ * dedicated ack means waiting forever. A name outside the native enum is
498
+ * dropped in silence.
448
499
  */
449
500
  interface RequestPermissionPayload {
450
- /** Nome do `AppPermission` a solicitar (ex.: `'bluetoothScan'`). */
501
+ /** Name of the permission to request, e.g. `'bluetoothScan'`. */
451
502
  permission: string;
452
503
  }
453
504
  /**
454
- * Payload do evento `navigationBack` (native → client). Vazio — o pedido de
455
- * back não carrega dados; o cliente só reage voltando uma página interna.
505
+ * Payload of the `navigationBack` event (native → client). Empty — the request
506
+ * carries no data; react by going back exactly one internal page.
456
507
  */
457
508
  type NavigationBackPayload = Record<string, never>;
509
+ /**
510
+ * Arguments of the `navigationState` command (client → native). The client
511
+ * reports whether it has internal history to go back to; the native side caches
512
+ * it and uses it to decide what the OS back button does. See
513
+ * `BridgeClient.reportNavigation`, and call it on every internal navigation —
514
+ * not once at startup.
515
+ */
516
+ interface NavigationStatePayload {
517
+ /** `true` when the client has a previous internal page to return to. */
518
+ canGoBack: boolean;
519
+ }
458
520
  /**
459
521
  * Language state of the native app (native → client).
460
522
  *
@@ -513,10 +575,10 @@ interface LanguagePayload {
513
575
  resolvedLanguage: string;
514
576
  }
515
577
  /**
516
- * Mapa discriminado de payloads por chave do catálogo. Usado por
517
- * `PayloadOf<K>` para inferência automática no `Bridge.on`.
578
+ * Discriminated map of payloads by catalogue key. Used by `PayloadOf<K>` for
579
+ * automatic inference in `on`.
518
580
  *
519
- * `dfu` é hub-only — sleep_whitelabel não trata; mantido permissivo.
581
+ * `dfu` is hub-only and stays permissive — a sandboxed client never receives it.
520
582
  */
521
583
  interface KeyPayloads {
522
584
  pingPong: PingPongPayload;
@@ -534,15 +596,13 @@ interface KeyPayloads {
534
596
  language: LanguagePayload;
535
597
  }
536
598
  /**
537
- * Payload tipado por chave. Distributivo:
538
- * - Chaves catalogadas em `KeyPayloads` → payload estrito.
539
- * - Demais chaves (futuras / extras do cliente) → `Record<string, unknown>`
540
- * permissivo para preservar extensibilidade.
599
+ * Payload typed by key. Distributive:
600
+ * - keys catalogued in `KeyPayloads` resolve to their strict payload;
601
+ * - any other key resolves to a permissive `Record<string, unknown>`, to keep
602
+ * the catalogue extensible.
541
603
  *
542
- * **v0.3.0 → v0.4.0:** quebra build-time (não runtime) para clientes
543
- * que usavam payload `unknown` em `Bridge.on` — agora vem tipado.
544
- * Cliente que precisa do shape permissivo pode usar
545
- * `(payload: Record<string, unknown>) => ...` explicitamente.
604
+ * A client that wants the permissive shape can annotate the callback
605
+ * explicitly: `(payload: Record<string, unknown>) => …`.
546
606
  */
547
607
  type PayloadOf<K extends BridgeKey = BridgeKey> = K extends keyof KeyPayloads ? KeyPayloads[K] : Record<string, unknown>;
548
608
 
@@ -561,188 +621,222 @@ interface MockState {
561
621
  }
562
622
  interface CondorBridge {
563
623
  /**
564
- * `true` quando o adapter ativo é mock (browser dev, sem
565
- * `window.flutter_inappwebview`). `false` quando rodando dentro do
566
- * WebView Flutter.
624
+ * `true` when the active adapter is the mock — a plain browser, with neither
625
+ * `window.CondorNative` nor `window.flutter_inappwebview`. `false` inside the
626
+ * Flutter WebView.
567
627
  */
568
628
  readonly isMock: boolean;
569
629
  /**
570
- * Envia handshake versionado ao nativo e aguarda `handshake-ack`.
630
+ * Sends a versioned handshake to the native side and awaits `handshake-ack`.
571
631
  *
572
- * Apenas um handshake pode estar pendente por vez. Chamadas paralelas
573
- * antes do primeiro `ack` chegar são rejeitadas com erro claro.
632
+ * Only one handshake may be in flight; a parallel call before the first ack
633
+ * arrives is rejected with an explicit error.
574
634
  *
575
- * **Em mock mode:** NÃO resolve sozinho. Cliente/teste deve chamar
576
- * `Bridge.mockEmit('handshake-ack', {nativeVersion, accepted})` para
577
- * destravar a Promise. Esta decisão (`Explícito`) força testes a serem
578
- * intencionais sobre o estado nativo.
635
+ * **In mock mode it does not resolve on its own.** The caller must emit
636
+ * `Bridge.mockEmit('handshake-ack', { nativeVersion, accepted })` to unblock
637
+ * the promise. That is deliberate: it forces tests to be explicit about the
638
+ * native state they are simulating.
579
639
  */
580
640
  handshake(opts: HandshakeOptions): Promise<HandshakeAck>;
581
641
  /**
582
- * Subscreve a uma chave observável. Retorna função `unsubscribe`.
642
+ * Subscribes to an observable key. Returns an `unsubscribe` function.
583
643
  *
584
- * Múltiplos subscribers por chave são permitidos. Erro em um subscriber
585
- * não afeta os outros (capturado e logado em `console.error`).
644
+ * Multiple subscribers per key are allowed; a throw in one does not affect
645
+ * the others (it is caught and logged with `console.error`).
586
646
  */
587
647
  on<K extends BridgeKey>(key: K, cb: (payload: PayloadOf<K>) => void): () => void;
588
648
  /**
589
- * Envia mensagem genérica ao nativo. Use para comandos UI→BG que não
590
- * tenham helper dedicado. `type` é a string que o native usa para
591
- * roteamento no `_onJsMessage`.
649
+ * Sends a generic message to the native side, for commands without a
650
+ * dedicated helper. `type` is the string the native side routes on.
651
+ *
652
+ * Fire-and-forget: no return value and no delivery confirmation. A payload
653
+ * above 64 KB, or an unknown `type`, is dropped natively without a reply.
592
654
  *
593
- * Em mock mode: grava no buffer interno + `console.debug`.
655
+ * In mock mode it is recorded in the internal buffer and logged with
656
+ * `console.debug`.
594
657
  */
595
658
  send(type: string, payload?: Record<string, unknown>): void;
596
659
  /**
597
- * Abre a tela nativa de Settings do app. Equivalente a
660
+ * Opens the app's native Settings screen. Equivalent to
598
661
  * `Bridge.send('openSettings')`.
599
662
  */
600
663
  openSettings(): void;
601
664
  /**
602
- * Dispara `window.CondorBridge.emit(key, payload)` programaticamente —
603
- * útil para simular eventos nativos em testes e dev em browser.
665
+ * Registers a transform applied to **every** JS → native message, the
666
+ * handshake included, immediately before it is sent. Pass `null` to remove.
667
+ *
668
+ * It exists so the shell can stamp its nonce (`payload._shellNonce`) at a
669
+ * single chokepoint. Wrapping `send` alone is not enough — `handshake` calls
670
+ * the native layer directly, and a message without the nonce is dropped and
671
+ * logged as a security event.
672
+ *
673
+ * **Client code must not use this.**
674
+ */
675
+ setOutgoingStamp(stamp: OutgoingStamp | null): void;
676
+ /**
677
+ * Fires `window.CondorBridge.emit(key, payload)` programmatically — useful to
678
+ * simulate native events in tests and in browser development.
604
679
  *
605
- * **Throws** se o adapter ativo não for mock.
680
+ * **Throws** when the active adapter is not the mock.
606
681
  */
607
682
  mockEmit(key: string, payload?: unknown): void;
608
683
  /**
609
- * Snapshot do estado do mock adapter (mensagens enviadas + chaves com
610
- * subscribers ativos). Useful para asserts em testes de cliente.
684
+ * Snapshot of the mock adapter state: messages sent, and keys with active
685
+ * subscribers. Useful for assertions in client tests.
611
686
  *
612
- * **Throws** se o adapter ativo não for mock.
687
+ * **Throws** when the active adapter is not the mock.
613
688
  */
614
689
  getMockState(): MockState;
615
690
  /**
616
- * Injeta painel HTML flutuante no `document.body` para inspecionar o
617
- * bridge em mock mode. Idempotente — re-install substitui o anterior.
691
+ * Injects a floating HTML panel into `document.body` for inspecting the
692
+ * bridge in mock mode. Idempotent — re-installing replaces the previous one.
618
693
  *
619
- * **Throws** se o adapter ativo não for mock OU se `document` não existir.
694
+ * **Throws** when the active adapter is not the mock, or `document` does not
695
+ * exist.
620
696
  */
621
697
  installDevPanel(options?: DevPanelOptions): void;
622
698
  }
623
699
  declare const Bridge: CondorBridge;
624
700
 
625
701
  /**
626
- * `BridgeClient` (v0.5.0) — API para código do cliente externo rodando
627
- * dentro de `<iframe sandbox="allow-scripts">` no `sleep_whitelabel`.
702
+ * `BridgeClient` — **the API for client code**, running inside
703
+ * `<iframe sandbox="allow-scripts">` in the Condor Canvas app.
628
704
  *
629
- * Surface idêntica ao `Bridge` legado (`on`/`send`/`openSettings`/`handshake`)
630
- * — cliente importa e não percebe que está em sandbox. Internamente,
631
- * todas as chamadas viram `window.parent.postMessage(...)` com schema
632
- * validado pelo shell antes de chegar ao nativo.
705
+ * This is what you want if you are building the bundle that renders as the
706
+ * app's Home. Every call becomes a `window.parent.postMessage(...)` that the
707
+ * Condor shell validates against a schema and a default-deny policy before it
708
+ * reaches the native layer. You never touch the native globals — the shell
709
+ * deletes them before your bundle runs.
633
710
  *
634
- * **Não expõe** `installDevPanel`, `mockEmit`, `isMock`, `getMockState` —
635
- * essas APIs são responsabilidade do shell (`BridgeShell`).
711
+ * It deliberately does **not** expose `installDevPanel`, `mockEmit`, `isMock`
712
+ * or `getMockState`: those belong to the shell (`BridgeShell`).
636
713
  *
637
- * **Threat model & Camada 2 do Risco 1** (vide
638
- * `sleep-white-label-content-transport-and-security.md`): este módulo
639
- * também responde ao `verify-sandbox` do shell — função de attestation
640
- * do próprio iframe sobre se ainda enxerga `window.flutter_inappwebview`
641
- * ou `window.CondorBridge` (que deveriam ter sido deletados pela Camada 1).
642
- * Se vier `true` em algum, o shell aborta o boot do app.
714
+ * The module also answers the shell's `verify-sandbox` probe — the iframe
715
+ * attesting whether it can still see the native globals. If any is reachable,
716
+ * the shell aborts the boot rather than render client code with a path to the
717
+ * native layer.
643
718
  */
644
719
 
645
720
  interface BridgeClientHandshakeAck {
646
721
  shellVersion: string;
647
722
  /**
648
- * Versão do schema do **nativo**, repassada pelo shell — `null` se o nativo
649
- * ainda não respondeu ou não existe (mock de browser).
723
+ * Schema version of the **native** side, relayed by the shell. `null` when
724
+ * the native side has not answered, does not exist (browser mock mode), or
725
+ * the shell is older than 0.6.0.
650
726
  *
651
- * É por este número que um cliente decide se pode depender de capacidade
652
- * nova: não há negociação de versão, e o cliente sandboxed não alcança o
653
- * nativo por nenhum outro caminho. Ex.: canais crus de luz
654
- * (`light_sensor_*` em `deviceLightSensors`) existem a partir da `0.16.0`.
727
+ * **This number is the only capability signal a client gets.** There is no
728
+ * version negotiation, and a sandboxed client reaches the native side by no
729
+ * other path. For example, the raw light channels (`light_sensor_*` in
730
+ * `deviceLightSensors`) exist from `0.16.0`. Always keep a working branch
731
+ * for `null`: your browser development environment always hits it.
655
732
  */
656
733
  nativeVersion: string | null;
734
+ /**
735
+ * Whether the native side accepted your `clientVersion`. It is **always
736
+ * `true`** — there is no hard validation — so it carries no information.
737
+ * Never branch on it; branch on {@link nativeVersion}.
738
+ */
657
739
  accepted: boolean;
658
740
  }
659
741
  interface CondorBridgeClient {
660
- /**
661
- * Versão do protocolo do client.
662
- */
742
+ /** Version of the client-side postMessage protocol. */
663
743
  readonly protocolVersion: string;
664
744
  /**
665
- * Subscreve a uma chave observável. Retorna função `unsubscribe`.
666
- * Múltiplos subscribers por chave permitidos. Erro em um subscriber
667
- * não afeta os outros.
745
+ * Subscribes to an observable key. Returns an `unsubscribe` function —
746
+ * call it when a view unmounts, or handlers accumulate and every one of them
747
+ * re-renders.
748
+ *
749
+ * Multiple subscribers per key are allowed, and a throw in one does not
750
+ * affect the others. Subscribe *before* sending the command that triggers
751
+ * the reply: the reply can arrive before your next statement runs.
668
752
  */
669
753
  on<K extends BridgeKey>(key: K, cb: (payload: PayloadOf<K>) => void): () => void;
670
754
  /**
671
- * Envia comando para o shell. Shell valida contra `ShellPolicy` antes
672
- * de tocar o nativo. Comandos não permitidos → `nack` (logado em
673
- * `console.warn` na v0.5.0).
755
+ * Sends a command to the shell, which validates it against the policy before
756
+ * it touches the native layer.
757
+ *
758
+ * **Fire-and-forget: no return value, no throw, no delivery confirmation.**
759
+ * A command that is not allowed produces a `nack` that surfaces only as a
760
+ * `console.warn`. A malformed one is dropped in silence by the native side.
761
+ * Replies, when a command has one, arrive on the corresponding key — see
762
+ * `docs/bridge-contract.md`.
674
763
  */
675
764
  send(command: string, args?: Record<string, unknown>): void;
676
- /**
677
- * Abre Settings nativa. Equivalente a `send('openSettings')`.
678
- */
765
+ /** Opens the native Settings screen. Equivalent to `send('openSettings')`. */
679
766
  openSettings(): void;
680
767
  /**
681
- * Pede UMA página de sensor logs e resolve com a resposta correlacionada
682
- * pelo `requestId` — inclusive quando ela traz `success: false`, que é
683
- * resposta e não falha de transporte. A Promise só **rejeita** por timeout.
768
+ * Requests ONE page of sensor logs and resolves with the reply correlated by
769
+ * `requestId` — **including when it carries `success: false`**, which is an
770
+ * answer, not a transport failure. The promise only **rejects** on timeout,
771
+ * so always check `success` before reading `logs`.
684
772
  *
685
- * Dispensa o cliente de manter o mapa `requestId → resolve` à mão. Para
686
- * varrer um período, encadeie as chamadas usando o `startingAfter` devolvido
687
- * até `hasMore` virar `false`: os cursores são dependentes, então as páginas
688
- * **não** podem ser buscadas em paralelo.
773
+ * Saves you from keeping a `requestId → resolve` map by hand. To sweep a
774
+ * period, chain calls using the returned `startingAfter` until `hasMore`
775
+ * becomes `false`: the cursors are dependent, so pages **cannot** be fetched
776
+ * in parallel.
689
777
  */
690
778
  fetchSensorLog(query: SensorLogQueryPayload, opts?: {
691
779
  timeoutMs?: number;
692
780
  }): Promise<SensorLogPayload>;
693
781
  /**
694
- * Solicita UMA permissão via **diálogo nativo in-app** (não abre Settings).
695
- * Equivalente a `send('requestPermission', { permission })`. Use no botão
696
- * "Conceder" do card Necessárias, com um nome vindo de
697
- * `systemStatus.missingEssentialPermissions` (ex.: `'bluetoothScan'`).
698
- * O resultado chega via re-emissão de `systemStatus` (sem chave dedicada).
782
+ * Requests ONE permission through the **in-app native dialog** (it does not
783
+ * open Settings). Use it for the "Grant" button of your required-permissions
784
+ * card, with a name taken from `systemStatus.missingEssentialPermissions`,
785
+ * e.g. `'bluetoothScan'`.
786
+ *
787
+ * **There is no result key.** The outcome arrives as a fresh `systemStatus`
788
+ * emission; your card disappears when the array empties. A name outside the
789
+ * native enum is dropped in silence.
699
790
  */
700
791
  requestPermission(permission: string): void;
701
792
  /**
702
- * Handshake com o shell. Resolve com `{ shellVersion, accepted }`.
703
- * Timeout em 5s se shell não responder.
793
+ * Handshakes with the shell. Resolves with
794
+ * `{ shellVersion, nativeVersion, accepted }` — read `nativeVersion` to learn
795
+ * what this device can do, and ignore `accepted`, which is always `true`.
704
796
  *
705
- * Apenas um handshake pode estar pendente por vez.
797
+ * Rejects after 5 s if the shell does not answer. Only one handshake may be
798
+ * in flight at a time.
706
799
  */
707
800
  handshake(opts: {
708
801
  clientVersion: string;
709
802
  }): Promise<BridgeClientHandshakeAck>;
710
803
  /**
711
- * Reporta ao nativo se o cliente tem histórico interno para voltar.
712
- * **Chame a cada mudança de rota interna** (e no boot). O nativo cacheia
713
- * este estado e o usa para decidir o botão/gesture de voltar do SO:
714
- * - `true` → o back do SO pede uma volta interna (evento `navigationBack`,
715
- * vide `onBack`).
716
- * - `false` → o back do SO fica na home (não sai da página).
804
+ * Reports to the native side whether the client has internal history to go
805
+ * back to. **Call it on every internal navigation**, and at boot — not once.
806
+ * The native side caches the value and uses it to decide what the OS back
807
+ * button does:
808
+ * - `true` — back asks you to go back one page (the `navigationBack` event,
809
+ * see {@link onBack});
810
+ * - `false` — back stays with the app and does not leave the Home.
717
811
  *
718
- * Equivalente a `send('navigationState', { canGoBack })`.
812
+ * Equivalent to `send('navigationState', { canGoBack })`.
719
813
  */
720
814
  reportNavigation(canGoBack: boolean): void;
721
815
  /**
722
- * Assina o pedido de back do SO (evento `navigationBack`). O callback deve
723
- * voltar **uma** página interna do cliente e então chamar
724
- * `reportNavigation(...)` com o novo estado. Só é chamado quando o cliente
725
- * havia reportado `reportNavigation(true)`. Retorna função `unsubscribe`.
816
+ * Subscribes to the OS back request (the `navigationBack` event). The
817
+ * callback must go back **one** internal page and then call
818
+ * {@link reportNavigation} with the new state. It only fires when you had
819
+ * reported `reportNavigation(true)`. Returns an `unsubscribe` function.
726
820
  */
727
821
  onBack(cb: () => void): () => void;
728
822
  }
729
823
  declare const BridgeClient: CondorBridgeClient;
730
824
 
731
825
  /**
732
- * Schemas Valibot dos `postMessage` shell↔client (v0.5.0).
826
+ * Valibot schemas for the shell↔client `postMessage` protocol.
733
827
  *
734
- * Quando o conteúdo do cliente roda dentro de `<iframe sandbox="allow-scripts">`
735
- * (origem null), a comunicação com o shell Condor-controlado acontece via
736
- * `window.postMessage`. Estes schemas são o **único contrato aceito** —
737
- * qualquer mensagem mal-formada é descartada silenciosamente pelo shell.
828
+ * Client content runs inside `<iframe sandbox="allow-scripts">` with a `null`
829
+ * origin, so it talks to the Condor-controlled shell over `window.postMessage`.
830
+ * These schemas are the **only accepted contract** — any malformed message is
831
+ * discarded by the shell in silence, which is why hand-rolled `postMessage`
832
+ * calls appear to do nothing. Use `BridgeClient`.
738
833
  *
739
- * **Threat model:** o iframe pode ser hostil (supply-chain comprometida,
740
- * XSS no código do cliente). Estes schemas + a `ShellPolicy` aplicada
741
- * no `BridgeShell` formam o boundary estrutural — vide
742
- * `condor_wiki/cross-cutting/sleep-white-label-content-transport-and-security.md`.
834
+ * **Threat model:** the iframe may be hostile (a compromised supply chain, XSS
835
+ * in the client code). These schemas plus the `ShellPolicy` enforced by
836
+ * `BridgeShell` form the structural boundary.
743
837
  */
744
838
 
745
- declare const ClientToShellMessage: v.VariantSchema<"type", [v.ObjectSchema<{
839
+ declare const ClientToShellMessageSchema: v.VariantSchema<"type", [v.ObjectSchema<{
746
840
  readonly type: v.LiteralSchema<"subscribe", undefined>;
747
841
  readonly key: v.PicklistSchema<[string, ...string[]], undefined>;
748
842
  }, undefined>, v.ObjectSchema<{
@@ -770,9 +864,11 @@ declare const ClientToShellMessage: v.VariantSchema<"type", [v.ObjectSchema<{
770
864
  readonly type: v.LiteralSchema<"sandbox-verify-response", undefined>;
771
865
  readonly flutterBridgeReachable: v.BooleanSchema<undefined>;
772
866
  readonly condorBridgeReachable: v.BooleanSchema<undefined>;
867
+ readonly nativeChannelReachable: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
773
868
  }, undefined>], undefined>;
774
- type ClientToShellMessage = v.InferOutput<typeof ClientToShellMessage>;
775
- declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
869
+ /** Union of every client → shell message. Type only; the runtime schema is `ClientToShellMessageSchema`, which is not part of the public API. */
870
+ type ClientToShellMessage = v.InferOutput<typeof ClientToShellMessageSchema>;
871
+ declare const ShellToClientMessageSchema: v.VariantSchema<"type", [v.ObjectSchema<{
776
872
  readonly type: v.LiteralSchema<"event", undefined>;
777
873
  readonly key: v.PicklistSchema<[string, ...string[]], undefined>;
778
874
  readonly payload: v.UnknownSchema;
@@ -781,12 +877,12 @@ declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
781
877
  readonly ack: v.ObjectSchema<{
782
878
  readonly shellVersion: v.StringSchema<undefined>;
783
879
  /**
784
- * Versão do schema declarada pelo **nativo**, repassada pelo shell.
880
+ * Schema version declared by the **native** side, relayed by the shell.
785
881
  *
786
- * `null` quando o nativo ainda não respondeu (ou não existe, como no
787
- * mock de browser). O cliente roda dentro de um iframe sandboxed e não
788
- * fala com o nativo diretamente, então este é o **único** caminho pelo
789
- * qual ele descobre de qual versão são as capacidades disponíveis.
882
+ * `null` when the native side has not answered, or does not exist (browser
883
+ * mock mode). The client runs inside a sandboxed iframe and never talks to
884
+ * the native side directly, so this is the **only** way it can learn which
885
+ * capabilities are available.
790
886
  */
791
887
  readonly nativeVersion: v.NullableSchema<v.StringSchema<undefined>, undefined>;
792
888
  readonly accepted: v.BooleanSchema<undefined>;
@@ -799,22 +895,23 @@ declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
799
895
  }, undefined>, v.ObjectSchema<{
800
896
  readonly type: v.LiteralSchema<"verify-sandbox", undefined>;
801
897
  }, undefined>], undefined>;
802
- type ShellToClientMessage = v.InferOutput<typeof ShellToClientMessage>;
898
+ /** Union of every shell → client message. Type only; the runtime schema is `ShellToClientMessageSchema`, which is not part of the public API. */
899
+ type ShellToClientMessage = v.InferOutput<typeof ShellToClientMessageSchema>;
803
900
  /**
804
- * Policy default-deny aplicada pelo `BridgeShell`. Toda chave/comando
805
- * vindo do iframe é validado contra estas listas antes de tocar o
806
- * bridge nativo.
901
+ * Default-deny policy enforced by `BridgeShell`. Every key and command coming
902
+ * from the iframe is checked against these lists before it touches the native
903
+ * bridge.
807
904
  *
808
- * - `readOnlyKeys`: chaves que o cliente pode subscrever (observáveis).
809
- * - `allowedCommands`: comandos pass-through (sem confirmação humana).
810
- * - `requiresConfirmation`: comandos que disparam callback `onConfirmationRequest`
811
- * no shell — usuário decide aprovar/recusar via UI nativa do shell.
812
- * - `deniedCommands`: bloqueio explícito (defesa em profundidade — comandos
813
- * destrutivos como `dfu` que nunca devem fluir do iframe).
905
+ * - `readOnlyKeys` — keys the client may subscribe to.
906
+ * - `allowedCommands` — pass-through commands, no human confirmation.
907
+ * - `requiresConfirmation` — commands that fire the shell's
908
+ * `onConfirmationRequest` callback, so a person approves or declines.
909
+ * - `deniedCommands` — explicit block, defence in depth: destructive commands
910
+ * such as `dfu` that must never flow from the iframe.
814
911
  *
815
- * **Não embedada no package** — cliente do `BridgeShell` (no monorepo
816
- * `sleep_whitelabel`) provê a policy concreta. Mantém o package agnóstico
817
- * ao catálogo e permite policy evoluir sem novo release.
912
+ * **Not embedded in the package** — the `BridgeShell` host supplies the
913
+ * concrete policy. That keeps the package agnostic to the catalogue and lets
914
+ * the policy evolve without a release.
818
915
  */
819
916
  interface ShellPolicy {
820
917
  readOnlyKeys: string[];
@@ -823,66 +920,73 @@ interface ShellPolicy {
823
920
  deniedCommands: string[];
824
921
  }
825
922
  /**
826
- * Resposta do callback `onConfirmationRequest` do `BridgeShell`.
923
+ * Argument of the `BridgeShell` `onConfirmationRequest` callback.
827
924
  *
828
- * `approve: true` → comando flui para o nativo.
829
- * `approve: false` → shell envia `nack` ao iframe com `reason: 'user-rejected'`.
925
+ * Resolving `approve: true` lets the command flow to the native side.
926
+ * Resolving `approve: false` makes the shell send the iframe a `nack` with
927
+ * `reason: 'user-rejected'` — which client code only ever sees as a console
928
+ * warning, so the ack it was waiting for simply never arrives.
830
929
  */
831
930
  interface ConfirmationRequest {
832
931
  command: string;
833
932
  args: Record<string, unknown>;
834
933
  }
835
934
  /**
836
- * Estado retornado pelo `verifySandbox()` do `BridgeShell` após
837
- * round-trip com o iframe. Se algum dos `*Reachable` vier `true`, o
838
- * boundary está furado — Camada 1 (`bootstrapClientIframe` com override
839
- * prefix) falhou e a Camada 4 (dual WebView) deve ser acionada.
935
+ * Result of `BridgeShell.verifySandbox()` after a round trip with the iframe.
936
+ * If any `*Reachable` flag comes back `true`, the boundary is leaking: the
937
+ * override prefix injected by `bootstrapClientIframe` failed, and the app must
938
+ * not render client code with a path to the native layer.
840
939
  */
841
940
  interface SandboxVerifyResult {
842
941
  flutterBridgeReachable: boolean;
843
942
  condorBridgeReachable: boolean;
943
+ /** Whether `window.CondorNative` is visible inside the iframe. */
944
+ nativeChannelReachable: boolean;
945
+ /** `true` only when none of the three is reachable. */
844
946
  ok: boolean;
845
947
  }
846
948
 
847
949
  /**
848
- * `BridgeShell` (v0.5.0) — API para o shell Condor-controlado que envolve
849
- * o conteúdo do cliente externo dentro de `<iframe sandbox="allow-scripts">`.
850
- *
851
- * Responsabilidades:
852
- * 1. **Camada 1 do Risco 1** — `bootstrapClientIframe(iframe, clientPath)`:
853
- * busca HTML do cliente via `fetch`, prepende script de override que
854
- * deleta `window.flutter_inappwebview` e `window.CondorBridge` do
855
- * iframe, e seta como `srcdoc`. Override roda em parse-time, antes
856
- * de qualquer outro script.
857
- * 2. **Camada 2 do Risco 1** — `verifySandbox(iframe)`: round-trip
858
- * `postMessage` que pede ao iframe atestation autoritativa de que
859
- * `window.flutter_inappwebview` e `window.CondorBridge` são `undefined`.
860
- * 3. **Roteamento postMessage iframe ↔ nativo:** escuta `message` events
861
- * do iframe, valida com Valibot, aplica `ShellPolicy` default-deny,
862
- * e propaga para o `Bridge` legado (que fala com o nativo). Eventos
863
- * nativos voltam para o iframe via `postMessage`.
864
- * 4. **Confirmação humana para comandos sensíveis:** comandos em
865
- * `policy.requiresConfirmation` disparam callback
866
- * `onConfirmationRequest` antes de tocar o nativo — shell apresenta UI.
950
+ * `BridgeShell` — the API for the **Condor-controlled shell** that wraps
951
+ * external client content inside `<iframe sandbox="allow-scripts">`.
952
+ *
953
+ * This is not client-facing. If you are writing the bundle that renders as the
954
+ * app's Home, you want `BridgeClient`.
955
+ *
956
+ * Responsibilities:
957
+ * 1. **Layer 1 — `bootstrapClientIframe(iframe, clientPath)`:** fetches the
958
+ * client HTML, prepends an override script that removes the native globals
959
+ * from the iframe, and sets it as `srcdoc`. The override runs at parse time,
960
+ * before any other script.
961
+ * 2. **Layer 2 — `verifySandbox(iframe)`:** a `postMessage` round trip asking
962
+ * the iframe to attest that the native globals really are `undefined`.
963
+ * 3. **Routing between iframe and native:** listens for `message` events from
964
+ * the iframe, validates them with Valibot, applies the default-deny
965
+ * `ShellPolicy`, and forwards to `Bridge`. Native events go back to the
966
+ * iframe over `postMessage`.
967
+ * 4. **Human confirmation for sensitive commands:** commands listed in
968
+ * `policy.requiresConfirmation` fire the `onConfirmationRequest` callback
969
+ * before touching the native side, so the shell can present UI.
867
970
  */
868
971
 
869
972
  interface BridgeShellOptions {
870
973
  /**
871
- * Iframe que hospeda o cliente. Pode estar vazio no momento da
872
- * construção — `bootstrapClientIframe` será chamado depois para popular.
974
+ * The iframe hosting the client. It may be empty at construction time —
975
+ * `bootstrapClientIframe` is called later to populate it.
873
976
  */
874
977
  iframe: HTMLIFrameElement;
875
978
  /**
876
- * Policy default-deny. Toda chave/comando vindo do iframe é validado
877
- * contra estas listas. Vide `ShellPolicy`.
979
+ * The default-deny policy. Every key and command arriving from the iframe is
980
+ * checked against these lists. See `ShellPolicy`.
878
981
  */
879
982
  policy: ShellPolicy;
880
983
  /**
881
- * Callback síncrono ou assíncrono chamado quando o cliente pede um
882
- * comando em `policy.requiresConfirmation`. Shell apresenta UI de
883
- * confirmação; resolve com `true` (aprovar) ou `false` (recusar).
884
- * Se não fornecido, comandos `requiresConfirmation` são **negados**
885
- * automaticamente — fail-safe.
984
+ * Synchronous or asynchronous callback invoked when the client issues a
985
+ * command listed in `policy.requiresConfirmation`. The shell presents its
986
+ * confirmation UI and resolves `true` to approve or `false` to decline.
987
+ *
988
+ * If it is not provided, such commands are **denied** automatically —
989
+ * fail-safe.
886
990
  */
887
991
  onConfirmationRequest?: (req: ConfirmationRequest) => boolean | Promise<boolean>;
888
992
  }
@@ -893,61 +997,60 @@ declare class BridgeShell {
893
997
  private readonly subscriptions;
894
998
  private detached;
895
999
  /**
896
- * Versão do nativo, obtida uma vez e cacheada. `null` até o nativo
897
- * responder — e permanentemente `null` num browser sem nativo.
1000
+ * The native version, fetched once and cached. `null` until the native side
1001
+ * answers, and permanently `null` in a browser with no native host.
898
1002
  */
899
1003
  private nativeVersion;
900
- /** Handshake com o nativo em voo; garante uma única chamada. */
1004
+ /** In-flight native handshake; guarantees a single call. */
901
1005
  private nativeHandshake;
902
1006
  private pendingVerifies;
903
1007
  constructor(opts: BridgeShellOptions);
904
1008
  /**
905
- * **Camada 1 do Risco 1.** Bootstrap via fetch de URL.
1009
+ * **Layer 1.** Bootstraps by fetching a URL.
906
1010
  *
907
- * Atalho para o caso comum HTTP/HTTPS: faz `fetch(clientPath)` e
908
- * delega para `bootstrapClientIframeWithHtml`.
1011
+ * A shortcut for the common HTTP/HTTPS case: it runs `fetch(clientPath)` and
1012
+ * delegates to `bootstrapClientIframeWithHtml`.
909
1013
  *
910
- * **Não funciona em scheme `file://`** — `fetch` em WebView Android
911
- * (Chromium) bloqueia o scheme com "URL scheme 'file' is not supported".
912
- * Para asset bundled em APK ou bundle inlined no build-time, use
913
- * `bootstrapClientIframeWithHtml(htmlString)`.
1014
+ * **It does not work on the `file://` scheme** — Android's Chromium WebView
1015
+ * blocks it with "URL scheme 'file' is not supported". For an asset bundled
1016
+ * in the APK, or a bundle inlined at build time, use
1017
+ * `bootstrapClientIframeWithHtml(htmlString)` instead.
914
1018
  *
915
- * **Devo ser chamado uma vez no boot do shell**, antes de qualquer
916
- * outra interação. Após retornar, `verifySandbox()` deve ser invocado
917
- * para atestation (Camada 2).
1019
+ * **Call it once at shell boot**, before any other interaction. After it
1020
+ * returns, invoke `verifySandbox()` for attestation (Layer 2).
918
1021
  */
919
1022
  bootstrapClientIframe(clientPath?: string): Promise<void>;
920
1023
  /**
921
- * **Camada 1 do Risco 1.** Bootstrap a partir de HTML string.
1024
+ * **Layer 1.** Bootstraps from an HTML string.
922
1025
  *
923
- * Aceita HTML do cliente diretamente (já lido de arquivo, embedado em
924
- * build-time via `esbuild.define`, lido de IndexedDB, etc.) e seta
925
- * como `srcdoc` do iframe prefixado pelo override script. Override
926
- * roda em parse-time, antes de qualquer outro script do cliente.
1026
+ * Takes the client HTML directly — already read from a file, embedded at
1027
+ * build time, read from storage — and sets it as the iframe's `srcdoc`,
1028
+ * prefixed by the override script, which runs at parse time before any
1029
+ * client script.
927
1030
  *
928
- * Use quando `fetch` não é aplicável (asset bundled em `file://`,
929
- * bundle inlined no build, qualquer cenário sem rede). Para HTTP
930
- * use `bootstrapClientIframe(clientPath)`.
1031
+ * Use this when `fetch` does not apply: an asset bundled under `file://`, a
1032
+ * bundle inlined at build time, any scenario without network. Android
1033
+ * WebView blocks `fetch` on the `file://` scheme, so this is the path the
1034
+ * app actually takes. For HTTP, use `bootstrapClientIframe(clientPath)`.
931
1035
  *
932
- * **Devo ser chamado uma vez no boot do shell**, antes de qualquer
933
- * outra interação. Após retornar, `verifySandbox()` deve ser invocado
934
- * para atestation (Camada 2).
1036
+ * **Call it once at shell boot**, before any other interaction. After it
1037
+ * returns, invoke `verifySandbox()` for attestation (Layer 2).
935
1038
  */
936
1039
  bootstrapClientIframeWithHtml(clientHtml: string): Promise<void>;
937
1040
  private waitForIframeLoad;
938
1041
  /**
939
- * **Camada 2 do Risco 1.** Round-trip `postMessage` que pede ao iframe
940
- * atestation autoritativa do estado de `window.flutter_inappwebview`
941
- * e `window.CondorBridge`. Timeout em 2s.
1042
+ * **Layer 2.** A `postMessage` round trip asking the iframe for an
1043
+ * authoritative attestation of whether the native globals are reachable.
1044
+ * Times out after 2 s.
942
1045
  *
943
- * `ok` é `true` apenas se ambos forem `undefined` no iframe. Se vier
944
- * `false`, **shell deve abortar o boot do app** — Camada 1 falhou e
945
- * Camada 4 (dual WebView) precisa ser acionada manualmente.
1046
+ * `ok` is `true` only when none of them is reachable. If it comes back
1047
+ * `false`, **the shell must abort the app boot**: Layer 1 failed, and client
1048
+ * code would run with a path to the native layer.
946
1049
  */
947
1050
  verifySandbox(): Promise<SandboxVerifyResult>;
948
1051
  /**
949
- * Encerra o shell — remove listener `message`, cancela subscriptions
950
- * pendentes no `Bridge` nativo, limpa estado interno.
1052
+ * Tears the shell down: removes the `message` listener, cancels pending
1053
+ * subscriptions on the native `Bridge`, and clears internal state.
951
1054
  */
952
1055
  detach(): void;
953
1056
  private onMessage;
@@ -955,16 +1058,16 @@ declare class BridgeShell {
955
1058
  private handleUnsubscribe;
956
1059
  private handleSend;
957
1060
  /**
958
- * Obtém o `nativeVersion`, disparando o handshake com o nativo na primeira
959
- * vez e cacheando o resultado.
1061
+ * Resolves `nativeVersion`, firing the native handshake the first time and
1062
+ * caching the result.
960
1063
  *
961
- * ⚠️ `Bridge.handshake` **não tem timeout**: a Promise só resolve quando o
962
- * nativo emite `handshake-ack`. Num browser sem nativo ela nunca resolve,
963
- * então o `race` abaixo é o que garante que o cliente sempre receba
964
- * resposta — com `null` — em vez de ficar pendurado.
1064
+ * ⚠️ `Bridge.handshake` has **no timeout**: its promise only settles when the
1065
+ * native side emits `handshake-ack`. In a browser with no native host it
1066
+ * never settles, so the race below is what guarantees the client always gets
1067
+ * an answer — `null` — instead of hanging.
965
1068
  */
966
1069
  private resolveNativeVersion;
967
1070
  private postToClient;
968
1071
  }
969
1072
 
970
- export { type AutoRespondResponse, BRIDGE_KEYS, Bridge, BridgeClient, type BridgeClientHandshakeAck, type BridgeIncomingMessage, type BridgeKey, type BridgeOutgoingEvent, BridgeShell, type BridgeShellOptions, 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, type PayloadOf, type PingPongPayload, type RealTimeAckPayload, type RequestPermissionPayload, type SandboxVerifyResult, type SensorLogPayload, type SensorLogQueryPayload, type ShellPolicy, ShellToClientMessage, type SyncTimeAckPayload, type SystemStatusPayload, type UserDevicePayload };
1073
+ 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 };