@opentabs-dev/browser-extension 0.0.64 → 0.0.66

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.
Files changed (58) hide show
  1. package/dist/background-message-handlers.d.ts +5 -1
  2. package/dist/background-message-handlers.d.ts.map +1 -1
  3. package/dist/background-message-handlers.js +76 -2
  4. package/dist/background-message-handlers.js.map +1 -1
  5. package/dist/background.js +263 -140
  6. package/dist/browser-commands/interaction-commands.d.ts +2 -0
  7. package/dist/browser-commands/interaction-commands.d.ts.map +1 -1
  8. package/dist/browser-commands/interaction-commands.js +26 -3
  9. package/dist/browser-commands/interaction-commands.js.map +1 -1
  10. package/dist/browser-commands/key-press-command.d.ts +6 -0
  11. package/dist/browser-commands/key-press-command.d.ts.map +1 -1
  12. package/dist/browser-commands/key-press-command.js +137 -118
  13. package/dist/browser-commands/key-press-command.js.map +1 -1
  14. package/dist/dispatch-helpers.js +2 -2
  15. package/dist/dispatch-helpers.js.map +1 -1
  16. package/dist/extension-messages.d.ts +13 -1
  17. package/dist/extension-messages.d.ts.map +1 -1
  18. package/dist/iife-injection.d.ts +5 -4
  19. package/dist/iife-injection.d.ts.map +1 -1
  20. package/dist/iife-injection.js +21 -12
  21. package/dist/iife-injection.js.map +1 -1
  22. package/dist/message-router.d.ts +2 -0
  23. package/dist/message-router.d.ts.map +1 -1
  24. package/dist/message-router.js +16 -4
  25. package/dist/message-router.js.map +1 -1
  26. package/dist/offscreen/index.js +1 -0
  27. package/dist/offscreen/index.js.map +1 -1
  28. package/dist/side-panel/App.d.ts.map +1 -1
  29. package/dist/side-panel/App.js +22 -2
  30. package/dist/side-panel/App.js.map +1 -1
  31. package/dist/side-panel/bridge.d.ts +10 -1
  32. package/dist/side-panel/bridge.d.ts.map +1 -1
  33. package/dist/side-panel/bridge.js +5 -1
  34. package/dist/side-panel/bridge.js.map +1 -1
  35. package/dist/side-panel/components/FailedPluginCard.d.ts +6 -2
  36. package/dist/side-panel/components/FailedPluginCard.d.ts.map +1 -1
  37. package/dist/side-panel/components/FailedPluginCard.js +11 -3
  38. package/dist/side-panel/components/FailedPluginCard.js.map +1 -1
  39. package/dist/side-panel/components/PluginCard.d.ts.map +1 -1
  40. package/dist/side-panel/components/PluginCard.js +15 -2
  41. package/dist/side-panel/components/PluginCard.js.map +1 -1
  42. package/dist/side-panel/components/PluginList.d.ts +3 -1
  43. package/dist/side-panel/components/PluginList.d.ts.map +1 -1
  44. package/dist/side-panel/components/PluginList.js +2 -2
  45. package/dist/side-panel/components/PluginList.js.map +1 -1
  46. package/dist/side-panel/components/PluginMenu.d.ts.map +1 -1
  47. package/dist/side-panel/components/PluginMenu.js +2 -2
  48. package/dist/side-panel/components/PluginMenu.js.map +1 -1
  49. package/dist/side-panel/side-panel.js +763 -513
  50. package/dist/side-panel/styles.css +1 -1
  51. package/dist/tab-matching.d.ts +5 -3
  52. package/dist/tab-matching.d.ts.map +1 -1
  53. package/dist/tab-matching.js +27 -9
  54. package/dist/tab-matching.js.map +1 -1
  55. package/dist/tab-state.js +2 -2
  56. package/dist/tab-state.js.map +1 -1
  57. package/manifest.json +1 -1
  58. package/package.json +1 -1
@@ -1157,12 +1157,23 @@ var invalidatePluginCache = () => {
1157
1157
  };
1158
1158
 
1159
1159
  // dist/tab-matching.js
1160
- var urlMatchesPatterns = (url, patterns) => {
1160
+ var urlMatchesPatterns = (url, patterns, excludePatterns) => {
1161
+ let matched = false;
1161
1162
  for (const pattern of patterns) {
1162
- if (matchPattern(url, pattern))
1163
- return true;
1163
+ if (matchPattern(url, pattern)) {
1164
+ matched = true;
1165
+ break;
1166
+ }
1167
+ }
1168
+ if (!matched)
1169
+ return false;
1170
+ if (excludePatterns) {
1171
+ for (const pattern of excludePatterns) {
1172
+ if (matchPattern(url, pattern))
1173
+ return false;
1174
+ }
1164
1175
  }
1165
- return false;
1176
+ return true;
1166
1177
  };
1167
1178
  var matchPattern = (url, pattern) => {
1168
1179
  const matchResult = pattern.match(/^(\*|https?|ftp):\/\/(.+?)(\/.*)$/);
@@ -1227,8 +1238,10 @@ var findAllMatchingTabs = async (plugin) => {
1227
1238
  }
1228
1239
  }
1229
1240
  }
