@condorinst/mobile-bridge 0.15.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -197,16 +197,121 @@ interface UserDevicePayload {
197
197
  devicesTelemetry: Array<Record<string, unknown>>;
198
198
  devicesUserConfig: Array<Record<string, unknown>>;
199
199
  }
200
+ /**
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.
203
+ *
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.
209
+ *
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.
214
+ *
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`.
218
+ */
219
+ type LightSensorsRawChannels = {
220
+ light_sensor_f1: number;
221
+ light_sensor_f2: number;
222
+ light_sensor_f3: number;
223
+ light_sensor_f4: number;
224
+ light_sensor_f5: number;
225
+ light_sensor_f6: number;
226
+ light_sensor_f7: number;
227
+ light_sensor_f8: number;
228
+ /** Luz total (`visible` no wire do device). */
229
+ light_sensor_clear: number;
230
+ /** Infravermelho próximo. */
231
+ light_sensor_nir: number;
232
+ };
233
+ /**
234
+ * Leitura de tempo real de um ActLumus gen1 (`type: 'actlumus'`).
235
+ *
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.
240
+ */
241
+ type LightSensorsActlumus1Data = LightSensorsRawChannels & {
242
+ time: number;
243
+ wavelength390: number;
244
+ wavelength415: number;
245
+ wavelength445: number;
246
+ wavelength480: number;
247
+ wavelength515: number;
248
+ wavelength555: number;
249
+ wavelength590: number;
250
+ wavelength630: number;
251
+ wavelength680: number;
252
+ wavelength750: number;
253
+ melanopic: number;
254
+ photopic: number;
255
+ light_sensor_z: null;
256
+ light_sensor_y: null;
257
+ light_sensor_x: null;
258
+ light_sensor_fd: null;
259
+ };
260
+ /**
261
+ * Leitura de tempo real de um ActLumus Plus / gen2 (`type: 'actlumus2'`).
262
+ *
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`.
269
+ *
270
+ * `light_sensor_z/_y/_x` são as tristimulus CIE 1931 — Z no azul, Y no verde,
271
+ * X no vermelho.
272
+ */
273
+ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
274
+ time: number;
275
+ wavelength395: number;
276
+ wavelength426: number;
277
+ wavelength457: number;
278
+ wavelength488: number;
279
+ wavelength520: number;
280
+ wavelength551: number;
281
+ wavelength582: number;
282
+ wavelength613: number;
283
+ wavelength644: number;
284
+ wavelength675: number;
285
+ wavelength707: number;
286
+ wavelength738: number;
287
+ wavelength769: number;
288
+ wavelength800: number;
289
+ melanopic: number;
290
+ photopic: number;
291
+ light_sensor_z: number;
292
+ light_sensor_y: number;
293
+ light_sensor_x: number;
294
+ /** Flicker detect. Não entra na matriz do espectro. */
295
+ light_sensor_fd: number;
296
+ };
200
297
  /**
201
298
  * Stream real-time de sensores de luz (FEST). Shape:
202
- * `{serialNo, type, data: <lightSensors.toMap()>}`. O subobjeto `data`
203
- * varia por device type — permissivo no MVP.
299
+ * `{serialNo, type, data: <lightSensors.toMap()>}`.
300
+ *
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`.
204
305
  */
205
- interface DeviceLightSensorsPayload {
306
+ type DeviceLightSensorsPayload = {
206
307
  serialNo: number;
207
- type: string;
208
- data: Record<string, unknown>;
209
- }
308
+ type: 'actlumus';
309
+ data: LightSensorsActlumus1Data;
310
+ } | {
311
+ serialNo: number;
312
+ type: 'actlumus2';
313
+ data: LightSensorsActlumus2Data;
314
+ };
210
315
  /** Ack de comando `syncTime`. */
