@btraut/browser-bridge 0.13.0 → 0.13.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.
@@ -1,56 +1,5 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
- // If the importer is in node compatibility mode or this is not an ESM
20
- // file that has been converted to a CommonJS file using a Babel-
21
- // compatible transform (i.e. "__esModule" has not been set), then set
22
- // "default" to the CommonJS "module.exports" for node compatibility.
23
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
- mod
25
- ));
26
-
27
- // packages/shared/dist/contract-version.js
28
- var require_contract_version = __commonJS({
29
- "packages/shared/dist/contract-version.js"(exports) {
30
- "use strict";
31
- Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.resolveContractVersionMismatch = exports.DRIVE_WS_PROTOCOL_VERSION = exports.HTTP_CONTRACT_VERSION = exports.HTTP_CONTRACT_VERSION_HEADER = void 0;
33
- exports.HTTP_CONTRACT_VERSION_HEADER = "x-browser-bridge-contract-version";
34
- exports.HTTP_CONTRACT_VERSION = "2026-02-17.1";
35
- exports.DRIVE_WS_PROTOCOL_VERSION = "2026-02-17.1";
36
- var resolveContractVersionMismatch = (receivedVersion) => {
37
- if (!receivedVersion || receivedVersion.trim().length === 0) {
38
- return void 0;
39
- }
40
- if (receivedVersion === exports.HTTP_CONTRACT_VERSION) {
41
- return void 0;
42
- }
43
- return {
44
- expected: exports.HTTP_CONTRACT_VERSION,
45
- received: receivedVersion
46
- };
47
- };
48
- exports.resolveContractVersionMismatch = resolveContractVersionMismatch;
49
- }
50
- });
51
-
52
- // packages/extension/src/background.ts
53
- var import_contract_version = __toESM(require_contract_version());
1
+ // packages/shared/src/contract-version.ts
2
+ var DRIVE_WS_PROTOCOL_VERSION = "2026-02-17.1";
54
3
 
55
4
  // packages/extension/src/error-sanitizer.ts
56
5
  var TRAILING_PUNCTUATION_RE = /[.,;:!?]+$/;
@@ -487,6 +436,54 @@ var PermissionPromptController = class {
487
436
  }
488
437
  };
489
438
 
439
+ // packages/extension/src/drive-reliability.ts
440
+ var TRANSIENT_TAB_CHANNEL_ERROR_PATTERNS = [
441
+ "receiving end does not exist",
442
+ "message channel closed before a response was received",
443
+ "the message port closed before a response was received",
444
+ "extension port is moved into back/forward cache"
445
+ ];
446
+ var TAB_CHANNEL_RETRY_DELAYS_MS = [120, 200, 320, 500, 750, 1e3, 1200];
447
+ var normalizePathname = (pathname) => {
448
+ if (pathname.length === 0) {
449
+ return "/";
450
+ }
451
+ if (pathname.length > 1 && pathname.endsWith("/")) {
452
+ return pathname.slice(0, -1);
453
+ }
454
+ return pathname;
455
+ };
456
+ var isTransientTabChannelError = (message) => {
457
+ if (typeof message !== "string") {
458
+ return false;
459
+ }
460
+ const normalized = message.toLowerCase();
461
+ return TRANSIENT_TAB_CHANNEL_ERROR_PATTERNS.some(
462
+ (pattern) => normalized.includes(pattern)
463
+ );
464
+ };
465
+ var getTabChannelRetryDelayMs = (attempt) => {
466
+ if (!Number.isInteger(attempt) || attempt < 1) {
467
+ return void 0;
468
+ }
469
+ return TAB_CHANNEL_RETRY_DELAYS_MS[attempt - 1];
470
+ };
471
+ var isLikelyNavigationCommitted = (requestedUrl, tabUrl) => {
472
+ if (typeof requestedUrl !== "string" || typeof tabUrl !== "string") {
473
+ return false;
474
+ }
475
+ if (requestedUrl === tabUrl) {
476
+ return true;
477
+ }
478
+ try {
479
+ const requested = new URL(requestedUrl);
480
+ const actual = new URL(tabUrl);
481
+ return requested.origin === actual.origin && normalizePathname(requested.pathname) === normalizePathname(actual.pathname) && requested.search === actual.search;
482
+ } catch {
483
+ return requestedUrl === tabUrl;
484
+ }
485
+ };
486
+
490
487
  // packages/extension/src/connection-state.ts
491
488
  var toIso = (ms) => new Date(ms).toISOString();
