From 65a1103cdaa784e1a10460af9933121b051d6c93 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Sun, 30 Aug 2026 13:26:07 -0500 Subject: [PATCH] Derive capture scope from host_permissions, drop the target URL setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The popup's target URL pattern was doing two jobs. As a fallback for locate requests it is now dead — every step carries its firm's pattern. As the thing deciding which tab gets scraped it was still load-bearing: without it, capture falls back to whichever tab is active, which means scraping a banking or mail tab and storing it in the dashboard's database. So the setting goes, but the scoping moves to the manifest rather than disappearing. host_permissions already lists exactly the hosts this extension is allowed to read; capture now queries those (minus localhost, which is the dashboard mirroring its own output back). The two cannot drift apart, and adding a firm — which means adding its host to the manifest anyway — scopes capture without a second place to remember. With more than one firm open, capture prefers the active tab over the first match, so it follows attention rather than tab order. That case could not arise while a single pattern matched one site. Co-Authored-By: Claude Opus 5 --- extension/background.js | 43 ++++++++++++++++++++++------------------- extension/manifest.json | 2 +- extension/popup.html | 3 --- extension/popup.js | 3 +-- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/extension/background.js b/extension/background.js index 9b9217b..b9a256a 100644 --- a/extension/background.js +++ b/extension/background.js @@ -8,10 +8,6 @@ const DEFAULTS = { apiBase: 'http://localhost:3000', pollSeconds: 3, - // Chrome match pattern, e.g. "https://*.tradovate.com/*". Empty = whichever - // tab is active in the last focused window — which would mean scraping your - // banking or email tab if you switched to one. Default it to the broker. - targetUrlPattern: 'https://*.tradeify.co/*', // Optional CSS selector — matching elements get their on-screen position // measured alongside the HTML. elementSelector: '', @@ -97,22 +93,27 @@ function pageCapture(selector) { // ── Capture pipeline ──────────────────────────────────────────────────────── -async function pickTab(cfg) { - if (cfg.targetUrlPattern) { - try { - const tabs = await chrome.tabs.query({ url: cfg.targetUrlPattern }); - return tabs.find((t) => /^https?:/.test(t.url || '')); - } catch { - throw new Error(`Invalid target URL pattern: ${cfg.targetUrlPattern}`); - } - } +/** The sites this extension is allowed to touch, taken from the manifest rather + * than from a setting. + * + * These are the same hosts host_permissions grants, so they cannot drift out of + * sync with what the extension can actually read, and adding a firm — which + * means adding its host to the manifest anyway — scopes capture automatically. + * localhost is filtered out: that is the dashboard, and capturing it would just + * mirror our own output back. */ +function firmPatterns() { + return chrome.runtime.getManifest().host_permissions + .filter((p) => !/\/\/(localhost|127\.0\.0\.1)/.test(p)); +} - const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true }); - // chrome://, about:, the Web Store and PDF viewers can't be scripted. - if (!tab || !/^https?:/.test(tab.url || '')) return undefined; - // Skip the dashboard itself, otherwise it just captures its own output. - if (tab.url.startsWith(cfg.apiBase)) return undefined; - return tab; +async function pickTab(cfg) { + const tabs = (await chrome.tabs.query({ url: firmPatterns() })) + .filter((t) => /^https?:/.test(t.url || '')); + if (tabs.length === 0) return undefined; + + // Prefer the one being looked at, so with two firms open the capture follows + // attention rather than tab order. + return tabs.find((t) => t.active) ?? tabs[0]; } async function captureAndSend(cfg) { @@ -245,7 +246,9 @@ function waitForTabLoad(tabId, timeoutMs = 15000) { } async function resolveLocateTab(cfg, request) { - const pattern = request.urlPattern || cfg.targetUrlPattern; + // Every step carries its firm's pattern; the manifest hosts are the fallback + // for a bare request (the CLI's locate without --url). + const pattern = request.urlPattern || firmPatterns(); if (pattern) { const tabs = await chrome.tabs.query({ url: pattern }); const tab = tabs.find((t) => /^https?:/.test(t.url || '')); diff --git a/extension/manifest.json b/extension/manifest.json index 1f240df..819cbe6 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "AutoFirmer Capture", - "version": "0.5.0", + "version": "0.6.0", "description": "Scrapes the HTML of the target tab and posts it to the AutoFirmer dashboard while the AutoBuyer is switched on.", "permissions": ["scripting", "tabs", "storage", "alarms"], "host_permissions": [ diff --git a/extension/popup.html b/extension/popup.html index 28d74f3..b9c758e 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -27,9 +27,6 @@ - - - diff --git a/extension/popup.js b/extension/popup.js index 35c7eaf..aaa593c 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -1,8 +1,7 @@ -const FIELDS = ['apiBase', 'pollSeconds', 'targetUrlPattern', 'elementSelector']; +const FIELDS = ['apiBase', 'pollSeconds', 'elementSelector']; const DEFAULTS = { apiBase: 'http://localhost:3000', pollSeconds: 3, - targetUrlPattern: 'https://*.tradeify.co/*', elementSelector: '', };