1230
- if (allMatches.length <= 1)
1231
- return allMatches;
1241
+ const excludePatterns = plugin.excludePatterns ?? [];
1242
+ const filtered = excludePatterns.length > 0 ? allMatches.filter((tab) => !tab.url || !urlMatchesPatterns(tab.url, excludePatterns)) : allMatches;
1243
+ if (filtered.length <= 1)
1244
+ return filtered;
1232
1245
  let focusedWindowId;
1233
1246
  try {
1234
1247
  const focusedWindow = await chrome.windows.getLastFocused();
@@ -1245,7 +1258,7 @@ var findAllMatchingTabs = async (plugin) => {
1245
1258
  return 1;
1246
1259
  return 0;
1247
1260
  };
1248
- return allMatches.slice().sort((a, b) => rank(b) - rank(a));
1261
+ return filtered.slice().sort((a, b) => rank(b) - rank(a));
1249
1262
  };
1250
1263
 
1251
1264
  // dist/tab-state.js
@@ -1483,7 +1496,7 @@ var checkTabChanged = async (changedTabId, changeInfo) => {
1483
1496
  if (changeInfo.url) {
1484
1497
  const changedUrl = changeInfo.url;
1485
1498
  affectedPlugins = plugins.filter((p) => {
1486
- if (urlMatchesPatterns(changedUrl, p.urlPatterns))
1499
+ if (urlMatchesPatterns(changedUrl, p.urlPatterns, p.excludePatterns))
1487
1500
  return true;
1488
1501
  const cached = lastKnownState.get(p.name);
1489
1502
  return cached !== void 0 && getAggregateState(cached) !== "closed";
@@ -1499,7 +1512,7 @@ var checkTabChanged = async (changedTabId, changeInfo) => {
1499
1512
  if (!tabUrl)
1500
1513
  return;
1501
1514
  affectedPlugins = plugins.filter((p) => {
1502
- if (urlMatchesPatterns(tabUrl, p.urlPatterns))
1515
+ if (urlMatchesPatterns(tabUrl, p.urlPatterns, p.excludePatterns))
1503
1516
  return true;
1504
1517
  const cached = lastKnownState.get(p.name);
1505
1518
  return cached !== void 0 && getAggregateState(cached) !== "closed";
@@ -2075,11 +2088,12 @@ var handleBrowserClickElement = async (params, id) => {
2075
2088
  const el = document.querySelector(sel);
2076
2089
  if (!el)
2077
2090
  return { error: `Element not found: ${sel}` };
2078
- el.click();
2091
+ const rect = el.getBoundingClientRect();
2079
2092
  return {
2080
- clicked: true,
2081
2093
  tagName: el.tagName.toLowerCase(),
2082
- text: (el.textContent || "").trim().slice(0, maxPreview)
2094
+ text: (el.textContent || "").trim().slice(0, maxPreview),
2095
+ x: rect.left + rect.width / 2,
2096
+ y: rect.top + rect.height / 2
2083
2097
  };
2084
2098
  },
2085
2099
  args: [selector, TEXT_PREVIEW_MAX_LENGTH]
@@ -2087,7 +2101,27 @@ var handleBrowserClickElement = async (params, id) => {
2087
2101
  const result = extractScriptResult(results, id);
2088
2102
  if (!result)
2089
2103
  return;
2090
- sendSuccessResult(id, { clicked: result.clicked, tagName: result.tagName, text: result.text });
2104
+ const x = result.x;
2105
+ const y = result.y;
2106
+ await withDebugger(tabId, async () => {
2107
+ await chrome.debugger.sendCommand({ tabId }, "Input.dispatchMouseEvent", {
2108
+ type: "mousePressed",
2109
+ x,
2110
+ y,
2111
+ button: "left",
2112
+ buttons: 1,
2113
+ clickCount: 1
2114
+ });
2115
+ await chrome.debugger.sendCommand({ tabId }, "Input.dispatchMouseEvent", {
2116
+ type: "mouseReleased",
2117
+ x,
2118
+ y,
2119
+ button: "left",
2120
+ buttons: 0,
2121
+ clickCount: 1
2122
+ });
2123
+ });
2124
+ sendSuccessResult(id, { clicked: true, tagName: result.tagName, text: result.text });
2091
2125
  } catch (err2) {
2092
2126
  sendErrorResult(id, err2);
2093
2127
  }
@@ -2428,6 +2462,57 @@ var UNSHIFTED_PUNCTUATION_CODES = {
2428
2462
  "/": "Slash",
2429
2463
  "`": "Backquote"
2430
2464
  };
2465
+ var NAMED_KEY_CODES = {
2466
+ Enter: 13,
2467
+ Escape: 27,
2468
+ Tab: 9,
2469
+ Backspace: 8,
2470
+ Delete: 46,
2471
+ ArrowUp: 38,
2472
+ ArrowDown: 40,
2473
+ ArrowLeft: 37,
2474
+ ArrowRight: 39,
2475
+ Home: 36,
2476
+ End: 35,
2477
+ PageUp: 33,
2478
+ PageDown: 34,
2479
+ " ": 32,
2480
+ F1: 112,
2481
+ F2: 113,
2482
+ F3: 114,
2483
+ F4: 115,
2484
+ F5: 116,
2485
+ F6: 117,
2486
+ F7: 118,
2487
+ F8: 119,
2488
+ F9: 120,
2489
+ F10: 121,
2490
+ F11: 122,
2491
+ F12: 123,
2492
+ Insert: 45
2493
+ };
2494
+ var deriveCode = (key) => {
2495
+ if (key.length === 1) {
2496
+ const upper = key.toUpperCase();
2497
+ if (upper >= "A" && upper <= "Z")
2498
+ return `Key${upper}`;
2499
+ if (key >= "0" && key <= "9")
2500
+ return `Digit${key}`;
2501
+ if (key === " ")
2502
+ return "Space";
2503
+ return SHIFTED_PUNCTUATION_CODES[key] ?? UNSHIFTED_PUNCTUATION_CODES[key] ?? key;
2504
+ }
2505
+ return key;
2506
+ };
2507
+ var getVirtualKeyCode = (key) => {
2508
+ if (NAMED_KEY_CODES[key] !== void 0)
2509
+ return NAMED_KEY_CODES[key];
2510
+ if (key.length === 1)
2511
+ return key.toUpperCase().charCodeAt(0);
2512
+ return 0;
2513
+ };
2514
+ var buildModifiers = (shift, ctrl, alt, meta) => (alt ? 1 : 0) | (ctrl ? 2 : 0) | (meta ? 4 : 0) | (shift ? 8 : 0);
2515
+ var isPrintableKeyPress = (key, ctrl, meta) => key.length === 1 && !ctrl && !meta;
2431
2516
  var handleBrowserPressKey = async (params, id) => {
2432
2517
  try {
2433
2518
  const tabId = requireTabId(params, id);
@@ -2442,119 +2527,79 @@ var handleBrowserPressKey = async (params, id) => {
2442
2527
  const ctrlKey = modifiers.ctrl === true;
2443
2528
  const altKey = modifiers.alt === true;
2444
2529
  const metaKey = modifiers.meta === true;
2445
- const results = await chrome.scripting.executeScript({
2446
- target: { tabId },
2447
- world: "MAIN",
2448
- func: (k, sel, shift, ctrl, alt, meta, shiftedPunct, unshiftedPunct) => {
2449
- let target = null;
2450
- if (sel) {
2451
- target = document.querySelector(sel);
2452
- if (!target)
2530
+ let focusTarget;
2531
+ if (selector) {
2532
+ const focusResults = await chrome.scripting.executeScript({
2533
+ target: { tabId },
2534
+ world: "MAIN",
2535
+ func: (sel) => {
2536
+ const el = document.querySelector(sel);
2537
+ if (!el)
2453
2538
  return { error: `Element not found: ${sel}` };
2454
- target.focus();
2455
- } else {
2456
- target = document.activeElement ?? document.body;
2457
- }
2458
- const deriveCode = (k2) => {
2459
- if (k2.length === 1) {
2460
- const upper = k2.toUpperCase();
2461
- if (upper >= "A" && upper <= "Z")
2462
- return `Key${upper}`;
2463
- if (k2 >= "0" && k2 <= "9")
2464
- return `Digit${k2}`;
2465
- if (k2 === " ")
2466
- return "Space";
2467
- return shiftedPunct[k2] ?? unshiftedPunct[k2] ?? k2;
2468
- }
2469
- return k2;
2470
- };
2471
- const KEY_CODES = {
2472
- Enter: 13,
2473
- Escape: 27,
2474
- Tab: 9,
2475
- Backspace: 8,
2476
- Delete: 46,
2477
- ArrowUp: 38,
2478
- ArrowDown: 40,
2479
- ArrowLeft: 37,
2480
- ArrowRight: 39,
2481
- Home: 36,
2482
- End: 35,
2483
- PageUp: 33,
2484
- PageDown: 34,
2485
- " ": 32
2486
- };
2487
- const getKeyCode = (k2) => {
2488
- if (KEY_CODES[k2] !== void 0)
2489
- return KEY_CODES[k2];
2490
- if (k2.length === 1)
2491
- return k2.toUpperCase().charCodeAt(0);
2492
- return 0;
2493
- };
2494
- const code = deriveCode(k);
2495
- const keyCode = getKeyCode(k);
2496
- const isPrintable = k.length === 1;
2497
- const eventInit = {
2498
- key: k,
2539
+ el.focus();
2540
+ return {
2541
+ tagName: el.tagName.toLowerCase(),
2542
+ id: el.id || void 0
2543
+ };
2544
+ },
2545
+ args: [selector]
2546
+ });
2547
+ const focusResult = extractScriptResult(focusResults, id);
2548
+ if (!focusResult)
2549
+ return;
2550
+ focusTarget = { tagName: focusResult.tagName, id: focusResult.id };
2551
+ }
2552
+ const code = deriveCode(key);
2553
+ const windowsVirtualKeyCode = getVirtualKeyCode(key);
2554
+ const cdpModifiers = buildModifiers(shiftKey, ctrlKey, altKey, metaKey);
2555
+ const printable = isPrintableKeyPress(key, ctrlKey, metaKey);
2556
+ const text = printable ? key : key === "Enter" ? "\r" : key === "Tab" ? " " : "";
2557
+ await withDebugger(tabId, async () => {
2558
+ await chrome.debugger.sendCommand({ tabId }, "Input.dispatchKeyEvent", {
2559
+ type: printable ? "keyDown" : "rawKeyDown",
2560
+ modifiers: cdpModifiers,
2561
+ windowsVirtualKeyCode,
2562
+ code,
2563
+ key,
2564
+ text: printable ? text : ""
2565
+ });
2566
+ if (printable) {
2567
+ await chrome.debugger.sendCommand({ tabId }, "Input.dispatchKeyEvent", {
2568
+ type: "char",
2569
+ modifiers: cdpModifiers,
2570
+ windowsVirtualKeyCode: key.charCodeAt(0),
2499
2571
  code,
2500
- keyCode,
2501
- which: keyCode,
2502
- bubbles: true,
2503
- cancelable: true,
2504
- shiftKey: shift,
2505
- ctrlKey: ctrl,
2506
- metaKey: meta,
2507
- altKey: alt
2508
- };
2509
- target.dispatchEvent(new KeyboardEvent("keydown", eventInit));
2510
- if (isPrintable) {
2511
- target.dispatchEvent(new KeyboardEvent("keypress", eventInit));
2512
- }
2513
- target.dispatchEvent(new KeyboardEvent("keyup", eventInit));
2514
- if (isPrintable) {
2515
- const tag = target.tagName.toLowerCase();
2516
- const isEditable = tag === "input" || tag === "textarea" || target.isContentEditable;
2517
- if (isEditable) {
2518
- if (tag === "input" || tag === "textarea") {
2519
- const input = target;
2520
- const start = input.selectionStart ?? input.value.length;
2521
- const end = input.selectionEnd ?? start;
2522
- input.value = input.value.slice(0, start) + k + input.value.slice(end);
2523
- input.selectionStart = input.selectionEnd = start + 1;
2524
- } else {
2525
- const selection = window.getSelection();
2526
- if (selection && selection.rangeCount > 0) {
2527
- const range = selection.getRangeAt(0);
2528
- range.deleteContents();
2529
- range.insertNode(document.createTextNode(k));
2530
- range.collapse(false);
2531
- selection.removeAllRanges();
2532
- selection.addRange(range);
2533
- }
2534
- }
2535
- target.dispatchEvent(new InputEvent("input", {
2536
- bubbles: true,
2537
- cancelable: true,
2538
- inputType: "insertText",
2539
- data: k
2540
- }));
2541
- }
2542
- }
2543
- return {
2544
- pressed: true,
2545
- key: k,
2546
- target: {
2547
- tagName: target.tagName.toLowerCase(),
2548
- id: target.id || void 0
2549
- }
2550
- };
2551
- },
2552
- args: [key, selector, shiftKey, ctrlKey, altKey, metaKey, SHIFTED_PUNCTUATION_CODES, UNSHIFTED_PUNCTUATION_CODES]
2572
+ key,
2573
+ text
2574
+ });
2575
+ }
2576
+ await chrome.debugger.sendCommand({ tabId }, "Input.dispatchKeyEvent", {
2577
+ type: "keyUp",
2578
+ modifiers: cdpModifiers,
2579
+ windowsVirtualKeyCode,
2580
+ code,
2581
+ key
2582
+ });
2553
2583
  });
2554
- const result = extractScriptResult(results, id);
2555
- if (!result)
2556
- return;
2557
- sendSuccessResult(id, { pressed: result.pressed, key: result.key, target: result.target });
2584
+ if (!focusTarget) {
2585
+ const activeResults = await chrome.scripting.executeScript({
2586
+ target: { tabId },
2587
+ world: "MAIN",
2588
+ func: () => {
2589
+ const el = document.activeElement ?? document.body;
2590
+ return {
2591
+ tagName: el.tagName.toLowerCase(),
2592
+ id: el.id || void 0
2593
+ };
2594
+ },
2595
+ args: []
2596
+ });
2597
+ const activeResult = extractScriptResult(activeResults, id);
2598
+ if (!activeResult)
2599
+ return;
2600
+ focusTarget = { tagName: activeResult.tagName, id: activeResult.id };
2601
+ }
2602
+ sendSuccessResult(id, { pressed: true, key, target: focusTarget });
2558
2603
  } catch (err2) {
2559
2604
  sendErrorResult(id, err2);
2560
2605
  }
@@ -2967,21 +3012,26 @@ var injectAdapterFile = async (tabId, pluginName, adapterHash, adapterFilePath)
2967
3012
  }
2968
3013
  }
2969
3014
  };
2970
- var queryMatchingTabIds = async (urlPatterns) => {
2971
- const tabIds = /* @__PURE__ */ new Set();
3015
+ var queryMatchingTabIds = async (urlPatterns, excludePatterns) => {
3016
+ const seen = /* @__PURE__ */ new Set();
3017
+ const ids = [];
2972
3018
  for (const pattern of urlPatterns) {
2973
3019
  try {
2974
3020
  const tabs = await chrome.tabs.query({ url: pattern });
2975
3021
  for (const tab of tabs) {
2976
- if (tab.id !== void 0) {
2977
- tabIds.add(tab.id);
3022
+ if (tab.id !== void 0 && !seen.has(tab.id)) {
3023
+ if (excludePatterns && excludePatterns.length > 0 && tab.url && urlMatchesPatterns(tab.url, excludePatterns)) {
3024
+ continue;
3025
+ }
3026
+ seen.add(tab.id);
3027
+ ids.push(tab.id);
2978
3028
  }
2979
3029
  }
2980
3030
  } catch (err2) {
2981
3031
  console.warn(`[opentabs] chrome.tabs.query failed for pattern ${pattern}:`, err2);
2982
3032
  }
2983
3033
  }
2984
- return Array.from(tabIds);
3034
+ return ids;
2985
3035
  };
2986
3036
  var prepareForReinjection = async (tabId) => {
2987
3037
  await chrome.scripting.executeScript({
@@ -3014,12 +3064,12 @@ var prepareForReinjection = async (tabId) => {
3014
3064
  console.warn(`[opentabs] prepareForReinjection failed:`, err2);
3015
3065
  });
3016
3066
  };
3017
- var injectPluginIntoMatchingTabs = async (pluginName, urlPatterns, forceReinject = false, adapterHash, adapterFile, skipIfHashMatches) => {
3067
+ var injectPluginIntoMatchingTabs = async (pluginName, urlPatterns, forceReinject = false, adapterHash, adapterFile, skipIfHashMatches, excludePatterns) => {
3018
3068
  if (!isSafePluginName(pluginName)) {
3019
3069
  console.warn(`[opentabs] Skipping injection for unsafe plugin name: ${pluginName}`);
3020
3070
  return [];
3021
3071
  }
3022
- const tabIds = await queryMatchingTabIds(urlPatterns);
3072
+ const tabIds = await queryMatchingTabIds(urlPatterns, excludePatterns);
3023
3073
  const results = await Promise.allSettled(tabIds.map(async (tabId) => {
3024
3074
  if (!forceReinject && await isAdapterPresent(tabId, pluginName)) {
3025
3075
  return tabId;
@@ -3049,7 +3099,7 @@ var injectPluginsIntoTab = async (tabId, tabUrl) => {
3049
3099
  const plugins = Object.values(index);
3050
3100
  if (plugins.length === 0)
3051
3101
  return;
3052
- const matching = plugins.filter((p) => isSafePluginName(p.name) && urlMatchesPatterns(tabUrl, p.urlPatterns));
3102
+ const matching = plugins.filter((p) => isSafePluginName(p.name) && urlMatchesPatterns(tabUrl, p.urlPatterns, p.excludePatterns));
3053
3103
  if (matching.length === 0)
3054
3104
  return;
3055
3105
  const presenceResults = await Promise.allSettled(matching.map(async (plugin) => ({
@@ -3067,12 +3117,12 @@ var injectPluginsIntoTab = async (tabId, tabUrl) => {
3067
3117
  }
3068
3118
  }));
3069
3119
  };
3070
- var cleanupAdaptersInMatchingTabs = async (pluginName, urlPatterns) => {
3120
+ var cleanupAdaptersInMatchingTabs = async (pluginName, urlPatterns, excludePatterns) => {
3071
3121
  if (!isSafePluginName(pluginName)) {
3072
3122
  console.warn(`[opentabs] Skipping cleanup for unsafe plugin name: ${pluginName}`);
3073
3123
  return;
3074
3124
  }
3075
- const tabIds = await queryMatchingTabIds(urlPatterns);
3125
+ const tabIds = await queryMatchingTabIds(urlPatterns, excludePatterns);
3076
3126
  await Promise.allSettled(tabIds.map(async (tabId) => {
3077
3127
  try {
3078
3128
  await chrome.scripting.executeScript({
@@ -3120,7 +3170,7 @@ var reinjectStoredPlugins = async () => {
3120
3170
  const plugins = Object.values(index);
3121
3171
  if (plugins.length === 0)
3122
3172
  return;
3123
- const results = await Promise.allSettled(plugins.map((plugin) => injectPluginIntoMatchingTabs(plugin.name, plugin.urlPatterns, false, plugin.adapterHash, plugin.adapterFile)));
3173
+ const results = await Promise.allSettled(plugins.map((plugin) => injectPluginIntoMatchingTabs(plugin.name, plugin.urlPatterns, false, plugin.adapterHash, plugin.adapterFile, void 0, plugin.excludePatterns)));
3124
3174
  for (let i = 0; i < results.length; i++) {
3125
3175
  const result = results[i];
3126
3176
  if (result && result.status === "rejected") {
@@ -3417,7 +3467,7 @@ var dispatchWithTabFallback = async (config) => {
3417
3467
  continue;
3418
3468
  try {
3419
3469
  const currentTab = await chrome.tabs.get(tab.id);
3420
- if (!currentTab.url || !urlMatchesPatterns(currentTab.url, plugin.urlPatterns)) {
3470
+ if (!currentTab.url || !urlMatchesPatterns(currentTab.url, plugin.urlPatterns, plugin.excludePatterns)) {
3421
3471
  firstError ??= { code: JSONRPC_NO_USABLE_TAB, message: "Tab navigated away from matching URL" };
3422
3472
  continue;
3423
3473
  }
@@ -3489,7 +3539,7 @@ var dispatchToTargetedTab = async (config) => {
3489
3539
  });
3490
3540
  return;
3491
3541
  }
3492
- if (!tab.url || !urlMatchesPatterns(tab.url, plugin.urlPatterns)) {
3542
+ if (!tab.url || !urlMatchesPatterns(tab.url, plugin.urlPatterns, plugin.excludePatterns)) {
3493
3543
  sendToServer({
3494
3544
  jsonrpc: "2.0",
3495
3545
  error: {
@@ -3824,6 +3874,8 @@ var toPluginMeta = (p) => ({
3824
3874
  version: p.version,
3825
3875
  displayName: p.displayName,
3826
3876
  urlPatterns: p.urlPatterns,
3877
+ excludePatterns: p.excludePatterns.length > 0 ? p.excludePatterns : void 0,
3878
+ homepage: p.homepage,
3827
3879
  permission: p.permission,
3828
3880
  sourcePath: p.sourcePath,
3829
3881
  adapterHash: p.adapterHash,
@@ -3849,6 +3901,8 @@ var validatePluginPayload = (raw) => {
3849
3901
  return null;
3850
3902
  }
3851
3903
  const urlPatterns = Array.isArray(obj.urlPatterns) ? obj.urlPatterns.filter((p) => typeof p === "string") : [];
3904
+ const excludePatterns = Array.isArray(obj.excludePatterns) ? obj.excludePatterns.filter((p) => typeof p === "string") : [];
3905
+ const homepage = typeof obj.homepage === "string" && obj.homepage.length > 0 ? obj.homepage : void 0;
3852
3906
  const tools = Array.isArray(obj.tools) ? obj.tools.filter((t) => typeof t === "object" && t !== null && typeof t.name === "string" && typeof t.description === "string").map((t) => {
3853
3907
  if (t.permission !== "off" && t.permission !== "ask" && t.permission !== "auto") {
3854
3908
  console.warn(`[opentabs] Tool "${t.name}" in plugin "${obj.name}" has invalid "permission" field \u2014 defaulting to permission='off'. This is a server-side bug.`);
@@ -3868,6 +3922,8 @@ var validatePluginPayload = (raw) => {
3868
3922
  version: typeof obj.version === "string" ? obj.version : "0.0.0",
3869
3923
  displayName: typeof obj.displayName === "string" ? obj.displayName : obj.name,
3870
3924
  urlPatterns,
3925
+ excludePatterns,
3926
+ homepage,
3871
3927
  permission: obj.permission === "off" || obj.permission === "ask" || obj.permission === "auto" ? obj.permission : "off",
3872
3928
  sourcePath: typeof obj.sourcePath === "string" ? obj.sourcePath : void 0,
3873
3929
  adapterHash: typeof obj.adapterHash === "string" ? obj.adapterHash : void 0,
@@ -3915,7 +3971,7 @@ var handleSyncFull = async (params) => {
3915
3971
  await Promise.allSettled(removedNames.map((name) => {
3916
3972
  const meta = existingMeta[name];
3917
3973
  if (meta)
3918
- return cleanupAdaptersInMatchingTabs(name, meta.urlPatterns);
3974
+ return cleanupAdaptersInMatchingTabs(name, meta.urlPatterns, meta.excludePatterns);
3919
3975
  return Promise.resolve();
3920
3976
  }));
3921
3977
  await removePluginsBatch(removedNames);
@@ -3924,7 +3980,7 @@ var handleSyncFull = async (params) => {
3924
3980
  }
3925
3981
  const metas = uniquePlugins.map(toPluginMeta);
3926
3982
  await storePluginsBatch(metas);
3927
- const injectionResults = await Promise.allSettled(metas.map((meta) => injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.adapterHash, meta.adapterFile, meta.adapterHash)));
3983
+ const injectionResults = await Promise.allSettled(metas.map((meta) => injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.adapterHash, meta.adapterFile, meta.adapterHash, meta.excludePatterns)));
3928
3984
  for (const result of injectionResults) {
3929
3985
  if (result.status === "rejected") {
3930
3986
  console.warn("[opentabs] Plugin injection failed during sync.full:", result.reason);
@@ -3946,6 +4002,8 @@ var handleSyncFull = async (params) => {
3946
4002
  source: raw?.source === "npm" || raw?.source === "local" ? raw.source : "local",
3947
4003
  tabState: "closed",
3948
4004
  urlPatterns: p.urlPatterns,
4005
+ ...p.excludePatterns.length > 0 ? { excludePatterns: p.excludePatterns } : {},
4006
+ ...p.homepage ? { homepage: p.homepage } : {},
3949
4007
  tools: p.tools,
3950
4008
  reviewed: raw?.reviewed === true,
3951
4009
  iconSvg: p.iconSvg,
@@ -3989,7 +4047,7 @@ var handlePluginUpdate = async (params) => {
3989
4047
  return;
3990
4048
  const meta = toPluginMeta(validated);
3991
4049
  await storePluginsBatch([meta]);
3992
- await injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.adapterHash, meta.adapterFile);
4050
+ await injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.adapterHash, meta.adapterFile, void 0, meta.excludePatterns);
3993
4051
  const newState = await computePluginTabState(meta);
3994
4052
  await updateLastKnownState(meta.name, newState);
3995
4053
  sendTabStateNotification(meta.name, newState);
@@ -4002,6 +4060,8 @@ var handlePluginUpdate = async (params) => {
4002
4060
  source: params.source === "npm" || params.source === "local" ? params.source : "local",
4003
4061
  tabState: newState.state,
4004
4062
  urlPatterns: validated.urlPatterns,
4063
+ ...validated.excludePatterns.length > 0 ? { excludePatterns: validated.excludePatterns } : {},
4064
+ ...validated.homepage ? { homepage: validated.homepage } : {},
4005
4065
  tools: validated.tools,
4006
4066
  reviewed: params.reviewed === true,
4007
4067
  iconSvg: validated.iconSvg,
@@ -4045,7 +4105,7 @@ var handlePluginUninstall = async (params, id) => {
4045
4105
  const pluginMeta = meta[pluginName];
4046
4106
  if (pluginMeta) {
4047
4107
  try {
4048
- await cleanupAdaptersInMatchingTabs(pluginName, pluginMeta.urlPatterns);
4108
+ await cleanupAdaptersInMatchingTabs(pluginName, pluginMeta.urlPatterns, pluginMeta.excludePatterns);
4049
4109
  } catch (err2) {
4050
4110
  console.warn(`[opentabs] Failed to clean up adapters for ${pluginName}:`, err2);
4051
4111
  }
@@ -4266,11 +4326,14 @@ var handleBgGetFullState = (_message, sendResponse) => {
4266
4326
  const plugins = Object.values(metaIndex).map((meta) => {
4267
4327
  const serverPlugin = serverPluginMap.get(meta.name);
4268
4328
  let tabState = "closed";
4329
+ let tabs;
4269
4330
  const serialized = tabStates.get(meta.name);
4270
4331
  if (serialized) {
4271
4332
  try {
4272
4333
  const parsed = JSON.parse(serialized);
4273
4334
  tabState = parsed.state;
4335
+ if (parsed.tabs.length > 0)
4336
+ tabs = parsed.tabs;
4274
4337
  } catch {
4275
4338
  }
4276
4339
  }
@@ -4287,6 +4350,7 @@ var handleBgGetFullState = (_message, sendResponse) => {
4287
4350
  permission: serverPlugin?.permission ?? meta.permission,
4288
4351
  tools,
4289
4352
  tabState,
4353
+ tabs,
4290
4354
  source: serverPlugin?.source ?? "local",
4291
4355
  reviewed: serverPlugin?.reviewed ?? false,
4292
4356
  sdkVersion: serverPlugin?.sdkVersion,
@@ -4555,6 +4619,14 @@ var handleBgRemovePlugin = (message, sendResponse) => {
4555
4619
  sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4556
4620
  });
4557
4621
  };
4622
+ var handleBgRemoveFailedPlugin = (message, sendResponse) => {
4623
+ const specifier = message.specifier;
4624
+ sendServerRequest("plugin.removeBySpecifier", { specifier }).then((result) => {
4625
+ sendResponse(result);
4626
+ }).catch((err2) => {
4627
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4628
+ });
4629
+ };
4558
4630
  var handleBgUpdatePlugin = (message, sendResponse) => {
4559
4631
  const name = message.name;
4560
4632
  sendServerRequest("plugin.updateFromRegistry", { name }).then((result) => {
@@ -4568,6 +4640,53 @@ var handlePortChanged = (message, sendResponse) => {
4568
4640
  });
4569
4641
  sendResponse({ ok: true });
4570
4642
  };
4643
+ var lastFocusedTabId = /* @__PURE__ */ new Map();
4644
+ var pickNextTab = (sorted, pluginName) => {
4645
+ if (sorted.length === 0)
4646
+ return void 0;
4647
+ const lastId = lastFocusedTabId.get(pluginName);
4648
+ if (lastId === void 0) {
4649
+ return sorted.find((t) => !t.tab.active) ?? sorted[0];
4650
+ }
4651
+ const lastIdx = sorted.findIndex((t) => t.id === lastId);
4652
+ const nextIdx = (lastIdx + 1) % sorted.length;
4653
+ return sorted[nextIdx];
4654
+ };
4655
+ var handleBgOpenPluginTab = (message, sendResponse) => {
4656
+ const pluginName = message.pluginName;
4657
+ if (!pluginName) {
4658
+ sendResponse({ opened: false });
4659
+ return;
4660
+ }
4661
+ (async () => {
4662
+ const meta = await getPluginMeta(pluginName);
4663
+ if (!meta)
4664
+ return { opened: false };
4665
+ const tabs = await findAllMatchingTabs(meta);
4666
+ if (tabs.length > 0) {
4667
+ const withIds = [];
4668
+ for (const tab of tabs) {
4669
+ if (tab.id !== void 0)
4670
+ withIds.push({ tab, id: tab.id });
4671
+ }
4672
+ withIds.sort((a, b) => a.id - b.id);
4673
+ const pick = pickNextTab(withIds, pluginName);
4674
+ if (pick) {
4675
+ lastFocusedTabId.set(pluginName, pick.id);
4676
+ await chrome.tabs.update(pick.id, { active: true });
4677
+ if (pick.tab.windowId !== void 0) {
4678
+ await chrome.windows.update(pick.tab.windowId, { focused: true });
4679
+ }
4680
+ return { opened: true, tabId: pick.id };
4681
+ }
4682
+ }
4683
+ if (meta.homepage) {
4684
+ const newTab = await chrome.tabs.create({ url: meta.homepage });
4685
+ return { opened: true, tabId: newTab.id };
4686
+ }
4687
+ return { opened: false };
4688
+ })().then((result) => sendResponse(result)).catch(() => sendResponse({ opened: false }));
4689
+ };
4571
4690
  var backgroundHandlers = /* @__PURE__ */ new Map([
4572
4691
  ["offscreen:getUrl", handleOffscreenGetUrl],
4573
4692
  ["ws:state", handleWsState],
@@ -4580,7 +4699,9 @@ var backgroundHandlers = /* @__PURE__ */ new Map([
4580
4699
  ["bg:searchPlugins", handleBgSearchPlugins],
4581
4700
  ["bg:installPlugin", handleBgInstallPlugin],
4582
4701
  ["bg:removePlugin", handleBgRemovePlugin],
4702
+ ["bg:removeFailedPlugin", handleBgRemoveFailedPlugin],
4583
4703
  ["bg:updatePlugin", handleBgUpdatePlugin],
4704
+ ["bg:openPluginTab", handleBgOpenPluginTab],
4584
4705
  ["plugin:logs", handlePluginLogs],
4585
4706
  ["tool:progress", handleToolProgress],
4586
4707
  ["sp:confirmationResponse", handleSpConfirmationResponse],
@@ -4598,7 +4719,9 @@ var EXTENSION_ONLY_TYPES = /* @__PURE__ */ new Set([
4598
4719
  "bg:searchPlugins",
4599
4720
  "bg:installPlugin",
4600
4721
  "bg:removePlugin",
4722
+ "bg:removeFailedPlugin",
4601
4723
  "bg:updatePlugin",
4724
+ "bg:openPluginTab",
4602
4725
  "offscreen:getLogs",
4603
4726
  "sp:confirmationResponse",
4604
4727
  "port-changed"
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Clicks a DOM element matched by a CSS selector in a tab's page context.
3
+ * Resolves the element's bounding rect via scripting, then dispatches trusted
4
+ * (isTrusted: true) mouse events at the element center via CDP Input.dispatchMouseEvent.
3
5
  * @param params - Expects `{ tabId: number, selector: string }`.
4
6
  * @returns `{ clicked, tagName, text }` describing the clicked element.
5
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"interaction-commands.d.ts","sourceRoot":"","sources":["../../src/browser-commands/interaction-commands.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CA6Bd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAU,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,MAAM,GAAG,MAAM,KAAG,OAAO,CAAC,IAAI,CA2D9G,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAuDd,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAwDd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GACrC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAkCd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAwDd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAkCd,CAAC"}
1
+ {"version":3,"file":"interaction-commands.d.ts","sourceRoot":"","sources":["../../src/browser-commands/interaction-commands.ts"],"names":[],"mappings":"AAmBA;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAoDd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAU,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,MAAM,GAAG,MAAM,KAAG,OAAO,CAAC,IAAI,CA2D9G,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAuDd,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAwDd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GACrC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAkCd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAwDd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,MAAM,GAAG,MAAM,KAClB,OAAO,CAAC,IAAI,CAkCd,CAAC"}