@condorinst/mobile-bridge 0.8.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.
@@ -0,0 +1,624 @@
1
+ import * as v from 'valibot';
2
+
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.
19
+ */
20
+ interface MockMessage {
21
+ handlerName: string;
22
+ payload: unknown;
23
+ timestamp: number;
24
+ }
25
+
26
+ /**
27
+ * Dev panel — UI flutuante para inspecionar o bridge em mock mode.
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).
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.
37
+ *
38
+ * Estilos inline + classes prefixadas `condor-mock-panel-*` para reduzir
39
+ * conflito com CSS do cliente.
40
+ */
41
+
42
+ /**
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
48
+ */
49
+ interface AutoRespondResponse {
50
+ key: string;
51
+ payload: unknown;
52
+ }
53
+ interface DevPanelOptions {
54
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
55
+ maxMessages?: number;
56
+ theme?: 'dark' | 'light';
57
+ startCollapsed?: boolean;
58
+ /**
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.
63
+ *
64
+ * Sem esta callback, o checkbox "Auto-respond" não aparece no painel.
65
+ *
66
+ * @since v0.3.0
67
+ */
68
+ onSendForAutoRespond?: (msg: MockMessage) => AutoRespondResponse | null;
69
+ /**
70
+ * Estado inicial do toggle "Auto-respond". Default: `false`
71
+ * (manual permanece padrão; cliente decide quando ligar).
72
+ *
73
+ * @since v0.3.0
74
+ */
75
+ startAutoRespondEnabled?: boolean;
76
+ /**
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
81
+ */
82
+ autoRespondDelayMs?: number;
83
+ }
84
+
85
+ /**
86
+ * Catálogo das chaves do bridge JS↔Native.
87
+ *
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)`.
91
+ *
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.
96
+ */
97
+ declare const BRIDGE_KEYS: {
98
+ readonly pingPong: "pingPong";
99
+ readonly userDevice: "userDevice";
100
+ readonly deviceLightSensors: "deviceLightSensors";
101
+ readonly syncTime: "syncTime";
102
+ readonly realTime: "realTime";
103
+ readonly deleteMemory: "deleteMemory";
104
+ readonly deviceNotification: "deviceNotification";
105
+ readonly deviceConnection: "deviceConnection";
106
+ readonly sensorLog: "sensorLog";
107
+ readonly dfu: "dfu";
108
+ };
109
+ type BridgeKey = (typeof BRIDGE_KEYS)[keyof typeof BRIDGE_KEYS];
110
+
111
+ /**
112
+ * Mensagens do protocolo do bridge.
113
+ *
114
+ * Espelha o lado Dart (`BridgeIncomingMessage`/`BridgeOutgoingEvent` em
115
+ * `apps/sleep_whitelabel/lib/ui/pages/home/bridge/bridge_message.dart`).
116
+ */
117
+ /**
118
+ * Mensagem JS → Native, enviada via
119
+ * `window.flutter_inappwebview.callHandler('CondorBridge', msg)`.
120
+ */
121
+ interface BridgeIncomingMessage {
122
+ type: string;
123
+ version: string;
124
+ payload?: Record<string, unknown>;
125
+ }
126
+ /**
127
+ * Evento Native → JS, recebido via
128
+ * `window.CondorBridge.emit(key, payload)`.
129
+ */
130
+ interface BridgeOutgoingEvent {
131
+ key: string;
132
+ payload: unknown;
133
+ }
134
+ /**
135
+ * Resposta do handshake (`handshake-ack`).
136
+ *
137
+ * `nativeVersion`: versão semver do schema declarada pelo nativo.
138
+ * `accepted`: nativo aceita o `clientVersion` enviado. v0.1.0 não impõe
139
+ * validação semver dura — nativo retorna `accepted: true` sempre.
140
+ */
141
+ interface HandshakeAck {
142
+ nativeVersion: string;
143
+ accepted: boolean;
144
+ }
145
+ interface HandshakeOptions {
146
+ clientVersion: string;
147
+ }
148
+
149
+ /**
150
+ * Catálogo de payloads tipados por chave (v0.4.0+).
151
+ *
152
+ * Shapes derivadas autoritativamente do source code do `condor_core` no
153
+ * monorepo `mobile_applications` (ver §"Shapes derivadas" no execution
154
+ * log do step2 do trio `2026-06-04_sleep-whitelabel-bridge-keys`).
155
+ *
156
+ * **Direção:** BG → JS (observáveis). Os comandos JS → BG (`Bridge.send`)
157
+ * continuam permissivos (`Record<string, unknown>`) na v0.4.0 — sua
158
+ * tipagem estrita é trabalho separado e fora do MVP atual.
159
+ *
160
+ * **Migração da v0.3.0:** o tipo `PayloadOf<K>` era `Record<string, unknown>`
161
+ * para qualquer K. Agora é mapeado pelas chaves do catálogo, com
162
+ * fallback permissivo para chaves não-catalogadas (e.g., `dfu` continua
163
+ * `Record<string, unknown>` — hub-only, reservado).
164
+ */
165
+ /** Loop heartbeat boot/keepalive. */
166
+ interface PingPongPayload {
167
+ data: 'ping' | 'pong';
168
+ }
169
+ /**
170
+ * Snapshot do device store do usuário. Espelha `UserDevices.toMap()`
171
+ * (`packages/condor_core/lib/src/domain/entities/user_devices_entity.dart:144-151`).
172
+ *
173
+ * Os arrays internos são `Record<string, unknown>` no MVP — cada item
174
+ * tem seu próprio `toMap()` (DeviceConfig, DeviceTelemetry,
175
+ * DeviceUserConfig) cujo strict typing fica para release futura quando
176
+ * consumidor JS precisar.
177
+ */
178
+ interface UserDevicePayload {
179
+ lastUpdate: number;
180
+ devicesConfig: Array<Record<string, unknown>>;
181
+ devicesTelemetry: Array<Record<string, unknown>>;
182
+ devicesUserConfig: Array<Record<string, unknown>>;
183
+ }
184
+ /**
185
+ * Stream real-time de sensores de luz (FEST). Shape:
186
+ * `{serialNo, type, data: <lightSensors.toMap()>}`. O subobjeto `data`
187
+ * varia por device type — permissivo no MVP.
188
+ */
189
+ interface DeviceLightSensorsPayload {
190
+ serialNo: number;
191
+ type: string;
192
+ data: Record<string, unknown>;
193
+ }
194
+ /** Ack de comando `syncTime`. */
195
+ interface SyncTimeAckPayload {
196
+ serialNo: number;
197
+ success: boolean;
198
+ }
199
+ /** Ack/state do toggle `realTime`. */
200
+ interface RealTimeAckPayload {
201
+ serialNo: number;
202
+ success: boolean;
203
+ value: boolean;
204
+ }
205
+ /** Ack de comando `deleteMemory`. */
206
+ interface DeleteMemoryAckPayload {
207
+ serialNo: number;
208
+ success: boolean;
209
+ }
210
+ /**
211
+ * Status de conexão BLE. Espelha `_userDevicesService.openConnection`
212
+ * subscriber (`devices_notification_service.dart:38-44`).
213
+ */
214
+ interface DeviceConnectionPayload {
215
+ serialNo: number;
216
+ isConnect: boolean;
217
+ }
218
+ /**
219
+ * Evento/status de device (logs). Espelha `DevicesNotification.toMap()`
220
+ * (`devices_notification_entity.dart:33-41`).
221
+ */
222
+ interface DeviceNotificationPayload {
223
+ serialNo: number;
224
+ isConnect: boolean;
225
+ message: string | null;
226
+ messageCode: string | null;
227
+ isError: boolean;
228
+ }
229
+ /**
230
+ * Resposta do comando `sensorLog` (GET `user_cmd=log` do Mobile Lambda).
231
+ * Envelope com `requestId` ecoado para correlação JS (suporta paginação
232
+ * keyset concorrente) + `success`/`error`. No sucesso traz a página de logs
233
+ * (ordem cronológica ASC, temperatura em °C, timestamps UTC naive) e os
234
+ * cursores opacos. No erro, `error` carrega o código do contrato (ex.:
235
+ * `CONFLICTING_CURSORS`, `invalid session`).
236
+ */
237
+ interface SensorLogPayload {
238
+ requestId: string;
239
+ success: boolean;
240
+ logs?: Array<Record<string, unknown>>;
241
+ startingAfter?: string | null;
242
+ endingBefore?: string | null;
243
+ hasMore?: boolean;
244
+ hasPrevious?: boolean;
245
+ error?: string;
246
+ }
247
+ /**
248
+ * Mapa discriminado de payloads por chave do catálogo. Usado por
249
+ * `PayloadOf<K>` para inferência automática no `Bridge.on`.
250
+ *
251
+ * `dfu` é hub-only — sleep_whitelabel não trata; mantido permissivo.
252
+ */
253
+ interface KeyPayloads {
254
+ pingPong: PingPongPayload;
255
+ userDevice: UserDevicePayload;
256
+ deviceLightSensors: DeviceLightSensorsPayload;
257
+ syncTime: SyncTimeAckPayload;
258
+ realTime: RealTimeAckPayload;
259
+ deleteMemory: DeleteMemoryAckPayload;
260
+ deviceNotification: DeviceNotificationPayload;
261
+ deviceConnection: DeviceConnectionPayload;
262
+ sensorLog: SensorLogPayload;
263
+ dfu: Record<string, unknown>;
264
+ }
265
+ /**
266
+ * Payload tipado por chave. Distributivo:
267
+ * - Chaves catalogadas em `KeyPayloads` → payload estrito.
268
+ * - Demais chaves (futuras / extras do cliente) → `Record<string, unknown>`
269
+ * permissivo para preservar extensibilidade.
270
+ *
271
+ * **v0.3.0 → v0.4.0:** quebra build-time (não runtime) para clientes
272
+ * que usavam payload `unknown` em `Bridge.on` — agora vem tipado.
273
+ * Cliente que precisa do shape permissivo pode usar
274
+ * `(payload: Record<string, unknown>) => ...` explicitamente.
275
+ */
276
+ type PayloadOf<K extends BridgeKey = BridgeKey> = K extends keyof KeyPayloads ? KeyPayloads[K] : Record<string, unknown>;
277
+
278
+ interface WindowCondorBridge {
279
+ emit(key: string, payload: unknown): void;
280
+ }
281
+ declare global {
282
+ interface Window {
283
+ CondorBridge?: WindowCondorBridge;
284
+ }
285
+ }
286
+ interface MockState {
287
+ readonly isMock: true;
288
+ readonly messages: readonly MockMessage[];
289
+ readonly subscriberKeys: readonly string[];
290
+ }
291
+ interface CondorBridge {
292
+ /**
293
+ * `true` quando o adapter ativo é mock (browser dev, sem
294
+ * `window.flutter_inappwebview`). `false` quando rodando dentro do
295
+ * WebView Flutter.
296
+ */
297
+ readonly isMock: boolean;
298
+ /**
299
+ * Envia handshake versionado ao nativo e aguarda `handshake-ack`.
300
+ *
301
+ * Apenas um handshake pode estar pendente por vez. Chamadas paralelas
302
+ * antes do primeiro `ack` chegar são rejeitadas com erro claro.
303
+ *
304
+ * **Em mock mode:** NÃO resolve sozinho. Cliente/teste deve chamar
305
+ * `Bridge.mockEmit('handshake-ack', {nativeVersion, accepted})` para
306
+ * destravar a Promise. Esta decisão (`Explícito`) força testes a serem
307
+ * intencionais sobre o estado nativo.
308
+ */
309
+ handshake(opts: HandshakeOptions): Promise<HandshakeAck>;
310
+ /**
311
+ * Subscreve a uma chave observável. Retorna função `unsubscribe`.
312
+ *
313
+ * Múltiplos subscribers por chave são permitidos. Erro em um subscriber
314
+ * não afeta os outros (capturado e logado em `console.error`).
315
+ */
316
+ on<K extends BridgeKey>(key: K, cb: (payload: PayloadOf<K>) => void): () => void;
317
+ /**
318
+ * Envia mensagem genérica ao nativo. Use para comandos UI→BG que não
319
+ * tenham helper dedicado. `type` é a string que o native usa para
320
+ * roteamento no `_onJsMessage`.
321
+ *
322
+ * Em mock mode: grava no buffer interno + `console.debug`.
323
+ */
324
+ send(type: string, payload?: Record<string, unknown>): void;
325
+ /**
326
+ * Abre a tela nativa de Settings do app. Equivalente a
327
+ * `Bridge.send('openSettings')`.
328
+ */
329
+ openSettings(): void;
330
+ /**
331
+ * Dispara `window.CondorBridge.emit(key, payload)` programaticamente —
332
+ * útil para simular eventos nativos em testes e dev em browser.
333
+ *
334
+ * **Throws** se o adapter ativo não for mock.
335
+ */
336
+ mockEmit(key: string, payload?: unknown): void;
337
+ /**
338
+ * Snapshot do estado do mock adapter (mensagens enviadas + chaves com
339
+ * subscribers ativos). Useful para asserts em testes de cliente.
340
+ *
341
+ * **Throws** se o adapter ativo não for mock.
342
+ */
343
+ getMockState(): MockState;
344
+ /**
345
+ * Injeta painel HTML flutuante no `document.body` para inspecionar o
346
+ * bridge em mock mode. Idempotente — re-install substitui o anterior.
347
+ *
348
+ * **Throws** se o adapter ativo não for mock OU se `document` não existir.
349
+ */
350
+ installDevPanel(options?: DevPanelOptions): void;
351
+ }
352
+ declare const Bridge: CondorBridge;
353
+
354
+ /**
355
+ * `BridgeClient` (v0.5.0) — API para código do cliente externo rodando
356
+ * dentro de `<iframe sandbox="allow-scripts">` no `sleep_whitelabel`.
357
+ *
358
+ * Surface idêntica ao `Bridge` legado (`on`/`send`/`openSettings`/`handshake`)
359
+ * — cliente importa e não percebe que está em sandbox. Internamente,
360
+ * todas as chamadas viram `window.parent.postMessage(...)` com schema
361
+ * validado pelo shell antes de chegar ao nativo.
362
+ *
363
+ * **Não expõe** `installDevPanel`, `mockEmit`, `isMock`, `getMockState` —
364
+ * essas APIs são responsabilidade do shell (`BridgeShell`).
365
+ *
366
+ * **Threat model & Camada 2 do Risco 1** (vide
367
+ * `sleep-white-label-content-transport-and-security.md`): este módulo
368
+ * também responde ao `verify-sandbox` do shell — função de attestation
369
+ * do próprio iframe sobre se ainda enxerga `window.flutter_inappwebview`
370
+ * ou `window.CondorBridge` (que deveriam ter sido deletados pela Camada 1).
371
+ * Se vier `true` em algum, o shell aborta o boot do app.
372
+ */
373
+
374
+ interface BridgeClientHandshakeAck {
375
+ shellVersion: string;
376
+ accepted: boolean;
377
+ }
378
+ interface CondorBridgeClient {
379
+ /**
380
+ * Versão do protocolo do client.
381
+ */
382
+ readonly protocolVersion: string;
383
+ /**
384
+ * Subscreve a uma chave observável. Retorna função `unsubscribe`.
385
+ * Múltiplos subscribers por chave permitidos. Erro em um subscriber
386
+ * não afeta os outros.
387
+ */
388
+ on<K extends BridgeKey>(key: K, cb: (payload: PayloadOf<K>) => void): () => void;
389
+ /**
390
+ * Envia comando para o shell. Shell valida contra `ShellPolicy` antes
391
+ * de tocar o nativo. Comandos não permitidos → `nack` (logado em
392
+ * `console.warn` na v0.5.0).
393
+ */
394
+ send(command: string, args?: Record<string, unknown>): void;
395
+ /**
396
+ * Abre Settings nativa. Equivalente a `send('openSettings')`.
397
+ */
398
+ openSettings(): void;
399
+ /**
400
+ * Handshake com o shell. Resolve com `{ shellVersion, accepted }`.
401
+ * Timeout em 5s se shell não responder.
402
+ *
403
+ * Apenas um handshake pode estar pendente por vez.
404
+ */
405
+ handshake(opts: {
406
+ clientVersion: string;
407
+ }): Promise<BridgeClientHandshakeAck>;
408
+ }
409
+ declare const BridgeClient: CondorBridgeClient;
410
+
411
+ /**
412
+ * Schemas Valibot dos `postMessage` shell↔client (v0.5.0).
413
+ *
414
+ * Quando o conteúdo do cliente roda dentro de `<iframe sandbox="allow-scripts">`
415
+ * (origem null), a comunicação com o shell Condor-controlado acontece via
416
+ * `window.postMessage`. Estes schemas são o **único contrato aceito** —
417
+ * qualquer mensagem mal-formada é descartada silenciosamente pelo shell.
418
+ *
419
+ * **Threat model:** o iframe pode ser hostil (supply-chain comprometida,
420
+ * XSS no código do cliente). Estes schemas + a `ShellPolicy` aplicada
421
+ * no `BridgeShell` formam o boundary estrutural — vide
422
+ * `condor_wiki/cross-cutting/sleep-white-label-content-transport-and-security.md`.
423
+ */
424
+
425
+ declare const ClientToShellMessage: v.VariantSchema<"type", [v.ObjectSchema<{
426
+ readonly type: v.LiteralSchema<"subscribe", undefined>;
427
+ readonly key: v.PicklistSchema<[string, ...string[]], undefined>;
428
+ }, undefined>, v.ObjectSchema<{
429
+ readonly type: v.LiteralSchema<"unsubscribe", undefined>;
430
+ readonly key: v.PicklistSchema<[string, ...string[]], undefined>;
431
+ }, undefined>, v.ObjectSchema<{
432
+ readonly type: v.LiteralSchema<"send", undefined>;
433
+ readonly command: v.StringSchema<undefined>;
434
+ readonly args: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, {}>;
435
+ }, undefined>, v.ObjectSchema<{
436
+ readonly type: v.LiteralSchema<"handshake", undefined>;
437
+ readonly payload: v.ObjectSchema<{
438
+ readonly clientVersion: v.StringSchema<undefined>;
439
+ }, undefined>;
440
+ }, undefined>, v.ObjectSchema<{
441
+ readonly type: v.LiteralSchema<"jsError", undefined>;
442
+ readonly payload: v.ObjectSchema<{
443
+ readonly message: v.StringSchema<undefined>;
444
+ readonly stack: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
445
+ readonly source: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
446
+ readonly lineno: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
447
+ readonly colno: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
448
+ }, undefined>;
449
+ }, undefined>, v.ObjectSchema<{
450
+ readonly type: v.LiteralSchema<"sandbox-verify-response", undefined>;
451
+ readonly flutterBridgeReachable: v.BooleanSchema<undefined>;
452
+ readonly condorBridgeReachable: v.BooleanSchema<undefined>;
453
+ }, undefined>], undefined>;
454
+ type ClientToShellMessage = v.InferOutput<typeof ClientToShellMessage>;
455
+ declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
456
+ readonly type: v.LiteralSchema<"event", undefined>;
457
+ readonly key: v.PicklistSchema<[string, ...string[]], undefined>;
458
+ readonly payload: v.UnknownSchema;
459
+ }, undefined>, v.ObjectSchema<{
460
+ readonly type: v.LiteralSchema<"handshake-ack", undefined>;
461
+ readonly ack: v.ObjectSchema<{
462
+ readonly shellVersion: v.StringSchema<undefined>;
463
+ readonly accepted: v.BooleanSchema<undefined>;
464
+ }, undefined>;
465
+ }, undefined>, v.ObjectSchema<{
466
+ readonly type: v.LiteralSchema<"nack", undefined>;
467
+ readonly key: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
468
+ readonly command: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
469
+ readonly reason: v.StringSchema<undefined>;
470
+ }, undefined>, v.ObjectSchema<{
471
+ readonly type: v.LiteralSchema<"verify-sandbox", undefined>;
472
+ }, undefined>], undefined>;
473
+ type ShellToClientMessage = v.InferOutput<typeof ShellToClientMessage>;
474
+ /**
475
+ * Policy default-deny aplicada pelo `BridgeShell`. Toda chave/comando
476
+ * vindo do iframe é validado contra estas listas antes de tocar o
477
+ * bridge nativo.
478
+ *
479
+ * - `readOnlyKeys`: chaves que o cliente pode subscrever (observáveis).
480
+ * - `allowedCommands`: comandos pass-through (sem confirmação humana).
481
+ * - `requiresConfirmation`: comandos que disparam callback `onConfirmationRequest`
482
+ * no shell — usuário decide aprovar/recusar via UI nativa do shell.
483
+ * - `deniedCommands`: bloqueio explícito (defesa em profundidade — comandos
484
+ * destrutivos como `dfu` que nunca devem fluir do iframe).
485
+ *
486
+ * **Não embedada no package** — cliente do `BridgeShell` (no monorepo
487
+ * `sleep_whitelabel`) provê a policy concreta. Mantém o package agnóstico
488
+ * ao catálogo e permite policy evoluir sem novo release.
489
+ */
490
+ interface ShellPolicy {
491
+ readOnlyKeys: string[];
492
+ allowedCommands: string[];
493
+ requiresConfirmation: string[];
494
+ deniedCommands: string[];
495
+ }
496
+ /**
497
+ * Resposta do callback `onConfirmationRequest` do `BridgeShell`.
498
+ *
499
+ * `approve: true` → comando flui para o nativo.
500
+ * `approve: false` → shell envia `nack` ao iframe com `reason: 'user-rejected'`.
501
+ */
502
+ interface ConfirmationRequest {
503
+ command: string;
504
+ args: Record<string, unknown>;
505
+ }
506
+ /**
507
+ * Estado retornado pelo `verifySandbox()` do `BridgeShell` após
508
+ * round-trip com o iframe. Se algum dos `*Reachable` vier `true`, o
509
+ * boundary está furado — Camada 1 (`bootstrapClientIframe` com override
510
+ * prefix) falhou e a Camada 4 (dual WebView) deve ser acionada.
511
+ */
512
+ interface SandboxVerifyResult {
513
+ flutterBridgeReachable: boolean;
514
+ condorBridgeReachable: boolean;
515
+ ok: boolean;
516
+ }
517
+
518
+ /**
519
+ * `BridgeShell` (v0.5.0) — API para o shell Condor-controlado que envolve
520
+ * o conteúdo do cliente externo dentro de `<iframe sandbox="allow-scripts">`.
521
+ *
522
+ * Responsabilidades:
523
+ * 1. **Camada 1 do Risco 1** — `bootstrapClientIframe(iframe, clientPath)`:
524
+ * busca HTML do cliente via `fetch`, prepende script de override que
525
+ * deleta `window.flutter_inappwebview` e `window.CondorBridge` do
526
+ * iframe, e seta como `srcdoc`. Override roda em parse-time, antes
527
+ * de qualquer outro script.
528
+ * 2. **Camada 2 do Risco 1** — `verifySandbox(iframe)`: round-trip
529
+ * `postMessage` que pede ao iframe atestation autoritativa de que
530
+ * `window.flutter_inappwebview` e `window.CondorBridge` são `undefined`.
531
+ * 3. **Roteamento postMessage iframe ↔ nativo:** escuta `message` events
532
+ * do iframe, valida com Valibot, aplica `ShellPolicy` default-deny,
533
+ * e propaga para o `Bridge` legado (que fala com o nativo). Eventos
534
+ * nativos voltam para o iframe via `postMessage`.
535
+ * 4. **Confirmação humana para comandos sensíveis:** comandos em
536
+ * `policy.requiresConfirmation` disparam callback
537
+ * `onConfirmationRequest` antes de tocar o nativo — shell apresenta UI.
538
+ */
539
+
540
+ interface BridgeShellOptions {
541
+ /**
542
+ * Iframe que hospeda o cliente. Pode estar vazio no momento da
543
+ * construção — `bootstrapClientIframe` será chamado depois para popular.
544
+ */
545
+ iframe: HTMLIFrameElement;
546
+ /**
547
+ * Policy default-deny. Toda chave/comando vindo do iframe é validado
548
+ * contra estas listas. Vide `ShellPolicy`.
549
+ */
550
+ policy: ShellPolicy;
551
+ /**
552
+ * Callback síncrono ou assíncrono chamado quando o cliente pede um
553
+ * comando em `policy.requiresConfirmation`. Shell apresenta UI de
554
+ * confirmação; resolve com `true` (aprovar) ou `false` (recusar).
555
+ * Se não fornecido, comandos `requiresConfirmation` são **negados**
556
+ * automaticamente — fail-safe.
557
+ */
558
+ onConfirmationRequest?: (req: ConfirmationRequest) => boolean | Promise<boolean>;
559
+ }
560
+ declare class BridgeShell {
561
+ private readonly iframe;
562
+ private readonly policy;
563
+ private readonly onConfirm?;
564
+ private readonly subscriptions;
565
+ private detached;
566
+ private pendingVerifies;
567
+ constructor(opts: BridgeShellOptions);
568
+ /**
569
+ * **Camada 1 do Risco 1.** Bootstrap via fetch de URL.
570
+ *
571
+ * Atalho para o caso comum HTTP/HTTPS: faz `fetch(clientPath)` e
572
+ * delega para `bootstrapClientIframeWithHtml`.
573
+ *
574
+ * **Não funciona em scheme `file://`** — `fetch` em WebView Android
575
+ * (Chromium) bloqueia o scheme com "URL scheme 'file' is not supported".
576
+ * Para asset bundled em APK ou bundle inlined no build-time, use
577
+ * `bootstrapClientIframeWithHtml(htmlString)`.
578
+ *
579
+ * **Devo ser chamado uma vez no boot do shell**, antes de qualquer
580
+ * outra interação. Após retornar, `verifySandbox()` deve ser invocado
581
+ * para atestation (Camada 2).
582
+ */
583
+ bootstrapClientIframe(clientPath?: string): Promise<void>;
584
+ /**
585
+ * **Camada 1 do Risco 1.** Bootstrap a partir de HTML string.
586
+ *
587
+ * Aceita HTML do cliente diretamente (já lido de arquivo, embedado em
588
+ * build-time via `esbuild.define`, lido de IndexedDB, etc.) e seta
589
+ * como `srcdoc` do iframe prefixado pelo override script. Override
590
+ * roda em parse-time, antes de qualquer outro script do cliente.
591
+ *
592
+ * Use quando `fetch` não é aplicável (asset bundled em `file://`,
593
+ * bundle inlined no build, qualquer cenário sem rede). Para HTTP
594
+ * use `bootstrapClientIframe(clientPath)`.
595
+ *
596
+ * **Devo ser chamado uma vez no boot do shell**, antes de qualquer
597
+ * outra interação. Após retornar, `verifySandbox()` deve ser invocado
598
+ * para atestation (Camada 2).
599
+ */
600
+ bootstrapClientIframeWithHtml(clientHtml: string): Promise<void>;
601
+ private waitForIframeLoad;
602
+ /**
603
+ * **Camada 2 do Risco 1.** Round-trip `postMessage` que pede ao iframe
604
+ * atestation autoritativa do estado de `window.flutter_inappwebview`
605
+ * e `window.CondorBridge`. Timeout em 2s.
606
+ *
607
+ * `ok` é `true` apenas se ambos forem `undefined` no iframe. Se vier
608
+ * `false`, **shell deve abortar o boot do app** — Camada 1 falhou e
609
+ * Camada 4 (dual WebView) precisa ser acionada manualmente.
610
+ */
611
+ verifySandbox(): Promise<SandboxVerifyResult>;
612
+ /**
613
+ * Encerra o shell — remove listener `message`, cancela subscriptions
614
+ * pendentes no `Bridge` nativo, limpa estado interno.
615
+ */
616
+ detach(): void;
617
+ private onMessage;
618
+ private handleSubscribe;
619
+ private handleUnsubscribe;
620
+ private handleSend;
621
+ private postToClient;
622
+ }
623
+
624
+ 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 MockMessage, type MockState, type PayloadOf, type PingPongPayload, type RealTimeAckPayload, type SandboxVerifyResult, type SensorLogPayload, type ShellPolicy, ShellToClientMessage, type SyncTimeAckPayload, type UserDevicePayload };