@lingxia/bridge 0.9.0 → 0.11.1

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.
@@ -84,7 +84,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
84
84
  import { BRIDGE_ERROR } from "./types";
85
85
  import { toBridgeError, toNativeError } from "./invocation";
86
86
  import { installNativeComponentCoverageMonitor } from "./nativecomponents/coverage-monitor";
87
- import { BRIDGE_CONFIG, getCommunicationMethod, getPlatformOS, isAndroid, isHarmony, isIOS, isMacOS, isDesktop, } from "./runtime-env";
87
+ import { BRIDGE_CONFIG, getCommunicationMethod, getPlatformOS, isAndroid, isHarmony, isIOS, isMacOS, isWindows, isDesktop, isApple, isDevSession, isRunner, } from "./runtime-env";
88
88
  var NATIVE_HANDLER_NAME = "LingXia";
89
89
  var GLOBAL_RECEIVER_NAME = "__LingXiaRecvMessage";
90
90
  var DEFAULT_TIMEOUT_MS = 5000;
@@ -93,6 +93,7 @@ var HANDSHAKE_MAX_RETRIES = 3;
93
93
  var LOG_PREFIX = "[LX.Bridge]";
94
94
  var MESSAGE_PORT_TYPE = "messageport";
95
95
  var JS_INTERFACE_TYPE = "jsinterface";
96
+ var WEB_MESSAGE_TYPE = "webmessage";
96
97
  var OUTBOX_LIMIT = 256;
97
98
  var APPLE_DOWNSTREAM_URL = BRIDGE_CONFIG.appleDownstreamURL;
98
99
  var APPLE_RECONNECT_BASE_MS = 200;