492
489
  var ConnectionStateTracker = class {
@@ -1166,12 +1163,18 @@ var sendToTab = async (tabId, action, params, options) => {
1166
1163
  chrome.tabs.sendMessage(tabId, message, (response) => {
1167
1164
  const error = chrome.runtime.lastError;
1168
1165
  if (error) {
1166
+ const retryable = isTransientTabChannelError(error.message);
1169
1167
  finish({
1170
1168
  ok: false,
1171
1169
  error: {
1172
1170
  code: "EVALUATION_FAILED",
1173
1171
  message: error.message,
1174
- retryable: false
1172
+ retryable,
1173
+ ...retryable ? {
1174
+ details: {
1175
+ reason: "transient_tab_channel_error"
1176
+ }
1177
+ } : {}
1175
1178
  }
1176
1179
  });
1177
1180
  return;
@@ -1191,27 +1194,20 @@ var sendToTab = async (tabId, action, params, options) => {
1191
1194
  });
1192
1195
  });
1193
1196
  };
1194
- const MAX_ATTEMPTS = 5;
1195
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
1197
+ for (let attempt = 1; ; attempt += 1) {
1196
1198
  const result = await attemptSend();
1197
1199
  if (result.ok) {
1198
1200
  return result;
1199
1201
  }
1200
- const message = result.error?.message;
1201
- const isNoReceiver = typeof message === "string" && message.toLowerCase().includes("receiving end does not exist");
1202
- if (!isNoReceiver || attempt === MAX_ATTEMPTS) {
1202
+ if (!isTransientTabChannelError(result.error?.message)) {
1203
1203
  return result;
1204
1204
  }
1205
- await delayMs(200);
1206
- }
1207
- return {
1208
- ok: false,
1209
- error: {
1210
- code: "INTERNAL",
1211
- message: "Failed to send message to content script.",
1212
- retryable: false
1205
+ const retryDelayMs = getTabChannelRetryDelayMs(attempt);
1206
+ if (retryDelayMs === void 0) {
1207
+ return result;
1213
1208
  }
1214
- };
1209
+ await delayMs(retryDelayMs);
1210
+ }
1215
1211
  };
1216
1212
  var refreshAgentTabBranding = async (tabId) => {
1217
1213
  const result = await sendToTab(
@@ -1494,7 +1490,7 @@ var DriveSocket = class {
1494
1490
  }
1495
1491
  const params = {
1496
1492
  version: manifest.version,
1497
- protocol_version: import_contract_version.DRIVE_WS_PROTOCOL_VERSION,
1493
+ protocol_version: DRIVE_WS_PROTOCOL_VERSION,
1498
1494
  capabilities: buildNegotiatedCapabilities(debuggerCapabilityEnabled),
1499
1495
  core_host: endpoint.host,
1500
1496
  core_port: endpoint.port,
@@ -1817,26 +1813,38 @@ var DriveSocket = class {
1817
1813
  tabId = await getDefaultTabId();
1818
1814
  }
1819
1815
  const waitMode = params.wait === "none" || params.wait === "domcontentloaded" ? params.wait : "domcontentloaded";
1816
+ const domContentLoadedSignal = waitMode === "domcontentloaded" ? waitForDomContentLoaded(tabId, 3e4) : null;
1817
+ const warnings = [];
1820
1818
  await wrapChromeVoid(
1821
1819
  (callback) => chrome.tabs.update(tabId, { url }, () => callback())
1822
1820
  );
1823
1821
  markTabActive(tabId);
1824
- if (waitMode === "domcontentloaded") {
1822
+ if (domContentLoadedSignal) {
1825
1823
  try {
1826
- await waitForDomContentLoaded(tabId, 3e4);
1824
+ await domContentLoadedSignal;
1827
1825
  } catch (error) {
1828
- respondError({
1829
- code: "TIMEOUT",
1830
- message: error instanceof Error ? error.message : "Timed out waiting.",
1831
- retryable: true
1832
- });
1833
- return;
1826
+ const tab = await getTab(tabId).catch(() => void 0);
1827
+ if (tab && isLikelyNavigationCommitted(url, tab.url ?? void 0)) {
1828
+ warnings.push(
1829
+ "Timed out waiting for DOMContentLoaded, but the tab URL already updated to the requested target."
1830
+ );
1831
+ } else {
1832
+ respondError({
1833
+ code: "TIMEOUT",
1834
+ message: error instanceof Error ? error.message : "Timed out waiting.",
1835
+ retryable: true
1836
+ });
1837
+ return;
1838
+ }
1834
1839
  }
1835
1840
  }
1836
1841
  if (tabId === agentTabId) {
1837
1842
  void refreshAgentTabBranding(tabId);
1838
1843
  }
1839
- respondOk({ ok: true });
1844
+ respondOk({
1845
+ ok: true,
1846
+ ...warnings.length > 0 ? { warnings } : {}
1847
+ });
1840
1848
  return;
1841
1849
  }
1842
1850
  case "drive.go_back":
@@ -1882,14 +1890,18 @@ var DriveSocket = class {
1882
1890
  }
1883
1891
  } catch {
1884
1892
  if (!result.ok) {
1885
- respondError(result.error);
1893
+ respondError({
1894
+ ...result.error
1895
+ });
1886
1896
  return;
1887
1897
  }
1888
1898
  }
1889
1899
  if (tabId === agentTabId) {
1890
1900
  void refreshAgentTabBranding(tabId);
1891
1901
  }
1892
- respondOk({ ok: true });
1902
+ respondOk({
1903
+ ok: true
1904
+ });
1893
1905
  return;
1894
1906
  }
1895
1907
  case "drive.tab_list": {