diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 1dd59d9..d18a2a5 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -61,6 +61,44 @@ api.tabs.onRemoved.addListener((tabId) => { // --- Dynamic injection for custom domains --- +const DYNAMIC_SCRIPT_ID = 'ss-custom-domains'; + +/** + * Sync the dynamically registered content script with the current + * custom domains list. Uses scripting.registerContentScripts so custom + * domains inject at document_start (like built-in sites) and persist + * across service worker restarts. + */ +async function syncCustomDomainScripts() { + const settings = await Storage.getSettings(); + const customDomains = settings.customDomains || []; + + // Build match patterns from domains (e.g. "https://my-ai.com" → "https://my-ai.com/*") + const matches = customDomains.map(d => d.replace(/\/$/, '') + '/*'); + + try { + // Remove existing dynamic script first + await api.scripting.unregisterContentScripts({ ids: [DYNAMIC_SCRIPT_ID] }).catch(() => {}); + + if (matches.length > 0) { + await api.scripting.registerContentScripts([{ + id: DYNAMIC_SCRIPT_ID, + matches, + js: ['src/content/injector.js'], + css: ['src/content/content.css'], + runAt: 'document_start', + allFrames: true, + }]); + } + } catch (e) { + console.warn('[Silent Send] Failed to register custom domain scripts:', e); + } +} + +/** + * Inject on an already-open tab for a newly added custom domain. + * Only needed for tabs that were open before the content script was registered. + */ async function injectOnCustomDomain(tabId, tabUrl) { const settings = await Storage.getSettings(); const customDomains = settings.customDomains || []; @@ -362,6 +400,11 @@ api.storage.onChanged.addListener(async (changes, areaName) => { const settings = await Storage.getSettings(); await updateIcon(settings); + // Re-register dynamic content scripts when custom domains change + if (changes.ss_settings && areaName === 'local') { + await syncCustomDomainScripts(); + } + // Push to browser.storage.sync when local data changes (same-browser cross-device) if (areaName === 'local' && settings.browserSync) { await SilentSendSync.pushToSyncStorage(); @@ -487,6 +530,7 @@ api.runtime.onInstalled.addListener(async () => { api.action.setBadgeBackgroundColor({ color: '#6b7280' }); const settings = await Storage.getSettings(); await updateIcon(settings); + await syncCustomDomainScripts(); // Set up alarms on install await setupAutoSyncAlarm(); await setupOrgPolicyAlarm(); @@ -496,6 +540,7 @@ api.runtime.onInstalled.addListener(async () => { (async () => { const settings = await Storage.getSettings(); await updateIcon(settings); + await syncCustomDomainScripts(); // Check if extension is locked (encrypted data, no cached key) const locked = await Storage.isLocked(); diff --git a/src/options/options.js b/src/options/options.js index fcc17ac..9327b0d 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -620,9 +620,17 @@ function renderDomains() { btn.addEventListener('click', async () => { const idx = parseInt(btn.dataset.index, 10); const domains = settings.customDomains || []; - domains.splice(idx, 1); + const removed = domains.splice(idx, 1)[0]; settings.customDomains = domains; await Storage.saveSettings({ customDomains: domains }); + + // Revoke browser permission for the removed domain + if (removed) { + try { + await api.permissions.remove({ origins: [removed + '/*'] }); + } catch (e) { /* non-fatal */ } + } + renderDomains(); }); });