@lingxia/bridge 0.10.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, isWindows, 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;
@@ -159,11 +159,22 @@ function activateReceiver(receiver) {
159
159
  function isDebugEnabled(flag) {
160
160
  return debugFlags.all || debugFlags[flag];
161
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.
162
167
  function log() {
163
168
  var args = [];
164
169
  for (var _i = 0; _i < arguments.length; _i++) {
165
170
  args[_i] = arguments[_i];
166
171
  }
172
+ if (!isDevSession() &&
173
+ !debugFlags.all &&
174
+ !debugFlags.proto &&
175
+ !debugFlags.data) {
176
+ return;
177
+ }
167
178
  console.log.apply(console, __spreadArray([LOG_PREFIX], __read(args), false));
168
179
  }
169
180
  function warn() {
@@ -218,6 +229,17 @@ var appleDownstreamTask = null;
218
229
  var appleDownstreamAbortController = null;
219
230
  var appleReconnectTimer = null;
220
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;
221
243
  var portInitState = {
222
244
  listenerInstalled: false,
223
245
  promise: null,
@@ -262,6 +284,23 @@ function clearAppleReconnectTimer() {
262
284
  appleReconnectTimer = null;
263
285
  }
264
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
+ }
265
304
  function closeActiveChannelsFromTransport(reason) {
266
305
  var e_2, _a;
267
306
  try {
@@ -335,7 +374,7 @@ function processAppleDownstreamBuffer(buffer) {
335
374
  if (!line)
336
375
  continue;
337
376
  try {
338
- handleIncomingMessage(JSON.parse(line));
377
+ handleAppleDownstreamFrame(JSON.parse(line));
339
378
  }
340
379
  catch (e) {
341
380
  warn("Apple downstream parse error:", e, line);
@@ -343,9 +382,42 @@ function processAppleDownstreamBuffer(buffer) {
343
382
  }
344
383
  return remaining;
345
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
+ }
346
418
  function runAppleDownstream() {
347
419
  return __awaiter(this, void 0, void 0, function () {
348
- 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;
349
421
  return __generator(this, function (_b) {
350
422
  switch (_b.label) {
351
423
  case 0:
@@ -354,7 +426,9 @@ function runAppleDownstream() {
354
426
  }
355
427
  controller = new AbortController();
356
428
  appleDownstreamAbortController = controller;
357
- 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, {
358
432
  method: "GET",
359
433
  cache: "no-store",
360
434
  headers: { Accept: "application/x-ndjson" },
@@ -365,11 +439,16 @@ function runAppleDownstream() {
365
439
  if (!response.ok) {
366
440
  throw new Error("Apple downstream HTTP ".concat(response.status));
367
441
  }
442
+ if (response.headers.get("X-LingXia-Bridge-Shutdown") === "1") {
443
+ disposeAppleDownstream("Apple downstream closed");
444
+ return [2 /*return*/];
445
+ }
368
446
  if (!response.body) {
369
447
  throw new Error("Apple downstream response body unavailable");
370
448
  }
371
449
  appleDownstreamConnected = true;
372
450
  appleReconnectDelayMs = APPLE_RECONNECT_BASE_MS;
451
+ appleLastFrameAt = Date.now();
373
452
  if (isDebugEnabled("proto"))
374
453
  log("Apple downstream connected");
375
454
  startHandshake();
@@ -395,7 +474,7 @@ function runAppleDownstream() {
395
474
  tail = buffered.trim();
396
475
  if (tail) {
397
476
  try {
398
- handleIncomingMessage(JSON.parse(tail));
477
+ handleAppleDownstreamFrame(JSON.parse(tail));
399
478
  }
400
479
  catch (e) {
401
480
  warn("Apple downstream trailing parse error:", e, tail);
@@ -418,13 +497,18 @@ function runAppleDownstream() {
418
497
  });
419
498
  });
420
499
  }
421
- function scheduleAppleDownstreamReconnect(reason) {
422
- if (!useAppleDownstreamTransport())
500
+ function scheduleAppleDownstreamReconnect(reason, immediate) {
501
+ if (immediate === void 0) { immediate = false; }
502
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
423
503
  return;
424
504
  clearAppleReconnectTimer();
425
- var delay = appleReconnectDelayMs;
426
- appleReconnectDelayMs = Math.min(appleReconnectDelayMs * 2, APPLE_RECONNECT_MAX_MS);
427
- 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);
428
512
  if (delay <= APPLE_RECONNECT_BASE_MS) {
429
513
  log(message);
430
514
  }
@@ -437,12 +521,16 @@ function scheduleAppleDownstreamReconnect(reason) {
437
521
  }, delay);
438
522
  }
439
523
  function ensureAppleDownstream() {
440
- if (!useAppleDownstreamTransport())
524
+ if (!useAppleDownstreamTransport() || appleDownstreamDisposed)
441
525
  return;
442
526
  if (appleDownstreamTask)
443
527
  return;
444
528
  clearAppleReconnectTimer();
529
+ var completedCleanly = false;
445
530
  appleDownstreamTask = runAppleDownstream()
531
+ .then(function () {
532
+ completedCleanly = true;
533
+ })
446
534
  .catch(function (e) {
447
535
  if (appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.signal.aborted)
448
536
  return;
@@ -453,13 +541,72 @@ function ensureAppleDownstream() {
453
541
  var aborted = (_a = appleDownstreamAbortController === null || appleDownstreamAbortController === void 0 ? void 0 : appleDownstreamAbortController.signal.aborted) !== null && _a !== void 0 ? _a : false;
454
542
  appleDownstreamTask = null;
455
543
  appleDownstreamAbortController = null;
456
- var wasConnected = appleDownstreamConnected;
457
544
  appleDownstreamConnected = false;
458
- resetHandshakeState("Apple downstream closed", wasConnected);
459
- if (!aborted)
460
- 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
+ }
461
555
  });
462
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
+ }
463
610
  function getMessagePort() {
464
611
  var _a, _b;
465
612
  if (messagePort)
@@ -542,6 +689,9 @@ function createStreamHandle(id, cancelFn) {
542
689
  var _a;
543
690
  var listeners = createListenerBuckets();
544
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;
545
695
  var resolveResult = function () { };
546
696
  var rejectResult = function () { };
547
697
  var pendingData = [];
@@ -564,6 +714,7 @@ function createStreamHandle(id, cancelFn) {
564
714
  }
565
715
  },
566
716
  _a[Symbol.asyncIterator] = function () {
717
+ iteratorRequested = true;
567
718
  return {
568
719
  next: function () {
569
720
  if (pendingData.length > 0) {
@@ -593,7 +744,7 @@ function createStreamHandle(id, cancelFn) {
593
744
  if (pendingReads.length > 0) {
594
745
  pendingReads.shift().resolve({ done: false, value: payload });
595
746
  }
596
- else {
747
+ else if (iteratorRequested) {
597
748
  pendingData.push(payload);
598
749
  }
599
750
  try {
@@ -680,6 +831,9 @@ function createChannel(id, sendFn, closeFn) {
680
831
  var open = false;
681
832
  var outboundSeq = 0;
682
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;
683
837
  var pendingData = [];
684
838
  var pendingReads = [];
685
839
  var channel = (_a = {
@@ -732,6 +886,7 @@ function createChannel(id, sendFn, closeFn) {
732
886
  }
733
887
  },
734
888
  _a[Symbol.asyncIterator] = function () {
889
+ iteratorRequested = true;
735
890
  return {
736
891
  next: function () {
737
892
  if (pendingData.length > 0) {
@@ -764,7 +919,7 @@ function createChannel(id, sendFn, closeFn) {
764
919
  if (pendingReads.length > 0) {
765
920
  pendingReads.shift().resolve({ done: false, value: payload });
766
921
  }
767
- else {
922
+ else if (iteratorRequested || listeners.data.size === 0) {
768
923
  pendingData.push(payload);
769
924
  }
770
925
  try {
@@ -884,7 +1039,15 @@ function isTransportReady() {
884
1039
  communicationMethod === WEB_MESSAGE_TYPE);
885
1040
  }
886
1041
  function canSendAppMessages() {
887
- 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();
888
1051
  }
889
1052
  function rejectPendingRequest(reqId, err) {
890
1053
  var _a;
@@ -1230,7 +1393,7 @@ function applySnapshotFromResult(result) {
1230
1393
  }
1231
1394
  function handleIncomingMessage(msg) {
1232
1395
  var e_16, _a;
1233
- var _b, _c;
1396
+ var _b, _c, _d, _e;
1234
1397
  // Handle native component events (from Android NativeBridge.sendEventToView)
1235
1398
  if (msg && typeof msg === "object") {
1236
1399
  var obj = msg;
@@ -1271,15 +1434,15 @@ function handleIncomingMessage(msg) {
1271
1434
  handshakeRetryCount = 0;
1272
1435
  if (message.hostMethods) {
1273
1436
  try {
1274
- for (var _d = __values(Object.entries(message.hostMethods)), _e = _d.next(); !_e.done; _e = _d.next()) {
1275
- 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];
1276
1439
  hostMethodKinds[k] = v;
1277
1440
  }
1278
1441
  }
1279
1442
  catch (e_16_1) { e_16 = { error: e_16_1 }; }
1280
1443
  finally {
1281
1444
  try {
1282
- if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
1445
+ if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
1283
1446
  }
1284
1447
  finally { if (e_16) throw e_16.error; }
1285
1448
  }
@@ -1321,7 +1484,15 @@ function handleIncomingMessage(msg) {
1321
1484
  if (pendingReq.has(message.id)) {
1322
1485
  var req = pendingReq.get(message.id);
1323
1486
  if ((req === null || req === void 0 ? void 0 : req.mode) === "stream" && req.stream) {
1324
- 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;
1325
1496
  req.stream._emitData(message.payload);
1326
1497
  }
1327
1498
  else {
@@ -1399,8 +1570,10 @@ function handleIncomingMessage(msg) {
1399
1570
  case "ch.close": {
1400
1571
  var pendingChannel = pendingChannels.get(message.id);
1401
1572
  if (pendingChannel) {
1402
- pendingChannels.delete(message.id);
1403
- 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
+ });
1404
1577
  return;
1405
1578
  }
1406
1579
  var channel_3 = activeChannels.get(message.id);
@@ -1752,6 +1925,8 @@ export var LingXiaBridge = {
1752
1925
  isMacOS: isMacOS,
1753
1926
  isWindows: isWindows,
1754
1927
  isDesktop: isDesktop,
1928
+ isApple: isApple,
1929
+ isRunner: isRunner,
1755
1930
  getOS: getPlatformOS,
1756
1931
  },
1757
1932
  dom: {
@@ -1940,6 +2115,8 @@ export function initBridge() {
1940
2115
  activateReceiver(LingXiaBridge._receiveEvaluateMessage);
1941
2116
  if (useAppleDownstreamTransport()) {
1942
2117
  ensureAppleDownstream();
2118
+ startAppleWatchdog();
2119
+ installAppleForegroundReconnect();
1943
2120
  }
1944
2121
  else if (communicationMethod === MESSAGE_PORT_TYPE) {
1945
2122
  installMessagePortInitListener();
@@ -1954,6 +2131,9 @@ export function initBridge() {
1954
2131
  warn("Unknown method");
1955
2132
  }
1956
2133
  window.LingXiaBridge = LingXiaBridge;
2134
+ // Dev hook so repro/test pages can simulate a WebKit stream replacement.
2135
+ window.__lxForceDownstreamReconnect =
2136
+ forceDownstreamReconnect;
1957
2137
  installNativeComponentCoverageMonitor({
1958
2138
  os: getPlatformOS(),
1959
2139
  send: sendNativeComponentMessage,
package/dist/es5/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { LingXiaBridge, channel, initBridge, invoke, notify, stream, } from './bridge';
9
9
  export { isNativeError } from './invocation';
10
+ export { getDisplayLanguage } from './runtime-env';
10
11
  export { renderErrorUI, hasError, getErrorInfo } from './error';
11
12
  export { boot, bootWhenReady } from './boot';
12
13
  import { bootWhenReady } from './boot';
@@ -1,4 +1,12 @@
1
+ var _a;
1
2
  export var BRIDGE_CONFIG = (typeof window !== 'undefined' && window.__LX_BRIDGE_CFG) || {};
3
+ var displayLanguage = ((_a = BRIDGE_CONFIG.displayLanguage) === null || _a === void 0 ? void 0 : _a.trim()) || 'en-US';
4
+ if (typeof document !== 'undefined' && document.documentElement) {
5
+ document.documentElement.lang = displayLanguage;
6
+ }
7
+ export function getDisplayLanguage() {
8
+ return displayLanguage;
9
+ }
2
10
  export function getPlatformOS() {
3
11
  return BRIDGE_CONFIG.os || 'unknown';
4
12
  }
@@ -20,6 +28,24 @@ export function isWindows() {
20
28
  export function isDesktop() {
21
29
  return isMacOS() || isWindows();
22
30
  }
31
+ // iOS and macOS share the WKWebView transport, so features scoped to it (e.g.
32
+ // the streaming downstream) key off this rather than the two OS checks.
33
+ export function isApple() {
34
+ return isIOS() || isMacOS();
35
+ }
36
+ // True when attached to a `lingxia dev` session (the host sets `dev` in
37
+ // `__LX_BRIDGE_CFG`). Used to surface the bridge's own protocol/lifecycle trace
38
+ // only during development.
39
+ export function isDevSession() {
40
+ return BRIDGE_CONFIG.dev === true;
41
+ }
42
+ // True when running inside the LingXia Runner (the `lingxia dev` device
43
+ // simulator), which the host marks in `__LX_BRIDGE_CFG`. Unlike a real host
44
+ // app in dev mode, the Runner lacks host-declared surfaces such as the
45
+ // terminal — apps read this to hide those affordances.
46
+ export function isRunner() {
47
+ return BRIDGE_CONFIG.runner === true;
48
+ }
23
49
  export function getCommunicationMethod() {
24
50
  var _a, _b;
25
51
  if (BRIDGE_CONFIG.os === 'iOS' || BRIDGE_CONFIG.os === 'macOS')
@@ -1,9 +1,9 @@
1
- import { getPlatformOS } from '../runtime-env';
1
+ import { isDesktop } from '../runtime-env';
2
2
  var ANIMATION_MS = 300;
3
3
  var STYLE_ID = 'lx-action-sheet-style';
4
4
  var activeSheet = null;
5
5
  function isDesktopMode() {
6
- return getPlatformOS() === 'macOS';
6
+ return isDesktop();
7
7
  }
8
8
  function ensureActionSheetStyle() {
9
9
  if (document.getElementById(STYLE_ID))
package/dist/index.d.ts CHANGED
@@ -8,5 +8,6 @@
8
8
  export type { BridgeConfig, CallOptions, ChannelCloseEvent, ChannelOptions, ChannelOpenOptions, BridgeErrorCode, ChannelEventName, DataSubscriber, ErrorInfo, LingXiaBridgeInterface, LxChannel, LxBridgeError, LxMethod, LxMethodParams, LxMethodResult, LxMethodStreamData, LxStream, NativeComponentMessage, NativeChannel, NativeError, NativeStream, NotifyOptions, InvokeOptions, StateInfo, StreamCallOptions, StreamOptions, StreamEventName, } from './types';
9
9
  export { LingXiaBridge, channel, initBridge, invoke, notify, stream, } from './bridge';
10
10
  export { isNativeError } from './invocation';
11
+ export { getDisplayLanguage } from './runtime-env';
11
12
  export { renderErrorUI, hasError, getErrorInfo } from './error';
12
13
  export { boot, bootWhenReady } from './boot';
@@ -2,6 +2,7 @@ import type { BridgeConfig } from './types';
2
2
  export type CommunicationMethod = 'messageport' | 'jsinterface' | 'webmessage' | 'webkit' | 'unknown';
3
3
  export type PlatformOS = NonNullable<BridgeConfig['os']> | 'unknown';
4
4
  export declare const BRIDGE_CONFIG: BridgeConfig;
5
+ export declare function getDisplayLanguage(): string;
5
6
  export declare function getPlatformOS(): PlatformOS;
6
7
  export declare function isHarmony(): boolean;
7
8
  export declare function isIOS(): boolean;
@@ -9,4 +10,7 @@ export declare function isAndroid(): boolean;
9
10
  export declare function isMacOS(): boolean;
10
11
  export declare function isWindows(): boolean;
11
12
  export declare function isDesktop(): boolean;
13
+ export declare function isApple(): boolean;
14
+ export declare function isDevSession(): boolean;
15
+ export declare function isRunner(): boolean;
12
16
  export declare function getCommunicationMethod(): CommunicationMethod;
package/dist/types.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  export interface BridgeConfig {
2
2
  os?: 'Harmony' | 'iOS' | 'Android' | 'macOS' | 'Windows';
3
+ /** Effective host display language; distinct from the raw system locale. */
4
+ displayLanguage?: string;
3
5
  nonce?: string;
4
6
  appleDownstreamURL?: string;
7
+ dev?: boolean;
8
+ runner?: boolean;
5
9
  }
6
10
  export interface RuntimeConfig {
7
11
  error?: ErrorInfo;
@@ -161,6 +165,8 @@ export interface LingXiaBridgeInterface {
161
165
  isMacOS(): boolean;
162
166
  isWindows(): boolean;
163
167
  isDesktop(): boolean;
168
+ isApple(): boolean;
169
+ isRunner(): boolean;
164
170
  getOS(): string;
165
171
  };
166
172
  dom: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingxia/bridge",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "private": false,
5
5
  "description": "LingXia Web Runtime - Bridge, boot, and error handling for WebView",
6
6
  "repository": {