@opentabs-dev/browser-extension 0.0.34
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/background-log-state.d.ts +10 -0
- package/dist/background-log-state.d.ts.map +1 -0
- package/dist/background-log-state.js +11 -0
- package/dist/background-log-state.js.map +1 -0
- package/dist/background-message-handlers.d.ts +41 -0
- package/dist/background-message-handlers.d.ts.map +1 -0
- package/dist/background-message-handlers.js +214 -0
- package/dist/background-message-handlers.js.map +1 -0
- package/dist/background.d.ts +2 -0
- package/dist/background.d.ts.map +1 -0
- package/dist/background.js +3780 -0
- package/dist/background.js.map +1 -0
- package/dist/bg-log-state.d.ts +10 -0
- package/dist/bg-log-state.d.ts.map +1 -0
- package/dist/bg-log-state.js +11 -0
- package/dist/bg-log-state.js.map +1 -0
- package/dist/browser-commands/content-commands.d.ts +25 -0
- package/dist/browser-commands/content-commands.d.ts.map +1 -0
- package/dist/browser-commands/content-commands.js +166 -0
- package/dist/browser-commands/content-commands.js.map +1 -0
- package/dist/browser-commands/cookie-commands.d.ts +14 -0
- package/dist/browser-commands/cookie-commands.d.ts.map +1 -0
- package/dist/browser-commands/cookie-commands.js +99 -0
- package/dist/browser-commands/cookie-commands.js.map +1 -0
- package/dist/browser-commands/extension-commands.d.ts +12 -0
- package/dist/browser-commands/extension-commands.d.ts.map +1 -0
- package/dist/browser-commands/extension-commands.js +386 -0
- package/dist/browser-commands/extension-commands.js.map +1 -0
- package/dist/browser-commands/helpers.d.ts +35 -0
- package/dist/browser-commands/helpers.d.ts.map +1 -0
- package/dist/browser-commands/helpers.js +121 -0
- package/dist/browser-commands/helpers.js.map +1 -0
- package/dist/browser-commands/index.d.ts +11 -0
- package/dist/browser-commands/index.d.ts.map +1 -0
- package/dist/browser-commands/index.js +10 -0
- package/dist/browser-commands/index.js.map +1 -0
- package/dist/browser-commands/interaction-commands.d.ts +23 -0
- package/dist/browser-commands/interaction-commands.d.ts.map +1 -0
- package/dist/browser-commands/interaction-commands.js +353 -0
- package/dist/browser-commands/interaction-commands.js.map +1 -0
- package/dist/browser-commands/key-press-command.d.ts +2 -0
- package/dist/browser-commands/key-press-command.d.ts.map +1 -0
- package/dist/browser-commands/key-press-command.js +144 -0
- package/dist/browser-commands/key-press-command.js.map +1 -0
- package/dist/browser-commands/network-commands.d.ts +6 -0
- package/dist/browser-commands/network-commands.d.ts.map +1 -0
- package/dist/browser-commands/network-commands.js +69 -0
- package/dist/browser-commands/network-commands.js.map +1 -0
- package/dist/browser-commands/resource-commands.d.ts +37 -0
- package/dist/browser-commands/resource-commands.d.ts.map +1 -0
- package/dist/browser-commands/resource-commands.js +153 -0
- package/dist/browser-commands/resource-commands.js.map +1 -0
- package/dist/browser-commands/scroll-command.d.ts +2 -0
- package/dist/browser-commands/scroll-command.d.ts.map +1 -0
- package/dist/browser-commands/scroll-command.js +133 -0
- package/dist/browser-commands/scroll-command.js.map +1 -0
- package/dist/browser-commands/tab-commands.d.ts +33 -0
- package/dist/browser-commands/tab-commands.d.ts.map +1 -0
- package/dist/browser-commands/tab-commands.js +121 -0
- package/dist/browser-commands/tab-commands.js.map +1 -0
- package/dist/browser-commands.d.ts +36 -0
- package/dist/browser-commands.d.ts.map +1 -0
- package/dist/browser-commands.js +1931 -0
- package/dist/browser-commands.js.map +1 -0
- package/dist/confirmation-badge.d.ts +17 -0
- package/dist/confirmation-badge.d.ts.map +1 -0
- package/dist/confirmation-badge.js +64 -0
- package/dist/confirmation-badge.js.map +1 -0
- package/dist/constants.d.ts +79 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +79 -0
- package/dist/constants.js.map +1 -0
- package/dist/dispatch-helpers.d.ts +61 -0
- package/dist/dispatch-helpers.d.ts.map +1 -0
- package/dist/dispatch-helpers.js +149 -0
- package/dist/dispatch-helpers.js.map +1 -0
- package/dist/extension-messages.d.ts +146 -0
- package/dist/extension-messages.d.ts.map +1 -0
- package/dist/extension-messages.js +2 -0
- package/dist/extension-messages.js.map +1 -0
- package/dist/iife-injection.d.ts +55 -0
- package/dist/iife-injection.d.ts.map +1 -0
- package/dist/iife-injection.js +474 -0
- package/dist/iife-injection.js.map +1 -0
- package/dist/json-rpc-errors.d.ts +8 -0
- package/dist/json-rpc-errors.d.ts.map +1 -0
- package/dist/json-rpc-errors.js +8 -0
- package/dist/json-rpc-errors.js.map +1 -0
- package/dist/known-methods.d.ts +19 -0
- package/dist/known-methods.d.ts.map +1 -0
- package/dist/known-methods.js +68 -0
- package/dist/known-methods.js.map +1 -0
- package/dist/log-collector.d.ts +45 -0
- package/dist/log-collector.d.ts.map +1 -0
- package/dist/log-collector.js +99 -0
- package/dist/log-collector.js.map +1 -0
- package/dist/message-router.d.ts +28 -0
- package/dist/message-router.d.ts.map +1 -0
- package/dist/message-router.js +367 -0
- package/dist/message-router.js.map +1 -0
- package/dist/messaging.d.ts +15 -0
- package/dist/messaging.d.ts.map +1 -0
- package/dist/messaging.js +41 -0
- package/dist/messaging.js.map +1 -0
- package/dist/network-capture.d.ts +56 -0
- package/dist/network-capture.d.ts.map +1 -0
- package/dist/network-capture.js +374 -0
- package/dist/network-capture.js.map +1 -0
- package/dist/offscreen/index.d.ts +16 -0
- package/dist/offscreen/index.d.ts.map +1 -0
- package/dist/offscreen/index.js +549 -0
- package/dist/offscreen/index.js.map +1 -0
- package/dist/plugin-storage.d.ts +19 -0
- package/dist/plugin-storage.d.ts.map +1 -0
- package/dist/plugin-storage.js +100 -0
- package/dist/plugin-storage.js.map +1 -0
- package/dist/rate-limiter.d.ts +18 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +53 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resource-prompt-dispatch.d.ts +14 -0
- package/dist/resource-prompt-dispatch.d.ts.map +1 -0
- package/dist/resource-prompt-dispatch.js +195 -0
- package/dist/resource-prompt-dispatch.js.map +1 -0
- package/dist/sanitize-error.d.ts +8 -0
- package/dist/sanitize-error.d.ts.map +1 -0
- package/dist/sanitize-error.js +25 -0
- package/dist/sanitize-error.js.map +1 -0
- package/dist/sanitize-svg.d.ts +20 -0
- package/dist/sanitize-svg.d.ts.map +1 -0
- package/dist/sanitize-svg.js +296 -0
- package/dist/sanitize-svg.js.map +1 -0
- package/dist/side-panel/App.d.ts +3 -0
- package/dist/side-panel/App.d.ts.map +1 -0
- package/dist/side-panel/App.js +147 -0
- package/dist/side-panel/App.js.map +1 -0
- package/dist/side-panel/bridge.d.ts +50 -0
- package/dist/side-panel/bridge.d.ts.map +1 -0
- package/dist/side-panel/bridge.js +113 -0
- package/dist/side-panel/bridge.js.map +1 -0
- package/dist/side-panel/components/ConfirmationDialog.d.ts +16 -0
- package/dist/side-panel/components/ConfirmationDialog.d.ts.map +1 -0
- package/dist/side-panel/components/ConfirmationDialog.js +39 -0
- package/dist/side-panel/components/ConfirmationDialog.js.map +1 -0
- package/dist/side-panel/components/EmptyStates.d.ts +8 -0
- package/dist/side-panel/components/EmptyStates.d.ts.map +1 -0
- package/dist/side-panel/components/EmptyStates.js +40 -0
- package/dist/side-panel/components/EmptyStates.js.map +1 -0
- package/dist/side-panel/components/ErrorBoundary.d.ts +23 -0
- package/dist/side-panel/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/side-panel/components/ErrorBoundary.js +28 -0
- package/dist/side-panel/components/ErrorBoundary.js.map +1 -0
- package/dist/side-panel/components/FailedPluginCard.d.ts +6 -0
- package/dist/side-panel/components/FailedPluginCard.d.ts.map +1 -0
- package/dist/side-panel/components/FailedPluginCard.js +9 -0
- package/dist/side-panel/components/FailedPluginCard.js.map +1 -0
- package/dist/side-panel/components/Footer.d.ts +3 -0
- package/dist/side-panel/components/Footer.d.ts.map +1 -0
- package/dist/side-panel/components/Footer.js +32 -0
- package/dist/side-panel/components/Footer.js.map +1 -0
- package/dist/side-panel/components/Header.d.ts +5 -0
- package/dist/side-panel/components/Header.d.ts.map +1 -0
- package/dist/side-panel/components/Header.js +6 -0
- package/dist/side-panel/components/Header.js.map +1 -0
- package/dist/side-panel/components/PluginCard.d.ts +10 -0
- package/dist/side-panel/components/PluginCard.d.ts.map +1 -0
- package/dist/side-panel/components/PluginCard.js +50 -0
- package/dist/side-panel/components/PluginCard.js.map +1 -0
- package/dist/side-panel/components/PluginIcon.d.ts +21 -0
- package/dist/side-panel/components/PluginIcon.d.ts.map +1 -0
- package/dist/side-panel/components/PluginIcon.js +48 -0
- package/dist/side-panel/components/PluginIcon.js.map +1 -0
- package/dist/side-panel/components/PluginList.d.ts +11 -0
- package/dist/side-panel/components/PluginList.d.ts.map +1 -0
- package/dist/side-panel/components/PluginList.js +17 -0
- package/dist/side-panel/components/PluginList.js.map +1 -0
- package/dist/side-panel/components/ToolIcon.d.ts +7 -0
- package/dist/side-panel/components/ToolIcon.d.ts.map +1 -0
- package/dist/side-panel/components/ToolIcon.js +8 -0
- package/dist/side-panel/components/ToolIcon.js.map +1 -0
- package/dist/side-panel/components/ToolRow.d.ts +11 -0
- package/dist/side-panel/components/ToolRow.d.ts.map +1 -0
- package/dist/side-panel/components/ToolRow.js +8 -0
- package/dist/side-panel/components/ToolRow.js.map +1 -0
- package/dist/side-panel/components/VersionMismatchBanner.d.ts +3 -0
- package/dist/side-panel/components/VersionMismatchBanner.d.ts.map +1 -0
- package/dist/side-panel/components/VersionMismatchBanner.js +5 -0
- package/dist/side-panel/components/VersionMismatchBanner.js.map +1 -0
- package/dist/side-panel/components/retro/Accordion.d.ts +8 -0
- package/dist/side-panel/components/retro/Accordion.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Accordion.js +15 -0
- package/dist/side-panel/components/retro/Accordion.js.map +1 -0
- package/dist/side-panel/components/retro/Alert.d.ts +25 -0
- package/dist/side-panel/components/retro/Alert.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Alert.js +33 -0
- package/dist/side-panel/components/retro/Alert.js.map +1 -0
- package/dist/side-panel/components/retro/Badge.d.ts +15 -0
- package/dist/side-panel/components/retro/Badge.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Badge.js +23 -0
- package/dist/side-panel/components/retro/Badge.js.map +1 -0
- package/dist/side-panel/components/retro/Button.d.ts +13 -0
- package/dist/side-panel/components/retro/Button.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Button.js +33 -0
- package/dist/side-panel/components/retro/Button.js.map +1 -0
- package/dist/side-panel/components/retro/Empty.d.ts +31 -0
- package/dist/side-panel/components/retro/Empty.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Empty.js +25 -0
- package/dist/side-panel/components/retro/Empty.js.map +1 -0
- package/dist/side-panel/components/retro/Input.d.ts +8 -0
- package/dist/side-panel/components/retro/Input.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Input.js +7 -0
- package/dist/side-panel/components/retro/Input.js.map +1 -0
- package/dist/side-panel/components/retro/Loader.d.ts +14 -0
- package/dist/side-panel/components/retro/Loader.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Loader.js +30 -0
- package/dist/side-panel/components/retro/Loader.js.map +1 -0
- package/dist/side-panel/components/retro/Menu.d.ts +9 -0
- package/dist/side-panel/components/retro/Menu.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Menu.js +17 -0
- package/dist/side-panel/components/retro/Menu.js.map +1 -0
- package/dist/side-panel/components/retro/NumberStepper.d.ts +18 -0
- package/dist/side-panel/components/retro/NumberStepper.d.ts.map +1 -0
- package/dist/side-panel/components/retro/NumberStepper.js +38 -0
- package/dist/side-panel/components/retro/NumberStepper.js.map +1 -0
- package/dist/side-panel/components/retro/Progress.d.ts +9 -0
- package/dist/side-panel/components/retro/Progress.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Progress.js +6 -0
- package/dist/side-panel/components/retro/Progress.js.map +1 -0
- package/dist/side-panel/components/retro/Switch.d.ts +4 -0
- package/dist/side-panel/components/retro/Switch.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Switch.js +6 -0
- package/dist/side-panel/components/retro/Switch.js.map +1 -0
- package/dist/side-panel/components/retro/Text.d.ts +11 -0
- package/dist/side-panel/components/retro/Text.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Text.js +28 -0
- package/dist/side-panel/components/retro/Text.js.map +1 -0
- package/dist/side-panel/components/retro/Tooltip.d.ts +12 -0
- package/dist/side-panel/components/retro/Tooltip.d.ts.map +1 -0
- package/dist/side-panel/components/retro/Tooltip.js +32 -0
- package/dist/side-panel/components/retro/Tooltip.js.map +1 -0
- package/dist/side-panel/constants.d.ts +5 -0
- package/dist/side-panel/constants.d.ts.map +1 -0
- package/dist/side-panel/constants.js +5 -0
- package/dist/side-panel/constants.js.map +1 -0
- package/dist/side-panel/hooks/useServerNotifications.d.ts +21 -0
- package/dist/side-panel/hooks/useServerNotifications.d.ts.map +1 -0
- package/dist/side-panel/hooks/useServerNotifications.js +93 -0
- package/dist/side-panel/hooks/useServerNotifications.js.map +1 -0
- package/dist/side-panel/hooks/useTheme.d.ts +8 -0
- package/dist/side-panel/hooks/useTheme.d.ts.map +1 -0
- package/dist/side-panel/hooks/useTheme.js +30 -0
- package/dist/side-panel/hooks/useTheme.js.map +1 -0
- package/dist/side-panel/index.d.ts +2 -0
- package/dist/side-panel/index.d.ts.map +1 -0
- package/dist/side-panel/index.js +12 -0
- package/dist/side-panel/index.js.map +1 -0
- package/dist/side-panel/lib/cn.d.ts +3 -0
- package/dist/side-panel/lib/cn.d.ts.map +1 -0
- package/dist/side-panel/lib/cn.js +4 -0
- package/dist/side-panel/lib/cn.js.map +1 -0
- package/dist/side-panel/lib/utils.d.ts +3 -0
- package/dist/side-panel/lib/utils.d.ts.map +1 -0
- package/dist/side-panel/lib/utils.js +4 -0
- package/dist/side-panel/lib/utils.js.map +1 -0
- package/dist/side-panel/side-panel.js +78034 -0
- package/dist/side-panel/styles.css +2 -0
- package/dist/side-panel-state.d.ts +11 -0
- package/dist/side-panel-state.d.ts.map +1 -0
- package/dist/side-panel-state.js +38 -0
- package/dist/side-panel-state.js.map +1 -0
- package/dist/side-panel-toggle.d.ts +3 -0
- package/dist/side-panel-toggle.d.ts.map +1 -0
- package/dist/side-panel-toggle.js +31 -0
- package/dist/side-panel-toggle.js.map +1 -0
- package/dist/tab-matching.d.ts +51 -0
- package/dist/tab-matching.d.ts.map +1 -0
- package/dist/tab-matching.js +160 -0
- package/dist/tab-matching.js.map +1 -0
- package/dist/tab-state.d.ts +56 -0
- package/dist/tab-state.d.ts.map +1 -0
- package/dist/tab-state.js +282 -0
- package/dist/tab-state.js.map +1 -0
- package/dist/tool-dispatch.d.ts +19 -0
- package/dist/tool-dispatch.d.ts.map +1 -0
- package/dist/tool-dispatch.js +336 -0
- package/dist/tool-dispatch.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-16.png +0 -0
- package/icons/icon-32.png +0 -0
- package/icons/icon-48.png +0 -0
- package/icons/icon.svg +6 -0
- package/manifest.json +38 -0
- package/offscreen/offscreen.html +10 -0
- package/package.json +49 -0
- package/side-panel/dark-mode.js +29 -0
- package/side-panel/fonts/archivo-black-latin.woff2 +0 -0
- package/side-panel/fonts/space-grotesk-latin.woff2 +0 -0
- package/side-panel/fonts/space-mono-400-latin.woff2 +0 -0
- package/side-panel/fonts/space-mono-700-latin.woff2 +0 -0
- package/side-panel/fonts.css +34 -0
- package/side-panel/side-panel.html +16 -0
|
@@ -0,0 +1,3780 @@
|
|
|
1
|
+
// dist/confirmation-badge.js
|
|
2
|
+
var pendingConfirmationCount = 0;
|
|
3
|
+
var updateConfirmationBadge = () => {
|
|
4
|
+
if (pendingConfirmationCount > 0) {
|
|
5
|
+
chrome.action.setBadgeText({ text: String(pendingConfirmationCount) }).catch(() => {
|
|
6
|
+
});
|
|
7
|
+
chrome.action.setBadgeBackgroundColor({ color: "#ffdb33" }).catch(() => {
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
chrome.action.setBadgeText({ text: "" }).catch(() => {
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var notifyConfirmationRequest = (params) => {
|
|
15
|
+
pendingConfirmationCount++;
|
|
16
|
+
updateConfirmationBadge();
|
|
17
|
+
const tool = typeof params.tool === "string" ? params.tool : "unknown tool";
|
|
18
|
+
const domain = typeof params.domain === "string" ? params.domain : "unknown domain";
|
|
19
|
+
chrome.notifications.create(`opentabs-confirm-${typeof params.id === "string" ? params.id : Date.now()}`, {
|
|
20
|
+
type: "basic",
|
|
21
|
+
iconUrl: chrome.runtime.getURL("icons/icon-128.png"),
|
|
22
|
+
title: "OpenTabs: Approval Required",
|
|
23
|
+
message: `${tool} on ${domain} \u2014 Click to open side panel`,
|
|
24
|
+
priority: 2,
|
|
25
|
+
requireInteraction: true
|
|
26
|
+
}).catch(() => {
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var clearConfirmationBadge = () => {
|
|
30
|
+
pendingConfirmationCount = Math.max(0, pendingConfirmationCount - 1);
|
|
31
|
+
updateConfirmationBadge();
|
|
32
|
+
};
|
|
33
|
+
var clearAllConfirmationBadges = () => {
|
|
34
|
+
pendingConfirmationCount = 0;
|
|
35
|
+
updateConfirmationBadge();
|
|
36
|
+
};
|
|
37
|
+
var initConfirmationBadge = () => {
|
|
38
|
+
chrome.notifications.onClicked.addListener((notificationId) => {
|
|
39
|
+
if (notificationId.startsWith("opentabs-confirm-")) {
|
|
40
|
+
chrome.windows.getCurrent().then((w) => {
|
|
41
|
+
if (w.id !== void 0) {
|
|
42
|
+
chrome.sidePanel.open({ windowId: w.id }).catch(() => {
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}).catch(() => {
|
|
46
|
+
});
|
|
47
|
+
chrome.notifications.clear(notificationId).catch(() => {
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// dist/constants.js
|
|
54
|
+
var KEEPALIVE_ALARM = "opentabs-keepalive";
|
|
55
|
+
var KEEPALIVE_INTERVAL_MINUTES = 0.5;
|
|
56
|
+
var PLUGINS_META_KEY = "plugins_meta";
|
|
57
|
+
var WS_CONNECTED_KEY = "wsConnected";
|
|
58
|
+
var SCRIPT_TIMEOUT_MS = 25e3;
|
|
59
|
+
var MAX_SCRIPT_TIMEOUT_MS = 295e3;
|
|
60
|
+
var IS_READY_TIMEOUT_MS = 5e3;
|
|
61
|
+
var RELOAD_FLUSH_DELAY_MS = 100;
|
|
62
|
+
var INJECTION_RETRY_DELAY_MS = 200;
|
|
63
|
+
var SCREENSHOT_RENDER_DELAY_MS = 100;
|
|
64
|
+
var WS_FLUSH_DELAY_MS = 50;
|
|
65
|
+
var SERVER_PORT_KEY = "serverPort";
|
|
66
|
+
var DEFAULT_SERVER_PORT = 9515;
|
|
67
|
+
var TEXT_PREVIEW_MAX_LENGTH = 200;
|
|
68
|
+
var DEFAULT_WAIT_TIMEOUT_MS = 1e4;
|
|
69
|
+
var POLL_INTERVAL_MS = 100;
|
|
70
|
+
var DEFAULT_QUERY_LIMIT = 100;
|
|
71
|
+
var MAX_INPUT_SIZE = 10 * 1024 * 1024;
|
|
72
|
+
var SIDE_PANEL_TIMEOUT_MS = 3e3;
|
|
73
|
+
var CDP_VERSION = "1.3";
|
|
74
|
+
var EXEC_MAX_ASYNC_WAIT_MS = 1e4;
|
|
75
|
+
var EXEC_POLL_INTERVAL_MS = 50;
|
|
76
|
+
var EXEC_RESULT_TRUNCATION_LIMIT = 5e4;
|
|
77
|
+
var DEFAULT_LOG_LIMIT = 100;
|
|
78
|
+
var VALID_PLUGIN_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
79
|
+
var isValidPluginName = (name) => VALID_PLUGIN_NAME.test(name);
|
|
80
|
+
var buildWsUrl = (port) => `ws://localhost:${port}/ws`;
|
|
81
|
+
|
|
82
|
+
// dist/json-rpc-errors.js
|
|
83
|
+
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
84
|
+
var JSONRPC_INVALID_PARAMS = -32602;
|
|
85
|
+
var JSONRPC_INTERNAL_ERROR = -32603;
|
|
86
|
+
var JSONRPC_NO_USABLE_TAB = -32001;
|
|
87
|
+
var JSONRPC_ADAPTER_NOT_READY = -32002;
|
|
88
|
+
|
|
89
|
+
// dist/messaging.js
|
|
90
|
+
var sendToServer = (data) => {
|
|
91
|
+
const method = data.method ?? "unknown";
|
|
92
|
+
chrome.runtime.sendMessage({ type: "ws:send", data }).catch((err2) => {
|
|
93
|
+
console.warn(`[opentabs] sendToServer failed for "${method}":`, err2);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
var forwardToSidePanel = (message) => {
|
|
97
|
+
const type = message.type;
|
|
98
|
+
chrome.runtime.sendMessage(message).catch((err2) => {
|
|
99
|
+
console.warn(`[opentabs] forwardToSidePanel failed for "${type}":`, err2);
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
var sendTabStateNotification = (pluginName, stateInfo) => {
|
|
103
|
+
const params = {
|
|
104
|
+
plugin: pluginName,
|
|
105
|
+
state: stateInfo.state,
|
|
106
|
+
tabId: stateInfo.tabId,
|
|
107
|
+
url: stateInfo.url
|
|
108
|
+
};
|
|
109
|
+
sendToServer({
|
|
110
|
+
jsonrpc: "2.0",
|
|
111
|
+
method: "tab.stateChanged",
|
|
112
|
+
params
|
|
113
|
+
});
|
|
114
|
+
forwardToSidePanel({
|
|
115
|
+
type: "sp:serverMessage",
|
|
116
|
+
data: {
|
|
117
|
+
jsonrpc: "2.0",
|
|
118
|
+
method: "tab.stateChanged",
|
|
119
|
+
params
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// dist/sanitize-error.js
|
|
125
|
+
var MAX_LENGTH = 500;
|
|
126
|
+
var sanitizeErrorMessage = (message) => {
|
|
127
|
+
let sanitized = message.replace(/[a-z]:[/\\][^\s,;)}\]]+/gi, "[PATH]").replace(/\/[a-z0-9._-]+(?:\/[a-z0-9._-]+)+/gi, "[PATH]").replace(/https?:\/\/[^\s,;)}\]]+/gi, "[URL]").replace(/localhost:\d+/gi, "[LOCALHOST]").replace(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, "[IP]");
|
|
128
|
+
if (sanitized.length > MAX_LENGTH) {
|
|
129
|
+
sanitized = sanitized.slice(0, MAX_LENGTH - 3) + "...";
|
|
130
|
+
}
|
|
131
|
+
return sanitized;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ../shared/dist/error.js
|
|
135
|
+
var toErrorMessage = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
136
|
+
|
|
137
|
+
// ../shared/dist/index.js
|
|
138
|
+
var BLOCKED_URL_SCHEMES = [
|
|
139
|
+
"javascript:",
|
|
140
|
+
"data:",
|
|
141
|
+
"file:",
|
|
142
|
+
"chrome:",
|
|
143
|
+
"chrome-extension:",
|
|
144
|
+
"blob:"
|
|
145
|
+
];
|
|
146
|
+
var isBlockedUrlScheme = (url) => {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = new URL(url);
|
|
149
|
+
return BLOCKED_URL_SCHEMES.includes(parsed.protocol);
|
|
150
|
+
} catch {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// dist/browser-commands/helpers.js
|
|
156
|
+
var requireTabId = (params, id) => {
|
|
157
|
+
const tabId = params.tabId;
|
|
158
|
+
if (typeof tabId !== "number") {
|
|
159
|
+
sendToServer({
|
|
160
|
+
jsonrpc: "2.0",
|
|
161
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: "Missing or invalid tabId parameter" },
|
|
162
|
+
id
|
|
163
|
+
});
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return tabId;
|
|
167
|
+
};
|
|
168
|
+
var requireSelector = (params, id) => {
|
|
169
|
+
const selector = params.selector;
|
|
170
|
+
if (typeof selector !== "string" || selector.length === 0) {
|
|
171
|
+
sendToServer({
|
|
172
|
+
jsonrpc: "2.0",
|
|
173
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: "Missing or invalid selector parameter" },
|
|
174
|
+
id
|
|
175
|
+
});
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return selector;
|
|
179
|
+
};
|
|
180
|
+
var requireStringParam = (params, paramName, id) => {
|
|
181
|
+
const value = params[paramName];
|
|
182
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
183
|
+
sendToServer({
|
|
184
|
+
jsonrpc: "2.0",
|
|
185
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: `Missing or invalid ${paramName} parameter` },
|
|
186
|
+
id
|
|
187
|
+
});
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return value;
|
|
191
|
+
};
|
|
192
|
+
var requireUrl = (params, id) => {
|
|
193
|
+
const url = params.url;
|
|
194
|
+
if (typeof url !== "string") {
|
|
195
|
+
sendToServer({
|
|
196
|
+
jsonrpc: "2.0",
|
|
197
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: "Missing or invalid url parameter" },
|
|
198
|
+
id
|
|
199
|
+
});
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
if (isBlockedUrlScheme(url)) {
|
|
203
|
+
sendToServer({
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
error: {
|
|
206
|
+
code: JSONRPC_INVALID_PARAMS,
|
|
207
|
+
message: "URL scheme not allowed (javascript:, data:, file:, chrome:, blob: are blocked)"
|
|
208
|
+
},
|
|
209
|
+
id
|
|
210
|
+
});
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return url;
|
|
214
|
+
};
|
|
215
|
+
var extractScriptResult = (results, id, fallbackMsg = "No result from script execution") => {
|
|
216
|
+
const result = results[0]?.result;
|
|
217
|
+
if (!result) {
|
|
218
|
+
sendToServer({ jsonrpc: "2.0", error: { code: JSONRPC_INTERNAL_ERROR, message: fallbackMsg }, id });
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (result.error) {
|
|
222
|
+
sendToServer({
|
|
223
|
+
jsonrpc: "2.0",
|
|
224
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: sanitizeErrorMessage(result.error) },
|
|
225
|
+
id
|
|
226
|
+
});
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
};
|
|
231
|
+
var sendErrorResult = (id, err2) => {
|
|
232
|
+
sendToServer({
|
|
233
|
+
jsonrpc: "2.0",
|
|
234
|
+
error: { code: JSONRPC_INTERNAL_ERROR, message: sanitizeErrorMessage(toErrorMessage(err2)) },
|
|
235
|
+
id
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
var sendValidationError = (id, message) => {
|
|
239
|
+
sendToServer({
|
|
240
|
+
jsonrpc: "2.0",
|
|
241
|
+
error: { code: JSONRPC_INVALID_PARAMS, message },
|
|
242
|
+
id
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
var sendSuccessResult = (id, result) => {
|
|
246
|
+
sendToServer({ jsonrpc: "2.0", result, id });
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// dist/browser-commands/content-commands.js
|
|
250
|
+
var handleBrowserGetTabContent = async (params, id) => {
|
|
251
|
+
try {
|
|
252
|
+
const tabId = requireTabId(params, id);
|
|
253
|
+
if (tabId === null)
|
|
254
|
+
return;
|
|
255
|
+
const selector = typeof params.selector === "string" ? params.selector : "body";
|
|
256
|
+
const maxLength = typeof params.maxLength === "number" ? params.maxLength : 5e4;
|
|
257
|
+
const results = await chrome.scripting.executeScript({
|
|
258
|
+
target: { tabId },
|
|
259
|
+
world: "MAIN",
|
|
260
|
+
func: (sel, max) => {
|
|
261
|
+
const el = document.querySelector(sel);
|
|
262
|
+
if (!el)
|
|
263
|
+
return { error: `Element not found: ${sel}` };
|
|
264
|
+
return {
|
|
265
|
+
title: document.title,
|
|
266
|
+
url: document.URL,
|
|
267
|
+
content: (el.innerText || "").trim().slice(0, max)
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
args: [selector, maxLength]
|
|
271
|
+
});
|
|
272
|
+
const result = extractScriptResult(results, id);
|
|
273
|
+
if (!result)
|
|
274
|
+
return;
|
|
275
|
+
sendSuccessResult(id, { title: result.title, url: result.url, content: result.content });
|
|
276
|
+
} catch (err2) {
|
|
277
|
+
sendErrorResult(id, err2);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var handleBrowserGetPageHtml = async (params, id) => {
|
|
281
|
+
try {
|
|
282
|
+
const tabId = requireTabId(params, id);
|
|
283
|
+
if (tabId === null)
|
|
284
|
+
return;
|
|
285
|
+
const selector = typeof params.selector === "string" ? params.selector : "html";
|
|
286
|
+
const maxLength = typeof params.maxLength === "number" ? params.maxLength : 2e5;
|
|
287
|
+
const results = await chrome.scripting.executeScript({
|
|
288
|
+
target: { tabId },
|
|
289
|
+
world: "MAIN",
|
|
290
|
+
func: (sel, max) => {
|
|
291
|
+
const el = document.querySelector(sel);
|
|
292
|
+
if (!el)
|
|
293
|
+
return { error: `Element not found: ${sel}` };
|
|
294
|
+
const html = el.outerHTML;
|
|
295
|
+
return {
|
|
296
|
+
title: document.title,
|
|
297
|
+
url: document.URL,
|
|
298
|
+
html: html.length > max ? html.slice(0, max) + "... (truncated)" : html
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
args: [selector, maxLength]
|
|
302
|
+
});
|
|
303
|
+
const result = extractScriptResult(results, id);
|
|
304
|
+
if (!result)
|
|
305
|
+
return;
|
|
306
|
+
sendSuccessResult(id, { title: result.title, url: result.url, html: result.html });
|
|
307
|
+
} catch (err2) {
|
|
308
|
+
sendErrorResult(id, err2);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
var handleBrowserGetStorage = async (params, id) => {
|
|
312
|
+
try {
|
|
313
|
+
const tabId = requireTabId(params, id);
|
|
314
|
+
if (tabId === null)
|
|
315
|
+
return;
|
|
316
|
+
const storageType = typeof params.storageType === "string" ? params.storageType : "local";
|
|
317
|
+
if (storageType !== "local" && storageType !== "session") {
|
|
318
|
+
sendValidationError(id, "storageType must be 'local' or 'session'");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const key = typeof params.key === "string" ? params.key : void 0;
|
|
322
|
+
const MAX_VALUE_LENGTH = 1e4;
|
|
323
|
+
const MAX_RESPONSE_LENGTH = 5e5;
|
|
324
|
+
const results = await chrome.scripting.executeScript({
|
|
325
|
+
target: { tabId },
|
|
326
|
+
world: "MAIN",
|
|
327
|
+
func: (type, k, maxVal, maxResp) => {
|
|
328
|
+
const storage = type === "session" ? window.sessionStorage : window.localStorage;
|
|
329
|
+
if (k !== null) {
|
|
330
|
+
const value = storage.getItem(k);
|
|
331
|
+
return {
|
|
332
|
+
mode: "single",
|
|
333
|
+
key: k,
|
|
334
|
+
value: value !== null && value.length > maxVal ? value.slice(0, maxVal) + "... (truncated)" : value
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const entries = [];
|
|
338
|
+
let totalLength = 0;
|
|
339
|
+
const keys = Object.keys(storage);
|
|
340
|
+
for (const entryKey of keys) {
|
|
341
|
+
const raw = storage.getItem(entryKey);
|
|
342
|
+
if (raw === null)
|
|
343
|
+
continue;
|
|
344
|
+
const value = raw.length > maxVal ? raw.slice(0, maxVal) + "... (truncated)" : raw;
|
|
345
|
+
const entryLength = entryKey.length + value.length;
|
|
346
|
+
if (totalLength + entryLength > maxResp)
|
|
347
|
+
break;
|
|
348
|
+
entries.push({ key: entryKey, value });
|
|
349
|
+
totalLength += entryLength;
|
|
350
|
+
}
|
|
351
|
+
return { mode: "all", entries, count: keys.length };
|
|
352
|
+
},
|
|
353
|
+
args: [storageType, key ?? null, MAX_VALUE_LENGTH, MAX_RESPONSE_LENGTH]
|
|
354
|
+
});
|
|
355
|
+
const result = results[0]?.result;
|
|
356
|
+
if (!result) {
|
|
357
|
+
sendErrorResult(id, new Error("No result from script execution"));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (result.mode === "single") {
|
|
361
|
+
sendSuccessResult(id, { key: result.key, value: result.value });
|
|
362
|
+
} else {
|
|
363
|
+
sendSuccessResult(id, { entries: result.entries, count: result.count });
|
|
364
|
+
}
|
|
365
|
+
} catch (err2) {
|
|
366
|
+
sendErrorResult(id, err2);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
var handleBrowserScreenshotTab = async (params, id) => {
|
|
370
|
+
try {
|
|
371
|
+
const tabId = requireTabId(params, id);
|
|
372
|
+
if (tabId === null)
|
|
373
|
+
return;
|
|
374
|
+
const tab = await chrome.tabs.update(tabId, { active: true });
|
|
375
|
+
if (!tab) {
|
|
376
|
+
sendValidationError(id, `Tab ${tabId} not found`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
await chrome.windows.update(tab.windowId, { focused: true });
|
|
380
|
+
await new Promise((resolve) => setTimeout(resolve, SCREENSHOT_RENDER_DELAY_MS));
|
|
381
|
+
const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" });
|
|
382
|
+
const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
383
|
+
sendSuccessResult(id, { image: base64 });
|
|
384
|
+
} catch (err2) {
|
|
385
|
+
sendErrorResult(id, err2);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// dist/browser-commands/cookie-commands.js
|
|
390
|
+
var handleBrowserGetCookies = async (params, id) => {
|
|
391
|
+
try {
|
|
392
|
+
const url = requireUrl(params, id);
|
|
393
|
+
if (url === null)
|
|
394
|
+
return;
|
|
395
|
+
const filter = { url };
|
|
396
|
+
const name = params.name;
|
|
397
|
+
if (typeof name === "string") {
|
|
398
|
+
filter.name = name;
|
|
399
|
+
}
|
|
400
|
+
const cookies = await chrome.cookies.getAll(filter);
|
|
401
|
+
sendSuccessResult(id, {
|
|
402
|
+
cookies: cookies.map((c) => ({
|
|
403
|
+
name: c.name,
|
|
404
|
+
value: c.value,
|
|
405
|
+
domain: c.domain,
|
|
406
|
+
path: c.path,
|
|
407
|
+
secure: c.secure,
|
|
408
|
+
httpOnly: c.httpOnly,
|
|
409
|
+
sameSite: c.sameSite,
|
|
410
|
+
expirationDate: c.expirationDate
|
|
411
|
+
}))
|
|
412
|
+
});
|
|
413
|
+
} catch (err2) {
|
|
414
|
+
sendErrorResult(id, err2);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var handleBrowserSetCookie = async (params, id) => {
|
|
418
|
+
try {
|
|
419
|
+
const url = requireUrl(params, id);
|
|
420
|
+
if (url === null)
|
|
421
|
+
return;
|
|
422
|
+
const name = requireStringParam(params, "name", id);
|
|
423
|
+
if (name === null)
|
|
424
|
+
return;
|
|
425
|
+
const value = params.value;
|
|
426
|
+
if (typeof value !== "string") {
|
|
427
|
+
sendValidationError(id, "Missing or invalid value parameter");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const details = { url, name, value };
|
|
431
|
+
if (typeof params.domain === "string")
|
|
432
|
+
details.domain = params.domain;
|
|
433
|
+
if (typeof params.path === "string")
|
|
434
|
+
details.path = params.path;
|
|
435
|
+
if (typeof params.secure === "boolean")
|
|
436
|
+
details.secure = params.secure;
|
|
437
|
+
if (typeof params.httpOnly === "boolean")
|
|
438
|
+
details.httpOnly = params.httpOnly;
|
|
439
|
+
if (typeof params.expirationDate === "number")
|
|
440
|
+
details.expirationDate = params.expirationDate;
|
|
441
|
+
const cookie = await chrome.cookies.set(details);
|
|
442
|
+
if (!cookie) {
|
|
443
|
+
sendErrorResult(id, new Error("Failed to set cookie"));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
sendSuccessResult(id, {
|
|
447
|
+
name: cookie.name,
|
|
448
|
+
value: cookie.value,
|
|
449
|
+
domain: cookie.domain,
|
|
450
|
+
path: cookie.path,
|
|
451
|
+
secure: cookie.secure,
|
|
452
|
+
httpOnly: cookie.httpOnly,
|
|
453
|
+
sameSite: cookie.sameSite,
|
|
454
|
+
expirationDate: cookie.expirationDate
|
|
455
|
+
});
|
|
456
|
+
} catch (err2) {
|
|
457
|
+
sendErrorResult(id, err2);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var handleBrowserDeleteCookies = async (params, id) => {
|
|
461
|
+
try {
|
|
462
|
+
const url = requireUrl(params, id);
|
|
463
|
+
if (url === null)
|
|
464
|
+
return;
|
|
465
|
+
const name = requireStringParam(params, "name", id);
|
|
466
|
+
if (name === null)
|
|
467
|
+
return;
|
|
468
|
+
await chrome.cookies.remove({ url, name });
|
|
469
|
+
sendSuccessResult(id, { deleted: true, name, url });
|
|
470
|
+
} catch (err2) {
|
|
471
|
+
sendErrorResult(id, err2);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// dist/log-collector.js
|
|
476
|
+
var MAX_MESSAGE_LENGTH = 2e3;
|
|
477
|
+
var DEFAULT_MAX_ENTRIES = 500;
|
|
478
|
+
var formatArg = (arg) => {
|
|
479
|
+
if (typeof arg === "string")
|
|
480
|
+
return arg;
|
|
481
|
+
if (arg instanceof Error)
|
|
482
|
+
return `${arg.message}${arg.stack ? `
|
|
483
|
+
${arg.stack}` : ""}`;
|
|
484
|
+
try {
|
|
485
|
+
return JSON.stringify(arg);
|
|
486
|
+
} catch {
|
|
487
|
+
return String(arg);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
var formatMessage = (args) => {
|
|
491
|
+
const raw = args.map(formatArg).join(" ");
|
|
492
|
+
return raw.length > MAX_MESSAGE_LENGTH ? raw.slice(0, MAX_MESSAGE_LENGTH) : raw;
|
|
493
|
+
};
|
|
494
|
+
var LogCollector = class {
|
|
495
|
+
buffer = [];
|
|
496
|
+
maxEntries;
|
|
497
|
+
source;
|
|
498
|
+
totalCaptured = 0;
|
|
499
|
+
constructor(source, maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
500
|
+
this.source = source;
|
|
501
|
+
this.maxEntries = maxEntries;
|
|
502
|
+
}
|
|
503
|
+
capture(level, args) {
|
|
504
|
+
const entry = {
|
|
505
|
+
timestamp: Date.now(),
|
|
506
|
+
level,
|
|
507
|
+
source: this.source,
|
|
508
|
+
message: formatMessage(args)
|
|
509
|
+
};
|
|
510
|
+
if (this.buffer.length >= this.maxEntries) {
|
|
511
|
+
this.buffer.shift();
|
|
512
|
+
}
|
|
513
|
+
this.buffer.push(entry);
|
|
514
|
+
this.totalCaptured++;
|
|
515
|
+
}
|
|
516
|
+
getEntries(options) {
|
|
517
|
+
let entries = this.buffer;
|
|
518
|
+
if (options?.level) {
|
|
519
|
+
const level = options.level;
|
|
520
|
+
entries = entries.filter((e) => e.level === level);
|
|
521
|
+
}
|
|
522
|
+
if (options?.source) {
|
|
523
|
+
const source = options.source;
|
|
524
|
+
entries = entries.filter((e) => e.source === source);
|
|
525
|
+
}
|
|
526
|
+
if (options?.since !== void 0) {
|
|
527
|
+
const since = options.since;
|
|
528
|
+
entries = entries.filter((e) => e.timestamp >= since);
|
|
529
|
+
}
|
|
530
|
+
const result = [...entries].reverse();
|
|
531
|
+
if (options?.limit !== void 0 && options.limit > 0) {
|
|
532
|
+
return result.slice(0, options.limit);
|
|
533
|
+
}
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
clear() {
|
|
537
|
+
this.buffer.length = 0;
|
|
538
|
+
}
|
|
539
|
+
getStats() {
|
|
540
|
+
const oldest = this.buffer[0];
|
|
541
|
+
const newest = this.buffer[this.buffer.length - 1];
|
|
542
|
+
return {
|
|
543
|
+
totalCaptured: this.totalCaptured,
|
|
544
|
+
bufferSize: this.buffer.length,
|
|
545
|
+
oldestTimestamp: oldest?.timestamp ?? null,
|
|
546
|
+
newestTimestamp: newest?.timestamp ?? null
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var installLogCollector = (source, maxEntries) => {
|
|
551
|
+
const collector = new LogCollector(source, maxEntries);
|
|
552
|
+
const levels = ["log", "warn", "error", "info"];
|
|
553
|
+
for (const level of levels) {
|
|
554
|
+
const original = console[level].bind(console);
|
|
555
|
+
console[level] = (...args) => {
|
|
556
|
+
collector.capture(level, args);
|
|
557
|
+
original(...args);
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return collector;
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// dist/background-log-state.js
|
|
564
|
+
var bgLogCollector = installLogCollector("background");
|
|
565
|
+
|
|
566
|
+
// dist/network-capture.js
|
|
567
|
+
var MAX_BODY_LENGTH = 102400;
|
|
568
|
+
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
569
|
+
"authorization",
|
|
570
|
+
"cookie",
|
|
571
|
+
"set-cookie",
|
|
572
|
+
"x-csrf-token",
|
|
573
|
+
"x-xsrf-token",
|
|
574
|
+
"proxy-authorization",
|
|
575
|
+
"x-api-key",
|
|
576
|
+
"x-auth-token",
|
|
577
|
+
"x-access-token",
|
|
578
|
+
"x-api-token",
|
|
579
|
+
"www-authenticate"
|
|
580
|
+
]);
|
|
581
|
+
var scrubHeaders = (headers) => {
|
|
582
|
+
if (!headers)
|
|
583
|
+
return void 0;
|
|
584
|
+
const scrubbed = {};
|
|
585
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
586
|
+
const lower = k.toLowerCase();
|
|
587
|
+
if (!SENSITIVE_HEADERS.has(lower)) {
|
|
588
|
+
scrubbed[k] = v;
|
|
589
|
+
} else if (lower === "authorization" || lower === "proxy-authorization") {
|
|
590
|
+
const spaceIdx = v.indexOf(" ");
|
|
591
|
+
scrubbed[k] = spaceIdx > 0 ? `${v.slice(0, spaceIdx)} [REDACTED]` : "[REDACTED]";
|
|
592
|
+
} else {
|
|
593
|
+
scrubbed[k] = "[REDACTED]";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return scrubbed;
|
|
597
|
+
};
|
|
598
|
+
var captures = /* @__PURE__ */ new Map();
|
|
599
|
+
var headersToRecord = (raw) => {
|
|
600
|
+
if (!raw)
|
|
601
|
+
return void 0;
|
|
602
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
603
|
+
return raw;
|
|
604
|
+
}
|
|
605
|
+
if (Array.isArray(raw)) {
|
|
606
|
+
const record = {};
|
|
607
|
+
for (const entry of raw) {
|
|
608
|
+
if (typeof entry.name === "string") {
|
|
609
|
+
record[entry.name] = entry.value !== void 0 ? entry.value : "";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return record;
|
|
613
|
+
}
|
|
614
|
+
return void 0;
|
|
615
|
+
};
|
|
616
|
+
var stringProp = (obj, key, fallback) => {
|
|
617
|
+
const propertyValue = obj[key];
|
|
618
|
+
return typeof propertyValue === "string" ? propertyValue : fallback;
|
|
619
|
+
};
|
|
620
|
+
var truncateBody = (body) => body.length > MAX_BODY_LENGTH ? body.slice(0, MAX_BODY_LENGTH) + "... (truncated)" : body;
|
|
621
|
+
var evictOldestRequest = (state) => {
|
|
622
|
+
const shifted = state.requests.shift();
|
|
623
|
+
if (shifted) {
|
|
624
|
+
for (const [key, value] of state.requestIdToRequest) {
|
|
625
|
+
if (value === shifted) {
|
|
626
|
+
state.requestIdToRequest.delete(key);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var BINARY_MIME_PREFIXES = ["image/", "font/", "video/", "audio/"];
|
|
633
|
+
var BINARY_MIME_EXACT = /* @__PURE__ */ new Set(["application/octet-stream", "application/wasm"]);
|
|
634
|
+
var isBinaryMime = (mimeType) => {
|
|
635
|
+
if (!mimeType)
|
|
636
|
+
return false;
|
|
637
|
+
const lower = mimeType.toLowerCase();
|
|
638
|
+
if (BINARY_MIME_EXACT.has(lower))
|
|
639
|
+
return true;
|
|
640
|
+
return BINARY_MIME_PREFIXES.some((prefix) => lower.startsWith(prefix));
|
|
641
|
+
};
|
|
642
|
+
chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
643
|
+
const paramsRecord = params;
|
|
644
|
+
const tabId = source.tabId;
|
|
645
|
+
if (tabId === void 0)
|
|
646
|
+
return;
|
|
647
|
+
const state = captures.get(tabId);
|
|
648
|
+
if (!state)
|
|
649
|
+
return;
|
|
650
|
+
if (method === "Network.requestWillBeSent") {
|
|
651
|
+
const requestId = paramsRecord?.requestId;
|
|
652
|
+
const request = paramsRecord?.request;
|
|
653
|
+
if (!requestId || !request)
|
|
654
|
+
return;
|
|
655
|
+
const url = stringProp(request, "url", "");
|
|
656
|
+
if (state.urlFilter && !url.includes(state.urlFilter))
|
|
657
|
+
return;
|
|
658
|
+
const postData = typeof request.postData === "string" ? request.postData : void 0;
|
|
659
|
+
state.pendingRequests.set(requestId, {
|
|
660
|
+
url,
|
|
661
|
+
method: stringProp(request, "method", "GET"),
|
|
662
|
+
requestHeaders: headersToRecord(request.headers),
|
|
663
|
+
requestBody: postData ? truncateBody(postData) : void 0,
|
|
664
|
+
timestamp: Date.now()
|
|
665
|
+
});
|
|
666
|
+
} else if (method === "Network.responseReceived") {
|
|
667
|
+
const requestId = paramsRecord?.requestId;
|
|
668
|
+
const response = paramsRecord?.response;
|
|
669
|
+
if (!requestId || !response)
|
|
670
|
+
return;
|
|
671
|
+
const pending = state.pendingRequests.get(requestId);
|
|
672
|
+
if (!pending)
|
|
673
|
+
return;
|
|
674
|
+
const completed = {
|
|
675
|
+
url: pending.url ?? stringProp(response, "url", ""),
|
|
676
|
+
method: pending.method ?? "GET",
|
|
677
|
+
status: typeof response.status === "number" ? response.status : void 0,
|
|
678
|
+
statusText: typeof response.statusText === "string" ? response.statusText : void 0,
|
|
679
|
+
requestHeaders: pending.requestHeaders,
|
|
680
|
+
responseHeaders: headersToRecord(response.headers),
|
|
681
|
+
requestBody: pending.requestBody,
|
|
682
|
+
mimeType: typeof response.mimeType === "string" ? response.mimeType : void 0,
|
|
683
|
+
timestamp: pending.timestamp ?? Date.now()
|
|
684
|
+
};
|
|
685
|
+
state.pendingRequests.delete(requestId);
|
|
686
|
+
if (state.requests.length >= state.maxRequests) {
|
|
687
|
+
evictOldestRequest(state);
|
|
688
|
+
}
|
|
689
|
+
state.requests.push(completed);
|
|
690
|
+
state.requestIdToRequest.set(requestId, completed);
|
|
691
|
+
} else if (method === "Network.loadingFailed") {
|
|
692
|
+
const requestId = paramsRecord?.requestId;
|
|
693
|
+
if (!requestId)
|
|
694
|
+
return;
|
|
695
|
+
const pending = state.pendingRequests.get(requestId);
|
|
696
|
+
if (!pending)
|
|
697
|
+
return;
|
|
698
|
+
state.pendingRequests.delete(requestId);
|
|
699
|
+
state.requestIdToRequest.delete(requestId);
|
|
700
|
+
const errorText = typeof paramsRecord?.errorText === "string" ? paramsRecord.errorText : "Unknown error";
|
|
701
|
+
const completed = {
|
|
702
|
+
url: pending.url ?? "",
|
|
703
|
+
method: pending.method ?? "GET",
|
|
704
|
+
status: 0,
|
|
705
|
+
statusText: errorText,
|
|
706
|
+
requestHeaders: pending.requestHeaders,
|
|
707
|
+
requestBody: pending.requestBody,
|
|
708
|
+
timestamp: pending.timestamp ?? Date.now()
|
|
709
|
+
};
|
|
710
|
+
if (state.requests.length >= state.maxRequests) {
|
|
711
|
+
evictOldestRequest(state);
|
|
712
|
+
}
|
|
713
|
+
state.requests.push(completed);
|
|
714
|
+
} else if (method === "Network.loadingFinished") {
|
|
715
|
+
const requestId = paramsRecord?.requestId;
|
|
716
|
+
if (!requestId)
|
|
717
|
+
return;
|
|
718
|
+
const request = state.requestIdToRequest.get(requestId);
|
|
719
|
+
if (!request)
|
|
720
|
+
return;
|
|
721
|
+
state.requestIdToRequest.delete(requestId);
|
|
722
|
+
if (isBinaryMime(request.mimeType))
|
|
723
|
+
return;
|
|
724
|
+
chrome.debugger.sendCommand({ tabId }, "Network.getResponseBody", { requestId }, (result) => {
|
|
725
|
+
if (chrome.runtime.lastError || !result)
|
|
726
|
+
return;
|
|
727
|
+
const responseData = result;
|
|
728
|
+
if (typeof responseData.body !== "string")
|
|
729
|
+
return;
|
|
730
|
+
const body = responseData.base64Encoded ? new TextDecoder().decode(Uint8Array.from(atob(responseData.body), (c) => c.charCodeAt(0))) : responseData.body;
|
|
731
|
+
request.responseBody = truncateBody(body);
|
|
732
|
+
});
|
|
733
|
+
} else if (method === "Network.webSocketCreated") {
|
|
734
|
+
const url = paramsRecord?.url;
|
|
735
|
+
if (!url)
|
|
736
|
+
return;
|
|
737
|
+
if (state.urlFilter && !url.includes(state.urlFilter))
|
|
738
|
+
return;
|
|
739
|
+
const completed = {
|
|
740
|
+
url,
|
|
741
|
+
method: "GET",
|
|
742
|
+
status: 101,
|
|
743
|
+
statusText: "Switching Protocols",
|
|
744
|
+
requestHeaders: { Upgrade: "websocket", Connection: "Upgrade" },
|
|
745
|
+
timestamp: Date.now()
|
|
746
|
+
};
|
|
747
|
+
if (state.requests.length >= state.maxRequests) {
|
|
748
|
+
evictOldestRequest(state);
|
|
749
|
+
}
|
|
750
|
+
state.requests.push(completed);
|
|
751
|
+
} else if (method === "Runtime.consoleAPICalled") {
|
|
752
|
+
const type = paramsRecord?.type;
|
|
753
|
+
const args = paramsRecord?.args;
|
|
754
|
+
if (!type || !args)
|
|
755
|
+
return;
|
|
756
|
+
const messageParts = [];
|
|
757
|
+
for (const arg of args) {
|
|
758
|
+
if (arg.value !== void 0) {
|
|
759
|
+
messageParts.push(typeof arg.value === "string" ? arg.value : JSON.stringify(arg.value));
|
|
760
|
+
} else if (arg.description) {
|
|
761
|
+
messageParts.push(arg.description);
|
|
762
|
+
} else {
|
|
763
|
+
messageParts.push(arg.type ?? "undefined");
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (state.consoleLogs.length >= state.maxConsoleLogs) {
|
|
767
|
+
state.consoleLogs.shift();
|
|
768
|
+
}
|
|
769
|
+
state.consoleLogs.push({
|
|
770
|
+
level: type,
|
|
771
|
+
message: messageParts.join(" "),
|
|
772
|
+
timestamp: Date.now()
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
777
|
+
if (captures.has(tabId)) {
|
|
778
|
+
void chrome.debugger.detach({ tabId }).catch(() => {
|
|
779
|
+
});
|
|
780
|
+
captures.delete(tabId);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
var startCapture = async (tabId, maxRequests = 100, urlFilter, maxConsoleLogs = 500) => {
|
|
784
|
+
if (captures.has(tabId)) {
|
|
785
|
+
throw new Error(`Network capture already active for tab ${tabId}. Call stopCapture first.`);
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION);
|
|
789
|
+
} catch (err2) {
|
|
790
|
+
throw new Error(`Failed to attach debugger to tab ${tabId}: ${toErrorMessage(err2)}. Another debugger (e.g., DevTools) may already be attached.`);
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
await chrome.debugger.sendCommand({ tabId }, "Network.enable");
|
|
794
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
|
|
795
|
+
} catch (err2) {
|
|
796
|
+
await chrome.debugger.detach({ tabId }).catch(() => {
|
|
797
|
+
});
|
|
798
|
+
throw err2;
|
|
799
|
+
}
|
|
800
|
+
captures.set(tabId, {
|
|
801
|
+
requests: [],
|
|
802
|
+
consoleLogs: [],
|
|
803
|
+
maxRequests,
|
|
804
|
+
maxConsoleLogs,
|
|
805
|
+
urlFilter,
|
|
806
|
+
pendingRequests: /* @__PURE__ */ new Map(),
|
|
807
|
+
requestIdToRequest: /* @__PURE__ */ new Map()
|
|
808
|
+
});
|
|
809
|
+
};
|
|
810
|
+
var stopCapture = (tabId) => {
|
|
811
|
+
const state = captures.get(tabId);
|
|
812
|
+
if (!state)
|
|
813
|
+
return;
|
|
814
|
+
void chrome.debugger.detach({ tabId }).catch(() => {
|
|
815
|
+
});
|
|
816
|
+
captures.delete(tabId);
|
|
817
|
+
};
|
|
818
|
+
var getRequests = (tabId, clear = false) => {
|
|
819
|
+
const state = captures.get(tabId);
|
|
820
|
+
if (!state)
|
|
821
|
+
return [];
|
|
822
|
+
const requests = state.requests.map((req) => ({
|
|
823
|
+
...req,
|
|
824
|
+
requestHeaders: scrubHeaders(req.requestHeaders),
|
|
825
|
+
responseHeaders: scrubHeaders(req.responseHeaders)
|
|
826
|
+
}));
|
|
827
|
+
if (clear) {
|
|
828
|
+
state.requests = [];
|
|
829
|
+
state.requestIdToRequest.clear();
|
|
830
|
+
}
|
|
831
|
+
return requests;
|
|
832
|
+
};
|
|
833
|
+
var isCapturing = (tabId) => captures.has(tabId);
|
|
834
|
+
var getConsoleLogs = (tabId, clear = false, level) => {
|
|
835
|
+
const state = captures.get(tabId);
|
|
836
|
+
if (!state)
|
|
837
|
+
return [];
|
|
838
|
+
let logs = [...state.consoleLogs];
|
|
839
|
+
if (level && level !== "all") {
|
|
840
|
+
logs = logs.filter((entry) => entry.level === level);
|
|
841
|
+
}
|
|
842
|
+
if (clear) {
|
|
843
|
+
state.consoleLogs = [];
|
|
844
|
+
}
|
|
845
|
+
return logs;
|
|
846
|
+
};
|
|
847
|
+
var clearConsoleLogs = (tabId) => {
|
|
848
|
+
const state = captures.get(tabId);
|
|
849
|
+
if (state) {
|
|
850
|
+
state.consoleLogs = [];
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
var getActiveCapturesSummary = () => Array.from(captures.entries()).map(([tabId, state]) => ({
|
|
854
|
+
tabId,
|
|
855
|
+
requestCount: state.requests.length,
|
|
856
|
+
isCapturing: true
|
|
857
|
+
}));
|
|
858
|
+
|
|
859
|
+
// dist/plugin-storage.js
|
|
860
|
+
var metaCache = null;
|
|
861
|
+
var writeMutex = Promise.resolve();
|
|
862
|
+
var serialize = (fn) => {
|
|
863
|
+
const result = writeMutex.then(fn);
|
|
864
|
+
writeMutex = result.then(() => {
|
|
865
|
+
}, () => {
|
|
866
|
+
});
|
|
867
|
+
return result;
|
|
868
|
+
};
|
|
869
|
+
var storePluginsBatch = (metas) => serialize(async () => {
|
|
870
|
+
if (metas.length === 0)
|
|
871
|
+
return;
|
|
872
|
+
const index = await getAllPluginMeta();
|
|
873
|
+
for (const meta of metas) {
|
|
874
|
+
index[meta.name] = meta;
|
|
875
|
+
}
|
|
876
|
+
await chrome.storage.local.set({ [PLUGINS_META_KEY]: index });
|
|
877
|
+
metaCache = index;
|
|
878
|
+
});
|
|
879
|
+
var removePlugin = (pluginName) => serialize(async () => {
|
|
880
|
+
const index = await getAllPluginMeta();
|
|
881
|
+
if (!(pluginName in index))
|
|
882
|
+
return;
|
|
883
|
+
const { [pluginName]: _, ...rest } = index;
|
|
884
|
+
await chrome.storage.local.set({ [PLUGINS_META_KEY]: rest });
|
|
885
|
+
metaCache = rest;
|
|
886
|
+
});
|
|
887
|
+
var removePluginsBatch = (pluginNames) => serialize(async () => {
|
|
888
|
+
if (pluginNames.length === 0)
|
|
889
|
+
return;
|
|
890
|
+
const index = await getAllPluginMeta();
|
|
891
|
+
const removeSet = new Set(pluginNames);
|
|
892
|
+
const filtered = {};
|
|
893
|
+
for (const [name, meta] of Object.entries(index)) {
|
|
894
|
+
if (!removeSet.has(name)) {
|
|
895
|
+
filtered[name] = meta;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
await chrome.storage.local.set({ [PLUGINS_META_KEY]: filtered });
|
|
899
|
+
metaCache = filtered;
|
|
900
|
+
});
|
|
901
|
+
var VALID_TRUST_TIERS = /* @__PURE__ */ new Set(["official", "community", "local"]);
|
|
902
|
+
var isValidPluginMeta = (value) => {
|
|
903
|
+
if (typeof value !== "object" || value === null)
|
|
904
|
+
return false;
|
|
905
|
+
const obj = value;
|
|
906
|
+
return typeof obj.name === "string" && typeof obj.version === "string" && Array.isArray(obj.urlPatterns) && typeof obj.trustTier === "string" && VALID_TRUST_TIERS.has(obj.trustTier) && Array.isArray(obj.tools);
|
|
907
|
+
};
|
|
908
|
+
var getAllPluginMeta = async () => {
|
|
909
|
+
if (metaCache !== null) {
|
|
910
|
+
return { ...metaCache };
|
|
911
|
+
}
|
|
912
|
+
const data = await chrome.storage.local.get(PLUGINS_META_KEY);
|
|
913
|
+
const index = data[PLUGINS_META_KEY];
|
|
914
|
+
if (!index || typeof index !== "object") {
|
|
915
|
+
metaCache = {};
|
|
916
|
+
return {};
|
|
917
|
+
}
|
|
918
|
+
const validated = {};
|
|
919
|
+
for (const [key, value] of Object.entries(index)) {
|
|
920
|
+
if (isValidPluginMeta(value)) {
|
|
921
|
+
validated[key] = value;
|
|
922
|
+
} else {
|
|
923
|
+
console.warn(`[opentabs] Skipping corrupted plugin meta entry: ${key}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
metaCache = validated;
|
|
927
|
+
return { ...validated };
|
|
928
|
+
};
|
|
929
|
+
var getPluginMeta = async (pluginName) => {
|
|
930
|
+
const index = await getAllPluginMeta();
|
|
931
|
+
return index[pluginName];
|
|
932
|
+
};
|
|
933
|
+
var invalidatePluginCache = () => {
|
|
934
|
+
metaCache = null;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// dist/tab-matching.js
|
|
938
|
+
var urlMatchesPatterns = (url, patterns) => {
|
|
939
|
+
for (const pattern of patterns) {
|
|
940
|
+
if (matchPattern(url, pattern))
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
return false;
|
|
944
|
+
};
|
|
945
|
+
var matchPattern = (url, pattern) => {
|
|
946
|
+
const matchResult = pattern.match(/^(\*|https?|ftp):\/\/(.+?)(\/.*)$/);
|
|
947
|
+
if (!matchResult?.[1] || !matchResult[2] || !matchResult[3])
|
|
948
|
+
return false;
|
|
949
|
+
const [, scheme, hostWithPort, path] = matchResult;
|
|
950
|
+
let patternHost;
|
|
951
|
+
let patternPort;
|
|
952
|
+
const colonIdx = hostWithPort.lastIndexOf(":");
|
|
953
|
+
if (colonIdx > 0 && /^\d+$/.test(hostWithPort.slice(colonIdx + 1))) {
|
|
954
|
+
patternHost = hostWithPort.slice(0, colonIdx);
|
|
955
|
+
patternPort = hostWithPort.slice(colonIdx + 1);
|
|
956
|
+
} else {
|
|
957
|
+
patternHost = hostWithPort;
|
|
958
|
+
patternPort = "";
|
|
959
|
+
}
|
|
960
|
+
let parsed;
|
|
961
|
+
try {
|
|
962
|
+
parsed = new URL(url);
|
|
963
|
+
} catch {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
if (scheme !== "*" && parsed.protocol !== `${scheme}:`)
|
|
967
|
+
return false;
|
|
968
|
+
if (scheme === "*" && !["http:", "https:"].includes(parsed.protocol))
|
|
969
|
+
return false;
|
|
970
|
+
if (patternPort) {
|
|
971
|
+
if (parsed.port !== patternPort)
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
if (patternHost !== "*") {
|
|
975
|
+
if (patternHost.startsWith("*.")) {
|
|
976
|
+
const suffix = patternHost.slice(2);
|
|
977
|
+
if (parsed.hostname !== suffix && !parsed.hostname.endsWith(`.${suffix}`))
|
|
978
|
+
return false;
|
|
979
|
+
} else {
|
|
980
|
+
if (parsed.hostname !== patternHost)
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (path !== "/*") {
|
|
985
|
+
const pathRegex = new RegExp("^" + path.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
|
|
986
|
+
if (!pathRegex.test(parsed.pathname))
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
return true;
|
|
990
|
+
};
|
|
991
|
+
var findAllMatchingTabs = async (plugin) => {
|
|
992
|
+
const seen = /* @__PURE__ */ new Set();
|
|
993
|
+
const allMatches = [];
|
|
994
|
+
for (const pattern of plugin.urlPatterns) {
|
|
995
|
+
let tabs;
|
|
996
|
+
try {
|
|
997
|
+
tabs = await chrome.tabs.query({ url: pattern });
|
|
998
|
+
} catch {
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
for (const tab of tabs) {
|
|
1002
|
+
if (tab.id !== void 0 && !seen.has(tab.id)) {
|
|
1003
|
+
seen.add(tab.id);
|
|
1004
|
+
allMatches.push(tab);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (allMatches.length <= 1)
|
|
1009
|
+
return allMatches;
|
|
1010
|
+
let focusedWindowId;
|
|
1011
|
+
try {
|
|
1012
|
+
const focusedWindow = await chrome.windows.getLastFocused();
|
|
1013
|
+
focusedWindowId = focusedWindow.id;
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
const rank = (tab) => {
|
|
1017
|
+
const inFocused = focusedWindowId !== void 0 && tab.windowId === focusedWindowId;
|
|
1018
|
+
if (tab.active && inFocused)
|
|
1019
|
+
return 3;
|
|
1020
|
+
if (tab.active)
|
|
1021
|
+
return 2;
|
|
1022
|
+
if (inFocused)
|
|
1023
|
+
return 1;
|
|
1024
|
+
return 0;
|
|
1025
|
+
};
|
|
1026
|
+
return allMatches.slice().sort((a, b) => rank(b) - rank(a));
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
// dist/tab-state.js
|
|
1030
|
+
var lastKnownState = /* @__PURE__ */ new Map();
|
|
1031
|
+
var pluginLocks = /* @__PURE__ */ new Map();
|
|
1032
|
+
var withPluginLock = (pluginName, fn) => {
|
|
1033
|
+
const prev = pluginLocks.get(pluginName) ?? Promise.resolve();
|
|
1034
|
+
const next = prev.then(fn);
|
|
1035
|
+
pluginLocks.set(pluginName, next.catch((err2) => {
|
|
1036
|
+
console.warn("[opentabs] tab state operation failed for plugin", pluginName, ":", err2);
|
|
1037
|
+
}));
|
|
1038
|
+
return next;
|
|
1039
|
+
};
|
|
1040
|
+
var probeTabReadiness = async (tabId, pluginName) => {
|
|
1041
|
+
let timerId;
|
|
1042
|
+
try {
|
|
1043
|
+
const results = await Promise.race([
|
|
1044
|
+
chrome.scripting.executeScript({
|
|
1045
|
+
target: { tabId },
|
|
1046
|
+
world: "MAIN",
|
|
1047
|
+
func: async (pName) => {
|
|
1048
|
+
const ot = globalThis.__openTabs;
|
|
1049
|
+
const adapter = ot?.adapters?.[pName];
|
|
1050
|
+
if (!adapter || typeof adapter !== "object")
|
|
1051
|
+
return false;
|
|
1052
|
+
if (typeof adapter.isReady !== "function")
|
|
1053
|
+
return false;
|
|
1054
|
+
return await adapter.isReady();
|
|
1055
|
+
},
|
|
1056
|
+
args: [pluginName]
|
|
1057
|
+
}),
|
|
1058
|
+
new Promise((resolve) => {
|
|
1059
|
+
timerId = setTimeout(() => resolve(null), IS_READY_TIMEOUT_MS);
|
|
1060
|
+
})
|
|
1061
|
+
]);
|
|
1062
|
+
if (results === null) {
|
|
1063
|
+
console.warn(`[opentabs] isReady() timed out for plugin "${pluginName}" in tab ${tabId}`);
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
const readyResult = results[0];
|
|
1067
|
+
return readyResult?.result === true;
|
|
1068
|
+
} finally {
|
|
1069
|
+
if (timerId !== void 0)
|
|
1070
|
+
clearTimeout(timerId);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
var computePluginTabState = async (plugin) => {
|
|
1074
|
+
const tabs = await findAllMatchingTabs(plugin);
|
|
1075
|
+
if (tabs.length === 0) {
|
|
1076
|
+
return { state: "closed", tabId: null, url: null };
|
|
1077
|
+
}
|
|
1078
|
+
let firstUnavailable;
|
|
1079
|
+
for (const tab of tabs) {
|
|
1080
|
+
if (tab.id === void 0)
|
|
1081
|
+
continue;
|
|
1082
|
+
try {
|
|
1083
|
+
const ready = await probeTabReadiness(tab.id, plugin.name);
|
|
1084
|
+
if (ready) {
|
|
1085
|
+
return { state: "ready", tabId: tab.id, url: tab.url ?? null };
|
|
1086
|
+
}
|
|
1087
|
+
firstUnavailable ??= tab;
|
|
1088
|
+
} catch (err2) {
|
|
1089
|
+
console.warn(`[opentabs] computePluginTabState failed for plugin ${plugin.name} in tab ${tab.id}:`, err2);
|
|
1090
|
+
firstUnavailable ??= tab;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const fallbackTab = firstUnavailable ?? tabs[0];
|
|
1094
|
+
return {
|
|
1095
|
+
state: "unavailable",
|
|
1096
|
+
tabId: fallbackTab?.id ?? null,
|
|
1097
|
+
url: fallbackTab?.url ?? null
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
var sendTabSyncAll = async () => {
|
|
1101
|
+
const index = await getAllPluginMeta();
|
|
1102
|
+
const plugins = Object.values(index);
|
|
1103
|
+
if (plugins.length === 0)
|
|
1104
|
+
return;
|
|
1105
|
+
const settled = await Promise.allSettled(plugins.map(async (plugin) => [plugin.name, await computePluginTabState(plugin)]));
|
|
1106
|
+
const entries = [];
|
|
1107
|
+
for (const result of settled) {
|
|
1108
|
+
if (result.status === "fulfilled") {
|
|
1109
|
+
entries.push(result.value);
|
|
1110
|
+
} else {
|
|
1111
|
+
console.warn("[opentabs] Tab state computation failed during syncAll:", result.reason);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (entries.length === 0)
|
|
1115
|
+
return;
|
|
1116
|
+
const tabSyncPayload = Object.fromEntries(entries);
|
|
1117
|
+
const pluginNamesInSync = /* @__PURE__ */ new Set();
|
|
1118
|
+
await Promise.all(entries.map(([pluginName, stateInfo]) => {
|
|
1119
|
+
pluginNamesInSync.add(pluginName);
|
|
1120
|
+
return withPluginLock(pluginName, () => {
|
|
1121
|
+
lastKnownState.set(pluginName, stateInfo.state);
|
|
1122
|
+
return Promise.resolve();
|
|
1123
|
+
});
|
|
1124
|
+
}));
|
|
1125
|
+
for (const key of lastKnownState.keys()) {
|
|
1126
|
+
if (!pluginNamesInSync.has(key)) {
|
|
1127
|
+
lastKnownState.delete(key);
|
|
1128
|
+
pluginLocks.delete(key);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
sendToServer({
|
|
1132
|
+
jsonrpc: "2.0",
|
|
1133
|
+
method: "tab.syncAll",
|
|
1134
|
+
params: { tabs: tabSyncPayload }
|
|
1135
|
+
});
|
|
1136
|
+
for (const [pluginName, stateInfo] of entries) {
|
|
1137
|
+
forwardToSidePanel({
|
|
1138
|
+
type: "sp:serverMessage",
|
|
1139
|
+
data: {
|
|
1140
|
+
jsonrpc: "2.0",
|
|
1141
|
+
method: "tab.stateChanged",
|
|
1142
|
+
params: { plugin: pluginName, state: stateInfo.state, tabId: stateInfo.tabId, url: stateInfo.url }
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
var clearTabStateCache = () => {
|
|
1148
|
+
lastKnownState.clear();
|
|
1149
|
+
pluginLocks.clear();
|
|
1150
|
+
};
|
|
1151
|
+
var clearPluginTabState = (pluginName) => {
|
|
1152
|
+
lastKnownState.delete(pluginName);
|
|
1153
|
+
pluginLocks.delete(pluginName);
|
|
1154
|
+
};
|
|
1155
|
+
var updateLastKnownState = (pluginName, state) => withPluginLock(pluginName, () => {
|
|
1156
|
+
lastKnownState.set(pluginName, state);
|
|
1157
|
+
return Promise.resolve();
|
|
1158
|
+
});
|
|
1159
|
+
var getLastKnownStates = () => lastKnownState;
|
|
1160
|
+
var notifyAffectedPlugins = async (affectedPlugins) => {
|
|
1161
|
+
await Promise.all(affectedPlugins.map((plugin) => withPluginLock(plugin.name, async () => {
|
|
1162
|
+
const newState = await computePluginTabState(plugin);
|
|
1163
|
+
const previous = lastKnownState.get(plugin.name);
|
|
1164
|
+
if (previous === newState.state)
|
|
1165
|
+
return;
|
|
1166
|
+
lastKnownState.set(plugin.name, newState.state);
|
|
1167
|
+
sendTabStateNotification(plugin.name, newState);
|
|
1168
|
+
})));
|
|
1169
|
+
};
|
|
1170
|
+
var checkTabRemoved = async (_removedTabId) => {
|
|
1171
|
+
const index = await getAllPluginMeta();
|
|
1172
|
+
const plugins = Object.values(index);
|
|
1173
|
+
if (plugins.length === 0)
|
|
1174
|
+
return;
|
|
1175
|
+
await notifyAffectedPlugins(plugins);
|
|
1176
|
+
};
|
|
1177
|
+
var checkTabChanged = async (changedTabId, changeInfo) => {
|
|
1178
|
+
const index = await getAllPluginMeta();
|
|
1179
|
+
const plugins = Object.values(index);
|
|
1180
|
+
if (plugins.length === 0)
|
|
1181
|
+
return;
|
|
1182
|
+
let affectedPlugins;
|
|
1183
|
+
if (changeInfo.url) {
|
|
1184
|
+
const changedUrl = changeInfo.url;
|
|
1185
|
+
affectedPlugins = plugins.filter((p) => urlMatchesPatterns(changedUrl, p.urlPatterns) || lastKnownState.has(p.name) && lastKnownState.get(p.name) !== "closed");
|
|
1186
|
+
} else if (changeInfo.status === "complete") {
|
|
1187
|
+
let tabUrl;
|
|
1188
|
+
try {
|
|
1189
|
+
const tab = await chrome.tabs.get(changedTabId);
|
|
1190
|
+
tabUrl = tab.url;
|
|
1191
|
+
} catch {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (!tabUrl)
|
|
1195
|
+
return;
|
|
1196
|
+
affectedPlugins = plugins.filter((p) => urlMatchesPatterns(tabUrl, p.urlPatterns) || lastKnownState.has(p.name) && lastKnownState.get(p.name) !== "closed");
|
|
1197
|
+
} else {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (affectedPlugins.length === 0)
|
|
1201
|
+
return;
|
|
1202
|
+
await notifyAffectedPlugins(affectedPlugins);
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
// dist/browser-commands/extension-commands.js
|
|
1206
|
+
var handleExtensionGetState = async (id) => {
|
|
1207
|
+
try {
|
|
1208
|
+
const sessionData = await chrome.storage.session.get(WS_CONNECTED_KEY).catch(() => ({}));
|
|
1209
|
+
const wsConnected2 = typeof sessionData[WS_CONNECTED_KEY] === "boolean" ? sessionData[WS_CONNECTED_KEY] : false;
|
|
1210
|
+
const localData = await chrome.storage.local.get(SERVER_PORT_KEY).catch(() => ({}));
|
|
1211
|
+
const port = typeof localData[SERVER_PORT_KEY] === "number" && localData[SERVER_PORT_KEY] > 0 ? localData[SERVER_PORT_KEY] : DEFAULT_SERVER_PORT;
|
|
1212
|
+
const mcpServerUrl = buildWsUrl(port);
|
|
1213
|
+
const pluginIndex = await getAllPluginMeta();
|
|
1214
|
+
const lastKnownStates = getLastKnownStates();
|
|
1215
|
+
const plugins = Object.values(pluginIndex).map((meta) => ({
|
|
1216
|
+
name: meta.name,
|
|
1217
|
+
version: meta.version,
|
|
1218
|
+
displayName: meta.displayName,
|
|
1219
|
+
urlPatterns: meta.urlPatterns,
|
|
1220
|
+
toolCount: meta.tools.length,
|
|
1221
|
+
tabState: lastKnownStates.get(meta.name) ?? "closed"
|
|
1222
|
+
}));
|
|
1223
|
+
const networkCaptures = getActiveCapturesSummary();
|
|
1224
|
+
let offscreenExists = false;
|
|
1225
|
+
try {
|
|
1226
|
+
const contexts = await chrome.runtime.getContexts({
|
|
1227
|
+
contextTypes: ["OFFSCREEN_DOCUMENT"]
|
|
1228
|
+
});
|
|
1229
|
+
offscreenExists = contexts.length > 0;
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
sendSuccessResult(id, {
|
|
1233
|
+
connection: { wsConnected: wsConnected2, mcpServerUrl },
|
|
1234
|
+
plugins,
|
|
1235
|
+
networkCaptures,
|
|
1236
|
+
offscreen: { exists: offscreenExists }
|
|
1237
|
+
});
|
|
1238
|
+
} catch (err2) {
|
|
1239
|
+
sendErrorResult(id, err2);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
var handleExtensionGetLogs = async (params, id) => {
|
|
1243
|
+
try {
|
|
1244
|
+
const filterOptions = {};
|
|
1245
|
+
if (typeof params.level === "string" && params.level !== "all") {
|
|
1246
|
+
filterOptions.level = params.level;
|
|
1247
|
+
}
|
|
1248
|
+
if (typeof params.source === "string" && params.source !== "all") {
|
|
1249
|
+
filterOptions.source = params.source;
|
|
1250
|
+
}
|
|
1251
|
+
if (typeof params.limit === "number") {
|
|
1252
|
+
filterOptions.limit = params.limit;
|
|
1253
|
+
}
|
|
1254
|
+
if (typeof params.since === "number") {
|
|
1255
|
+
filterOptions.since = params.since;
|
|
1256
|
+
}
|
|
1257
|
+
const bgEntries = bgLogCollector.getEntries(filterOptions);
|
|
1258
|
+
const bgStats = bgLogCollector.getStats();
|
|
1259
|
+
let offscreenEntries = [];
|
|
1260
|
+
let offscreenStats = {
|
|
1261
|
+
totalCaptured: 0,
|
|
1262
|
+
bufferSize: 0,
|
|
1263
|
+
oldestTimestamp: null,
|
|
1264
|
+
newestTimestamp: null
|
|
1265
|
+
};
|
|
1266
|
+
try {
|
|
1267
|
+
const raw = await chrome.runtime.sendMessage({
|
|
1268
|
+
type: "offscreen:getLogs",
|
|
1269
|
+
options: Object.keys(filterOptions).length > 0 ? filterOptions : void 0
|
|
1270
|
+
});
|
|
1271
|
+
const response = raw;
|
|
1272
|
+
if (response && Array.isArray(response.entries)) {
|
|
1273
|
+
offscreenEntries = response.entries;
|
|
1274
|
+
}
|
|
1275
|
+
if (response?.stats) {
|
|
1276
|
+
offscreenStats = response.stats;
|
|
1277
|
+
}
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
const merged = [...bgEntries, ...offscreenEntries].sort((a, b) => b.timestamp - a.timestamp);
|
|
1281
|
+
const limit = filterOptions.limit ?? DEFAULT_LOG_LIMIT;
|
|
1282
|
+
const entries = merged.slice(0, limit);
|
|
1283
|
+
sendSuccessResult(id, {
|
|
1284
|
+
entries,
|
|
1285
|
+
stats: {
|
|
1286
|
+
totalBackground: bgStats.totalCaptured,
|
|
1287
|
+
totalOffscreen: offscreenStats.totalCaptured,
|
|
1288
|
+
bufferSize: bgStats.bufferSize + offscreenStats.bufferSize
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
} catch (err2) {
|
|
1292
|
+
sendErrorResult(id, err2);
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
var handleExtensionGetSidePanel = async (id) => {
|
|
1296
|
+
try {
|
|
1297
|
+
const sidePanelResult = await Promise.race([
|
|
1298
|
+
chrome.runtime.sendMessage({ type: "sp:getState" }).then((raw) => raw),
|
|
1299
|
+
new Promise((resolve) => setTimeout(() => resolve(null), SIDE_PANEL_TIMEOUT_MS))
|
|
1300
|
+
]);
|
|
1301
|
+
if (!sidePanelResult || typeof sidePanelResult !== "object") {
|
|
1302
|
+
sendSuccessResult(id, { open: false });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const response = sidePanelResult;
|
|
1306
|
+
sendSuccessResult(id, { open: true, state: response.state, html: response.html });
|
|
1307
|
+
} catch {
|
|
1308
|
+
sendSuccessResult(id, { open: false });
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
var handleExtensionCheckAdapter = async (params, id) => {
|
|
1312
|
+
try {
|
|
1313
|
+
const pluginName = requireStringParam(params, "plugin", id);
|
|
1314
|
+
if (pluginName === null)
|
|
1315
|
+
return;
|
|
1316
|
+
const meta = await getPluginMeta(pluginName);
|
|
1317
|
+
if (!meta) {
|
|
1318
|
+
sendValidationError(id, `Plugin not found: "${pluginName}"`);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const matchingTabs = await findAllMatchingTabs(meta);
|
|
1322
|
+
const tabResults = await Promise.allSettled(matchingTabs.map(async (tab) => {
|
|
1323
|
+
const tabId = tab.id;
|
|
1324
|
+
if (tabId === void 0)
|
|
1325
|
+
return null;
|
|
1326
|
+
const inspectResults = await chrome.scripting.executeScript({
|
|
1327
|
+
target: { tabId },
|
|
1328
|
+
world: "MAIN",
|
|
1329
|
+
func: (pName) => {
|
|
1330
|
+
const ot = globalThis.__openTabs;
|
|
1331
|
+
const adapter = ot?.adapters?.[pName];
|
|
1332
|
+
if (!adapter || typeof adapter !== "object") {
|
|
1333
|
+
return { adapterPresent: false };
|
|
1334
|
+
}
|
|
1335
|
+
const toolNames = [];
|
|
1336
|
+
if (Array.isArray(adapter.tools)) {
|
|
1337
|
+
for (const tool of adapter.tools) {
|
|
1338
|
+
if (tool && typeof tool === "object" && typeof tool.name === "string") {
|
|
1339
|
+
toolNames.push(tool.name);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
adapterPresent: true,
|
|
1345
|
+
adapterHash: typeof adapter.__hash === "string" ? adapter.__hash : null,
|
|
1346
|
+
toolCount: toolNames.length,
|
|
1347
|
+
toolNames
|
|
1348
|
+
};
|
|
1349
|
+
},
|
|
1350
|
+
args: [pluginName]
|
|
1351
|
+
});
|
|
1352
|
+
const inspectResult = inspectResults[0]?.result;
|
|
1353
|
+
if (!inspectResult) {
|
|
1354
|
+
return {
|
|
1355
|
+
tabId,
|
|
1356
|
+
tabUrl: tab.url ?? "",
|
|
1357
|
+
adapterPresent: false,
|
|
1358
|
+
adapterHash: null,
|
|
1359
|
+
hashMatch: false,
|
|
1360
|
+
isReady: false,
|
|
1361
|
+
toolCount: 0,
|
|
1362
|
+
toolNames: []
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
if (!inspectResult.adapterPresent) {
|
|
1366
|
+
return {
|
|
1367
|
+
tabId,
|
|
1368
|
+
tabUrl: tab.url ?? "",
|
|
1369
|
+
adapterPresent: false,
|
|
1370
|
+
adapterHash: null,
|
|
1371
|
+
hashMatch: false,
|
|
1372
|
+
isReady: false,
|
|
1373
|
+
toolCount: 0,
|
|
1374
|
+
toolNames: []
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
let isReady = false;
|
|
1378
|
+
try {
|
|
1379
|
+
const readyResults = await Promise.race([
|
|
1380
|
+
chrome.scripting.executeScript({
|
|
1381
|
+
target: { tabId },
|
|
1382
|
+
world: "MAIN",
|
|
1383
|
+
func: async (pName) => {
|
|
1384
|
+
const ot = globalThis.__openTabs;
|
|
1385
|
+
const adapter = ot?.adapters?.[pName];
|
|
1386
|
+
if (!adapter || typeof adapter.isReady !== "function")
|
|
1387
|
+
return false;
|
|
1388
|
+
return await adapter.isReady();
|
|
1389
|
+
},
|
|
1390
|
+
args: [pluginName]
|
|
1391
|
+
}),
|
|
1392
|
+
new Promise((resolve) => setTimeout(() => resolve(null), IS_READY_TIMEOUT_MS))
|
|
1393
|
+
]);
|
|
1394
|
+
if (readyResults !== null) {
|
|
1395
|
+
const readyResult = readyResults[0];
|
|
1396
|
+
isReady = readyResult?.result === true;
|
|
1397
|
+
}
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
tabId,
|
|
1402
|
+
tabUrl: tab.url ?? "",
|
|
1403
|
+
adapterPresent: true,
|
|
1404
|
+
adapterHash: inspectResult.adapterHash ?? null,
|
|
1405
|
+
hashMatch: meta.adapterHash ? inspectResult.adapterHash === meta.adapterHash : false,
|
|
1406
|
+
isReady,
|
|
1407
|
+
toolCount: inspectResult.toolCount ?? 0,
|
|
1408
|
+
toolNames: inspectResult.toolNames ?? []
|
|
1409
|
+
};
|
|
1410
|
+
}));
|
|
1411
|
+
const matchingTabResults = [];
|
|
1412
|
+
for (const result of tabResults) {
|
|
1413
|
+
if (result.status === "fulfilled" && result.value !== null) {
|
|
1414
|
+
matchingTabResults.push(result.value);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
sendSuccessResult(id, {
|
|
1418
|
+
plugin: pluginName,
|
|
1419
|
+
expectedHash: meta.adapterHash ?? null,
|
|
1420
|
+
matchingTabs: matchingTabResults
|
|
1421
|
+
});
|
|
1422
|
+
} catch (err2) {
|
|
1423
|
+
sendErrorResult(id, err2);
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
var handleExtensionForceReconnect = async (id) => {
|
|
1427
|
+
try {
|
|
1428
|
+
sendSuccessResult(id, { reconnecting: true });
|
|
1429
|
+
await new Promise((resolve) => setTimeout(resolve, WS_FLUSH_DELAY_MS));
|
|
1430
|
+
await chrome.runtime.sendMessage({
|
|
1431
|
+
type: "bg:forceReconnect"
|
|
1432
|
+
});
|
|
1433
|
+
} catch (err2) {
|
|
1434
|
+
console.warn("[opentabs] extension.forceReconnect failed:", err2);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
var handleBrowserExecuteScript = async (params, id) => {
|
|
1438
|
+
try {
|
|
1439
|
+
const tabId = requireTabId(params, id);
|
|
1440
|
+
if (tabId === null)
|
|
1441
|
+
return;
|
|
1442
|
+
const execFile = requireStringParam(params, "execFile", id);
|
|
1443
|
+
if (execFile === null)
|
|
1444
|
+
return;
|
|
1445
|
+
if (!/^__exec-[a-f0-9-]+\.js$/.test(execFile)) {
|
|
1446
|
+
sendValidationError(id, "Invalid execFile format");
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
let timeoutId;
|
|
1450
|
+
const injectPromise = (async () => {
|
|
1451
|
+
await chrome.scripting.executeScript({
|
|
1452
|
+
target: { tabId },
|
|
1453
|
+
world: "MAIN",
|
|
1454
|
+
files: [`adapters/${execFile}`]
|
|
1455
|
+
});
|
|
1456
|
+
let elapsed = 0;
|
|
1457
|
+
while (elapsed <= EXEC_MAX_ASYNC_WAIT_MS) {
|
|
1458
|
+
const results = await chrome.scripting.executeScript({
|
|
1459
|
+
target: { tabId },
|
|
1460
|
+
world: "MAIN",
|
|
1461
|
+
func: (truncLimit) => {
|
|
1462
|
+
const ot = globalThis.__openTabs;
|
|
1463
|
+
if (!ot)
|
|
1464
|
+
return { pending: false, result: { error: "__openTabs not found" } };
|
|
1465
|
+
const result2 = ot.__lastExecResult;
|
|
1466
|
+
const isAsync = ot.__lastExecAsync === true;
|
|
1467
|
+
if (result2 && ("value" in result2 || "error" in result2)) {
|
|
1468
|
+
const captured = { ...result2 };
|
|
1469
|
+
if (captured.value === void 0)
|
|
1470
|
+
captured.value = null;
|
|
1471
|
+
if (captured.value !== null && typeof captured.value === "object") {
|
|
1472
|
+
try {
|
|
1473
|
+
const json = JSON.stringify(captured.value);
|
|
1474
|
+
captured.value = json.length > truncLimit ? json.slice(0, truncLimit) + "... (truncated)" : JSON.parse(json);
|
|
1475
|
+
} catch {
|
|
1476
|
+
captured.value = String(captured.value);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
delete ot.__lastExecResult;
|
|
1480
|
+
delete ot.__lastExecAsync;
|
|
1481
|
+
return { pending: false, result: captured };
|
|
1482
|
+
}
|
|
1483
|
+
if (isAsync)
|
|
1484
|
+
return { pending: true };
|
|
1485
|
+
return { pending: false, result: { error: "No result captured" } };
|
|
1486
|
+
},
|
|
1487
|
+
args: [EXEC_RESULT_TRUNCATION_LIMIT]
|
|
1488
|
+
});
|
|
1489
|
+
const first = results[0];
|
|
1490
|
+
const data = first?.result;
|
|
1491
|
+
if (data && !data.pending) {
|
|
1492
|
+
return { value: data.result };
|
|
1493
|
+
}
|
|
1494
|
+
await new Promise((resolve) => setTimeout(resolve, EXEC_POLL_INTERVAL_MS));
|
|
1495
|
+
elapsed += EXEC_POLL_INTERVAL_MS;
|
|
1496
|
+
}
|
|
1497
|
+
await chrome.scripting.executeScript({
|
|
1498
|
+
target: { tabId },
|
|
1499
|
+
world: "MAIN",
|
|
1500
|
+
func: () => {
|
|
1501
|
+
const ot = globalThis.__openTabs;
|
|
1502
|
+
if (ot) {
|
|
1503
|
+
delete ot.__lastExecResult;
|
|
1504
|
+
delete ot.__lastExecAsync;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}).catch(() => {
|
|
1508
|
+
});
|
|
1509
|
+
return { value: { error: `Async code did not resolve within ${EXEC_MAX_ASYNC_WAIT_MS}ms` } };
|
|
1510
|
+
})();
|
|
1511
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
1512
|
+
timeoutId = setTimeout(() => {
|
|
1513
|
+
reject(new Error(`Script execution timed out after ${SCRIPT_TIMEOUT_MS}ms`));
|
|
1514
|
+
}, SCRIPT_TIMEOUT_MS);
|
|
1515
|
+
});
|
|
1516
|
+
let result;
|
|
1517
|
+
try {
|
|
1518
|
+
result = await Promise.race([injectPromise, timeoutPromise]);
|
|
1519
|
+
} finally {
|
|
1520
|
+
clearTimeout(timeoutId);
|
|
1521
|
+
}
|
|
1522
|
+
sendSuccessResult(id, result);
|
|
1523
|
+
} catch (err2) {
|
|
1524
|
+
sendErrorResult(id, err2);
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
// dist/browser-commands/resource-commands.js
|
|
1529
|
+
var TEXT_MIME_PREFIXES = ["text/"];
|
|
1530
|
+
var TEXT_MIME_EXACT = /* @__PURE__ */ new Set([
|
|
1531
|
+
"application/javascript",
|
|
1532
|
+
"application/json",
|
|
1533
|
+
"application/xml",
|
|
1534
|
+
"application/xhtml+xml",
|
|
1535
|
+
"application/x-javascript",
|
|
1536
|
+
"application/ecmascript"
|
|
1537
|
+
]);
|
|
1538
|
+
var isTextMimeType = (mimeType) => {
|
|
1539
|
+
if (TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix)))
|
|
1540
|
+
return true;
|
|
1541
|
+
return TEXT_MIME_EXACT.has(mimeType);
|
|
1542
|
+
};
|
|
1543
|
+
var findFrameForResource = (tree, targetUrl) => {
|
|
1544
|
+
for (const r of tree.resources) {
|
|
1545
|
+
if (r.url === targetUrl) {
|
|
1546
|
+
return { frameId: tree.frame.id, mimeType: r.mimeType };
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (tree.childFrames) {
|
|
1550
|
+
for (const child of tree.childFrames) {
|
|
1551
|
+
const found = findFrameForResource(child, targetUrl);
|
|
1552
|
+
if (found)
|
|
1553
|
+
return found;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return null;
|
|
1557
|
+
};
|
|
1558
|
+
var withDebugger = async (tabId, fn) => {
|
|
1559
|
+
const alreadyAttached = isCapturing(tabId);
|
|
1560
|
+
if (!alreadyAttached) {
|
|
1561
|
+
try {
|
|
1562
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION);
|
|
1563
|
+
} catch (err2) {
|
|
1564
|
+
const msg = toErrorMessage(err2);
|
|
1565
|
+
throw new Error(msg.includes("Another debugger") ? "Failed to attach debugger \u2014 another debugger (e.g., DevTools) is already attached. Close DevTools or enable network capture first (browser_enable_network_capture) so this tool can reuse the existing debugger session." : `Failed to attach debugger: ${sanitizeErrorMessage(msg)}`);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
try {
|
|
1569
|
+
return await fn();
|
|
1570
|
+
} finally {
|
|
1571
|
+
if (!alreadyAttached) {
|
|
1572
|
+
await chrome.debugger.detach({ tabId }).catch(() => {
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
var handleBrowserListResources = async (params, id) => {
|
|
1578
|
+
try {
|
|
1579
|
+
const tabId = requireTabId(params, id);
|
|
1580
|
+
if (tabId === null)
|
|
1581
|
+
return;
|
|
1582
|
+
const typeFilter = typeof params.type === "string" ? params.type : void 0;
|
|
1583
|
+
await withDebugger(tabId, async () => {
|
|
1584
|
+
await chrome.debugger.sendCommand({ tabId }, "Page.enable");
|
|
1585
|
+
const treeResult = await chrome.debugger.sendCommand({ tabId }, "Page.getResourceTree");
|
|
1586
|
+
const frames = [];
|
|
1587
|
+
const resources = [];
|
|
1588
|
+
const walk = (node) => {
|
|
1589
|
+
frames.push({ url: node.frame.url, securityOrigin: node.frame.securityOrigin });
|
|
1590
|
+
for (const r of node.resources) {
|
|
1591
|
+
if (typeFilter && r.type !== typeFilter)
|
|
1592
|
+
continue;
|
|
1593
|
+
resources.push({
|
|
1594
|
+
url: r.url,
|
|
1595
|
+
type: r.type,
|
|
1596
|
+
mimeType: r.mimeType,
|
|
1597
|
+
contentLength: r.contentLength ?? -1
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
if (node.childFrames) {
|
|
1601
|
+
for (const child of node.childFrames)
|
|
1602
|
+
walk(child);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
walk(treeResult.frameTree);
|
|
1606
|
+
resources.sort((a, b) => a.type.localeCompare(b.type) || a.url.localeCompare(b.url));
|
|
1607
|
+
sendSuccessResult(id, { frames, resources });
|
|
1608
|
+
});
|
|
1609
|
+
} catch (err2) {
|
|
1610
|
+
sendErrorResult(id, err2);
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
var handleBrowserGetResourceContent = async (params, id) => {
|
|
1614
|
+
try {
|
|
1615
|
+
const tabId = requireTabId(params, id);
|
|
1616
|
+
if (tabId === null)
|
|
1617
|
+
return;
|
|
1618
|
+
const url = requireStringParam(params, "url", id);
|
|
1619
|
+
if (url === null)
|
|
1620
|
+
return;
|
|
1621
|
+
const maxLength = typeof params.maxLength === "number" ? params.maxLength : 5e5;
|
|
1622
|
+
await withDebugger(tabId, async () => {
|
|
1623
|
+
await chrome.debugger.sendCommand({ tabId }, "Page.enable");
|
|
1624
|
+
const treeResult = await chrome.debugger.sendCommand({ tabId }, "Page.getResourceTree");
|
|
1625
|
+
const match = findFrameForResource(treeResult.frameTree, url);
|
|
1626
|
+
if (!match) {
|
|
1627
|
+
sendValidationError(id, `Resource not found in page: ${url}. Use browser_list_resources to find valid resource URLs.`);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const contentResult = await chrome.debugger.sendCommand({ tabId }, "Page.getResourceContent", {
|
|
1631
|
+
frameId: match.frameId,
|
|
1632
|
+
url
|
|
1633
|
+
});
|
|
1634
|
+
let content = contentResult.content;
|
|
1635
|
+
let base64Encoded = contentResult.base64Encoded;
|
|
1636
|
+
if (base64Encoded && isTextMimeType(match.mimeType)) {
|
|
1637
|
+
try {
|
|
1638
|
+
content = new TextDecoder().decode(Uint8Array.from(atob(content), (c) => c.charCodeAt(0)));
|
|
1639
|
+
base64Encoded = false;
|
|
1640
|
+
} catch {
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
let truncated = false;
|
|
1644
|
+
if (!base64Encoded && content.length > maxLength) {
|
|
1645
|
+
content = content.slice(0, maxLength) + "... (truncated)";
|
|
1646
|
+
truncated = true;
|
|
1647
|
+
}
|
|
1648
|
+
sendSuccessResult(id, { url, content, base64Encoded, mimeType: match.mimeType, truncated });
|
|
1649
|
+
});
|
|
1650
|
+
} catch (err2) {
|
|
1651
|
+
sendErrorResult(id, err2);
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
// dist/browser-commands/interaction-commands.js
|
|
1656
|
+
var handleBrowserClickElement = async (params, id) => {
|
|
1657
|
+
try {
|
|
1658
|
+
const tabId = requireTabId(params, id);
|
|
1659
|
+
if (tabId === null)
|
|
1660
|
+
return;
|
|
1661
|
+
const selector = requireSelector(params, id);
|
|
1662
|
+
if (selector === null)
|
|
1663
|
+
return;
|
|
1664
|
+
const results = await chrome.scripting.executeScript({
|
|
1665
|
+
target: { tabId },
|
|
1666
|
+
world: "MAIN",
|
|
1667
|
+
func: (sel, maxPreview) => {
|
|
1668
|
+
const el = document.querySelector(sel);
|
|
1669
|
+
if (!el)
|
|
1670
|
+
return { error: `Element not found: ${sel}` };
|
|
1671
|
+
el.click();
|
|
1672
|
+
return {
|
|
1673
|
+
clicked: true,
|
|
1674
|
+
tagName: el.tagName.toLowerCase(),
|
|
1675
|
+
text: (el.textContent || "").trim().slice(0, maxPreview)
|
|
1676
|
+
};
|
|
1677
|
+
},
|
|
1678
|
+
args: [selector, TEXT_PREVIEW_MAX_LENGTH]
|
|
1679
|
+
});
|
|
1680
|
+
const result = extractScriptResult(results, id);
|
|
1681
|
+
if (!result)
|
|
1682
|
+
return;
|
|
1683
|
+
sendSuccessResult(id, { clicked: result.clicked, tagName: result.tagName, text: result.text });
|
|
1684
|
+
} catch (err2) {
|
|
1685
|
+
sendErrorResult(id, err2);
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
var handleBrowserTypeText = async (params, id) => {
|
|
1689
|
+
try {
|
|
1690
|
+
const tabId = requireTabId(params, id);
|
|
1691
|
+
if (tabId === null)
|
|
1692
|
+
return;
|
|
1693
|
+
const selector = requireSelector(params, id);
|
|
1694
|
+
if (selector === null)
|
|
1695
|
+
return;
|
|
1696
|
+
const text = requireStringParam(params, "text", id);
|
|
1697
|
+
if (text === null)
|
|
1698
|
+
return;
|
|
1699
|
+
const clear = typeof params.clear === "boolean" ? params.clear : true;
|
|
1700
|
+
const results = await chrome.scripting.executeScript({
|
|
1701
|
+
target: { tabId },
|
|
1702
|
+
world: "MAIN",
|
|
1703
|
+
func: (sel, txt, clr) => {
|
|
1704
|
+
const el = document.querySelector(sel);
|
|
1705
|
+
if (!el)
|
|
1706
|
+
return { error: `Element not found: ${sel}` };
|
|
1707
|
+
const tag = el.tagName.toLowerCase();
|
|
1708
|
+
const isEditable = tag === "input" || tag === "textarea" || el.isContentEditable;
|
|
1709
|
+
if (!isEditable)
|
|
1710
|
+
return { error: `Element is not a text input (found <${tag}>)` };
|
|
1711
|
+
if (tag === "input" || tag === "textarea") {
|
|
1712
|
+
const input = el;
|
|
1713
|
+
input.focus();
|
|
1714
|
+
input.value = clr ? txt : input.value + txt;
|
|
1715
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1716
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1717
|
+
return { typed: true, tagName: tag, value: input.value };
|
|
1718
|
+
}
|
|
1719
|
+
const htmlEl = el;
|
|
1720
|
+
htmlEl.focus();
|
|
1721
|
+
if (clr)
|
|
1722
|
+
htmlEl.textContent = "";
|
|
1723
|
+
const selection = window.getSelection();
|
|
1724
|
+
if (selection) {
|
|
1725
|
+
if (selection.rangeCount === 0) {
|
|
1726
|
+
const range2 = document.createRange();
|
|
1727
|
+
range2.selectNodeContents(htmlEl);
|
|
1728
|
+
range2.collapse(false);
|
|
1729
|
+
selection.removeAllRanges();
|
|
1730
|
+
selection.addRange(range2);
|
|
1731
|
+
}
|
|
1732
|
+
const range = selection.getRangeAt(0);
|
|
1733
|
+
range.deleteContents();
|
|
1734
|
+
range.insertNode(document.createTextNode(txt));
|
|
1735
|
+
range.collapse(false);
|
|
1736
|
+
selection.removeAllRanges();
|
|
1737
|
+
selection.addRange(range);
|
|
1738
|
+
}
|
|
1739
|
+
htmlEl.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: txt }));
|
|
1740
|
+
return { typed: true, tagName: tag, value: htmlEl.textContent || "" };
|
|
1741
|
+
},
|
|
1742
|
+
args: [selector, text, clear]
|
|
1743
|
+
});
|
|
1744
|
+
const result = extractScriptResult(results, id);
|
|
1745
|
+
if (!result)
|
|
1746
|
+
return;
|
|
1747
|
+
sendSuccessResult(id, { typed: result.typed, tagName: result.tagName, value: result.value });
|
|
1748
|
+
} catch (err2) {
|
|
1749
|
+
sendErrorResult(id, err2);
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
var handleBrowserSelectOption = async (params, id) => {
|
|
1753
|
+
try {
|
|
1754
|
+
const tabId = requireTabId(params, id);
|
|
1755
|
+
if (tabId === null)
|
|
1756
|
+
return;
|
|
1757
|
+
const selector = requireSelector(params, id);
|
|
1758
|
+
if (selector === null)
|
|
1759
|
+
return;
|
|
1760
|
+
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1761
|
+
const label = typeof params.label === "string" ? params.label : void 0;
|
|
1762
|
+
if (value === void 0 && label === void 0) {
|
|
1763
|
+
sendValidationError(id, "At least one of value or label must be provided");
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const results = await chrome.scripting.executeScript({
|
|
1767
|
+
target: { tabId },
|
|
1768
|
+
world: "MAIN",
|
|
1769
|
+
func: (sel, val, lbl) => {
|
|
1770
|
+
const el = document.querySelector(sel);
|
|
1771
|
+
if (!el)
|
|
1772
|
+
return { error: `Element not found: ${sel}` };
|
|
1773
|
+
if (el.tagName.toLowerCase() !== "select")
|
|
1774
|
+
return { error: `Element is not a <select>: ${sel}` };
|
|
1775
|
+
const select = el;
|
|
1776
|
+
const options = Array.from(select.options);
|
|
1777
|
+
let matchedIndex = -1;
|
|
1778
|
+
for (let i = 0; i < options.length; i++) {
|
|
1779
|
+
const opt = options[i];
|
|
1780
|
+
if (!opt)
|
|
1781
|
+
continue;
|
|
1782
|
+
const isMatch = val !== null ? opt.value === val : (opt.textContent || "").trim() === lbl;
|
|
1783
|
+
if (isMatch) {
|
|
1784
|
+
matchedIndex = i;
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (matchedIndex === -1) {
|
|
1789
|
+
const criterion = val !== null ? `value="${val}"` : `label="${String(lbl)}"`;
|
|
1790
|
+
return { error: `Option not found: ${criterion}` };
|
|
1791
|
+
}
|
|
1792
|
+
select.selectedIndex = matchedIndex;
|
|
1793
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1794
|
+
const selectedOpt = options[matchedIndex];
|
|
1795
|
+
return {
|
|
1796
|
+
selected: true,
|
|
1797
|
+
value: selectedOpt ? selectedOpt.value : "",
|
|
1798
|
+
label: selectedOpt ? (selectedOpt.textContent || "").trim() : ""
|
|
1799
|
+
};
|
|
1800
|
+
},
|
|
1801
|
+
args: [selector, value ?? null, label ?? null]
|
|
1802
|
+
});
|
|
1803
|
+
const result = extractScriptResult(results, id);
|
|
1804
|
+
if (!result)
|
|
1805
|
+
return;
|
|
1806
|
+
sendSuccessResult(id, { selected: result.selected, value: result.value, label: result.label });
|
|
1807
|
+
} catch (err2) {
|
|
1808
|
+
sendErrorResult(id, err2);
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
var handleBrowserWaitForElement = async (params, id) => {
|
|
1812
|
+
try {
|
|
1813
|
+
const tabId = requireTabId(params, id);
|
|
1814
|
+
if (tabId === null)
|
|
1815
|
+
return;
|
|
1816
|
+
const selector = requireSelector(params, id);
|
|
1817
|
+
if (selector === null)
|
|
1818
|
+
return;
|
|
1819
|
+
const timeout = typeof params.timeout === "number" ? params.timeout : DEFAULT_WAIT_TIMEOUT_MS;
|
|
1820
|
+
const visible = typeof params.visible === "boolean" ? params.visible : false;
|
|
1821
|
+
const results = await chrome.scripting.executeScript({
|
|
1822
|
+
target: { tabId },
|
|
1823
|
+
world: "MAIN",
|
|
1824
|
+
func: (sel, tmo, vis, maxPreview, pollMs) => new Promise((resolve) => {
|
|
1825
|
+
let elapsed = 0;
|
|
1826
|
+
const poll = setInterval(() => {
|
|
1827
|
+
const el = document.querySelector(sel);
|
|
1828
|
+
if (el) {
|
|
1829
|
+
const htmlEl = el;
|
|
1830
|
+
const isVisible = !vis || htmlEl.offsetParent !== null && getComputedStyle(htmlEl).display !== "none";
|
|
1831
|
+
if (isVisible) {
|
|
1832
|
+
clearInterval(poll);
|
|
1833
|
+
resolve({
|
|
1834
|
+
found: true,
|
|
1835
|
+
tagName: el.tagName.toLowerCase(),
|
|
1836
|
+
text: (el.textContent || "").trim().slice(0, maxPreview)
|
|
1837
|
+
});
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
elapsed += pollMs;
|
|
1842
|
+
if (elapsed >= tmo) {
|
|
1843
|
+
clearInterval(poll);
|
|
1844
|
+
resolve({ error: `Timeout waiting for element: ${sel} (${tmo}ms)` });
|
|
1845
|
+
}
|
|
1846
|
+
}, pollMs);
|
|
1847
|
+
}),
|
|
1848
|
+
args: [selector, timeout, visible, TEXT_PREVIEW_MAX_LENGTH, POLL_INTERVAL_MS]
|
|
1849
|
+
});
|
|
1850
|
+
const result = extractScriptResult(results, id);
|
|
1851
|
+
if (!result)
|
|
1852
|
+
return;
|
|
1853
|
+
sendSuccessResult(id, { found: result.found, tagName: result.tagName, text: result.text });
|
|
1854
|
+
} catch (err2) {
|
|
1855
|
+
sendErrorResult(id, err2);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var handleBrowserQueryElements = async (params, id) => {
|
|
1859
|
+
try {
|
|
1860
|
+
const tabId = requireTabId(params, id);
|
|
1861
|
+
if (tabId === null)
|
|
1862
|
+
return;
|
|
1863
|
+
const selector = requireSelector(params, id);
|
|
1864
|
+
if (selector === null)
|
|
1865
|
+
return;
|
|
1866
|
+
const limit = typeof params.limit === "number" ? params.limit : DEFAULT_QUERY_LIMIT;
|
|
1867
|
+
const attributes = Array.isArray(params.attributes) ? params.attributes.filter((a) => typeof a === "string") : ["id", "class", "href", "src", "type", "name", "value", "placeholder"];
|
|
1868
|
+
const results = await chrome.scripting.executeScript({
|
|
1869
|
+
target: { tabId },
|
|
1870
|
+
world: "MAIN",
|
|
1871
|
+
func: (sel, lim, attrs, maxPreview) => {
|
|
1872
|
+
const all = document.querySelectorAll(sel);
|
|
1873
|
+
const elements = Array.from(all).slice(0, lim).map((el) => ({
|
|
1874
|
+
tagName: el.tagName.toLowerCase(),
|
|
1875
|
+
text: (el.textContent || "").trim().slice(0, maxPreview),
|
|
1876
|
+
attributes: Object.fromEntries(attrs.filter((a) => el.hasAttribute(a)).map((a) => [a, el.getAttribute(a)]))
|
|
1877
|
+
}));
|
|
1878
|
+
return { count: all.length, elements };
|
|
1879
|
+
},
|
|
1880
|
+
args: [selector, limit, attributes, TEXT_PREVIEW_MAX_LENGTH]
|
|
1881
|
+
});
|
|
1882
|
+
const result = extractScriptResult(results, id, "No result from query");
|
|
1883
|
+
if (!result)
|
|
1884
|
+
return;
|
|
1885
|
+
sendSuccessResult(id, { count: result.count, elements: result.elements });
|
|
1886
|
+
} catch (err2) {
|
|
1887
|
+
sendErrorResult(id, err2);
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
var handleBrowserHoverElement = async (params, id) => {
|
|
1891
|
+
try {
|
|
1892
|
+
const tabId = requireTabId(params, id);
|
|
1893
|
+
if (tabId === null)
|
|
1894
|
+
return;
|
|
1895
|
+
const selector = requireSelector(params, id);
|
|
1896
|
+
if (selector === null)
|
|
1897
|
+
return;
|
|
1898
|
+
const results = await chrome.scripting.executeScript({
|
|
1899
|
+
target: { tabId },
|
|
1900
|
+
world: "MAIN",
|
|
1901
|
+
func: (sel, maxPreview) => {
|
|
1902
|
+
const el = document.querySelector(sel);
|
|
1903
|
+
if (!el)
|
|
1904
|
+
return { error: `Element not found: ${sel}` };
|
|
1905
|
+
const rect = el.getBoundingClientRect();
|
|
1906
|
+
const clientX = rect.left + rect.width / 2;
|
|
1907
|
+
const clientY = rect.top + rect.height / 2;
|
|
1908
|
+
const pointerOpts = {
|
|
1909
|
+
clientX,
|
|
1910
|
+
clientY,
|
|
1911
|
+
pointerId: 1,
|
|
1912
|
+
pointerType: "mouse"
|
|
1913
|
+
};
|
|
1914
|
+
const mouseOpts = { clientX, clientY };
|
|
1915
|
+
el.dispatchEvent(new PointerEvent("pointerenter", { bubbles: false, ...pointerOpts }));
|
|
1916
|
+
el.dispatchEvent(new PointerEvent("pointerover", { bubbles: true, ...pointerOpts }));
|
|
1917
|
+
el.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, ...mouseOpts }));
|
|
1918
|
+
el.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, ...mouseOpts }));
|
|
1919
|
+
el.dispatchEvent(new PointerEvent("pointermove", { bubbles: true, ...pointerOpts }));
|
|
1920
|
+
el.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, ...mouseOpts }));
|
|
1921
|
+
return {
|
|
1922
|
+
hovered: true,
|
|
1923
|
+
tagName: el.tagName.toLowerCase(),
|
|
1924
|
+
text: (el.textContent || "").trim().slice(0, maxPreview),
|
|
1925
|
+
bounds: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
|
1926
|
+
};
|
|
1927
|
+
},
|
|
1928
|
+
args: [selector, TEXT_PREVIEW_MAX_LENGTH]
|
|
1929
|
+
});
|
|
1930
|
+
const result = extractScriptResult(results, id);
|
|
1931
|
+
if (!result)
|
|
1932
|
+
return;
|
|
1933
|
+
sendSuccessResult(id, {
|
|
1934
|
+
hovered: result.hovered,
|
|
1935
|
+
tagName: result.tagName,
|
|
1936
|
+
text: result.text,
|
|
1937
|
+
bounds: result.bounds
|
|
1938
|
+
});
|
|
1939
|
+
} catch (err2) {
|
|
1940
|
+
sendErrorResult(id, err2);
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
var handleBrowserHandleDialog = async (params, id) => {
|
|
1944
|
+
try {
|
|
1945
|
+
const tabId = requireTabId(params, id);
|
|
1946
|
+
if (tabId === null)
|
|
1947
|
+
return;
|
|
1948
|
+
const action = requireStringParam(params, "action", id);
|
|
1949
|
+
if (action === null)
|
|
1950
|
+
return;
|
|
1951
|
+
if (action !== "accept" && action !== "dismiss") {
|
|
1952
|
+
sendValidationError(id, "action must be 'accept' or 'dismiss'");
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
const accept = action === "accept";
|
|
1956
|
+
const promptText = typeof params.promptText === "string" ? params.promptText : void 0;
|
|
1957
|
+
await withDebugger(tabId, async () => {
|
|
1958
|
+
await chrome.debugger.sendCommand({ tabId }, "Page.enable");
|
|
1959
|
+
try {
|
|
1960
|
+
await chrome.debugger.sendCommand({ tabId }, "Page.handleJavaScriptDialog", {
|
|
1961
|
+
accept,
|
|
1962
|
+
...promptText !== void 0 ? { promptText } : {}
|
|
1963
|
+
});
|
|
1964
|
+
sendSuccessResult(id, { handled: true, action });
|
|
1965
|
+
} catch (err2) {
|
|
1966
|
+
const msg = toErrorMessage(err2);
|
|
1967
|
+
const isNoDialog = msg.includes("No dialog is showing") || msg.includes("no dialog");
|
|
1968
|
+
sendErrorResult(id, isNoDialog ? new Error("No JavaScript dialog is currently open on this tab") : err2);
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
} catch (err2) {
|
|
1972
|
+
sendErrorResult(id, err2);
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
// dist/browser-commands/key-press-command.js
|
|
1977
|
+
var handleBrowserPressKey = async (params, id) => {
|
|
1978
|
+
try {
|
|
1979
|
+
const tabId = requireTabId(params, id);
|
|
1980
|
+
if (tabId === null)
|
|
1981
|
+
return;
|
|
1982
|
+
const key = requireStringParam(params, "key", id);
|
|
1983
|
+
if (key === null)
|
|
1984
|
+
return;
|
|
1985
|
+
const selector = typeof params.selector === "string" && params.selector.length > 0 ? params.selector : null;
|
|
1986
|
+
const modifiers = typeof params.modifiers === "object" && params.modifiers !== null ? params.modifiers : {};
|
|
1987
|
+
const shiftKey = modifiers.shift === true;
|
|
1988
|
+
const ctrlKey = modifiers.ctrl === true;
|
|
1989
|
+
const altKey = modifiers.alt === true;
|
|
1990
|
+
const metaKey = modifiers.meta === true;
|
|
1991
|
+
const results = await chrome.scripting.executeScript({
|
|
1992
|
+
target: { tabId },
|
|
1993
|
+
world: "MAIN",
|
|
1994
|
+
func: (k, sel, shift, ctrl, alt, meta) => {
|
|
1995
|
+
let target = null;
|
|
1996
|
+
if (sel) {
|
|
1997
|
+
target = document.querySelector(sel);
|
|
1998
|
+
if (!target)
|
|
1999
|
+
return { error: `Element not found: ${sel}` };
|
|
2000
|
+
target.focus();
|
|
2001
|
+
} else {
|
|
2002
|
+
target = document.activeElement ?? document.body;
|
|
2003
|
+
}
|
|
2004
|
+
const deriveCode = (k2) => {
|
|
2005
|
+
if (k2.length === 1) {
|
|
2006
|
+
const upper = k2.toUpperCase();
|
|
2007
|
+
if (upper >= "A" && upper <= "Z")
|
|
2008
|
+
return `Key${upper}`;
|
|
2009
|
+
if (k2 >= "0" && k2 <= "9")
|
|
2010
|
+
return `Digit${k2}`;
|
|
2011
|
+
if (k2 === " ")
|
|
2012
|
+
return "Space";
|
|
2013
|
+
return k2;
|
|
2014
|
+
}
|
|
2015
|
+
return k2;
|
|
2016
|
+
};
|
|
2017
|
+
const KEY_CODES = {
|
|
2018
|
+
Enter: 13,
|
|
2019
|
+
Escape: 27,
|
|
2020
|
+
Tab: 9,
|
|
2021
|
+
Backspace: 8,
|
|
2022
|
+
Delete: 46,
|
|
2023
|
+
ArrowUp: 38,
|
|
2024
|
+
ArrowDown: 40,
|
|
2025
|
+
ArrowLeft: 37,
|
|
2026
|
+
ArrowRight: 39,
|
|
2027
|
+
Home: 36,
|
|
2028
|
+
End: 35,
|
|
2029
|
+
PageUp: 33,
|
|
2030
|
+
PageDown: 34,
|
|
2031
|
+
" ": 32
|
|
2032
|
+
};
|
|
2033
|
+
const getKeyCode = (k2) => {
|
|
2034
|
+
if (KEY_CODES[k2] !== void 0)
|
|
2035
|
+
return KEY_CODES[k2];
|
|
2036
|
+
if (k2.length === 1)
|
|
2037
|
+
return k2.toUpperCase().charCodeAt(0);
|
|
2038
|
+
return 0;
|
|
2039
|
+
};
|
|
2040
|
+
const code = deriveCode(k);
|
|
2041
|
+
const keyCode = getKeyCode(k);
|
|
2042
|
+
const isPrintable = k.length === 1;
|
|
2043
|
+
const eventInit = {
|
|
2044
|
+
key: k,
|
|
2045
|
+
code,
|
|
2046
|
+
keyCode,
|
|
2047
|
+
which: keyCode,
|
|
2048
|
+
bubbles: true,
|
|
2049
|
+
cancelable: true,
|
|
2050
|
+
shiftKey: shift,
|
|
2051
|
+
ctrlKey: ctrl,
|
|
2052
|
+
metaKey: meta,
|
|
2053
|
+
altKey: alt
|
|
2054
|
+
};
|
|
2055
|
+
target.dispatchEvent(new KeyboardEvent("keydown", eventInit));
|
|
2056
|
+
if (isPrintable) {
|
|
2057
|
+
target.dispatchEvent(new KeyboardEvent("keypress", eventInit));
|
|
2058
|
+
}
|
|
2059
|
+
target.dispatchEvent(new KeyboardEvent("keyup", eventInit));
|
|
2060
|
+
if (isPrintable) {
|
|
2061
|
+
const tag = target.tagName.toLowerCase();
|
|
2062
|
+
const isEditable = tag === "input" || tag === "textarea" || target.isContentEditable;
|
|
2063
|
+
if (isEditable) {
|
|
2064
|
+
if (tag === "input" || tag === "textarea") {
|
|
2065
|
+
const input = target;
|
|
2066
|
+
const start = input.selectionStart ?? input.value.length;
|
|
2067
|
+
const end = input.selectionEnd ?? start;
|
|
2068
|
+
input.value = input.value.slice(0, start) + k + input.value.slice(end);
|
|
2069
|
+
input.selectionStart = input.selectionEnd = start + 1;
|
|
2070
|
+
} else {
|
|
2071
|
+
const selection = window.getSelection();
|
|
2072
|
+
if (selection && selection.rangeCount > 0) {
|
|
2073
|
+
const range = selection.getRangeAt(0);
|
|
2074
|
+
range.deleteContents();
|
|
2075
|
+
range.insertNode(document.createTextNode(k));
|
|
2076
|
+
range.collapse(false);
|
|
2077
|
+
selection.removeAllRanges();
|
|
2078
|
+
selection.addRange(range);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
target.dispatchEvent(new InputEvent("input", {
|
|
2082
|
+
bubbles: true,
|
|
2083
|
+
cancelable: true,
|
|
2084
|
+
inputType: "insertText",
|
|
2085
|
+
data: k
|
|
2086
|
+
}));
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
return {
|
|
2090
|
+
pressed: true,
|
|
2091
|
+
key: k,
|
|
2092
|
+
target: {
|
|
2093
|
+
tagName: target.tagName.toLowerCase(),
|
|
2094
|
+
id: target.id || void 0
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
},
|
|
2098
|
+
args: [key, selector, shiftKey, ctrlKey, altKey, metaKey]
|
|
2099
|
+
});
|
|
2100
|
+
const result = extractScriptResult(results, id);
|
|
2101
|
+
if (!result)
|
|
2102
|
+
return;
|
|
2103
|
+
sendSuccessResult(id, { pressed: result.pressed, key: result.key, target: result.target });
|
|
2104
|
+
} catch (err2) {
|
|
2105
|
+
sendErrorResult(id, err2);
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// dist/browser-commands/scroll-command.js
|
|
2110
|
+
var handleBrowserScroll = async (params, id) => {
|
|
2111
|
+
try {
|
|
2112
|
+
const tabId = requireTabId(params, id);
|
|
2113
|
+
if (tabId === null)
|
|
2114
|
+
return;
|
|
2115
|
+
const selector = typeof params.selector === "string" && params.selector.length > 0 ? params.selector : null;
|
|
2116
|
+
const direction = typeof params.direction === "string" ? params.direction : null;
|
|
2117
|
+
if (direction !== null && direction !== "up" && direction !== "down" && direction !== "left" && direction !== "right") {
|
|
2118
|
+
sendValidationError(id, `Invalid direction: "${direction}". Must be one of: up, down, left, right`);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
const distance = typeof params.distance === "number" ? params.distance : null;
|
|
2122
|
+
const position = typeof params.position === "object" && params.position !== null ? params.position : null;
|
|
2123
|
+
const container = typeof params.container === "string" && params.container.length > 0 ? params.container : null;
|
|
2124
|
+
const results = await chrome.scripting.executeScript({
|
|
2125
|
+
target: { tabId },
|
|
2126
|
+
world: "MAIN",
|
|
2127
|
+
func: (sel, dir, dist, pos, ctr, maxPreview) => {
|
|
2128
|
+
let scrollEl = null;
|
|
2129
|
+
if (ctr) {
|
|
2130
|
+
scrollEl = document.querySelector(ctr);
|
|
2131
|
+
if (!scrollEl)
|
|
2132
|
+
return { error: `Container not found: ${ctr}` };
|
|
2133
|
+
}
|
|
2134
|
+
const getMetrics = () => {
|
|
2135
|
+
if (scrollEl) {
|
|
2136
|
+
return {
|
|
2137
|
+
scrollPosition: { x: scrollEl.scrollLeft, y: scrollEl.scrollTop },
|
|
2138
|
+
scrollSize: { width: scrollEl.scrollWidth, height: scrollEl.scrollHeight },
|
|
2139
|
+
viewportSize: { width: scrollEl.clientWidth, height: scrollEl.clientHeight }
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
return {
|
|
2143
|
+
scrollPosition: { x: window.scrollX, y: window.scrollY },
|
|
2144
|
+
scrollSize: {
|
|
2145
|
+
width: document.documentElement.scrollWidth,
|
|
2146
|
+
height: document.documentElement.scrollHeight
|
|
2147
|
+
},
|
|
2148
|
+
viewportSize: { width: window.innerWidth, height: window.innerHeight }
|
|
2149
|
+
};
|
|
2150
|
+
};
|
|
2151
|
+
if (sel) {
|
|
2152
|
+
const el = document.querySelector(sel);
|
|
2153
|
+
if (!el)
|
|
2154
|
+
return { error: `Element not found: ${sel}` };
|
|
2155
|
+
el.scrollIntoView({ behavior: "instant", block: "center" });
|
|
2156
|
+
const text = (el.textContent || "").trim().slice(0, maxPreview);
|
|
2157
|
+
return {
|
|
2158
|
+
scrolledTo: { tagName: el.tagName.toLowerCase(), text },
|
|
2159
|
+
...getMetrics()
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
if (dir) {
|
|
2163
|
+
const metrics = getMetrics();
|
|
2164
|
+
const defaultVertical = metrics.viewportSize.height;
|
|
2165
|
+
const defaultHorizontal = metrics.viewportSize.width;
|
|
2166
|
+
let dx = 0;
|
|
2167
|
+
let dy = 0;
|
|
2168
|
+
if (dir === "down")
|
|
2169
|
+
dy = dist ?? defaultVertical;
|
|
2170
|
+
else if (dir === "up")
|
|
2171
|
+
dy = -(dist ?? defaultVertical);
|
|
2172
|
+
else if (dir === "right")
|
|
2173
|
+
dx = dist ?? defaultHorizontal;
|
|
2174
|
+
else if (dir === "left")
|
|
2175
|
+
dx = -(dist ?? defaultHorizontal);
|
|
2176
|
+
if (scrollEl) {
|
|
2177
|
+
scrollEl.scrollBy({ left: dx, top: dy, behavior: "instant" });
|
|
2178
|
+
} else {
|
|
2179
|
+
window.scrollBy({ left: dx, top: dy, behavior: "instant" });
|
|
2180
|
+
}
|
|
2181
|
+
return getMetrics();
|
|
2182
|
+
}
|
|
2183
|
+
if (pos) {
|
|
2184
|
+
const opts = { behavior: "instant" };
|
|
2185
|
+
if (pos.x !== void 0)
|
|
2186
|
+
opts.left = pos.x;
|
|
2187
|
+
if (pos.y !== void 0)
|
|
2188
|
+
opts.top = pos.y;
|
|
2189
|
+
if (scrollEl) {
|
|
2190
|
+
scrollEl.scrollTo(opts);
|
|
2191
|
+
} else {
|
|
2192
|
+
window.scrollTo(opts);
|
|
2193
|
+
}
|
|
2194
|
+
return getMetrics();
|
|
2195
|
+
}
|
|
2196
|
+
return getMetrics();
|
|
2197
|
+
},
|
|
2198
|
+
args: [
|
|
2199
|
+
selector,
|
|
2200
|
+
direction,
|
|
2201
|
+
distance,
|
|
2202
|
+
position ? {
|
|
2203
|
+
x: position.x,
|
|
2204
|
+
y: position.y
|
|
2205
|
+
} : null,
|
|
2206
|
+
container,
|
|
2207
|
+
TEXT_PREVIEW_MAX_LENGTH
|
|
2208
|
+
]
|
|
2209
|
+
});
|
|
2210
|
+
const result = extractScriptResult(results, id);
|
|
2211
|
+
if (!result)
|
|
2212
|
+
return;
|
|
2213
|
+
sendSuccessResult(id, {
|
|
2214
|
+
...result.scrolledTo !== void 0 ? { scrolledTo: result.scrolledTo } : {},
|
|
2215
|
+
scrollPosition: result.scrollPosition,
|
|
2216
|
+
scrollSize: result.scrollSize,
|
|
2217
|
+
viewportSize: result.viewportSize
|
|
2218
|
+
});
|
|
2219
|
+
} catch (err2) {
|
|
2220
|
+
sendErrorResult(id, err2);
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
|
|
2224
|
+
// dist/browser-commands/network-commands.js
|
|
2225
|
+
var handleBrowserEnableNetworkCapture = async (params, id) => {
|
|
2226
|
+
try {
|
|
2227
|
+
const tabId = requireTabId(params, id);
|
|
2228
|
+
if (tabId === null)
|
|
2229
|
+
return;
|
|
2230
|
+
const maxRequests = typeof params.maxRequests === "number" ? params.maxRequests : 100;
|
|
2231
|
+
const urlFilter = typeof params.urlFilter === "string" ? params.urlFilter : void 0;
|
|
2232
|
+
const maxConsoleLogs = typeof params.maxConsoleLogs === "number" ? params.maxConsoleLogs : 500;
|
|
2233
|
+
await startCapture(tabId, maxRequests, urlFilter, maxConsoleLogs);
|
|
2234
|
+
sendSuccessResult(id, { enabled: true, tabId });
|
|
2235
|
+
} catch (err2) {
|
|
2236
|
+
sendErrorResult(id, err2);
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
var handleBrowserGetNetworkRequests = (params, id) => {
|
|
2240
|
+
try {
|
|
2241
|
+
const tabId = requireTabId(params, id);
|
|
2242
|
+
if (tabId === null)
|
|
2243
|
+
return;
|
|
2244
|
+
const clear = typeof params.clear === "boolean" ? params.clear : false;
|
|
2245
|
+
const requests = getRequests(tabId, clear);
|
|
2246
|
+
sendSuccessResult(id, { requests });
|
|
2247
|
+
} catch (err2) {
|
|
2248
|
+
sendErrorResult(id, err2);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
var handleBrowserDisableNetworkCapture = (params, id) => {
|
|
2252
|
+
try {
|
|
2253
|
+
const tabId = requireTabId(params, id);
|
|
2254
|
+
if (tabId === null)
|
|
2255
|
+
return;
|
|
2256
|
+
stopCapture(tabId);
|
|
2257
|
+
sendSuccessResult(id, { disabled: true, tabId });
|
|
2258
|
+
} catch (err2) {
|
|
2259
|
+
sendErrorResult(id, err2);
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
var handleBrowserGetConsoleLogs = (params, id) => {
|
|
2263
|
+
try {
|
|
2264
|
+
const tabId = requireTabId(params, id);
|
|
2265
|
+
if (tabId === null)
|
|
2266
|
+
return;
|
|
2267
|
+
const clear = typeof params.clear === "boolean" ? params.clear : false;
|
|
2268
|
+
const level = typeof params.level === "string" ? params.level : void 0;
|
|
2269
|
+
const logs = getConsoleLogs(tabId, clear, level);
|
|
2270
|
+
sendSuccessResult(id, { logs });
|
|
2271
|
+
} catch (err2) {
|
|
2272
|
+
sendErrorResult(id, err2);
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
var handleBrowserClearConsoleLogs = (params, id) => {
|
|
2276
|
+
try {
|
|
2277
|
+
const tabId = requireTabId(params, id);
|
|
2278
|
+
if (tabId === null)
|
|
2279
|
+
return;
|
|
2280
|
+
clearConsoleLogs(tabId);
|
|
2281
|
+
sendSuccessResult(id, { cleared: true });
|
|
2282
|
+
} catch (err2) {
|
|
2283
|
+
sendErrorResult(id, err2);
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
// dist/browser-commands/tab-commands.js
|
|
2288
|
+
var handleBrowserListTabs = async (id) => {
|
|
2289
|
+
try {
|
|
2290
|
+
const tabs = await chrome.tabs.query({});
|
|
2291
|
+
const result = tabs.map((tab) => ({
|
|
2292
|
+
id: tab.id,
|
|
2293
|
+
title: tab.title ?? "",
|
|
2294
|
+
url: tab.url ?? "",
|
|
2295
|
+
active: tab.active,
|
|
2296
|
+
windowId: tab.windowId
|
|
2297
|
+
}));
|
|
2298
|
+
sendSuccessResult(id, result);
|
|
2299
|
+
} catch (err2) {
|
|
2300
|
+
sendErrorResult(id, err2);
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
var handleBrowserOpenTab = async (params, id) => {
|
|
2304
|
+
try {
|
|
2305
|
+
const url = requireUrl(params, id);
|
|
2306
|
+
if (url === null)
|
|
2307
|
+
return;
|
|
2308
|
+
const tab = await chrome.tabs.create({ url });
|
|
2309
|
+
sendSuccessResult(id, { id: tab.id, title: tab.title ?? "", url: tab.url ?? url, windowId: tab.windowId });
|
|
2310
|
+
} catch (err2) {
|
|
2311
|
+
sendErrorResult(id, err2);
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
var handleBrowserCloseTab = async (params, id) => {
|
|
2315
|
+
try {
|
|
2316
|
+
const tabId = requireTabId(params, id);
|
|
2317
|
+
if (tabId === null)
|
|
2318
|
+
return;
|
|
2319
|
+
await chrome.tabs.remove(tabId);
|
|
2320
|
+
sendSuccessResult(id, { ok: true });
|
|
2321
|
+
} catch (err2) {
|
|
2322
|
+
sendErrorResult(id, err2);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
var handleBrowserNavigateTab = async (params, id) => {
|
|
2326
|
+
try {
|
|
2327
|
+
const tabId = requireTabId(params, id);
|
|
2328
|
+
if (tabId === null)
|
|
2329
|
+
return;
|
|
2330
|
+
const url = requireUrl(params, id);
|
|
2331
|
+
if (url === null)
|
|
2332
|
+
return;
|
|
2333
|
+
const tab = await chrome.tabs.update(tabId, { url });
|
|
2334
|
+
sendSuccessResult(id, { id: tab?.id ?? tabId, title: tab?.title ?? "", url: tab?.url ?? url });
|
|
2335
|
+
} catch (err2) {
|
|
2336
|
+
sendErrorResult(id, err2);
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
var handleBrowserFocusTab = async (params, id) => {
|
|
2340
|
+
try {
|
|
2341
|
+
const tabId = requireTabId(params, id);
|
|
2342
|
+
if (tabId === null)
|
|
2343
|
+
return;
|
|
2344
|
+
const tab = await chrome.tabs.update(tabId, { active: true });
|
|
2345
|
+
if (!tab) {
|
|
2346
|
+
sendValidationError(id, `Tab ${tabId} not found`);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
await chrome.windows.update(tab.windowId, { focused: true });
|
|
2350
|
+
sendSuccessResult(id, { id: tab.id, title: tab.title ?? "", url: tab.url ?? "", active: true });
|
|
2351
|
+
} catch (err2) {
|
|
2352
|
+
sendErrorResult(id, err2);
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
var handleBrowserGetTabInfo = async (params, id) => {
|
|
2356
|
+
try {
|
|
2357
|
+
const tabId = requireTabId(params, id);
|
|
2358
|
+
if (tabId === null)
|
|
2359
|
+
return;
|
|
2360
|
+
const tab = await chrome.tabs.get(tabId);
|
|
2361
|
+
sendSuccessResult(id, {
|
|
2362
|
+
id: tab.id,
|
|
2363
|
+
title: tab.title ?? "",
|
|
2364
|
+
url: tab.url ?? "",
|
|
2365
|
+
status: tab.status ?? "",
|
|
2366
|
+
active: tab.active,
|
|
2367
|
+
windowId: tab.windowId,
|
|
2368
|
+
favIconUrl: tab.favIconUrl ?? "",
|
|
2369
|
+
incognito: tab.incognito
|
|
2370
|
+
});
|
|
2371
|
+
} catch (err2) {
|
|
2372
|
+
sendErrorResult(id, err2);
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
|
|
2376
|
+
// dist/iife-injection.js
|
|
2377
|
+
var RESERVED_NAMES = /* @__PURE__ */ new Set(["system", "browser", "opentabs", "extension", "config", "plugin", "tool", "mcp"]);
|
|
2378
|
+
var isSafePluginName = (name) => isValidPluginName(name) && !RESERVED_NAMES.has(name);
|
|
2379
|
+
var isAdapterPresent = async (tabId, pluginName) => {
|
|
2380
|
+
try {
|
|
2381
|
+
const results = await chrome.scripting.executeScript({
|
|
2382
|
+
target: { tabId },
|
|
2383
|
+
world: "MAIN",
|
|
2384
|
+
func: (pName) => {
|
|
2385
|
+
const ot = globalThis.__openTabs;
|
|
2386
|
+
return ot?.adapters?.[pName] !== void 0;
|
|
2387
|
+
},
|
|
2388
|
+
args: [pluginName]
|
|
2389
|
+
});
|
|
2390
|
+
const first = results[0];
|
|
2391
|
+
return first?.result === true;
|
|
2392
|
+
} catch (err2) {
|
|
2393
|
+
console.warn(`[opentabs] isAdapterPresent failed for tab ${String(tabId)}, plugin ${pluginName}:`, err2);
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
var verifyAdapterVersion = async (tabId, pluginName, expectedVersion) => {
|
|
2398
|
+
try {
|
|
2399
|
+
const results = await chrome.scripting.executeScript({
|
|
2400
|
+
target: { tabId },
|
|
2401
|
+
world: "MAIN",
|
|
2402
|
+
func: (pName) => {
|
|
2403
|
+
const ot = globalThis.__openTabs;
|
|
2404
|
+
return ot?.adapters?.[pName]?.version;
|
|
2405
|
+
},
|
|
2406
|
+
args: [pluginName]
|
|
2407
|
+
});
|
|
2408
|
+
const first = results[0];
|
|
2409
|
+
const version = first?.result;
|
|
2410
|
+
if (version !== expectedVersion) {
|
|
2411
|
+
console.warn(`[opentabs] Adapter version mismatch for ${pluginName}: expected ${expectedVersion}, got ${String(version)}`);
|
|
2412
|
+
return false;
|
|
2413
|
+
}
|
|
2414
|
+
return true;
|
|
2415
|
+
} catch {
|
|
2416
|
+
console.warn(`[opentabs] Failed to verify adapter version for ${pluginName}`);
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
var readAdapterHash = async (tabId, pluginName) => {
|
|
2421
|
+
try {
|
|
2422
|
+
const results = await chrome.scripting.executeScript({
|
|
2423
|
+
target: { tabId },
|
|
2424
|
+
world: "MAIN",
|
|
2425
|
+
func: (pName) => {
|
|
2426
|
+
const ot = globalThis.__openTabs;
|
|
2427
|
+
return ot?.adapters?.[pName]?.__adapterHash;
|
|
2428
|
+
},
|
|
2429
|
+
args: [pluginName]
|
|
2430
|
+
});
|
|
2431
|
+
const first = results[0];
|
|
2432
|
+
return typeof first?.result === "string" ? first.result : void 0;
|
|
2433
|
+
} catch {
|
|
2434
|
+
return void 0;
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
var verifyAdapterHash = async (tabId, pluginName, expectedHash) => {
|
|
2438
|
+
const hash = await readAdapterHash(tabId, pluginName);
|
|
2439
|
+
return hash === expectedHash;
|
|
2440
|
+
};
|
|
2441
|
+
var injectLogRelay = async (tabId) => {
|
|
2442
|
+
const nonce = crypto.randomUUID();
|
|
2443
|
+
try {
|
|
2444
|
+
await chrome.scripting.executeScript({
|
|
2445
|
+
target: { tabId },
|
|
2446
|
+
world: "ISOLATED",
|
|
2447
|
+
func: (n) => {
|
|
2448
|
+
const guard = "__opentabs_log_relay";
|
|
2449
|
+
const win = window;
|
|
2450
|
+
if (win[guard]) {
|
|
2451
|
+
const nonceSet = win.__opentabs_log_nonces;
|
|
2452
|
+
if (nonceSet)
|
|
2453
|
+
nonceSet.add(n);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
win[guard] = true;
|
|
2457
|
+
const nonces = /* @__PURE__ */ new Set([n]);
|
|
2458
|
+
win.__opentabs_log_nonces = nonces;
|
|
2459
|
+
window.addEventListener("message", (event) => {
|
|
2460
|
+
if (event.source !== window)
|
|
2461
|
+
return;
|
|
2462
|
+
const data = event.data;
|
|
2463
|
+
if (!data || data.type !== "opentabs:plugin-logs")
|
|
2464
|
+
return;
|
|
2465
|
+
if (typeof data.nonce !== "string" || !nonces.has(data.nonce))
|
|
2466
|
+
return;
|
|
2467
|
+
const plugin = data.plugin;
|
|
2468
|
+
const entries = data.entries;
|
|
2469
|
+
if (typeof plugin !== "string" || !Array.isArray(entries) || entries.length === 0)
|
|
2470
|
+
return;
|
|
2471
|
+
chrome.runtime.sendMessage({ type: "plugin:logs", plugin, entries }).catch(() => {
|
|
2472
|
+
});
|
|
2473
|
+
});
|
|
2474
|
+
},
|
|
2475
|
+
args: [nonce]
|
|
2476
|
+
});
|
|
2477
|
+
await chrome.scripting.executeScript({
|
|
2478
|
+
target: { tabId },
|
|
2479
|
+
world: "MAIN",
|
|
2480
|
+
func: (n) => {
|
|
2481
|
+
const ot = globalThis.__openTabs ?? {};
|
|
2482
|
+
globalThis.__openTabs = ot;
|
|
2483
|
+
ot._logNonce = n;
|
|
2484
|
+
},
|
|
2485
|
+
args: [nonce]
|
|
2486
|
+
});
|
|
2487
|
+
} catch (err2) {
|
|
2488
|
+
console.warn(`[opentabs] injectLogRelay failed for tab ${String(tabId)}:`, err2);
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
var injectAdapterFile = async (tabId, pluginName, version, adapterHash, adapterFilePath) => {
|
|
2492
|
+
await injectLogRelay(tabId);
|
|
2493
|
+
const adapterFile = adapterFilePath ?? `adapters/${pluginName}.js`;
|
|
2494
|
+
try {
|
|
2495
|
+
await chrome.scripting.executeScript({
|
|
2496
|
+
target: { tabId },
|
|
2497
|
+
world: "MAIN",
|
|
2498
|
+
files: [adapterFile]
|
|
2499
|
+
});
|
|
2500
|
+
} catch (err2) {
|
|
2501
|
+
throw new Error(`Failed to inject adapter file '${adapterFile}' into tab ${String(tabId)}: ${toErrorMessage(err2)}`);
|
|
2502
|
+
}
|
|
2503
|
+
if (version) {
|
|
2504
|
+
await verifyAdapterVersion(tabId, pluginName, version);
|
|
2505
|
+
}
|
|
2506
|
+
if (adapterHash) {
|
|
2507
|
+
const hashMatched = await verifyAdapterHash(tabId, pluginName, adapterHash);
|
|
2508
|
+
if (!hashMatched) {
|
|
2509
|
+
await new Promise((resolve) => setTimeout(resolve, INJECTION_RETRY_DELAY_MS));
|
|
2510
|
+
try {
|
|
2511
|
+
await chrome.scripting.executeScript({
|
|
2512
|
+
target: { tabId },
|
|
2513
|
+
world: "MAIN",
|
|
2514
|
+
files: [adapterFile]
|
|
2515
|
+
});
|
|
2516
|
+
} catch (err2) {
|
|
2517
|
+
throw new Error(`Failed to re-inject adapter file '${adapterFile}' into tab ${String(tabId)}: ${toErrorMessage(err2)}`);
|
|
2518
|
+
}
|
|
2519
|
+
if (version) {
|
|
2520
|
+
await verifyAdapterVersion(tabId, pluginName, version);
|
|
2521
|
+
}
|
|
2522
|
+
const retryMatched = await verifyAdapterHash(tabId, pluginName, adapterHash);
|
|
2523
|
+
if (!retryMatched) {
|
|
2524
|
+
const actualHash = await readAdapterHash(tabId, pluginName);
|
|
2525
|
+
throw new Error(`Adapter hash mismatch for ${pluginName} after retry: expected ${adapterHash}, got ${String(actualHash)}`);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
var queryMatchingTabIds = async (urlPatterns) => {
|
|
2531
|
+
const tabIds = /* @__PURE__ */ new Set();
|
|
2532
|
+
for (const pattern of urlPatterns) {
|
|
2533
|
+
try {
|
|
2534
|
+
const tabs = await chrome.tabs.query({ url: pattern });
|
|
2535
|
+
for (const tab of tabs) {
|
|
2536
|
+
if (tab.id !== void 0) {
|
|
2537
|
+
tabIds.add(tab.id);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
} catch (err2) {
|
|
2541
|
+
console.warn(`[opentabs] chrome.tabs.query failed for pattern ${pattern}:`, err2);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
return Array.from(tabIds);
|
|
2545
|
+
};
|
|
2546
|
+
var prepareForReinjection = async (tabId) => {
|
|
2547
|
+
await chrome.scripting.executeScript({
|
|
2548
|
+
target: { tabId },
|
|
2549
|
+
world: "MAIN",
|
|
2550
|
+
func: () => {
|
|
2551
|
+
const ot = globalThis.__openTabs;
|
|
2552
|
+
if (!ot)
|
|
2553
|
+
return;
|
|
2554
|
+
const adapters = ot.adapters;
|
|
2555
|
+
if (!adapters)
|
|
2556
|
+
return;
|
|
2557
|
+
const newAdapters = {};
|
|
2558
|
+
for (const key of Object.keys(adapters)) {
|
|
2559
|
+
newAdapters[key] = adapters[key];
|
|
2560
|
+
}
|
|
2561
|
+
const newOt = {};
|
|
2562
|
+
for (const key of Object.keys(ot)) {
|
|
2563
|
+
if (key === "adapters")
|
|
2564
|
+
continue;
|
|
2565
|
+
const desc = Object.getOwnPropertyDescriptor(ot, key);
|
|
2566
|
+
if (desc)
|
|
2567
|
+
Object.defineProperty(newOt, key, desc);
|
|
2568
|
+
}
|
|
2569
|
+
newOt.adapters = newAdapters;
|
|
2570
|
+
globalThis.__openTabs = newOt;
|
|
2571
|
+
},
|
|
2572
|
+
args: []
|
|
2573
|
+
}).catch((err2) => {
|
|
2574
|
+
console.warn(`[opentabs] prepareForReinjection failed:`, err2);
|
|
2575
|
+
});
|
|
2576
|
+
};
|
|
2577
|
+
var injectPluginIntoMatchingTabs = async (pluginName, urlPatterns, forceReinject = false, version, adapterHash, adapterFile) => {
|
|
2578
|
+
if (!isSafePluginName(pluginName)) {
|
|
2579
|
+
console.warn(`[opentabs] Skipping injection for unsafe plugin name: ${pluginName}`);
|
|
2580
|
+
return [];
|
|
2581
|
+
}
|
|
2582
|
+
const tabIds = await queryMatchingTabIds(urlPatterns);
|
|
2583
|
+
const results = await Promise.allSettled(tabIds.map(async (tabId) => {
|
|
2584
|
+
if (!forceReinject && await isAdapterPresent(tabId, pluginName)) {
|
|
2585
|
+
return tabId;
|
|
2586
|
+
}
|
|
2587
|
+
if (forceReinject) {
|
|
2588
|
+
await prepareForReinjection(tabId);
|
|
2589
|
+
}
|
|
2590
|
+
await injectAdapterFile(tabId, pluginName, version, adapterHash, adapterFile);
|
|
2591
|
+
return tabId;
|
|
2592
|
+
}));
|
|
2593
|
+
const injectedTabIds = [];
|
|
2594
|
+
for (const result of results) {
|
|
2595
|
+
if (result.status === "fulfilled") {
|
|
2596
|
+
injectedTabIds.push(result.value);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return injectedTabIds;
|
|
2600
|
+
};
|
|
2601
|
+
var injectPluginsIntoTab = async (tabId, tabUrl) => {
|
|
2602
|
+
const index = await getAllPluginMeta();
|
|
2603
|
+
const plugins = Object.values(index);
|
|
2604
|
+
if (plugins.length === 0)
|
|
2605
|
+
return;
|
|
2606
|
+
const matching = plugins.filter((p) => isSafePluginName(p.name) && urlMatchesPatterns(tabUrl, p.urlPatterns));
|
|
2607
|
+
if (matching.length === 0)
|
|
2608
|
+
return;
|
|
2609
|
+
const presenceResults = await Promise.allSettled(matching.map(async (plugin) => ({
|
|
2610
|
+
plugin,
|
|
2611
|
+
present: await isAdapterPresent(tabId, plugin.name)
|
|
2612
|
+
})));
|
|
2613
|
+
const needsInjection = presenceResults.filter((r) => r.status === "fulfilled" && !r.value.present).map((r) => r.value.plugin);
|
|
2614
|
+
if (needsInjection.length === 0)
|
|
2615
|
+
return;
|
|
2616
|
+
await Promise.allSettled(needsInjection.map(async (plugin) => {
|
|
2617
|
+
try {
|
|
2618
|
+
await injectAdapterFile(tabId, plugin.name, plugin.version, plugin.adapterHash, plugin.adapterFile);
|
|
2619
|
+
} catch (err2) {
|
|
2620
|
+
console.warn(`[opentabs] Injection failed for tab ${String(tabId)}, plugin ${plugin.name}:`, err2);
|
|
2621
|
+
}
|
|
2622
|
+
}));
|
|
2623
|
+
};
|
|
2624
|
+
var cleanupAdaptersInMatchingTabs = async (pluginName, urlPatterns) => {
|
|
2625
|
+
if (!isSafePluginName(pluginName)) {
|
|
2626
|
+
console.warn(`[opentabs] Skipping cleanup for unsafe plugin name: ${pluginName}`);
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
const tabIds = await queryMatchingTabIds(urlPatterns);
|
|
2630
|
+
await Promise.allSettled(tabIds.map(async (tabId) => {
|
|
2631
|
+
try {
|
|
2632
|
+
await chrome.scripting.executeScript({
|
|
2633
|
+
target: { tabId },
|
|
2634
|
+
world: "MAIN",
|
|
2635
|
+
func: (pName) => {
|
|
2636
|
+
const ot = globalThis.__openTabs;
|
|
2637
|
+
const adapters = ot?.adapters;
|
|
2638
|
+
if (!adapters)
|
|
2639
|
+
return;
|
|
2640
|
+
const adapter = adapters[pName];
|
|
2641
|
+
if (adapter) {
|
|
2642
|
+
if (typeof adapter.teardown === "function") {
|
|
2643
|
+
try {
|
|
2644
|
+
adapter.teardown();
|
|
2645
|
+
} catch (e) {
|
|
2646
|
+
console.warn("[opentabs] teardown error:", e);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (!Reflect.deleteProperty(adapters, pName)) {
|
|
2650
|
+
const newAdapters = {};
|
|
2651
|
+
for (const key of Object.keys(adapters)) {
|
|
2652
|
+
if (key !== pName) {
|
|
2653
|
+
const desc = Object.getOwnPropertyDescriptor(adapters, key);
|
|
2654
|
+
if (desc)
|
|
2655
|
+
Object.defineProperty(newAdapters, key, desc);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
delete globalThis.__openTabs;
|
|
2659
|
+
globalThis.__openTabs = Object.assign({}, ot, {
|
|
2660
|
+
adapters: newAdapters
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
},
|
|
2665
|
+
args: [pluginName]
|
|
2666
|
+
});
|
|
2667
|
+
} catch (err2) {
|
|
2668
|
+
console.warn(`[opentabs] Cleanup failed for tab ${String(tabId)}, plugin ${pluginName}:`, err2);
|
|
2669
|
+
}
|
|
2670
|
+
}));
|
|
2671
|
+
};
|
|
2672
|
+
var reinjectStoredPlugins = async () => {
|
|
2673
|
+
const index = await getAllPluginMeta();
|
|
2674
|
+
const plugins = Object.values(index);
|
|
2675
|
+
if (plugins.length === 0)
|
|
2676
|
+
return;
|
|
2677
|
+
const results = await Promise.allSettled(plugins.map((plugin) => injectPluginIntoMatchingTabs(plugin.name, plugin.urlPatterns, false, plugin.version, plugin.adapterHash, plugin.adapterFile)));
|
|
2678
|
+
for (let i = 0; i < results.length; i++) {
|
|
2679
|
+
const result = results[i];
|
|
2680
|
+
if (result && result.status === "rejected") {
|
|
2681
|
+
const plugin = plugins[i];
|
|
2682
|
+
console.warn(`[opentabs] Failed to reinject stored plugin ${plugin?.name ?? "unknown"}:`, result.reason);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
|
|
2687
|
+
// dist/rate-limiter.js
|
|
2688
|
+
var METHOD_LIMITS = /* @__PURE__ */ new Map([
|
|
2689
|
+
// Expensive operations — tight limits
|
|
2690
|
+
["browser.screenshotTab", { maxRequests: 2, windowMs: 1e3 }],
|
|
2691
|
+
["browser.enableNetworkCapture", { maxRequests: 2, windowMs: 1e3 }],
|
|
2692
|
+
["browser.executeScript", { maxRequests: 15, windowMs: 1e3 }],
|
|
2693
|
+
// Tool dispatch — allow bursts of sequential/concurrent tool calls from agents
|
|
2694
|
+
["tool.dispatch", { maxRequests: 30, windowMs: 1e3 }]
|
|
2695
|
+
]);
|
|
2696
|
+
var DEFAULT_LIMIT = { maxRequests: 20, windowMs: 1e3 };
|
|
2697
|
+
var EXEMPT_METHODS = /* @__PURE__ */ new Set(["extension.reload", "sync.full", "plugin.update", "plugin.uninstall"]);
|
|
2698
|
+
var methodTimestamps = /* @__PURE__ */ new Map();
|
|
2699
|
+
var checkRateLimit = (method, now = Date.now()) => {
|
|
2700
|
+
if (EXEMPT_METHODS.has(method))
|
|
2701
|
+
return true;
|
|
2702
|
+
const config = METHOD_LIMITS.get(method) ?? DEFAULT_LIMIT;
|
|
2703
|
+
const cutoff = now - config.windowMs;
|
|
2704
|
+
const timestamps = (methodTimestamps.get(method) ?? []).filter((t) => t > cutoff);
|
|
2705
|
+
if (timestamps.length >= config.maxRequests) {
|
|
2706
|
+
methodTimestamps.set(method, timestamps);
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
timestamps.push(now);
|
|
2710
|
+
methodTimestamps.set(method, timestamps);
|
|
2711
|
+
return true;
|
|
2712
|
+
};
|
|
2713
|
+
|
|
2714
|
+
// dist/dispatch-helpers.js
|
|
2715
|
+
var isAdapterNotReady = (result) => result.type === "error" && result.code === JSONRPC_ADAPTER_NOT_READY;
|
|
2716
|
+
var resolvePlugin = async (pluginName, id) => {
|
|
2717
|
+
const plugin = await getPluginMeta(pluginName);
|
|
2718
|
+
if (!plugin) {
|
|
2719
|
+
sendToServer({
|
|
2720
|
+
jsonrpc: "2.0",
|
|
2721
|
+
error: { code: JSONRPC_INTERNAL_ERROR, message: `Plugin "${pluginName}" not found` },
|
|
2722
|
+
id
|
|
2723
|
+
});
|
|
2724
|
+
return null;
|
|
2725
|
+
}
|
|
2726
|
+
return plugin;
|
|
2727
|
+
};
|
|
2728
|
+
var executeWithTimeout = async (scriptPromise, timeoutMs, fallbackMessage) => {
|
|
2729
|
+
let timeoutId;
|
|
2730
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
2731
|
+
timeoutId = setTimeout(() => {
|
|
2732
|
+
reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
|
|
2733
|
+
}, timeoutMs);
|
|
2734
|
+
});
|
|
2735
|
+
let results;
|
|
2736
|
+
try {
|
|
2737
|
+
results = await Promise.race([scriptPromise, timeoutPromise]);
|
|
2738
|
+
} finally {
|
|
2739
|
+
clearTimeout(timeoutId);
|
|
2740
|
+
}
|
|
2741
|
+
const firstResult = results[0];
|
|
2742
|
+
const result = firstResult?.result;
|
|
2743
|
+
if (!result || typeof result !== "object" || !("type" in result)) {
|
|
2744
|
+
return { type: "error", code: JSONRPC_INTERNAL_ERROR, message: fallbackMessage };
|
|
2745
|
+
}
|
|
2746
|
+
return result;
|
|
2747
|
+
};
|
|
2748
|
+
var dispatchWithTabFallback = async (config) => {
|
|
2749
|
+
const { id, pluginName, plugin, operationName, executeOnTab } = config;
|
|
2750
|
+
const matchingTabs = await findAllMatchingTabs(plugin);
|
|
2751
|
+
if (matchingTabs.length === 0) {
|
|
2752
|
+
sendToServer({
|
|
2753
|
+
jsonrpc: "2.0",
|
|
2754
|
+
error: { code: JSONRPC_NO_USABLE_TAB, message: `No matching tab for plugin "${pluginName}" (state: closed)` },
|
|
2755
|
+
id
|
|
2756
|
+
});
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
let firstError;
|
|
2760
|
+
for (const tab of matchingTabs) {
|
|
2761
|
+
if (tab.id === void 0)
|
|
2762
|
+
continue;
|
|
2763
|
+
try {
|
|
2764
|
+
const currentTab = await chrome.tabs.get(tab.id);
|
|
2765
|
+
if (!currentTab.url || !urlMatchesPatterns(currentTab.url, plugin.urlPatterns)) {
|
|
2766
|
+
firstError ??= { code: JSONRPC_NO_USABLE_TAB, message: "Tab navigated away from matching URL" };
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
} catch {
|
|
2770
|
+
firstError ??= { code: JSONRPC_NO_USABLE_TAB, message: `Tab closed before ${operationName}` };
|
|
2771
|
+
continue;
|
|
2772
|
+
}
|
|
2773
|
+
try {
|
|
2774
|
+
const result = await executeOnTab(tab.id);
|
|
2775
|
+
if (result.type === "success") {
|
|
2776
|
+
sendToServer({ jsonrpc: "2.0", result: { output: result.output }, id });
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
if (isAdapterNotReady(result) && matchingTabs.length > 1) {
|
|
2780
|
+
firstError ??= { code: result.code, message: sanitizeErrorMessage(result.message) };
|
|
2781
|
+
continue;
|
|
2782
|
+
}
|
|
2783
|
+
sendToServer({
|
|
2784
|
+
jsonrpc: "2.0",
|
|
2785
|
+
error: { code: result.code, message: sanitizeErrorMessage(result.message), data: result.data },
|
|
2786
|
+
id
|
|
2787
|
+
});
|
|
2788
|
+
return;
|
|
2789
|
+
} catch (err2) {
|
|
2790
|
+
const msg = toErrorMessage(err2);
|
|
2791
|
+
const isTabGone = msg.includes("No tab with id") || msg.includes("Cannot access");
|
|
2792
|
+
if (isTabGone && matchingTabs.length > 1) {
|
|
2793
|
+
firstError ??= { code: JSONRPC_NO_USABLE_TAB, message: `Tab closed before ${operationName}` };
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
sendToServer({
|
|
2797
|
+
jsonrpc: "2.0",
|
|
2798
|
+
error: {
|
|
2799
|
+
code: isTabGone ? JSONRPC_NO_USABLE_TAB : JSONRPC_INTERNAL_ERROR,
|
|
2800
|
+
message: isTabGone ? `Tab closed before ${operationName}` : `Script execution failed: ${sanitizeErrorMessage(msg)}`
|
|
2801
|
+
},
|
|
2802
|
+
id
|
|
2803
|
+
});
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
if (firstError) {
|
|
2808
|
+
sendToServer({
|
|
2809
|
+
jsonrpc: "2.0",
|
|
2810
|
+
error: { code: firstError.code, message: firstError.message, data: firstError.data },
|
|
2811
|
+
id
|
|
2812
|
+
});
|
|
2813
|
+
} else {
|
|
2814
|
+
sendToServer({
|
|
2815
|
+
jsonrpc: "2.0",
|
|
2816
|
+
error: { code: JSONRPC_NO_USABLE_TAB, message: "No usable tab found (all matching tabs have undefined IDs)" },
|
|
2817
|
+
id
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// dist/resource-prompt-dispatch.js
|
|
2823
|
+
var executeResourceReadOnTab = async (tabId, pluginName, resourceUri) => {
|
|
2824
|
+
const scriptPromise = chrome.scripting.executeScript({
|
|
2825
|
+
target: { tabId },
|
|
2826
|
+
world: "MAIN",
|
|
2827
|
+
func: async (pName, uri) => {
|
|
2828
|
+
const ot = globalThis.__openTabs;
|
|
2829
|
+
const adapter = ot?.adapters?.[pName];
|
|
2830
|
+
if (!adapter || typeof adapter !== "object") {
|
|
2831
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" not injected or not ready` };
|
|
2832
|
+
}
|
|
2833
|
+
if (!Object.isFrozen(adapter)) {
|
|
2834
|
+
return {
|
|
2835
|
+
type: "error",
|
|
2836
|
+
code: -32002,
|
|
2837
|
+
message: `Adapter "${pName}" failed integrity check (not frozen)`
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
if (typeof adapter.isReady !== "function") {
|
|
2841
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" has no isReady function` };
|
|
2842
|
+
}
|
|
2843
|
+
let ready;
|
|
2844
|
+
try {
|
|
2845
|
+
ready = await adapter.isReady();
|
|
2846
|
+
} catch {
|
|
2847
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" isReady() threw an error` };
|
|
2848
|
+
}
|
|
2849
|
+
if (!ready) {
|
|
2850
|
+
return {
|
|
2851
|
+
type: "error",
|
|
2852
|
+
code: -32002,
|
|
2853
|
+
message: `Plugin "${pName}" is not ready (state: unavailable)`
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
if (!Array.isArray(adapter.resources)) {
|
|
2857
|
+
return { type: "error", code: -32603, message: `Adapter "${pName}" has no resources array` };
|
|
2858
|
+
}
|
|
2859
|
+
const resource = adapter.resources.find((r) => r.uri === uri);
|
|
2860
|
+
if (!resource || typeof resource.read !== "function") {
|
|
2861
|
+
return { type: "error", code: -32603, message: `Resource "${uri}" not found in adapter "${pName}"` };
|
|
2862
|
+
}
|
|
2863
|
+
try {
|
|
2864
|
+
const output = await resource.read(uri);
|
|
2865
|
+
return { type: "success", output };
|
|
2866
|
+
} catch (err2) {
|
|
2867
|
+
const caughtError = err2;
|
|
2868
|
+
return {
|
|
2869
|
+
type: "error",
|
|
2870
|
+
code: -32603,
|
|
2871
|
+
message: caughtError.message ?? "Resource read failed"
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
},
|
|
2875
|
+
args: [pluginName, resourceUri]
|
|
2876
|
+
});
|
|
2877
|
+
return executeWithTimeout(scriptPromise, SCRIPT_TIMEOUT_MS, "No result from resource read");
|
|
2878
|
+
};
|
|
2879
|
+
var executePromptGetOnTab = async (tabId, pluginName, promptName, promptArgs) => {
|
|
2880
|
+
const scriptPromise = chrome.scripting.executeScript({
|
|
2881
|
+
target: { tabId },
|
|
2882
|
+
world: "MAIN",
|
|
2883
|
+
func: async (pName, pPromptName, pArgs) => {
|
|
2884
|
+
const ot = globalThis.__openTabs;
|
|
2885
|
+
const adapter = ot?.adapters?.[pName];
|
|
2886
|
+
if (!adapter || typeof adapter !== "object") {
|
|
2887
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" not injected or not ready` };
|
|
2888
|
+
}
|
|
2889
|
+
if (!Object.isFrozen(adapter)) {
|
|
2890
|
+
return {
|
|
2891
|
+
type: "error",
|
|
2892
|
+
code: -32002,
|
|
2893
|
+
message: `Adapter "${pName}" failed integrity check (not frozen)`
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
if (typeof adapter.isReady !== "function") {
|
|
2897
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" has no isReady function` };
|
|
2898
|
+
}
|
|
2899
|
+
let ready;
|
|
2900
|
+
try {
|
|
2901
|
+
ready = await adapter.isReady();
|
|
2902
|
+
} catch {
|
|
2903
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" isReady() threw an error` };
|
|
2904
|
+
}
|
|
2905
|
+
if (!ready) {
|
|
2906
|
+
return {
|
|
2907
|
+
type: "error",
|
|
2908
|
+
code: -32002,
|
|
2909
|
+
message: `Plugin "${pName}" is not ready (state: unavailable)`
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
if (!Array.isArray(adapter.prompts)) {
|
|
2913
|
+
return { type: "error", code: -32603, message: `Adapter "${pName}" has no prompts array` };
|
|
2914
|
+
}
|
|
2915
|
+
const prompt = adapter.prompts.find((p) => p.name === pPromptName);
|
|
2916
|
+
if (!prompt || typeof prompt.render !== "function") {
|
|
2917
|
+
return {
|
|
2918
|
+
type: "error",
|
|
2919
|
+
code: -32603,
|
|
2920
|
+
message: `Prompt "${pPromptName}" not found in adapter "${pName}"`
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
try {
|
|
2924
|
+
const output = await prompt.render(pArgs);
|
|
2925
|
+
return { type: "success", output };
|
|
2926
|
+
} catch (err2) {
|
|
2927
|
+
const caughtError = err2;
|
|
2928
|
+
return {
|
|
2929
|
+
type: "error",
|
|
2930
|
+
code: -32603,
|
|
2931
|
+
message: caughtError.message ?? "Prompt render failed"
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
},
|
|
2935
|
+
args: [pluginName, promptName, promptArgs]
|
|
2936
|
+
});
|
|
2937
|
+
return executeWithTimeout(scriptPromise, SCRIPT_TIMEOUT_MS, "No result from prompt render");
|
|
2938
|
+
};
|
|
2939
|
+
var handleResourceRead = async (params, id) => {
|
|
2940
|
+
const pluginName = requireStringParam(params, "plugin", id);
|
|
2941
|
+
if (!pluginName)
|
|
2942
|
+
return;
|
|
2943
|
+
const resourceUri = requireStringParam(params, "uri", id);
|
|
2944
|
+
if (!resourceUri)
|
|
2945
|
+
return;
|
|
2946
|
+
const plugin = await resolvePlugin(pluginName, id);
|
|
2947
|
+
if (!plugin)
|
|
2948
|
+
return;
|
|
2949
|
+
await dispatchWithTabFallback({
|
|
2950
|
+
id,
|
|
2951
|
+
pluginName,
|
|
2952
|
+
plugin,
|
|
2953
|
+
operationName: "resource read",
|
|
2954
|
+
executeOnTab: (tabId) => executeResourceReadOnTab(tabId, pluginName, resourceUri)
|
|
2955
|
+
});
|
|
2956
|
+
};
|
|
2957
|
+
var handlePromptGet = async (params, id) => {
|
|
2958
|
+
const pluginName = requireStringParam(params, "plugin", id);
|
|
2959
|
+
if (!pluginName)
|
|
2960
|
+
return;
|
|
2961
|
+
const promptName = requireStringParam(params, "prompt", id);
|
|
2962
|
+
if (!promptName)
|
|
2963
|
+
return;
|
|
2964
|
+
const rawArgs = params.arguments;
|
|
2965
|
+
if (rawArgs !== void 0 && rawArgs !== null && (typeof rawArgs !== "object" || Array.isArray(rawArgs))) {
|
|
2966
|
+
sendToServer({
|
|
2967
|
+
jsonrpc: "2.0",
|
|
2968
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: 'Invalid "arguments" param (expected object)' },
|
|
2969
|
+
id
|
|
2970
|
+
});
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
const rawObj = rawArgs ?? {};
|
|
2974
|
+
const promptArgs = {};
|
|
2975
|
+
for (const [key, val] of Object.entries(rawObj)) {
|
|
2976
|
+
promptArgs[key] = String(val);
|
|
2977
|
+
}
|
|
2978
|
+
const plugin = await resolvePlugin(pluginName, id);
|
|
2979
|
+
if (!plugin)
|
|
2980
|
+
return;
|
|
2981
|
+
await dispatchWithTabFallback({
|
|
2982
|
+
id,
|
|
2983
|
+
pluginName,
|
|
2984
|
+
plugin,
|
|
2985
|
+
operationName: "prompt get",
|
|
2986
|
+
executeOnTab: (tabId) => executePromptGetOnTab(tabId, pluginName, promptName, promptArgs)
|
|
2987
|
+
});
|
|
2988
|
+
};
|
|
2989
|
+
|
|
2990
|
+
// dist/tool-dispatch.js
|
|
2991
|
+
var progressCallbacks = /* @__PURE__ */ new Map();
|
|
2992
|
+
var notifyDispatchProgress = (dispatchId) => {
|
|
2993
|
+
const cb = progressCallbacks.get(dispatchId);
|
|
2994
|
+
if (cb)
|
|
2995
|
+
cb();
|
|
2996
|
+
};
|
|
2997
|
+
var getPluginLink = (plugin) => {
|
|
2998
|
+
if (plugin.trustTier === "local" && plugin.sourcePath) {
|
|
2999
|
+
return plugin.sourcePath;
|
|
3000
|
+
}
|
|
3001
|
+
if (plugin.trustTier === "official") {
|
|
3002
|
+
return `https://npmjs.com/package/@opentabs-dev/plugin-${plugin.name}`;
|
|
3003
|
+
}
|
|
3004
|
+
return `https://npmjs.com/package/opentabs-plugin-${plugin.name}`;
|
|
3005
|
+
};
|
|
3006
|
+
var injectToolInvocationLog = async (tabId, pluginName, toolName, link) => {
|
|
3007
|
+
try {
|
|
3008
|
+
await chrome.scripting.executeScript({
|
|
3009
|
+
target: { tabId },
|
|
3010
|
+
world: "MAIN",
|
|
3011
|
+
func: (pName, tName, lnk) => {
|
|
3012
|
+
console.warn(`[opentabs] ${pName}.${tName} invoked \u2014 ${lnk}`);
|
|
3013
|
+
},
|
|
3014
|
+
args: [pluginName, toolName, link]
|
|
3015
|
+
});
|
|
3016
|
+
} catch {
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
var injectProgressListener = async (tabId, dispatchId) => {
|
|
3020
|
+
try {
|
|
3021
|
+
await chrome.scripting.executeScript({
|
|
3022
|
+
target: { tabId },
|
|
3023
|
+
world: "ISOLATED",
|
|
3024
|
+
func: (dId) => {
|
|
3025
|
+
const eventName = `opentabs:progress:${dId}`;
|
|
3026
|
+
const handler = (e) => {
|
|
3027
|
+
const detail = e.detail;
|
|
3028
|
+
if (!detail)
|
|
3029
|
+
return;
|
|
3030
|
+
void chrome.runtime.sendMessage({
|
|
3031
|
+
type: "tool:progress",
|
|
3032
|
+
dispatchId: detail.dispatchId,
|
|
3033
|
+
progress: detail.progress,
|
|
3034
|
+
total: detail.total,
|
|
3035
|
+
message: detail.message
|
|
3036
|
+
});
|
|
3037
|
+
};
|
|
3038
|
+
document.addEventListener(eventName, handler);
|
|
3039
|
+
const cleanupKey = `__opentabs_progress_cleanup_${dId}`;
|
|
3040
|
+
const doc = document;
|
|
3041
|
+
doc[cleanupKey] = () => {
|
|
3042
|
+
document.removeEventListener(eventName, handler);
|
|
3043
|
+
doc[cleanupKey] = void 0;
|
|
3044
|
+
};
|
|
3045
|
+
},
|
|
3046
|
+
args: [dispatchId]
|
|
3047
|
+
});
|
|
3048
|
+
} catch {
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
var removeProgressListener = (tabId, dispatchId) => {
|
|
3052
|
+
chrome.scripting.executeScript({
|
|
3053
|
+
target: { tabId },
|
|
3054
|
+
world: "ISOLATED",
|
|
3055
|
+
func: (dId) => {
|
|
3056
|
+
const cleanupKey = `__opentabs_progress_cleanup_${dId}`;
|
|
3057
|
+
const cleanup = document[cleanupKey];
|
|
3058
|
+
if (cleanup)
|
|
3059
|
+
cleanup();
|
|
3060
|
+
},
|
|
3061
|
+
args: [dispatchId]
|
|
3062
|
+
}).catch(() => {
|
|
3063
|
+
});
|
|
3064
|
+
};
|
|
3065
|
+
var executeToolOnTab = async (tabId, pluginName, toolName, input, dispatchId) => {
|
|
3066
|
+
let timeoutId;
|
|
3067
|
+
const startTs = Date.now();
|
|
3068
|
+
const scriptPromise = chrome.scripting.executeScript({
|
|
3069
|
+
target: { tabId },
|
|
3070
|
+
world: "MAIN",
|
|
3071
|
+
func: async (pName, tName, tInput, dId) => {
|
|
3072
|
+
const ot = globalThis.__openTabs;
|
|
3073
|
+
const adapter = ot?.adapters?.[pName];
|
|
3074
|
+
if (!adapter || typeof adapter !== "object") {
|
|
3075
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" not injected or not ready` };
|
|
3076
|
+
}
|
|
3077
|
+
if (!Object.isFrozen(adapter)) {
|
|
3078
|
+
return {
|
|
3079
|
+
type: "error",
|
|
3080
|
+
code: -32002,
|
|
3081
|
+
message: `Adapter "${pName}" failed integrity check (not frozen)`
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
if (typeof adapter.isReady !== "function") {
|
|
3085
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" has no isReady function` };
|
|
3086
|
+
}
|
|
3087
|
+
if (!Array.isArray(adapter.tools)) {
|
|
3088
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" has no tools array` };
|
|
3089
|
+
}
|
|
3090
|
+
let ready;
|
|
3091
|
+
try {
|
|
3092
|
+
ready = await adapter.isReady();
|
|
3093
|
+
} catch {
|
|
3094
|
+
return { type: "error", code: -32002, message: `Adapter "${pName}" isReady() threw an error` };
|
|
3095
|
+
}
|
|
3096
|
+
if (!ready) {
|
|
3097
|
+
return {
|
|
3098
|
+
type: "error",
|
|
3099
|
+
code: -32002,
|
|
3100
|
+
message: `Plugin "${pName}" is not ready (state: unavailable)`
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
const tool = adapter.tools.find((t) => t.name === tName);
|
|
3104
|
+
if (!tool || typeof tool.handle !== "function") {
|
|
3105
|
+
return { type: "error", code: -32603, message: `Tool "${tName}" not found in adapter "${pName}"` };
|
|
3106
|
+
}
|
|
3107
|
+
const context = {
|
|
3108
|
+
reportProgress(opts) {
|
|
3109
|
+
try {
|
|
3110
|
+
document.dispatchEvent(new CustomEvent(`opentabs:progress:${dId}`, {
|
|
3111
|
+
detail: {
|
|
3112
|
+
dispatchId: dId,
|
|
3113
|
+
progress: opts.progress ?? 0,
|
|
3114
|
+
total: opts.total ?? 0,
|
|
3115
|
+
message: opts.message
|
|
3116
|
+
}
|
|
3117
|
+
}));
|
|
3118
|
+
} catch {
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
try {
|
|
3123
|
+
const output = await tool.handle(tInput, context);
|
|
3124
|
+
return { type: "success", output };
|
|
3125
|
+
} catch (err2) {
|
|
3126
|
+
const caughtError = err2;
|
|
3127
|
+
if (typeof caughtError.code !== "string") {
|
|
3128
|
+
return {
|
|
3129
|
+
type: "error",
|
|
3130
|
+
code: -32603,
|
|
3131
|
+
message: caughtError.message ?? "Tool execution failed"
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
const data = { code: caughtError.code };
|
|
3135
|
+
if (typeof caughtError.retryable === "boolean")
|
|
3136
|
+
data.retryable = caughtError.retryable;
|
|
3137
|
+
if (typeof caughtError.retryAfterMs === "number")
|
|
3138
|
+
data.retryAfterMs = caughtError.retryAfterMs;
|
|
3139
|
+
if (typeof caughtError.category === "string")
|
|
3140
|
+
data.category = caughtError.category;
|
|
3141
|
+
return {
|
|
3142
|
+
type: "error",
|
|
3143
|
+
code: -32603,
|
|
3144
|
+
message: caughtError.message ?? "Tool execution failed",
|
|
3145
|
+
data
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
},
|
|
3149
|
+
args: [pluginName, toolName, input, dispatchId]
|
|
3150
|
+
});
|
|
3151
|
+
let timeoutReject;
|
|
3152
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
3153
|
+
timeoutReject = reject;
|
|
3154
|
+
timeoutId = setTimeout(() => {
|
|
3155
|
+
reject(new Error(`Script execution timed out after ${SCRIPT_TIMEOUT_MS}ms`));
|
|
3156
|
+
}, SCRIPT_TIMEOUT_MS);
|
|
3157
|
+
});
|
|
3158
|
+
progressCallbacks.set(dispatchId, () => {
|
|
3159
|
+
clearTimeout(timeoutId);
|
|
3160
|
+
const elapsed = Date.now() - startTs;
|
|
3161
|
+
const remainingMax = MAX_SCRIPT_TIMEOUT_MS - elapsed;
|
|
3162
|
+
if (remainingMax <= 0) {
|
|
3163
|
+
timeoutReject?.(new Error(`Script execution exceeded absolute max timeout of ${MAX_SCRIPT_TIMEOUT_MS}ms`));
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
const nextTimeout = Math.min(SCRIPT_TIMEOUT_MS, remainingMax);
|
|
3167
|
+
timeoutId = setTimeout(() => {
|
|
3168
|
+
timeoutReject?.(new Error(`Script execution timed out after ${SCRIPT_TIMEOUT_MS}ms`));
|
|
3169
|
+
}, nextTimeout);
|
|
3170
|
+
});
|
|
3171
|
+
let results;
|
|
3172
|
+
try {
|
|
3173
|
+
results = await Promise.race([scriptPromise, timeoutPromise]);
|
|
3174
|
+
} finally {
|
|
3175
|
+
clearTimeout(timeoutId);
|
|
3176
|
+
progressCallbacks.delete(dispatchId);
|
|
3177
|
+
}
|
|
3178
|
+
const firstResult = results[0];
|
|
3179
|
+
const result = firstResult?.result;
|
|
3180
|
+
if (!result || typeof result !== "object" || !("type" in result)) {
|
|
3181
|
+
return { type: "error", code: JSONRPC_INTERNAL_ERROR, message: "No result from tool execution" };
|
|
3182
|
+
}
|
|
3183
|
+
return result;
|
|
3184
|
+
};
|
|
3185
|
+
var handleToolDispatch = async (params, id) => {
|
|
3186
|
+
const dispatchId = typeof params.dispatchId === "string" ? params.dispatchId : String(id);
|
|
3187
|
+
const pluginName = requireStringParam(params, "plugin", id);
|
|
3188
|
+
if (!pluginName)
|
|
3189
|
+
return;
|
|
3190
|
+
const toolName = requireStringParam(params, "tool", id);
|
|
3191
|
+
if (!toolName)
|
|
3192
|
+
return;
|
|
3193
|
+
const rawInput = params.input;
|
|
3194
|
+
if (rawInput !== void 0 && rawInput !== null && (typeof rawInput !== "object" || Array.isArray(rawInput))) {
|
|
3195
|
+
sendToServer({
|
|
3196
|
+
jsonrpc: "2.0",
|
|
3197
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: 'Invalid "input" param (expected object)' },
|
|
3198
|
+
id
|
|
3199
|
+
});
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const input = rawInput ?? {};
|
|
3203
|
+
let inputJson;
|
|
3204
|
+
try {
|
|
3205
|
+
inputJson = JSON.stringify(input);
|
|
3206
|
+
} catch (err2) {
|
|
3207
|
+
sendToServer({
|
|
3208
|
+
jsonrpc: "2.0",
|
|
3209
|
+
error: {
|
|
3210
|
+
code: JSONRPC_INVALID_PARAMS,
|
|
3211
|
+
message: `Failed to serialize tool input: ${toErrorMessage(err2)}`
|
|
3212
|
+
},
|
|
3213
|
+
id
|
|
3214
|
+
});
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
if (inputJson.length > MAX_INPUT_SIZE) {
|
|
3218
|
+
sendToServer({
|
|
3219
|
+
jsonrpc: "2.0",
|
|
3220
|
+
error: {
|
|
3221
|
+
code: JSONRPC_INVALID_PARAMS,
|
|
3222
|
+
message: `Tool input too large: ${(inputJson.length / 1024 / 1024).toFixed(1)}MB (limit: 10MB)`
|
|
3223
|
+
},
|
|
3224
|
+
id
|
|
3225
|
+
});
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
const plugin = await resolvePlugin(pluginName, id);
|
|
3229
|
+
if (!plugin)
|
|
3230
|
+
return;
|
|
3231
|
+
const link = getPluginLink(plugin);
|
|
3232
|
+
await dispatchWithTabFallback({
|
|
3233
|
+
id,
|
|
3234
|
+
pluginName,
|
|
3235
|
+
plugin,
|
|
3236
|
+
operationName: "tool execution",
|
|
3237
|
+
executeOnTab: async (tabId) => {
|
|
3238
|
+
await injectToolInvocationLog(tabId, pluginName, toolName, link);
|
|
3239
|
+
await injectProgressListener(tabId, dispatchId);
|
|
3240
|
+
try {
|
|
3241
|
+
return await executeToolOnTab(tabId, pluginName, toolName, input, dispatchId);
|
|
3242
|
+
} finally {
|
|
3243
|
+
removeProgressListener(tabId, dispatchId);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
};
|
|
3248
|
+
|
|
3249
|
+
// dist/message-router.js
|
|
3250
|
+
var wrapAsync = (method, fn) => (params, id) => {
|
|
3251
|
+
if (id !== void 0) {
|
|
3252
|
+
fn(params, id).catch((err2) => console.warn(`[opentabs] ${method} handler failed:`, err2));
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3255
|
+
var wrapSync = (method, fn) => (params, id) => {
|
|
3256
|
+
if (id !== void 0) {
|
|
3257
|
+
try {
|
|
3258
|
+
fn(params, id);
|
|
3259
|
+
} catch (err2) {
|
|
3260
|
+
console.warn(`[opentabs] ${method} handler failed:`, err2);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
var wrapNotification = (method, fn) => (params) => {
|
|
3265
|
+
fn(params).catch((err2) => console.warn(`[opentabs] ${method} handler failed:`, err2));
|
|
3266
|
+
};
|
|
3267
|
+
var SIDE_PANEL_METHODS = /* @__PURE__ */ new Set([
|
|
3268
|
+
"tab.stateChanged",
|
|
3269
|
+
"tool.invocationStart",
|
|
3270
|
+
"tool.invocationEnd",
|
|
3271
|
+
"plugins.changed",
|
|
3272
|
+
"confirmation.request"
|
|
3273
|
+
]);
|
|
3274
|
+
var toPluginMeta = (p) => ({
|
|
3275
|
+
name: p.name,
|
|
3276
|
+
version: p.version,
|
|
3277
|
+
displayName: p.displayName,
|
|
3278
|
+
urlPatterns: p.urlPatterns,
|
|
3279
|
+
trustTier: p.trustTier,
|
|
3280
|
+
sourcePath: p.sourcePath,
|
|
3281
|
+
adapterHash: p.adapterHash,
|
|
3282
|
+
adapterFile: p.adapterFile,
|
|
3283
|
+
iconSvg: p.iconSvg,
|
|
3284
|
+
iconInactiveSvg: p.iconInactiveSvg,
|
|
3285
|
+
tools: p.tools
|
|
3286
|
+
});
|
|
3287
|
+
var validatePluginPayload = (raw) => {
|
|
3288
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
3289
|
+
console.warn("[opentabs] Rejecting plugin payload: not an object");
|
|
3290
|
+
return null;
|
|
3291
|
+
}
|
|
3292
|
+
const obj = raw;
|
|
3293
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
3294
|
+
console.warn('[opentabs] Rejecting plugin payload: missing or invalid "name"');
|
|
3295
|
+
return null;
|
|
3296
|
+
}
|
|
3297
|
+
if (/[/\\]|\.\./.test(obj.name) || !isValidPluginName(obj.name)) {
|
|
3298
|
+
console.warn(`[opentabs] Rejecting plugin payload: unsafe name "${obj.name}"`);
|
|
3299
|
+
return null;
|
|
3300
|
+
}
|
|
3301
|
+
const urlPatterns = Array.isArray(obj.urlPatterns) ? obj.urlPatterns.filter((p) => typeof p === "string") : [];
|
|
3302
|
+
const tools = Array.isArray(obj.tools) ? obj.tools.filter((t) => typeof t === "object" && t !== null && typeof t.name === "string" && typeof t.description === "string" && typeof t.enabled === "boolean").map((t) => ({
|
|
3303
|
+
name: t.name,
|
|
3304
|
+
displayName: typeof t.displayName === "string" ? t.displayName : t.name,
|
|
3305
|
+
description: t.description,
|
|
3306
|
+
icon: typeof t.icon === "string" ? t.icon : "wrench",
|
|
3307
|
+
enabled: t.enabled
|
|
3308
|
+
})) : [];
|
|
3309
|
+
return {
|
|
3310
|
+
name: obj.name,
|
|
3311
|
+
version: typeof obj.version === "string" ? obj.version : "0.0.0",
|
|
3312
|
+
displayName: typeof obj.displayName === "string" ? obj.displayName : obj.name,
|
|
3313
|
+
urlPatterns,
|
|
3314
|
+
trustTier: obj.trustTier === "official" || obj.trustTier === "community" || obj.trustTier === "local" ? obj.trustTier : "local",
|
|
3315
|
+
sourcePath: typeof obj.sourcePath === "string" ? obj.sourcePath : void 0,
|
|
3316
|
+
adapterHash: typeof obj.adapterHash === "string" ? obj.adapterHash : void 0,
|
|
3317
|
+
adapterFile: typeof obj.adapterFile === "string" ? obj.adapterFile : void 0,
|
|
3318
|
+
iconSvg: typeof obj.iconSvg === "string" ? obj.iconSvg : void 0,
|
|
3319
|
+
iconInactiveSvg: typeof obj.iconInactiveSvg === "string" ? obj.iconInactiveSvg : void 0,
|
|
3320
|
+
tools
|
|
3321
|
+
};
|
|
3322
|
+
};
|
|
3323
|
+
var handleExtensionReload = (_params, id) => {
|
|
3324
|
+
if (id !== void 0) {
|
|
3325
|
+
sendToServer({ jsonrpc: "2.0", result: { reloading: true }, id });
|
|
3326
|
+
}
|
|
3327
|
+
void chrome.storage.session.set({ [WS_CONNECTED_KEY]: false }).catch(() => {
|
|
3328
|
+
}).then(() => {
|
|
3329
|
+
setTimeout(() => {
|
|
3330
|
+
chrome.runtime.reload();
|
|
3331
|
+
}, RELOAD_FLUSH_DELAY_MS);
|
|
3332
|
+
});
|
|
3333
|
+
};
|
|
3334
|
+
var handleSyncFull = async (params) => {
|
|
3335
|
+
const rawPlugins = params.plugins;
|
|
3336
|
+
if (!Array.isArray(rawPlugins))
|
|
3337
|
+
return;
|
|
3338
|
+
const validated = [];
|
|
3339
|
+
for (const raw of rawPlugins) {
|
|
3340
|
+
const result = validatePluginPayload(raw);
|
|
3341
|
+
if (result)
|
|
3342
|
+
validated.push(result);
|
|
3343
|
+
}
|
|
3344
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
3345
|
+
for (const p of validated) {
|
|
3346
|
+
if (deduped.has(p.name)) {
|
|
3347
|
+
console.warn(`[opentabs] Duplicate plugin in sync.full: ${p.name} \u2014 using last occurrence`);
|
|
3348
|
+
}
|
|
3349
|
+
deduped.set(p.name, p);
|
|
3350
|
+
}
|
|
3351
|
+
const uniquePlugins = Array.from(deduped.values());
|
|
3352
|
+
const syncedNames = new Set(uniquePlugins.map((p) => p.name));
|
|
3353
|
+
const existingMeta = await getAllPluginMeta();
|
|
3354
|
+
const removedNames = Object.keys(existingMeta).filter((name) => !syncedNames.has(name));
|
|
3355
|
+
if (removedNames.length > 0) {
|
|
3356
|
+
await Promise.allSettled(removedNames.map((name) => {
|
|
3357
|
+
const meta = existingMeta[name];
|
|
3358
|
+
if (meta)
|
|
3359
|
+
return cleanupAdaptersInMatchingTabs(name, meta.urlPatterns);
|
|
3360
|
+
return Promise.resolve();
|
|
3361
|
+
}));
|
|
3362
|
+
await removePluginsBatch(removedNames);
|
|
3363
|
+
for (const name of removedNames)
|
|
3364
|
+
clearPluginTabState(name);
|
|
3365
|
+
}
|
|
3366
|
+
const metas = uniquePlugins.map(toPluginMeta);
|
|
3367
|
+
await storePluginsBatch(metas);
|
|
3368
|
+
const injectionResults = await Promise.allSettled(metas.map((meta) => injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.version, meta.adapterHash, meta.adapterFile)));
|
|
3369
|
+
for (const result of injectionResults) {
|
|
3370
|
+
if (result.status === "rejected") {
|
|
3371
|
+
console.warn("[opentabs] Plugin injection failed during sync.full:", result.reason);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
await sendTabSyncAll();
|
|
3375
|
+
forwardToSidePanel({
|
|
3376
|
+
type: "sp:serverMessage",
|
|
3377
|
+
data: { jsonrpc: "2.0", method: "plugins.changed" }
|
|
3378
|
+
});
|
|
3379
|
+
};
|
|
3380
|
+
var handlePluginUpdate = async (params) => {
|
|
3381
|
+
const validated = validatePluginPayload(params);
|
|
3382
|
+
if (!validated)
|
|
3383
|
+
return;
|
|
3384
|
+
const meta = toPluginMeta(validated);
|
|
3385
|
+
await storePluginsBatch([meta]);
|
|
3386
|
+
await injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.version, meta.adapterHash, meta.adapterFile);
|
|
3387
|
+
const newState = await computePluginTabState(meta);
|
|
3388
|
+
await updateLastKnownState(meta.name, newState.state);
|
|
3389
|
+
sendTabStateNotification(meta.name, newState);
|
|
3390
|
+
forwardToSidePanel({
|
|
3391
|
+
type: "sp:serverMessage",
|
|
3392
|
+
data: { jsonrpc: "2.0", method: "plugins.changed" }
|
|
3393
|
+
});
|
|
3394
|
+
};
|
|
3395
|
+
var handlePluginUninstall = async (params, id) => {
|
|
3396
|
+
const pluginName = params.name;
|
|
3397
|
+
if (typeof pluginName !== "string" || pluginName.length === 0) {
|
|
3398
|
+
sendToServer({
|
|
3399
|
+
jsonrpc: "2.0",
|
|
3400
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: "Missing plugin name" },
|
|
3401
|
+
id
|
|
3402
|
+
});
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
if (!isValidPluginName(pluginName)) {
|
|
3406
|
+
sendToServer({
|
|
3407
|
+
jsonrpc: "2.0",
|
|
3408
|
+
error: { code: JSONRPC_INVALID_PARAMS, message: `Invalid plugin name format: "${pluginName}"` },
|
|
3409
|
+
id
|
|
3410
|
+
});
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
const meta = await getAllPluginMeta();
|
|
3414
|
+
const pluginMeta = meta[pluginName];
|
|
3415
|
+
if (pluginMeta) {
|
|
3416
|
+
try {
|
|
3417
|
+
await cleanupAdaptersInMatchingTabs(pluginName, pluginMeta.urlPatterns);
|
|
3418
|
+
} catch (err2) {
|
|
3419
|
+
console.warn(`[opentabs] Failed to clean up adapters for ${pluginName}:`, err2);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
await removePlugin(pluginName);
|
|
3423
|
+
clearPluginTabState(pluginName);
|
|
3424
|
+
sendToServer({
|
|
3425
|
+
jsonrpc: "2.0",
|
|
3426
|
+
result: { success: true },
|
|
3427
|
+
id
|
|
3428
|
+
});
|
|
3429
|
+
};
|
|
3430
|
+
var methodHandlers = /* @__PURE__ */ new Map([
|
|
3431
|
+
["extension.reload", handleExtensionReload],
|
|
3432
|
+
["sync.full", wrapNotification("sync.full", handleSyncFull)],
|
|
3433
|
+
["plugin.update", wrapNotification("plugin.update", handlePluginUpdate)],
|
|
3434
|
+
["plugin.uninstall", wrapAsync("plugin.uninstall", handlePluginUninstall)],
|
|
3435
|
+
["tool.dispatch", wrapAsync("tool.dispatch", handleToolDispatch)],
|
|
3436
|
+
["resource.read", wrapAsync("resource.read", handleResourceRead)],
|
|
3437
|
+
["prompt.get", wrapAsync("prompt.get", handlePromptGet)],
|
|
3438
|
+
["browser.listTabs", wrapAsync("browser.listTabs", (_params, id) => handleBrowserListTabs(id))],
|
|
3439
|
+
["browser.openTab", wrapAsync("browser.openTab", handleBrowserOpenTab)],
|
|
3440
|
+
["browser.closeTab", wrapAsync("browser.closeTab", handleBrowserCloseTab)],
|
|
3441
|
+
["browser.navigateTab", wrapAsync("browser.navigateTab", handleBrowserNavigateTab)],
|
|
3442
|
+
["browser.focusTab", wrapAsync("browser.focusTab", handleBrowserFocusTab)],
|
|
3443
|
+
["browser.getTabInfo", wrapAsync("browser.getTabInfo", handleBrowserGetTabInfo)],
|
|
3444
|
+
["browser.screenshotTab", wrapAsync("browser.screenshotTab", handleBrowserScreenshotTab)],
|
|
3445
|
+
["browser.getTabContent", wrapAsync("browser.getTabContent", handleBrowserGetTabContent)],
|
|
3446
|
+
["browser.getPageHtml", wrapAsync("browser.getPageHtml", handleBrowserGetPageHtml)],
|
|
3447
|
+
["browser.getStorage", wrapAsync("browser.getStorage", handleBrowserGetStorage)],
|
|
3448
|
+
["browser.clickElement", wrapAsync("browser.clickElement", handleBrowserClickElement)],
|
|
3449
|
+
["browser.typeText", wrapAsync("browser.typeText", handleBrowserTypeText)],
|
|
3450
|
+
["browser.selectOption", wrapAsync("browser.selectOption", handleBrowserSelectOption)],
|
|
3451
|
+
["browser.waitForElement", wrapAsync("browser.waitForElement", handleBrowserWaitForElement)],
|
|
3452
|
+
["browser.queryElements", wrapAsync("browser.queryElements", handleBrowserQueryElements)],
|
|
3453
|
+
["browser.getCookies", wrapAsync("browser.getCookies", handleBrowserGetCookies)],
|
|
3454
|
+
["browser.setCookie", wrapAsync("browser.setCookie", handleBrowserSetCookie)],
|
|
3455
|
+
["browser.deleteCookies", wrapAsync("browser.deleteCookies", handleBrowserDeleteCookies)],
|
|
3456
|
+
["browser.enableNetworkCapture", wrapAsync("browser.enableNetworkCapture", handleBrowserEnableNetworkCapture)],
|
|
3457
|
+
["browser.getNetworkRequests", wrapSync("browser.getNetworkRequests", handleBrowserGetNetworkRequests)],
|
|
3458
|
+
["browser.disableNetworkCapture", wrapSync("browser.disableNetworkCapture", handleBrowserDisableNetworkCapture)],
|
|
3459
|
+
["browser.getConsoleLogs", wrapSync("browser.getConsoleLogs", handleBrowserGetConsoleLogs)],
|
|
3460
|
+
["browser.clearConsoleLogs", wrapSync("browser.clearConsoleLogs", handleBrowserClearConsoleLogs)],
|
|
3461
|
+
["browser.executeScript", wrapAsync("browser.executeScript", handleBrowserExecuteScript)],
|
|
3462
|
+
["browser.listResources", wrapAsync("browser.listResources", handleBrowserListResources)],
|
|
3463
|
+
["browser.getResourceContent", wrapAsync("browser.getResourceContent", handleBrowserGetResourceContent)],
|
|
3464
|
+
["browser.pressKey", wrapAsync("browser.pressKey", handleBrowserPressKey)],
|
|
3465
|
+
["browser.scroll", wrapAsync("browser.scroll", handleBrowserScroll)],
|
|
3466
|
+
["browser.hoverElement", wrapAsync("browser.hoverElement", handleBrowserHoverElement)],
|
|
3467
|
+
["browser.handleDialog", wrapAsync("browser.handleDialog", handleBrowserHandleDialog)],
|
|
3468
|
+
["extension.getState", wrapAsync("extension.getState", (_params, id) => handleExtensionGetState(id))],
|
|
3469
|
+
["extension.getLogs", wrapAsync("extension.getLogs", handleExtensionGetLogs)],
|
|
3470
|
+
["extension.getSidePanel", wrapAsync("extension.getSidePanel", (_params, id) => handleExtensionGetSidePanel(id))],
|
|
3471
|
+
["extension.checkAdapter", wrapAsync("extension.checkAdapter", handleExtensionCheckAdapter)],
|
|
3472
|
+
[
|
|
3473
|
+
"extension.forceReconnect",
|
|
3474
|
+
wrapAsync("extension.forceReconnect", (_params, id) => handleExtensionForceReconnect(id))
|
|
3475
|
+
]
|
|
3476
|
+
]);
|
|
3477
|
+
var handleServerMessage = (message) => {
|
|
3478
|
+
const method = message.method;
|
|
3479
|
+
const id = message.id;
|
|
3480
|
+
const params = message.params ?? {};
|
|
3481
|
+
const isResponse = id !== void 0 && !method;
|
|
3482
|
+
if (isResponse || method && SIDE_PANEL_METHODS.has(method)) {
|
|
3483
|
+
forwardToSidePanel({ type: "sp:serverMessage", data: message });
|
|
3484
|
+
}
|
|
3485
|
+
if (method === "confirmation.request") {
|
|
3486
|
+
notifyConfirmationRequest(params);
|
|
3487
|
+
}
|
|
3488
|
+
if (!method)
|
|
3489
|
+
return;
|
|
3490
|
+
const handler = methodHandlers.get(method);
|
|
3491
|
+
if (handler) {
|
|
3492
|
+
if (!checkRateLimit(method)) {
|
|
3493
|
+
console.warn(`[opentabs] Rate limited: ${method}`);
|
|
3494
|
+
if (id !== void 0) {
|
|
3495
|
+
sendToServer({
|
|
3496
|
+
jsonrpc: "2.0",
|
|
3497
|
+
error: { code: JSONRPC_INTERNAL_ERROR, message: `Rate limited: ${method}` },
|
|
3498
|
+
id
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
handler(params, id);
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
if (id !== void 0) {
|
|
3507
|
+
sendToServer({
|
|
3508
|
+
jsonrpc: "2.0",
|
|
3509
|
+
error: { code: JSONRPC_METHOD_NOT_FOUND, message: `Method not found: ${method}` },
|
|
3510
|
+
id
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
var methodHandlerNames = Array.from(methodHandlers.keys());
|
|
3515
|
+
|
|
3516
|
+
// dist/background-message-handlers.js
|
|
3517
|
+
var wsConnected = false;
|
|
3518
|
+
var lastDisconnectReason;
|
|
3519
|
+
var restoreWsConnectedState = () => {
|
|
3520
|
+
chrome.storage.session.get(WS_CONNECTED_KEY).then((data) => {
|
|
3521
|
+
if (typeof data[WS_CONNECTED_KEY] === "boolean") {
|
|
3522
|
+
wsConnected = data[WS_CONNECTED_KEY];
|
|
3523
|
+
}
|
|
3524
|
+
}).catch(() => {
|
|
3525
|
+
});
|
|
3526
|
+
};
|
|
3527
|
+
var persistWsConnected = (connected) => {
|
|
3528
|
+
wsConnected = connected;
|
|
3529
|
+
chrome.storage.session.set({ [WS_CONNECTED_KEY]: connected }).catch(() => {
|
|
3530
|
+
});
|
|
3531
|
+
};
|
|
3532
|
+
var handleOffscreenGetUrl = (_message, sendResponse) => {
|
|
3533
|
+
(async () => {
|
|
3534
|
+
const stored = await chrome.storage.local.get(SERVER_PORT_KEY).catch(() => ({}));
|
|
3535
|
+
const port = typeof stored[SERVER_PORT_KEY] === "number" && stored[SERVER_PORT_KEY] > 0 ? stored[SERVER_PORT_KEY] : void 0;
|
|
3536
|
+
const url = port ? buildWsUrl(port) : void 0;
|
|
3537
|
+
sendResponse({ url });
|
|
3538
|
+
})().catch(() => {
|
|
3539
|
+
sendResponse({ url: void 0 });
|
|
3540
|
+
});
|
|
3541
|
+
};
|
|
3542
|
+
var handleWsState = (message, sendResponse) => {
|
|
3543
|
+
const wasConnected = wsConnected;
|
|
3544
|
+
const nowConnected = message.connected;
|
|
3545
|
+
persistWsConnected(nowConnected);
|
|
3546
|
+
lastDisconnectReason = nowConnected ? void 0 : message.disconnectReason;
|
|
3547
|
+
forwardToSidePanel({
|
|
3548
|
+
type: "sp:connectionState",
|
|
3549
|
+
data: {
|
|
3550
|
+
connected: nowConnected,
|
|
3551
|
+
disconnectReason: lastDisconnectReason
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
if (!nowConnected && wasConnected) {
|
|
3555
|
+
clearTabStateCache();
|
|
3556
|
+
clearAllConfirmationBadges();
|
|
3557
|
+
}
|
|
3558
|
+
sendResponse({ ok: true });
|
|
3559
|
+
};
|
|
3560
|
+
var handleWsMessage = (message, sendResponse) => {
|
|
3561
|
+
handleServerMessage(message.data);
|
|
3562
|
+
sendResponse({ ok: true });
|
|
3563
|
+
};
|
|
3564
|
+
var handleBgSend = (message, sendResponse) => {
|
|
3565
|
+
sendToServer(message.data);
|
|
3566
|
+
sendResponse({ ok: true });
|
|
3567
|
+
};
|
|
3568
|
+
var handleBgGetConnectionState = (_message, sendResponse) => {
|
|
3569
|
+
sendResponse({
|
|
3570
|
+
connected: wsConnected,
|
|
3571
|
+
disconnectReason: wsConnected ? void 0 : lastDisconnectReason
|
|
3572
|
+
});
|
|
3573
|
+
};
|
|
3574
|
+
var handlePluginLogs = (message, sendResponse) => {
|
|
3575
|
+
const entries = message.entries;
|
|
3576
|
+
if (wsConnected && Array.isArray(entries)) {
|
|
3577
|
+
const plugin = message.plugin;
|
|
3578
|
+
for (const entry of entries) {
|
|
3579
|
+
if (typeof entry !== "object" || entry === null)
|
|
3580
|
+
continue;
|
|
3581
|
+
const e = entry;
|
|
3582
|
+
sendToServer({
|
|
3583
|
+
jsonrpc: "2.0",
|
|
3584
|
+
method: "plugin.log",
|
|
3585
|
+
params: {
|
|
3586
|
+
plugin,
|
|
3587
|
+
level: e.level,
|
|
3588
|
+
message: e.message,
|
|
3589
|
+
data: e.data,
|
|
3590
|
+
ts: e.ts
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
sendResponse({ ok: true });
|
|
3596
|
+
};
|
|
3597
|
+
var handleToolProgress = (message, sendResponse) => {
|
|
3598
|
+
const dispatchId = message.dispatchId;
|
|
3599
|
+
const progress = message.progress;
|
|
3600
|
+
const total = message.total;
|
|
3601
|
+
if (wsConnected && typeof dispatchId === "string" && typeof progress === "number" && typeof total === "number") {
|
|
3602
|
+
sendToServer({
|
|
3603
|
+
jsonrpc: "2.0",
|
|
3604
|
+
method: "tool.progress",
|
|
3605
|
+
params: {
|
|
3606
|
+
dispatchId,
|
|
3607
|
+
progress,
|
|
3608
|
+
total,
|
|
3609
|
+
message: typeof message.message === "string" ? message.message : void 0
|
|
3610
|
+
}
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
if (typeof dispatchId === "string") {
|
|
3614
|
+
notifyDispatchProgress(dispatchId);
|
|
3615
|
+
}
|
|
3616
|
+
sendResponse({ ok: true });
|
|
3617
|
+
};
|
|
3618
|
+
var handleSpConfirmationResponse = (message, sendResponse) => {
|
|
3619
|
+
if (wsConnected) {
|
|
3620
|
+
sendToServer({
|
|
3621
|
+
jsonrpc: "2.0",
|
|
3622
|
+
method: "confirmation.response",
|
|
3623
|
+
params: message.data
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
clearConfirmationBadge();
|
|
3627
|
+
sendResponse({ ok: true });
|
|
3628
|
+
};
|
|
3629
|
+
var handleSpConfirmationTimeout = (_message, sendResponse) => {
|
|
3630
|
+
clearConfirmationBadge();
|
|
3631
|
+
sendResponse({ ok: true });
|
|
3632
|
+
};
|
|
3633
|
+
var handlePortChanged = (message, sendResponse) => {
|
|
3634
|
+
chrome.runtime.sendMessage(message).catch(() => {
|
|
3635
|
+
});
|
|
3636
|
+
sendResponse({ ok: true });
|
|
3637
|
+
};
|
|
3638
|
+
var backgroundHandlers = /* @__PURE__ */ new Map([
|
|
3639
|
+
["offscreen:getUrl", handleOffscreenGetUrl],
|
|
3640
|
+
["ws:state", handleWsState],
|
|
3641
|
+
["ws:message", handleWsMessage],
|
|
3642
|
+
["bg:send", handleBgSend],
|
|
3643
|
+
["bg:getConnectionState", handleBgGetConnectionState],
|
|
3644
|
+
["plugin:logs", handlePluginLogs],
|
|
3645
|
+
["tool:progress", handleToolProgress],
|
|
3646
|
+
["sp:confirmationResponse", handleSpConfirmationResponse],
|
|
3647
|
+
["sp:confirmationTimeout", handleSpConfirmationTimeout],
|
|
3648
|
+
["port-changed", handlePortChanged]
|
|
3649
|
+
]);
|
|
3650
|
+
var EXTENSION_ONLY_TYPES = /* @__PURE__ */ new Set([
|
|
3651
|
+
"offscreen:getUrl",
|
|
3652
|
+
"ws:state",
|
|
3653
|
+
"ws:message",
|
|
3654
|
+
"bg:send",
|
|
3655
|
+
"bg:getConnectionState",
|
|
3656
|
+
"offscreen:getLogs"
|
|
3657
|
+
]);
|
|
3658
|
+
var initBackgroundMessageHandlers = () => {
|
|
3659
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
3660
|
+
if (EXTENSION_ONLY_TYPES.has(message.type) && sender.id !== chrome.runtime.id) {
|
|
3661
|
+
console.warn(`[opentabs] Rejected ${message.type} from unauthorized sender:`, sender.id ?? sender.url);
|
|
3662
|
+
return false;
|
|
3663
|
+
}
|
|
3664
|
+
const handler = backgroundHandlers.get(message.type);
|
|
3665
|
+
if (handler) {
|
|
3666
|
+
handler(message, sendResponse);
|
|
3667
|
+
return true;
|
|
3668
|
+
}
|
|
3669
|
+
return false;
|
|
3670
|
+
});
|
|
3671
|
+
};
|
|
3672
|
+
var backgroundHandlerNames = [...backgroundHandlers.keys()];
|
|
3673
|
+
|
|
3674
|
+
// dist/side-panel-toggle.js
|
|
3675
|
+
var openWindows = /* @__PURE__ */ new Set();
|
|
3676
|
+
var initSidePanelToggle = () => {
|
|
3677
|
+
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false }).catch(() => {
|
|
3678
|
+
});
|
|
3679
|
+
const canToggle = "onOpened" in chrome.sidePanel;
|
|
3680
|
+
if (canToggle) {
|
|
3681
|
+
chrome.sidePanel.onOpened.addListener(({ windowId }) => {
|
|
3682
|
+
openWindows.add(windowId);
|
|
3683
|
+
});
|
|
3684
|
+
chrome.sidePanel.onClosed.addListener(({ windowId }) => {
|
|
3685
|
+
openWindows.delete(windowId);
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
chrome.action.onClicked.addListener(({ windowId }) => {
|
|
3689
|
+
if (canToggle && openWindows.has(windowId)) {
|
|
3690
|
+
chrome.sidePanel.close({ windowId }).catch(() => {
|
|
3691
|
+
});
|
|
3692
|
+
} else {
|
|
3693
|
+
chrome.sidePanel.open({ windowId }).catch(() => {
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
});
|
|
3697
|
+
};
|
|
3698
|
+
|
|
3699
|
+
// dist/background.js
|
|
3700
|
+
initSidePanelToggle();
|
|
3701
|
+
restoreWsConnectedState();
|
|
3702
|
+
var creatingOffscreen = null;
|
|
3703
|
+
var ensureOffscreenDocument = async () => {
|
|
3704
|
+
if (creatingOffscreen)
|
|
3705
|
+
return creatingOffscreen;
|
|
3706
|
+
creatingOffscreen = (async () => {
|
|
3707
|
+
try {
|
|
3708
|
+
await chrome.offscreen.createDocument({
|
|
3709
|
+
url: "offscreen/offscreen.html",
|
|
3710
|
+
reasons: [chrome.offscreen.Reason.WORKERS],
|
|
3711
|
+
justification: "Maintain persistent WebSocket connection to MCP server"
|
|
3712
|
+
});
|
|
3713
|
+
} catch {
|
|
3714
|
+
}
|
|
3715
|
+
})();
|
|
3716
|
+
await creatingOffscreen;
|
|
3717
|
+
creatingOffscreen = null;
|
|
3718
|
+
};
|
|
3719
|
+
var setupKeepaliveAlarm = async () => {
|
|
3720
|
+
const existing = await chrome.alarms.get(KEEPALIVE_ALARM);
|
|
3721
|
+
if (!existing) {
|
|
3722
|
+
await chrome.alarms.create(KEEPALIVE_ALARM, {
|
|
3723
|
+
periodInMinutes: KEEPALIVE_INTERVAL_MINUTES
|
|
3724
|
+
});
|
|
3725
|
+
}
|
|
3726
|
+
};
|
|
3727
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
3728
|
+
if (changeInfo.status === "complete" && tab.url) {
|
|
3729
|
+
injectPluginsIntoTab(tabId, tab.url).then(() => checkTabChanged(tabId, changeInfo)).catch((err2) => console.warn("[opentabs] tab injection failed:", err2));
|
|
3730
|
+
} else if (changeInfo.url) {
|
|
3731
|
+
checkTabChanged(tabId, changeInfo).catch((err2) => console.warn("[opentabs] tab state check failed:", err2));
|
|
3732
|
+
}
|
|
3733
|
+
});
|
|
3734
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
3735
|
+
checkTabRemoved(tabId).catch((err2) => console.warn("[opentabs] tab state check failed:", err2));
|
|
3736
|
+
});
|
|
3737
|
+
chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => {
|
|
3738
|
+
checkTabRemoved(removedTabId).catch((err2) => console.warn("[opentabs] tab state check failed for replaced tab:", err2));
|
|
3739
|
+
chrome.tabs.get(addedTabId).then(async (tab) => {
|
|
3740
|
+
if (tab.url) {
|
|
3741
|
+
await injectPluginsIntoTab(addedTabId, tab.url);
|
|
3742
|
+
}
|
|
3743
|
+
await checkTabChanged(addedTabId, { status: "complete" });
|
|
3744
|
+
}).catch((err2) => console.warn("[opentabs] tab replacement handling failed:", err2));
|
|
3745
|
+
});
|
|
3746
|
+
initBackgroundMessageHandlers();
|
|
3747
|
+
chrome.alarms.onAlarm.addListener(() => {
|
|
3748
|
+
ensureOffscreenDocument().catch((err2) => console.warn("[opentabs] offscreen creation failed:", err2));
|
|
3749
|
+
});
|
|
3750
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
3751
|
+
void (async () => {
|
|
3752
|
+
await ensureOffscreenDocument();
|
|
3753
|
+
await setupKeepaliveAlarm();
|
|
3754
|
+
await reinjectStoredPlugins();
|
|
3755
|
+
})();
|
|
3756
|
+
});
|
|
3757
|
+
chrome.runtime.onStartup.addListener(() => {
|
|
3758
|
+
void (async () => {
|
|
3759
|
+
await ensureOffscreenDocument();
|
|
3760
|
+
await setupKeepaliveAlarm();
|
|
3761
|
+
await reinjectStoredPlugins();
|
|
3762
|
+
})();
|
|
3763
|
+
});
|
|
3764
|
+
ensureOffscreenDocument().catch((err2) => console.warn("[opentabs] offscreen creation failed:", err2));
|
|
3765
|
+
setupKeepaliveAlarm().catch((err2) => console.warn("[opentabs] keepalive alarm failed:", err2));
|
|
3766
|
+
reinjectStoredPlugins().catch((err2) => console.warn("[opentabs] plugin reinjection failed:", err2));
|
|
3767
|
+
initConfirmationBadge();
|
|
3768
|
+
chrome.storage.onChanged.addListener((changes, area) => {
|
|
3769
|
+
if (area !== "local")
|
|
3770
|
+
return;
|
|
3771
|
+
const portChange = changes[SERVER_PORT_KEY];
|
|
3772
|
+
if (typeof portChange?.newValue === "number" && portChange.newValue > 0) {
|
|
3773
|
+
const newUrl = buildWsUrl(portChange.newValue);
|
|
3774
|
+
chrome.runtime.sendMessage({ type: "ws:setUrl", url: newUrl }).catch(() => {
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
if (changes[PLUGINS_META_KEY]) {
|
|
3778
|
+
invalidatePluginCache();
|
|
3779
|
+
}
|
|
3780
|
+
});
|