@@ -158,11 +159,22 @@ function activateReceiver(receiver) {
158
159
  function isDebugEnabled(flag) {
159
160
  return debugFlags.all || debugFlags[flag];
160
161
  }
162
+ // `log` is the bridge's own protocol/lifecycle trace. Native log capture
163
+ // forwards whatever the page emits to `console`, so the bridge itself decides
164
+ // whether to surface this framework chatter: only in a `lingxia dev` session
165
+ // (or when a debug flag is set). Shipped apps stay quiet, leaving the captured
166
+ // stream to the page's own output plus bridge warnings/errors.
161
167
  function log() {
162
168
  var args = [];
163
169
  for (var _i = 0; _i < arguments.length; _i++) {
164
170
  args[_i] = arguments[_i];
165
171
  }
172
+ if (!isDevSession() &&
173
+ !debugFlags.all &&
174
+ !debugFlags.proto &&
175
+ !debugFlags.data) {
176
+ return;
177
+ }
166
178
  console.log.apply(console, __spreadArray([LOG_PREFIX], __read(args), false));
167
179
  }
168
180
  function warn() {
@@ -217,6 +229,17 @@ var appleDownstreamTask = null;
217
229
  var appleDownstreamAbortController = null;
218
230
  var appleReconnectTimer = null;
219
231
  var appleReconnectDelayMs = APPLE_RECONNECT_BASE_MS;
232
+ var appleDownstreamDisposed = false;
233
+ // Highest transport frame seq processed. Sent as `?from=` on reconnect so the
234
+ // host replays the gap; survives reconnects so a WebKit-replaced stream resumes
235
+ // without losing frames or tearing down the bridge session.
236
+ var appleLastFrameSeq = 0;
237
+ // Liveness tracking: the host heartbeats an idle connection, so a gap longer
238
+ // than a couple of heartbeats means the connection is silently dead and we
239
+ // reconnect proactively instead of waiting for a read error that may never come.
240
+ var APPLE_DOWNSTREAM_STALE_MS = 35000;
241
+ var appleLastFrameAt = 0;
242
+ var appleWatchdogTimer = null;
220
243
  var portInitState = {
221
244
  listenerInstalled: false,
222
245
  promise: null,
@@ -261,6 +284,23 @@ function clearAppleReconnectTimer() {
261
284
  appleReconnectTimer = null;
262
285
  }
263
286
  }
287
+ function disposeAppleDownstream(reason) {
288
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
289
+ return;
290
+ appleDownstreamDisposed = true;
291
+ clearAppleReconnectTimer();
292
+ if (appleWatchdogTimer !== null) {
293
+ clearInterval(appleWatchdogTimer);
294
+ appleWatchdogTimer = null;
295
+ }
296
+ if (typeof document !== "undefined") {
297
+ document.removeEventListener("visibilitychange", handleAppleVisibilityChange);
298
+ }
299
+ appleForcedReconnectPending = false;
300
+ appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.abort();
301
+ appleDownstreamConnected = false;
302
+ resetHandshakeState(reason, true);
303
+ }
264
304
  function closeActiveChannelsFromTransport(reason) {
265
305
  var e_2, _a;
266
306
  try {
@@ -334,7 +374,7 @@ function processAppleDownstreamBuffer(buffer) {
334
374
  if (!line)
335
375
  continue;
336
376
  try {
337
- handleIncomingMessage(JSON.parse(line));
377
+ handleAppleDownstreamFrame(JSON.parse(line));
338
378
  }
339
379
  catch (e) {
340
380
  warn("Apple downstream parse error:", e, line);
@@ -342,9 +382,42 @@ function processAppleDownstreamBuffer(buffer) {
342
382
  }
343
383
  return remaining;
344
384
  }
385
+ // Unwrap the transport envelope: {"lxff":seq,"m":<message>} carries a business
386
+ // message with its frame seq; {"lxreset":true} means the host cannot replay our
387
+ // resume point and we must re-handshake.
388
+ function handleAppleDownstreamFrame(frame) {
389
+ if (!frame || typeof frame !== "object")
390
+ return;
391
+ // Any frame — data, heartbeat, or reset — proves the connection is alive.
392
+ appleLastFrameAt = Date.now();
393
+ var record = frame;
394
+ if (record.lxshutdown === true) {
395
+ disposeAppleDownstream("Apple downstream closed");
396
+ return;
397
+ }
398
+ if (record.lxhb !== undefined)
399
+ return; // heartbeat: liveness only, nothing to dispatch
400
+ if (record.lxreset === true) {
401
+ appleLastFrameSeq = 0;
402
+ resetHandshakeState("Apple downstream reset", true);
403
+ startHandshake();
404
+ return;
405
+ }
406
+ if (typeof record.lxff === "number") {
407
+ // Replayed frames after a reconnect can repeat the last-seen seq; ignore
408
+ // anything we have already processed so the session is not double-fed.
409
+ if (record.lxff <= appleLastFrameSeq)
410
+ return;
411
+ appleLastFrameSeq = record.lxff;
412
+ handleIncomingMessage(record.m);
413
+ return;
414
+ }
415
+ // Legacy/un-enveloped line (e.g. a bare keepalive); pass through.
416
+ handleIncomingMessage(frame);
417
+ }
345
418
  function runAppleDownstream() {
346
419
  return __awaiter(this, void 0, void 0, function () {
347
- var controller, response, reader, decoder, buffered, _a, value, done, tail, e_5;
420
+ var controller, separator, url, response, reader, decoder, buffered, _a, value, done, tail, e_5;
348
421
  return __generator(this, function (_b) {
349
422
  switch (_b.label) {
350
423
  case 0:
@@ -353,7 +426,9 @@ function runAppleDownstream() {
353
426
  }
354
427
  controller = new AbortController();
355
428
  appleDownstreamAbortController = controller;
356
- return [4 /*yield*/, fetch(APPLE_DOWNSTREAM_URL, {
429
+ separator = APPLE_DOWNSTREAM_URL.includes("?") ? "&" : "?";
430
+ url = "".concat(APPLE_DOWNSTREAM_URL).concat(separator, "from=").concat(appleLastFrameSeq);
431
+ return [4 /*yield*/, fetch(url, {
357
432
  method: "GET",
358
433
  cache: "no-store",
359
434
  headers: { Accept: "application/x-ndjson" },
@@ -364,11 +439,16 @@ function runAppleDownstream() {
364
439
  if (!response.ok) {
365
440
  throw new Error("Apple downstream HTTP ".concat(response.status));
366
441
  }
442
+ if (response.headers.get("X-LingXia-Bridge-Shutdown") === "1") {
443
+ disposeAppleDownstream("Apple downstream closed");
444
+ return [2 /*return*/];
445
+ }
367
446
  if (!response.body) {
368
447
  throw new Error("Apple downstream response body unavailable");
369
448
  }
370
449
  appleDownstreamConnected = true;
371
450
  appleReconnectDelayMs = APPLE_RECONNECT_BASE_MS;
451
+ appleLastFrameAt = Date.now();
372
452
  if (isDebugEnabled("proto"))
373
453
  log("Apple downstream connected");
374
454
  startHandshake();
@@ -394,7 +474,7 @@ function runAppleDownstream() {
394
474
  tail = buffered.trim();
395
475
  if (tail) {
396
476
  try {
397
- handleIncomingMessage(JSON.parse(tail));
477
+ handleAppleDownstreamFrame(JSON.parse(tail));
398
478
  }
399
479
  catch (e) {
400
480
  warn("Apple downstream trailing parse error:", e, tail);
@@ -417,13 +497,18 @@ function runAppleDownstream() {
417
497
  });
418
498
  });
419
499
  }
420
- function scheduleAppleDownstreamReconnect(reason) {
421
- if (!useAppleDownstreamTransport())
500
+ function scheduleAppleDownstreamReconnect(reason, immediate) {
501
+ if (immediate === void 0) { immediate = false; }
502
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
422
503
  return;
423
504
  clearAppleReconnectTimer();
424
- var delay = appleReconnectDelayMs;
425
- appleReconnectDelayMs = Math.min(appleReconnectDelayMs * 2, APPLE_RECONNECT_MAX_MS);
426
- var message = "Apple downstream disconnected, retrying in ".concat(delay, "ms: ").concat(reason);
505
+ var delay = immediate ? 0 : appleReconnectDelayMs;
506
+ if (!immediate) {
507
+ appleReconnectDelayMs = Math.min(appleReconnectDelayMs * 2, APPLE_RECONNECT_MAX_MS);
508
+ }
509
+ var message = immediate
510
+ ? "Apple downstream completed, reconnecting immediately: ".concat(reason)
511
+ : "Apple downstream disconnected, retrying in ".concat(delay, "ms: ").concat(reason);
427
512
  if (delay <= APPLE_RECONNECT_BASE_MS) {
428
513
  log(message);
429
514
  }
@@ -436,12 +521,16 @@ function scheduleAppleDownstreamReconnect(reason) {
436
521
  }, delay);
437
522
  }
438
523
  function ensureAppleDownstream() {
439
- if (!useAppleDownstreamTransport())
524
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
440
525
  return;
441
526
  if (appleDownstreamTask)
442
527
  return;
443
528
  clearAppleReconnectTimer();
529
+ var completedCleanly = false;
444
530
  appleDownstreamTask = runAppleDownstream()
531
+ .then(function () {
532
+ completedCleanly = true;
533
+ })
445
534
  .catch(function (e) {
446
535
  if (appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.signal.aborted)
447
536
  return;
@@ -452,13 +541,72 @@ function ensureAppleDownstream() {
452
541
  var aborted = (_a = appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.signal.aborted) !== null && _a !== void 0 ? _a : false;
453
542
  appleDownstreamTask = null;
454
543
  appleDownstreamAbortController = null;
455
- var wasConnected = appleDownstreamConnected;
456
544
  appleDownstreamConnected = false;
457
- resetHandshakeState("Apple downstream closed", wasConnected);
458
- if (!aborted)
459
- scheduleAppleDownstreamReconnect("stream closed");
545
+ // A dropped transport is not a dead session: reconnect resumes from the
546
+ // last seq and the host replays the gap, so the handshake and in-flight
547
+ // streams stay intact. Only a host-sent reset (handled on the frame path)
548
+ // or a real abort tears things down.
549
+ if (!aborted && !appleDownstreamDisposed) {
550
+ // Native intentionally completes bootstrap/replay responses to flush
551
+ // WebKit. Reconnect without the failure backoff so a live stream can
552
+ // catch up and settle onto a long-lived downstream.
553
+ scheduleAppleDownstreamReconnect("stream closed", completedCleanly);
554
+ }
460
555
  });
461
556
  }
557
+ // Drop the current downstream and reconnect, exactly as WebKit does when it
558
+ // replaces the streaming fetch. The reconnect carries the real `from=<lastSeq>`,
559
+ // so nothing is lost. Driven by the staleness watchdog and the foreground
560
+ // handler, and exposed as a dev hook for repro harnesses. A no-op off Apple.
561
+ var appleForcedReconnectPending = false;
562
+ function forceDownstreamReconnect() {
563
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
564
+ return;
565
+ // Coalesce a burst of triggers (rapid foreground toggles, repeated calls)
566
+ // into a single reconnect so they cannot stack concurrent fetches.
567
+ if (appleForcedReconnectPending)
568
+ return;
569
+ appleForcedReconnectPending = true;
570
+ var task = appleDownstreamTask;
571
+ appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.abort();
572
+ var reconnect = function () {
573
+ appleForcedReconnectPending = false;
574
+ ensureAppleDownstream();
575
+ };
576
+ if (task)
577
+ task.finally(reconnect);
578
+ else
579
+ reconnect();
580
+ }
581
+ // A silently dead connection (half-open socket) never surfaces a read error, so
582
+ // poll for heartbeat staleness and reconnect when the host has gone quiet.
583
+ function startAppleWatchdog() {
584
+ if (!useAppleDownstreamTransport() ||
585
+ appleDownstreamDisposed ||
586
+ appleWatchdogTimer !== null)
587
+ return;
588
+ appleWatchdogTimer = setInterval(function () {
589
+ // No `connected` guard: a reconnect that hung before delivering a frame
590
+ // also goes stale, and forcing a reconnect aborts and retries it.
591
+ if (appleLastFrameAt === 0)
592
+ return;
593
+ if (Date.now() - appleLastFrameAt > APPLE_DOWNSTREAM_STALE_MS) {
594
+ warn("Apple downstream stale (no heartbeat), forcing reconnect");
595
+ forceDownstreamReconnect();
596
+ }
597
+ }, 5000);
598
+ }
599
+ // WebKit suspends/throttles a backgrounded webview and may tear the stream
600
+ // down; reconnect the moment it returns so the UI is never left stale.
601
+ function handleAppleVisibilityChange() {
602
+ if (document.visibilityState === "visible")
603
+ forceDownstreamReconnect();
604
+ }
605
+ function installAppleForegroundReconnect() {
606
+ if (!useAppleDownstreamTransport() || typeof document === "undefined")
607
+ return;
608
+ document.addEventListener("visibilitychange", handleAppleVisibilityChange);
609
+ }
462
610
  function getMessagePort() {
463
611
  var _a, _b;
464
612
  if (messagePort)
@@ -506,7 +654,8 @@ function postToNative(message) {
506
654
  messagePort.postMessage(messageString);
507
655
  return;
508
656
  }
509
- if (communicationMethod === JS_INTERFACE_TYPE &&
657
+ if ((communicationMethod === JS_INTERFACE_TYPE ||
658
+ communicationMethod === WEB_MESSAGE_TYPE) &&
510
659
  ((_c = window.LingXiaProxy) === null || _c === void 0 ? void 0 : _c.postMessage)) {
511
660
  window.LingXiaProxy.postMessage(messageString);
512
661
  return;
@@ -540,6 +689,9 @@ function createStreamHandle(id, cancelFn) {
540
689
  var _a;
541
690
  var listeners = createListenerBuckets();
542
691
  var done = false;
692
+ // Only buffer for the async iterator; long-lived streams consumed via
693
+ // .on("data") would otherwise accumulate payloads forever.
694
+ var iteratorRequested = false;
543
695
  var resolveResult = function () { };
544
696
  var rejectResult = function () { };
545
697
  var pendingData = [];
@@ -562,6 +714,7 @@ function createStreamHandle(id, cancelFn) {
562
714
  }
563
715
  },
564
716
  _a[Symbol.asyncIterator] = function () {
717
+ iteratorRequested = true;
565
718
  return {
566
719
  next: function () {
567
720
  if (pendingData.length > 0) {
@@ -591,7 +744,7 @@ function createStreamHandle(id, cancelFn) {
591
744
  if (pendingReads.length > 0) {
592
745
  pendingReads.shift().resolve({ done: false, value: payload });
593
746
  }
594
- else {
747
+ else if (iteratorRequested) {
595
748
  pendingData.push(payload);
596
749
  }
597
750
  try {
@@ -678,6 +831,9 @@ function createChannel(id, sendFn, closeFn) {
678
831
  var open = false;
679
832
  var outboundSeq = 0;
680
833
  var closed = false;
834
+ // Only buffer for the async iterator or pre-first-listener replay;
835
+ // otherwise a listener-driven channel would accumulate payloads forever.
836
+ var iteratorRequested = false;
681
837
  var pendingData = [];
682
838
  var pendingReads = [];
683
839
  var channel = (_a = {
@@ -693,8 +849,35 @@ function createChannel(id, sendFn, closeFn) {
693
849
  sendFn(payload, outboundSeq++);
694
850
  },
695
851
  on: function (event, listener) {
696
- if (event === "data")
852
+ var e_9, _a;
853
+ if (event === "data") {
697
854
  listeners.data.add(listener);
855
+ // Flush data buffered before this listener attached. The host can send
856
+ // ch.data immediately after the open ack, before the consumer (e.g. a
857
+ // wrapped channel's onMessage) attaches its listener; without this those
858
+ // early events would sit in pendingData and never reach the listener.
859
+ if (pendingData.length > 0) {
860
+ var buffered = pendingData.splice(0);
861
+ try {
862
+ for (var buffered_1 = __values(buffered), buffered_1_1 = buffered_1.next(); !buffered_1_1.done; buffered_1_1 = buffered_1.next()) {
863
+ var payload = buffered_1_1.value;
864
+ try {
865
+ listener(payload);
866
+ }
867
+ catch (e) {
868
+ warn("Channel listener failed:", e);
869
+ }
870
+ }
871
+ }
872
+ catch (e_9_1) { e_9 = { error: e_9_1 }; }
873
+ finally {
874
+ try {
875
+ if (buffered_1_1 && !buffered_1_1.done && (_a = buffered_1.return)) _a.call(buffered_1);
876
+ }
877
+ finally { if (e_9) throw e_9.error; }
878
+ }
879
+ }
880
+ }
698
881
  if (event === "close")
699
882
  listeners.close.add(listener);
700
883
  if (event === "error")
@@ -703,6 +886,7 @@ function createChannel(id, sendFn, closeFn) {
703
886
  }
704
887
  },
705
888
  _a[Symbol.asyncIterator] = function () {
889
+ iteratorRequested = true;
706
890
  return {
707
891
  next: function () {
708
892
  if (pendingData.length > 0) {
@@ -729,13 +913,13 @@ function createChannel(id, sendFn, closeFn) {
729
913
  channel._emitClose(code, reason);
730
914
  },
731
915
  _a._emitData = function (payload) {
732
- var e_9, _a;
916
+ var e_10, _a;
733
917
  if (!open)
734
918
  return;
735
919
  if (pendingReads.length > 0) {
736
920
  pendingReads.shift().resolve({ done: false, value: payload });
737
921
  }
738
- else {
922
+ else if (iteratorRequested || listeners.data.size === 0) {
739
923
  pendingData.push(payload);
740
924
  }
741
925
  try {
@@ -749,16 +933,16 @@ function createChannel(id, sendFn, closeFn) {
749
933
  }
750
934
  }
751
935
  }
752
- catch (e_9_1) { e_9 = { error: e_9_1 }; }
936
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
753
937
  finally {
754
938
  try {
755
939
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
756
940
  }
757
- finally { if (e_9) throw e_9.error; }
941
+ finally { if (e_10) throw e_10.error; }
758
942
  }
759
943
  },
760
944
  _a._emitClose = function (code, reason) {
761
- var e_10, _a;
945
+ var e_11, _a;
762
946
  closed = true;
763
947
  while (pendingReads.length > 0) {
764
948
  pendingReads.shift().resolve({ done: true, value: undefined });
@@ -774,16 +958,16 @@ function createChannel(id, sendFn, closeFn) {
774
958
  }
775
959
  }
776
960
  }
777
- catch (e_10_1) { e_10 = { error: e_10_1 }; }
961
+ catch (e_11_1) { e_11 = { error: e_11_1 }; }
778
962
  finally {
779
963
  try {
780
964
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
781
965
  }
782
- finally { if (e_10) throw e_10.error; }
966
+ finally { if (e_11) throw e_11.error; }
783
967
  }
784
968
  },
785
969
  _a._reject = function (err) {
786
- var e_11, _a;
970
+ var e_12, _a;
787
971
  closed = true;
788
972
  while (pendingReads.length > 0) {
789
973
  pendingReads.shift().reject(err);
@@ -799,12 +983,12 @@ function createChannel(id, sendFn, closeFn) {
799
983
  }
800
984
  }
801
985
  }
802
- catch (e_11_1) { e_11 = { error: e_11_1 }; }
986
+ catch (e_12_1) { e_12 = { error: e_12_1 }; }
803
987
  finally {
804
988
  try {
805
989
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
806
990
  }
807
- finally { if (e_11) throw e_11.error; }
991
+ finally { if (e_12) throw e_12.error; }
808
992
  }
809
993
  },
810
994
  _a._markOpen = function () {
@@ -851,10 +1035,19 @@ function isTransportReady() {
851
1035
  if (useAppleDownstreamTransport())
852
1036
  return appleDownstreamConnected;
853
1037
  return (communicationMethod === "webkit" ||
854
- communicationMethod === JS_INTERFACE_TYPE);
1038
+ communicationMethod === JS_INTERFACE_TYPE ||
1039
+ communicationMethod === WEB_MESSAGE_TYPE);
855
1040
  }
856
1041
  function canSendAppMessages() {
857
- return isTransportReady() && handshakeDone;
1042
+ if (!handshakeDone)
1043
+ return false;
1044
+ // Apple upstream posts through WKScriptMessageHandler independently of the
1045
+ // downstream fetch. During a replay reconnect, native retains responses by
1046
+ // sequence, so treating that short gap as an unsendable session can strand
1047
+ // requests in the outbox with no second `ready` event to flush them.
1048
+ if (useAppleDownstreamTransport())
1049
+ return true;
1050
+ return isTransportReady();
858
1051
  }
859
1052
  function rejectPendingRequest(reqId, err) {
860
1053
  var _a;
@@ -1000,7 +1193,7 @@ function startHandshake() {
1000
1193
  helloSent = true;
1001
1194
  postToNative(hello);
1002
1195
  handshakeTimer = setTimeout(function () {
1003
- var e_12, _a;
1196
+ var e_13, _a;
1004
1197
  if (handshakeDone)
1005
1198
  return;
1006
1199
  handshakeRetryCount++;
@@ -1032,12 +1225,12 @@ function startHandshake() {
1032
1225
  });
1033
1226
  }
1034
1227
  }
1035
- catch (e_12_1) { e_12 = { error: e_12_1 }; }
1228
+ catch (e_13_1) { e_13 = { error: e_13_1 }; }
1036
1229
  finally {
1037
1230
  try {
1038
1231
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1039
1232
  }
1040
- finally { if (e_12) throw e_12.error; }
1233
+ finally { if (e_13) throw e_13.error; }
1041
1234
  }
1042
1235
  }
1043
1236
  }, HANDSHAKE_TIMEOUT_MS);
@@ -1088,7 +1281,7 @@ function getContainerAndKey(root, pointer, autoCreate) {
1088
1281
  return { container: current, key: segments[segments.length - 1] };
1089
1282
  }
1090
1283
  function applyJsonPatch(target, ops) {
1091
- var e_13, _a, e_14, _b;
1284
+ var e_14, _a, e_15, _b;
1092
1285
  try {
1093
1286
  for (var ops_1 = __values(ops), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) {
1094
1287
  var op = ops_1_1.value;
@@ -1096,17 +1289,17 @@ function applyJsonPatch(target, ops) {
1096
1289
  var _c = getContainerAndKey(target, op.path, autoCreate), container = _c.container, key = _c.key;
1097
1290
  if (key === "$root") {
1098
1291
  try {
1099
- for (var _d = (e_14 = void 0, __values(Object.keys(target))), _e = _d.next(); !_e.done; _e = _d.next()) {
1292
+ for (var _d = (e_15 = void 0, __values(Object.keys(target))), _e = _d.next(); !_e.done; _e = _d.next()) {
1100
1293
  var k = _e.value;
1101
1294
  delete target[k];
1102
1295
  }
1103
1296
  }
1104
- catch (e_14_1) { e_14 = { error: e_14_1 }; }
1297
+ catch (e_15_1) { e_15 = { error: e_15_1 }; }
1105
1298
  finally {
1106
1299
  try {
1107
1300
  if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
1108
1301
  }
1109
- finally { if (e_14) throw e_14.error; }
1302
+ finally { if (e_15) throw e_15.error; }
1110
1303
  }
1111
1304
  if (op.op !== "remove") {
1112
1305
  var v = op.value;
@@ -1136,12 +1329,12 @@ function applyJsonPatch(target, ops) {
1136
1329
  obj[key] = op.value;
1137
1330
  }
1138
1331
  }
1139
- catch (e_13_1) { e_13 = { error: e_13_1 }; }
1332
+ catch (e_14_1) { e_14 = { error: e_14_1 }; }
1140
1333
  finally {
1141
1334
  try {
1142
1335
  if (ops_1_1 && !ops_1_1.done && (_a = ops_1.return)) _a.call(ops_1);
1143
1336
  }
1144
- finally { if (e_13) throw e_13.error; }
1337
+ finally { if (e_14) throw e_14.error; }
1145
1338
  }
1146
1339
  }
1147
1340
  function notifyStateSubscribers(initial) {
@@ -1199,8 +1392,8 @@ function applySnapshotFromResult(result) {
1199
1392
  return true;
1200
1393
  }
1201
1394
  function handleIncomingMessage(msg) {
1202
- var e_15, _a;
1203
- var _b, _c;
1395
+ var e_16, _a;
1396
+ var _b, _c, _d, _e;
1204
1397
  // Handle native component events (from Android NativeBridge.sendEventToView)
1205
1398
  if (msg && typeof msg === "object") {
1206
1399
  var obj = msg;
@@ -1241,17 +1434,17 @@ function handleIncomingMessage(msg) {
1241
1434
  handshakeRetryCount = 0;
1242
1435
  if (message.hostMethods) {
1243
1436
  try {
1244
- for (var _d = __values(Object.entries(message.hostMethods)), _e = _d.next(); !_e.done; _e = _d.next()) {
1245
- var _f = __read(_e.value, 2), k = _f[0], v = _f[1];
1437
+ for (var _f = __values(Object.entries(message.hostMethods)), _g = _f.next(); !_g.done; _g = _f.next()) {
1438
+ var _h = __read(_g.value, 2), k = _h[0], v = _h[1];
1246
1439
  hostMethodKinds[k] = v;
1247
1440
  }
1248
1441
  }
1249
- catch (e_15_1) { e_15 = { error: e_15_1 }; }
1442
+ catch (e_16_1) { e_16 = { error: e_16_1 }; }
1250
1443
  finally {
1251
1444
  try {
1252
- if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
1445
+ if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
1253
1446
  }
1254
- finally { if (e_15) throw e_15.error; }
1447
+ finally { if (e_16) throw e_16.error; }
1255
1448
  }
1256
1449
  }
1257
1450
  if (isDebugEnabled("proto"))
@@ -1291,7 +1484,15 @@ function handleIncomingMessage(msg) {
1291
1484
  if (pendingReq.has(message.id)) {
1292
1485
  var req = pendingReq.get(message.id);
1293
1486
  if ((req === null || req === void 0 ? void 0 : req.mode) === "stream" && req.stream) {
1294
- armRequestTimer(message.id);
1487
+ // The first frame proves the handler is live. Long-lived streams
1488
+ // (bookmarks.watch, proxy.watch) are idle between pushes, so the
1489
+ // request timeout must not treat silence as death — disarm it for
1490
+ // good instead of re-arming per frame.
1491
+ if (req.timerId !== null) {
1492
+ clearTimeout(req.timerId);
1493
+ req.timerId = null;
1494
+ }
1495
+ req.timeoutMs = 0;
1295
1496
  req.stream._emitData(message.payload);
1296
1497
  }
1297
1498
  else {
@@ -1369,8 +1570,10 @@ function handleIncomingMessage(msg) {
1369
1570
  case "ch.close": {
1370
1571
  var pendingChannel = pendingChannels.get(message.id);
1371
1572
  if (pendingChannel) {
1372
- pendingChannels.delete(message.id);
1373
- pendingChannel.channel._emitClose(message.code, message.reason);
1573
+ rejectPendingChannel(message.id, {
1574
+ code: (_d = message.code) !== null && _d !== void 0 ? _d : BRIDGE_ERROR.STREAM_CLOSED,
1575
+ message: (_e = message.reason) !== null && _e !== void 0 ? _e : "Channel closed before opening",
1576
+ });
1374
1577
  return;
1375
1578
  }
1376
1579
  var channel_3 = activeChannels.get(message.id);
@@ -1720,7 +1923,10 @@ export var LingXiaBridge = {
1720
1923
  isIOS: isIOS,
1721
1924
  isAndroid: isAndroid,
1722
1925
  isMacOS: isMacOS,
1926
+ isWindows: isWindows,
1723
1927
  isDesktop: isDesktop,
1928
+ isApple: isApple,
1929
+ isRunner: isRunner,
1724
1930
  getOS: getPlatformOS,
1725
1931
  },
1726
1932
  dom: {
@@ -1778,23 +1984,23 @@ function wrapNativeStream(handle) {
1778
1984
  var eventListeners = new Set();
1779
1985
  var errorListeners = new Set();
1780
1986
  handle.on("data", function (event) {
1781
- var e_16, _a;
1987
+ var e_17, _a;
1782
1988
  try {
1783
1989
  for (var eventListeners_1 = __values(eventListeners), eventListeners_1_1 = eventListeners_1.next(); !eventListeners_1_1.done; eventListeners_1_1 = eventListeners_1.next()) {
1784
1990
  var listener = eventListeners_1_1.value;
1785
1991
  listener(event);
1786
1992
  }
1787
1993
  }
1788
- catch (e_16_1) { e_16 = { error: e_16_1 }; }
1994
+ catch (e_17_1) { e_17 = { error: e_17_1 }; }
1789
1995
  finally {
1790
1996
  try {
1791
1997
  if (eventListeners_1_1 && !eventListeners_1_1.done && (_a = eventListeners_1.return)) _a.call(eventListeners_1);
1792
1998
  }
1793
- finally { if (e_16) throw e_16.error; }
1999
+ finally { if (e_17) throw e_17.error; }
1794
2000
  }
1795
2001
  });
1796
2002
  handle.on("error", function (error) {
1797
- var e_17, _a;
2003
+ var e_18, _a;
1798
2004
  var nativeError = toNativeError(error);
1799
2005
  try {
1800
2006
  for (var errorListeners_1 = __values(errorListeners), errorListeners_1_1 = errorListeners_1.next(); !errorListeners_1_1.done; errorListeners_1_1 = errorListeners_1.next()) {
@@ -1802,12 +2008,12 @@ function wrapNativeStream(handle) {
1802
2008
  listener(nativeError);
1803
2009
  }
1804
2010
  }
1805
- catch (e_17_1) { e_17 = { error: e_17_1 }; }
2011
+ catch (e_18_1) { e_18 = { error: e_18_1 }; }
1806
2012
  finally {
1807
2013
  try {
1808
2014
  if (errorListeners_1_1 && !errorListeners_1_1.done && (_a = errorListeners_1.return)) _a.call(errorListeners_1);
1809
2015
  }
1810
- finally { if (e_17) throw e_17.error; }
2016
+ finally { if (e_18) throw e_18.error; }
1811
2017
  }
1812
2018
  });
1813
2019
  return {
@@ -1833,23 +2039,23 @@ function wrapNativeChannel(channel) {
1833
2039
  var messageListeners = new Set();
1834
2040
  var closeListeners = new Set();
1835
2041
  channel.on("data", function (message) {
1836
- var e_18, _a;
2042
+ var e_19, _a;
1837
2043
  try {
1838
2044
  for (var messageListeners_1 = __values(messageListeners), messageListeners_1_1 = messageListeners_1.next(); !messageListeners_1_1.done; messageListeners_1_1 = messageListeners_1.next()) {
1839
2045
  var listener = messageListeners_1_1.value;
1840
2046
  listener(message);
1841
2047
  }
1842
2048
  }
1843
- catch (e_18_1) { e_18 = { error: e_18_1 }; }
2049
+ catch (e_19_1) { e_19 = { error: e_19_1 }; }
1844
2050
  finally {
1845
2051
  try {
1846
2052
  if (messageListeners_1_1 && !messageListeners_1_1.done && (_a = messageListeners_1.return)) _a.call(messageListeners_1);
1847
2053
  }
1848
- finally { if (e_18) throw e_18.error; }
2054
+ finally { if (e_19) throw e_19.error; }
1849
2055
  }
1850
2056
  });
1851
2057
  channel.on("close", function (code, reason) {
1852
- var e_19, _a;
2058
+ var e_20, _a;
1853
2059
  var event = { code: code, reason: reason };
1854
2060
  try {
1855
2061
  for (var closeListeners_1 = __values(closeListeners), closeListeners_1_1 = closeListeners_1.next(); !closeListeners_1_1.done; closeListeners_1_1 = closeListeners_1.next()) {
@@ -1857,12 +2063,12 @@ function wrapNativeChannel(channel) {
1857
2063
  listener(event);
1858
2064
  }
1859
2065
  }
1860
- catch (e_19_1) { e_19 = { error: e_19_1 }; }
2066
+ catch (e_20_1) { e_20 = { error: e_20_1 }; }
1861
2067
  finally {
1862
2068
  try {
1863
2069
  if (closeListeners_1_1 && !closeListeners_1_1.done && (_a = closeListeners_1.return)) _a.call(closeListeners_1);
1864
2070
  }
1865
- finally { if (e_19) throw e_19.error; }
2071
+ finally { if (e_20) throw e_20.error; }
1866
2072
  }
1867
2073
  });
1868
2074
  return {
@@ -1909,19 +2115,25 @@ export function initBridge() {
1909
2115
  activateReceiver(LingXiaBridge._receiveEvaluateMessage);
1910
2116
  if (useAppleDownstreamTransport()) {
1911
2117
  ensureAppleDownstream();
2118
+ startAppleWatchdog();
2119
+ installAppleForegroundReconnect();
1912
2120
  }
1913
2121
  else if (communicationMethod === MESSAGE_PORT_TYPE) {
1914
2122
  installMessagePortInitListener();
1915
2123
  getMessagePort().catch(function (e) { return warn("Port init failed:", e); });
1916
2124
  }
1917
2125
  else if (communicationMethod === "webkit" ||
1918
- communicationMethod === JS_INTERFACE_TYPE) {
2126
+ communicationMethod === JS_INTERFACE_TYPE ||
2127
+ communicationMethod === WEB_MESSAGE_TYPE) {
1919
2128
  startHandshake();
1920
2129
  }
1921
2130
  else {
1922
2131
  warn("Unknown method");
1923
2132
  }
1924
2133
  window.LingXiaBridge = LingXiaBridge;
2134
+ // Dev hook so repro/test pages can simulate a WebKit stream replacement.
2135
+ window.__lxForceDownstreamReconnect =
2136
+ forceDownstreamReconnect;
1925
2137
  installNativeComponentCoverageMonitor({
1926
2138
  os: getPlatformOS(),
1927
2139
  send: sendNativeComponentMessage,