@btraut/browser-bridge 0.8.0 → 0.9.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/CHANGELOG.md +24 -0
- package/README.md +41 -4
- package/dist/api.js +3926 -3239
- package/dist/api.js.map +4 -4
- package/dist/index.js +1027 -142
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +232 -38
- package/extension/dist/background.js.map +2 -2
- package/extension/dist/content.js +6 -2
- package/extension/dist/content.js.map +2 -2
- package/extension/dist/options-ui.js +59 -1
- package/extension/dist/options-ui.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
|
@@ -418,8 +418,19 @@ var DEBUGGER_PROTOCOL_VERSION = "1.3";
|
|
|
418
418
|
var DEBUGGER_IDLE_TIMEOUT_KEY = "debuggerIdleTimeoutMs";
|
|
419
419
|
var DEFAULT_DEBUGGER_IDLE_TIMEOUT_MS = 15e3;
|
|
420
420
|
var DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS = 1e4;
|
|
421
|
+
var DEFAULT_SEND_TO_TAB_TIMEOUT_MS = 1e4;
|
|
422
|
+
var HISTORY_DISPATCH_TIMEOUT_MS = 2e3;
|
|
423
|
+
var HISTORY_NAVIGATION_SIGNAL_TIMEOUT_MS = 8e3;
|
|
424
|
+
var HISTORY_POST_NAV_DOM_GRACE_TIMEOUT_MS = 2e3;
|
|
421
425
|
var AGENT_TAB_ID_KEY = "agentTabId";
|
|
422
426
|
var AGENT_TAB_GROUP_TITLE = "\u{1F309} Browser Bridge";
|
|
427
|
+
var AGENT_TAB_FAVICON_ASSET_PATH = "assets/icons/icon-32.png";
|
|
428
|
+
var getAgentTabBootstrapUrl = () => {
|
|
429
|
+
const faviconUrl = typeof chrome.runtime?.getURL === "function" ? chrome.runtime.getURL(AGENT_TAB_FAVICON_ASSET_PATH) : AGENT_TAB_FAVICON_ASSET_PATH;
|
|
430
|
+
return `data:text/html;charset=UTF-8,${encodeURIComponent(
|
|
431
|
+
`<!doctype html><html><head><meta charset="utf-8"><title>${AGENT_TAB_GROUP_TITLE}</title><link rel="icon" type="image/png" href="${faviconUrl}"></head><body></body></html>`
|
|
432
|
+
)}`;
|
|
433
|
+
};
|
|
423
434
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
424
435
|
var makeEventId = /* @__PURE__ */ (() => {
|
|
425
436
|
let counter = 0;
|
|
@@ -471,6 +482,80 @@ var delayMs = async (ms) => {
|
|
|
471
482
|
self.setTimeout(resolve, ms);
|
|
472
483
|
});
|
|
473
484
|
};
|
|
485
|
+
var CAPTURE_VISIBLE_TAB_MIN_INTERVAL_MS = 400;
|
|
486
|
+
var CAPTURE_VISIBLE_TAB_MAX_RETRIES = 3;
|
|
487
|
+
var CAPTURE_VISIBLE_TAB_RETRY_BASE_DELAY_MS = 500;
|
|
488
|
+
var captureVisibleTabQueue = Promise.resolve();
|
|
489
|
+
var captureVisibleTabLastCallAt = 0;
|
|
490
|
+
var isCaptureVisibleTabRateLimitedMessage = (message) => {
|
|
491
|
+
const normalized = message.toLowerCase();
|
|
492
|
+
const hasCaptureSignal = normalized.includes("capturevisibletab") || normalized.includes("max_capture_visible_tab_calls_per_second");
|
|
493
|
+
const hasRateSignal = normalized.includes("too often") || normalized.includes("rate limit") || normalized.includes("rate-limit");
|
|
494
|
+
return hasCaptureSignal && hasRateSignal;
|
|
495
|
+
};
|
|
496
|
+
var isCaptureVisibleTabRateLimitedError = (error) => {
|
|
497
|
+
return error instanceof Error && isCaptureVisibleTabRateLimitedMessage(error.message);
|
|
498
|
+
};
|
|
499
|
+
var randomJitterMs = (maxMs) => {
|
|
500
|
+
return Math.floor(Math.random() * Math.max(1, maxMs));
|
|
501
|
+
};
|
|
502
|
+
var runCaptureVisibleTabOperation = async (operation) => {
|
|
503
|
+
const previous = captureVisibleTabQueue;
|
|
504
|
+
let releaseQueue;
|
|
505
|
+
captureVisibleTabQueue = new Promise((resolve) => {
|
|
506
|
+
releaseQueue = resolve;
|
|
507
|
+
});
|
|
508
|
+
await previous.catch(() => void 0);
|
|
509
|
+
try {
|
|
510
|
+
return await operation();
|
|
511
|
+
} finally {
|
|
512
|
+
releaseQueue?.();
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
var captureVisibleTabWithThrottle = async (windowId) => {
|
|
516
|
+
return await runCaptureVisibleTabOperation(async () => {
|
|
517
|
+
for (let attempt = 0; attempt <= CAPTURE_VISIBLE_TAB_MAX_RETRIES; attempt += 1) {
|
|
518
|
+
const waitMs = captureVisibleTabLastCallAt + CAPTURE_VISIBLE_TAB_MIN_INTERVAL_MS - Date.now();
|
|
519
|
+
if (waitMs > 0) {
|
|
520
|
+
await delayMs(waitMs);
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
const dataUrl = await wrapChromeCallback(
|
|
524
|
+
(callback) => chrome.tabs.captureVisibleTab(windowId, { format: "png" }, callback)
|
|
525
|
+
);
|
|
526
|
+
captureVisibleTabLastCallAt = Date.now();
|
|
527
|
+
return dataUrl;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
captureVisibleTabLastCallAt = Date.now();
|
|
530
|
+
if (!isCaptureVisibleTabRateLimitedError(error) || attempt >= CAPTURE_VISIBLE_TAB_MAX_RETRIES) {
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
const backoffMs = CAPTURE_VISIBLE_TAB_RETRY_BASE_DELAY_MS * (attempt + 1) + randomJitterMs(120);
|
|
534
|
+
await delayMs(backoffMs);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
throw new Error("captureVisibleTab failed unexpectedly.");
|
|
538
|
+
});
|
|
539
|
+
};
|
|
540
|
+
var mapScreenshotCaptureError = (error, fallbackMessage) => {
|
|
541
|
+
const message = error instanceof Error && error.message ? error.message : fallbackMessage;
|
|
542
|
+
if (isCaptureVisibleTabRateLimitedError(error)) {
|
|
543
|
+
return {
|
|
544
|
+
code: "RATE_LIMITED",
|
|
545
|
+
message: "Screenshot capture hit Chrome capture rate limits. Please retry shortly.",
|
|
546
|
+
retryable: true,
|
|
547
|
+
details: {
|
|
548
|
+
reason: "capture_visible_tab_rate_limited",
|
|
549
|
+
original_message: message
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
code: "ARTIFACT_IO_ERROR",
|
|
555
|
+
message,
|
|
556
|
+
retryable: false
|
|
557
|
+
};
|
|
558
|
+
};
|
|
474
559
|
var parseDataUrl = (dataUrl) => {
|
|
475
560
|
const match = /^data:([^;]+);base64,(.*)$/s.exec(dataUrl);
|
|
476
561
|
if (!match) {
|
|
@@ -722,7 +807,10 @@ var ensureAgentTabGroup = async (tabId, windowId) => {
|
|
|
722
807
|
};
|
|
723
808
|
var createAgentWindow = async () => {
|
|
724
809
|
const created = await wrapChromeCallback(
|
|
725
|
-
(callback) => chrome.windows.create(
|
|
810
|
+
(callback) => chrome.windows.create(
|
|
811
|
+
{ url: getAgentTabBootstrapUrl(), focused: true },
|
|
812
|
+
callback
|
|
813
|
+
)
|
|
726
814
|
);
|
|
727
815
|
const windowId = created.id;
|
|
728
816
|
if (typeof windowId !== "number") {
|
|
@@ -809,14 +897,42 @@ var getDefaultTabId = async () => {
|
|
|
809
897
|
return await getActiveTabId();
|
|
810
898
|
}
|
|
811
899
|
};
|
|
812
|
-
var sendToTab = async (tabId, action, params) => {
|
|
900
|
+
var sendToTab = async (tabId, action, params, options) => {
|
|
901
|
+
const timeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(1, Math.floor(options.timeoutMs)) : DEFAULT_SEND_TO_TAB_TIMEOUT_MS;
|
|
813
902
|
const attemptSend = async () => {
|
|
814
903
|
return await new Promise((resolve) => {
|
|
815
904
|
const message = { action, params };
|
|
905
|
+
let settled = false;
|
|
906
|
+
const finish = (result) => {
|
|
907
|
+
if (settled) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
settled = true;
|
|
911
|
+
if (timeout !== void 0) {
|
|
912
|
+
clearTimeout(timeout);
|
|
913
|
+
}
|
|
914
|
+
resolve(result);
|
|
915
|
+
};
|
|
916
|
+
let timeout;
|
|
917
|
+
timeout = self.setTimeout(() => {
|
|
918
|
+
finish({
|
|
919
|
+
ok: false,
|
|
920
|
+
error: {
|
|
921
|
+
code: "TIMEOUT",
|
|
922
|
+
message: `Timed out waiting for content response after ${timeoutMs}ms.`,
|
|
923
|
+
retryable: true,
|
|
924
|
+
details: {
|
|
925
|
+
action,
|
|
926
|
+
tab_id: tabId,
|
|
927
|
+
timeout_ms: timeoutMs
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}, timeoutMs);
|
|
816
932
|
chrome.tabs.sendMessage(tabId, message, (response) => {
|
|
817
933
|
const error = chrome.runtime.lastError;
|
|
818
934
|
if (error) {
|
|
819
|
-
|
|
935
|
+
finish({
|
|
820
936
|
ok: false,
|
|
821
937
|
error: {
|
|
822
938
|
code: "EVALUATION_FAILED",
|
|
@@ -827,7 +943,7 @@ var sendToTab = async (tabId, action, params) => {
|
|
|
827
943
|
return;
|
|
828
944
|
}
|
|
829
945
|
if (!response || typeof response !== "object") {
|
|
830
|
-
|
|
946
|
+
finish({
|
|
831
947
|
ok: false,
|
|
832
948
|
error: {
|
|
833
949
|
code: "EVALUATION_FAILED",
|
|
@@ -837,7 +953,7 @@ var sendToTab = async (tabId, action, params) => {
|
|
|
837
953
|
});
|
|
838
954
|
return;
|
|
839
955
|
}
|
|
840
|
-
|
|
956
|
+
finish(response);
|
|
841
957
|
});
|
|
842
958
|
});
|
|
843
959
|
};
|
|
@@ -863,6 +979,67 @@ var sendToTab = async (tabId, action, params) => {
|
|
|
863
979
|
}
|
|
864
980
|
};
|
|
865
981
|
};
|
|
982
|
+
var waitForHistoryNavigationSignal = async (tabId, timeoutMs) => {
|
|
983
|
+
return await new Promise((resolve, reject) => {
|
|
984
|
+
let timeout;
|
|
985
|
+
const cleanup = () => {
|
|
986
|
+
if (timeout !== void 0) {
|
|
987
|
+
clearTimeout(timeout);
|
|
988
|
+
}
|
|
989
|
+
chrome.webNavigation.onCommitted.removeListener(onCommitted);
|
|
990
|
+
chrome.webNavigation.onHistoryStateUpdated.removeListener(
|
|
991
|
+
onHistoryStateUpdated
|
|
992
|
+
);
|
|
993
|
+
chrome.webNavigation.onReferenceFragmentUpdated.removeListener(
|
|
994
|
+
onReferenceFragmentUpdated
|
|
995
|
+
);
|
|
996
|
+
chrome.tabs.onUpdated.removeListener(onTabUpdated);
|
|
997
|
+
};
|
|
998
|
+
const resolveSignal = () => {
|
|
999
|
+
cleanup();
|
|
1000
|
+
resolve();
|
|
1001
|
+
};
|
|
1002
|
+
const onCommitted = (details) => {
|
|
1003
|
+
if (details.tabId !== tabId || details.frameId !== 0) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
resolveSignal();
|
|
1007
|
+
};
|
|
1008
|
+
const onHistoryStateUpdated = (details) => {
|
|
1009
|
+
if (details.tabId !== tabId || details.frameId !== 0) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
resolveSignal();
|
|
1013
|
+
};
|
|
1014
|
+
const onReferenceFragmentUpdated = (details) => {
|
|
1015
|
+
if (details.tabId !== tabId || details.frameId !== 0) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
resolveSignal();
|
|
1019
|
+
};
|
|
1020
|
+
const onTabUpdated = (updatedTabId, changeInfo) => {
|
|
1021
|
+
if (updatedTabId !== tabId) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (typeof changeInfo.url !== "string" || changeInfo.url.length === 0) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
resolveSignal();
|
|
1028
|
+
};
|
|
1029
|
+
chrome.webNavigation.onCommitted.addListener(onCommitted);
|
|
1030
|
+
chrome.webNavigation.onHistoryStateUpdated.addListener(
|
|
1031
|
+
onHistoryStateUpdated
|
|
1032
|
+
);
|
|
1033
|
+
chrome.webNavigation.onReferenceFragmentUpdated.addListener(
|
|
1034
|
+
onReferenceFragmentUpdated
|
|
1035
|
+
);
|
|
1036
|
+
chrome.tabs.onUpdated.addListener(onTabUpdated);
|
|
1037
|
+
timeout = self.setTimeout(() => {
|
|
1038
|
+
cleanup();
|
|
1039
|
+
reject(new Error("Timed out waiting for history navigation signal."));
|
|
1040
|
+
}, timeoutMs);
|
|
1041
|
+
});
|
|
1042
|
+
};
|
|
866
1043
|
var waitForDomContentLoaded = async (tabId, timeoutMs) => {
|
|
867
1044
|
return await new Promise((resolve, reject) => {
|
|
868
1045
|
let timeout;
|
|
@@ -1313,21 +1490,37 @@ var DriveSocket = class {
|
|
|
1313
1490
|
if (tabId === void 0) {
|
|
1314
1491
|
tabId = await getDefaultTabId();
|
|
1315
1492
|
}
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1493
|
+
const navigationSignal = waitForHistoryNavigationSignal(
|
|
1494
|
+
tabId,
|
|
1495
|
+
HISTORY_NAVIGATION_SIGNAL_TIMEOUT_MS
|
|
1496
|
+
);
|
|
1497
|
+
const result = await sendToTab(
|
|
1498
|
+
tabId,
|
|
1499
|
+
message.action,
|
|
1500
|
+
void 0,
|
|
1501
|
+
{
|
|
1502
|
+
timeoutMs: HISTORY_DISPATCH_TIMEOUT_MS
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1505
|
+
if (!result.ok && result.error.code !== "TIMEOUT") {
|
|
1318
1506
|
respondError(result.error);
|
|
1319
1507
|
return;
|
|
1320
1508
|
}
|
|
1321
1509
|
markTabActive(tabId);
|
|
1322
1510
|
try {
|
|
1323
|
-
await
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1511
|
+
await navigationSignal;
|
|
1512
|
+
try {
|
|
1513
|
+
await waitForDomContentLoaded(
|
|
1514
|
+
tabId,
|
|
1515
|
+
HISTORY_POST_NAV_DOM_GRACE_TIMEOUT_MS
|
|
1516
|
+
);
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
} catch {
|
|
1520
|
+
if (!result.ok) {
|
|
1521
|
+
respondError(result.error);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1331
1524
|
}
|
|
1332
1525
|
respondOk({ ok: true });
|
|
1333
1526
|
return;
|
|
@@ -1901,10 +2094,14 @@ var DriveSocket = class {
|
|
|
1901
2094
|
if (tabId === void 0) {
|
|
1902
2095
|
tabId = await getDefaultTabId();
|
|
1903
2096
|
}
|
|
2097
|
+
const timeoutMs = message.action === "drive.wait_for" && typeof params.timeout_ms === "number" && Number.isFinite(params.timeout_ms) ? Math.max(1, Math.floor(params.timeout_ms) + 1e3) : void 0;
|
|
1904
2098
|
const result = await sendToTab(
|
|
1905
2099
|
tabId,
|
|
1906
2100
|
message.action,
|
|
1907
|
-
params
|
|
2101
|
+
params,
|
|
2102
|
+
{
|
|
2103
|
+
timeoutMs
|
|
2104
|
+
}
|
|
1908
2105
|
);
|
|
1909
2106
|
if (result.ok) {
|
|
1910
2107
|
respondOk(result.result ?? { ok: true });
|
|
@@ -1959,13 +2156,7 @@ var DriveSocket = class {
|
|
|
1959
2156
|
return;
|
|
1960
2157
|
}
|
|
1961
2158
|
const captureVisible = async () => {
|
|
1962
|
-
return await
|
|
1963
|
-
(callback) => chrome.tabs.captureVisibleTab(
|
|
1964
|
-
windowId,
|
|
1965
|
-
{ format: "png" },
|
|
1966
|
-
callback
|
|
1967
|
-
)
|
|
1968
|
-
);
|
|
2159
|
+
return await captureVisibleTabWithThrottle(windowId);
|
|
1969
2160
|
};
|
|
1970
2161
|
const scrollTo = async (top, left) => {
|
|
1971
2162
|
const result = await sendToTab(tabId, "drive.scroll", {
|
|
@@ -2111,11 +2302,12 @@ var DriveSocket = class {
|
|
|
2111
2302
|
);
|
|
2112
2303
|
respondOk(rendered);
|
|
2113
2304
|
} catch (error) {
|
|
2114
|
-
respondError(
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2305
|
+
respondError(
|
|
2306
|
+
mapScreenshotCaptureError(
|
|
2307
|
+
error,
|
|
2308
|
+
"Failed to capture screenshot."
|
|
2309
|
+
)
|
|
2310
|
+
);
|
|
2119
2311
|
}
|
|
2120
2312
|
return;
|
|
2121
2313
|
}
|
|
@@ -2247,11 +2439,12 @@ var DriveSocket = class {
|
|
|
2247
2439
|
);
|
|
2248
2440
|
respondOk(await canvasToResult(cropCanvas));
|
|
2249
2441
|
} catch (error) {
|
|
2250
|
-
respondError(
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2442
|
+
respondError(
|
|
2443
|
+
mapScreenshotCaptureError(
|
|
2444
|
+
error,
|
|
2445
|
+
"Failed to capture element screenshot."
|
|
2446
|
+
)
|
|
2447
|
+
);
|
|
2255
2448
|
} finally {
|
|
2256
2449
|
try {
|
|
2257
2450
|
await scrollTo(metaInfo.scrollY, metaInfo.scrollX);
|
|
@@ -2265,11 +2458,12 @@ var DriveSocket = class {
|
|
|
2265
2458
|
const canvas = await captureFullPageCanvas(metaInfo);
|
|
2266
2459
|
respondOk(await canvasToResult(canvas));
|
|
2267
2460
|
} catch (error) {
|
|
2268
|
-
respondError(
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2461
|
+
respondError(
|
|
2462
|
+
mapScreenshotCaptureError(
|
|
2463
|
+
error,
|
|
2464
|
+
"Failed to capture full page screenshot."
|
|
2465
|
+
)
|
|
2466
|
+
);
|
|
2273
2467
|
}
|
|
2274
2468
|
return;
|
|
2275
2469
|
}
|