211
316
  interface SyncTimeAckPayload {
212
317
  serialNo: number;
@@ -539,6 +644,16 @@ declare const Bridge: CondorBridge;
539
644
 
540
645
  interface BridgeClientHandshakeAck {
541
646
  shellVersion: string;
647
+ /**
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).
650
+ *
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`.
655
+ */
656
+ nativeVersion: string | null;
542
657
  accepted: boolean;
543
658
  }
544
659
  interface CondorBridgeClient {
@@ -665,6 +780,15 @@ declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
665
780
  readonly type: v.LiteralSchema<"handshake-ack", undefined>;
666
781
  readonly ack: v.ObjectSchema<{
667
782
  readonly shellVersion: v.StringSchema<undefined>;
783
+ /**
784
+ * Versão do schema declarada pelo **nativo**, repassada pelo shell.
785
+ *
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.
790
+ */
791
+ readonly nativeVersion: v.NullableSchema<v.StringSchema<undefined>, undefined>;
668
792
  readonly accepted: v.BooleanSchema<undefined>;
669
793
  }, undefined>;
670
794
  }, undefined>, v.ObjectSchema<{
@@ -768,6 +892,13 @@ declare class BridgeShell {
768
892
  private readonly onConfirm?;
769
893
  private readonly subscriptions;
770
894
  private detached;
895
+ /**
896
+ * Versão do nativo, obtida uma vez e cacheada. `null` até o nativo
897
+ * responder — e permanentemente `null` num browser sem nativo.
898
+ */
899
+ private nativeVersion;
900
+ /** Handshake com o nativo em voo; garante uma única chamada. */
901
+ private nativeHandshake;
771
902
  private pendingVerifies;
772
903
  constructor(opts: BridgeShellOptions);
773
904
  /**
@@ -823,7 +954,17 @@ declare class BridgeShell {
823
954
  private handleSubscribe;
824
955
  private handleUnsubscribe;
825
956
  private handleSend;
957
+ /**
958
+ * Obtém o `nativeVersion`, disparando o handshake com o nativo na primeira
959
+ * vez e cacheando o resultado.
960
+ *
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.
965
+ */
966
+ private resolveNativeVersion;
826
967
  private postToClient;
827
968
  }
828
969
 
829
- 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 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -197,16 +197,121 @@ interface UserDevicePayload {
197
197
  devicesTelemetry: Array<Record<string, unknown>>;
198
198
  devicesUserConfig: Array<Record<string, unknown>>;
199
199
  }
200
+ /**
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.
203
+ *
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.
209
+ *
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.
214
+ *
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`.
218
+ */
219
+ type LightSensorsRawChannels = {
220
+ light_sensor_f1: number;
221
+ light_sensor_f2: number;
222
+ light_sensor_f3: number;
223
+ light_sensor_f4: number;
224
+ light_sensor_f5: number;
225
+ light_sensor_f6: number;
226
+ light_sensor_f7: number;
227
+ light_sensor_f8: number;
228
+ /** Luz total (`visible` no wire do device). */
229
+ light_sensor_clear: number;
230
+ /** Infravermelho próximo. */
231
+ light_sensor_nir: number;
232
+ };
233
+ /**
234
+ * Leitura de tempo real de um ActLumus gen1 (`type: 'actlumus'`).
235
+ *
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.
240
+ */
241
+ type LightSensorsActlumus1Data = LightSensorsRawChannels & {
242
+ time: number;
243
+ wavelength390: number;
244
+ wavelength415: number;
245
+ wavelength445: number;
246
+ wavelength480: number;
247
+ wavelength515: number;
248
+ wavelength555: number;
249
+ wavelength590: number;
250
+ wavelength630: number;
251
+ wavelength680: number;
252
+ wavelength750: number;
253
+ melanopic: number;
254
+ photopic: number;
255
+ light_sensor_z: null;
256
+ light_sensor_y: null;
257
+ light_sensor_x: null;
258
+ light_sensor_fd: null;
259
+ };
260
+ /**
261
+ * Leitura de tempo real de um ActLumus Plus / gen2 (`type: 'actlumus2'`).
262
+ *
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`.
269
+ *
270
+ * `light_sensor_z/_y/_x` são as tristimulus CIE 1931 — Z no azul, Y no verde,
271
+ * X no vermelho.
272
+ */
273
+ type LightSensorsActlumus2Data = LightSensorsRawChannels & {
274
+ time: number;
275
+ wavelength395: number;
276
+ wavelength426: number;
277
+ wavelength457: number;
278
+ wavelength488: number;
279
+ wavelength520: number;
280
+ wavelength551: number;
281
+ wavelength582: number;
282
+ wavelength613: number;
283
+ wavelength644: number;
284
+ wavelength675: number;
285
+ wavelength707: number;
286
+ wavelength738: number;
287
+ wavelength769: number;
288
+ wavelength800: number;
289
+ melanopic: number;
290
+ photopic: number;
291
+ light_sensor_z: number;
292
+ light_sensor_y: number;
293
+ light_sensor_x: number;
294
+ /** Flicker detect. Não entra na matriz do espectro. */
295
+ light_sensor_fd: number;
296
+ };
200
297
  /**
201
298
  * Stream real-time de sensores de luz (FEST). Shape:
202
- * `{serialNo, type, data: <lightSensors.toMap()>}`. O subobjeto `data`
203
- * varia por device type — permissivo no MVP.
299
+ * `{serialNo, type, data: <lightSensors.toMap()>}`.
300
+ *
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`.
204
305
  */
205
- interface DeviceLightSensorsPayload {
306
+ type DeviceLightSensorsPayload = {
206
307
  serialNo: number;
207
- type: string;
208
- data: Record<string, unknown>;
209
- }
308
+ type: 'actlumus';
309
+ data: LightSensorsActlumus1Data;
310
+ } | {
311
+ serialNo: number;
312
+ type: 'actlumus2';
313
+ data: LightSensorsActlumus2Data;
314
+ };
210
315
  /** Ack de comando `syncTime`. */
211
316
  interface SyncTimeAckPayload {
212
317
  serialNo: number;
@@ -539,6 +644,16 @@ declare const Bridge: CondorBridge;
539
644
 
540
645
  interface BridgeClientHandshakeAck {
541
646
  shellVersion: string;
647
+ /**
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).
650
+ *
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`.
655
+ */
656
+ nativeVersion: string | null;
542
657
  accepted: boolean;
543
658
  }
544
659
  interface CondorBridgeClient {
@@ -665,6 +780,15 @@ declare const ShellToClientMessage: v.VariantSchema<"type", [v.ObjectSchema<{
665
780
  readonly type: v.LiteralSchema<"handshake-ack", undefined>;
666
781
  readonly ack: v.ObjectSchema<{
667
782
  readonly shellVersion: v.StringSchema<undefined>;
783
+ /**
784
+ * Versão do schema declarada pelo **nativo**, repassada pelo shell.
785
+ *
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.
790
+ */
791
+ readonly nativeVersion: v.NullableSchema<v.StringSchema<undefined>, undefined>;
668
792
  readonly accepted: v.BooleanSchema<undefined>;
669
793
  }, undefined>;
670
794
  }, undefined>, v.ObjectSchema<{
@@ -768,6 +892,13 @@ declare class BridgeShell {
768
892
  private readonly onConfirm?;
769
893
  private readonly subscriptions;
770
894
  private detached;
895
+ /**
896
+ * Versão do nativo, obtida uma vez e cacheada. `null` até o nativo
897
+ * responder — e permanentemente `null` num browser sem nativo.
898
+ */
899
+ private nativeVersion;
900
+ /** Handshake com o nativo em voo; garante uma única chamada. */
901
+ private nativeHandshake;
771
902
  private pendingVerifies;
772
903
  constructor(opts: BridgeShellOptions);
773
904
  /**
@@ -823,7 +954,17 @@ declare class BridgeShell {
823
954
  private handleSubscribe;
824
955
  private handleUnsubscribe;
825
956
  private handleSend;
957
+ /**
958
+ * Obtém o `nativeVersion`, disparando o handshake com o nativo na primeira
959
+ * vez e cacheando o resultado.
960
+ *
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.
965
+ */
966
+ private resolveNativeVersion;
826
967
  private postToClient;
827
968
  }
828
969
 
829
- 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 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 };
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 };
package/dist/index.js CHANGED
@@ -632,6 +632,8 @@ function onMessage(event) {
632
632
  pendingHandshake2 = null;
633
633
  resolve({
634
634
  shellVersion: msg.ack?.shellVersion ?? "unknown",
635
+ // `?? null` cobre o shell anterior à 0.17.0, que não mandava o campo.
636
+ nativeVersion: msg.ack?.nativeVersion ?? null,
635
637
  accepted: msg.ack?.accepted ?? false
636
638
  });
637
639
  }
@@ -790,6 +792,15 @@ var HandshakeAckMessage = v.object({
790
792
  type: v.literal("handshake-ack"),
791
793
  ack: v.object({
792
794
  shellVersion: v.string(),
795
+ /**
796
+ * Versão do schema declarada pelo **nativo**, repassada pelo shell.
797
+ *
798
+ * `null` quando o nativo ainda não respondeu (ou não existe, como no
799
+ * mock de browser). O cliente roda dentro de um iframe sandboxed e não
800
+ * fala com o nativo diretamente, então este é o **único** caminho pelo
801
+ * qual ele descobre de qual versão são as capacidades disponíveis.
802
+ */
803
+ nativeVersion: v.nullable(v.string()),
793
804
  accepted: v.boolean()
794
805
  })
795
806
  });
@@ -810,7 +821,8 @@ var ShellToClientMessage = v.variant("type", [
810
821
  ]);
811
822
 
812
823
  // src/bridge-shell.ts
813
- var SHELL_VERSION = "0.5.1";
824
+ var SHELL_VERSION = "0.6.0";
825
+ var NATIVE_HANDSHAKE_TIMEOUT_MS = 2e3;
814
826
  var VERIFY_TIMEOUT_MS = 2e3;
815
827
  var OVERRIDE_PREFIX = `<script>
816
828
  (function(){
@@ -848,6 +860,13 @@ var BridgeShell = class {
848
860
  constructor(opts) {
849
861
  this.subscriptions = /* @__PURE__ */ new Map();
850
862
  this.detached = false;
863
+ /**
864
+ * Versão do nativo, obtida uma vez e cacheada. `null` até o nativo
865
+ * responder — e permanentemente `null` num browser sem nativo.
866
+ */
867
+ this.nativeVersion = null;
868
+ /** Handshake com o nativo em voo; garante uma única chamada. */
869
+ this.nativeHandshake = null;
851
870
  // Map de `verify-sandbox` round-trips pendentes — key gerada por nonce.
852
871
  this.pendingVerifies = /* @__PURE__ */ new Map();
853
872
  this.onMessage = async (event) => {
@@ -872,9 +891,11 @@ var BridgeShell = class {
872
891
  await this.handleSend(msg.command, msg.args ?? {});
873
892
  break;
874
893
  case "handshake":
875
- this.postToClient({
876
- type: "handshake-ack",
877
- ack: { shellVersion: SHELL_VERSION, accepted: true }
894
+ void this.resolveNativeVersion().then((nativeVersion) => {
895
+ this.postToClient({
896
+ type: "handshake-ack",
897
+ ack: { shellVersion: SHELL_VERSION, nativeVersion, accepted: true }
898
+ });
878
899
  });
879
900
  break;
880
901
  case "jsError":
@@ -1053,6 +1074,29 @@ var BridgeShell = class {
1053
1074
  }
1054
1075
  Bridge.send(command, args);
1055
1076
  }
1077
+ /**
1078
+ * Obtém o `nativeVersion`, disparando o handshake com o nativo na primeira
1079
+ * vez e cacheando o resultado.
1080
+ *
1081
+ * ⚠️ `Bridge.handshake` **não tem timeout**: a Promise só resolve quando o
1082
+ * nativo emite `handshake-ack`. Num browser sem nativo ela nunca resolve,
1083
+ * então o `race` abaixo é o que garante que o cliente sempre receba
1084
+ * resposta — com `null` — em vez de ficar pendurado.
1085
+ */
1086
+ async resolveNativeVersion() {
1087
+ if (this.nativeVersion !== null) return this.nativeVersion;
1088
+ if (!this.nativeHandshake) {
1089
+ this.nativeHandshake = Bridge.handshake({ clientVersion: SHELL_VERSION }).then((ack) => {
1090
+ this.nativeVersion = ack.nativeVersion;
1091
+ }).catch(() => {
1092
+ });
1093
+ }
1094
+ await Promise.race([
1095
+ this.nativeHandshake,
1096
+ new Promise((resolve) => setTimeout(resolve, NATIVE_HANDSHAKE_TIMEOUT_MS))
1097
+ ]);
1098
+ return this.nativeVersion;
1099
+ }
1056
1100
  postToClient(msg) {
1057
1101
  if (this.detached) return;
1058
1102
  this.iframe.contentWindow?.postMessage(msg, "*");