diff --git a/manifest.firefox.json b/manifest.firefox.json index 4df5dab..e01cb92 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.9.18", + "version": "0.9.19", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "browser_specific_settings": { "gecko": { diff --git a/manifest.json b/manifest.json index e6e213d..22cf700 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.9.18", + "version": "0.9.19", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "permissions": [ "storage", diff --git a/package.json b/package.json index 53dc201..2c8ed42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silent-send", - "version": "0.9.18", + "version": "0.9.19", "private": true, "license": "MIT", "description": "Browser extension that substitutes personal data before sending to AI services", diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 769f10e..c128216 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -163,6 +163,17 @@ api.runtime.onMessage.addListener((message, sender, sendResponse) => { }); const messageHandlers = { + async 'get:decrypted-config'(_message, _sender, sendResponse) { + try { + const mappings = await Storage.getMappings(); + const identity = await Storage.getIdentity(); + const settings = await Storage.getSettings(); + sendResponse({ mappings, identity, settings }); + } catch { + sendResponse(null); + } + }, + async 'substitution:performed'(message, sender) { const tabId = sender.tab?.id; if (tabId == null) return; diff --git a/src/content/injector.js b/src/content/injector.js index 195cb2c..4c3d5a2 100644 --- a/src/content/injector.js +++ b/src/content/injector.js @@ -57,18 +57,45 @@ // Load mappings and settings, then inject into page async function init() { + let mappings, identity, settings; + const result = await api.storage.local.get(['ss_mappings', 'ss_identity', 'ss_settings']); - const settings = result.ss_settings || { enabled: true }; + const isEncrypted = result.ss_mappings?._ssLocalEncrypted || + result.ss_identity?._ssLocalEncrypted; - // Check if data is encrypted (locked) — pass empty config - // The background will send decrypted data via vault:unlocked when ready - const isLocked = result.ss_mappings?._ssLocalEncrypted || - result.ss_identity?._ssLocalEncrypted; - const mappings = isLocked ? [] : (result.ss_mappings || []); - const identityData = isLocked ? {} : (result.ss_identity || {}); + if (isEncrypted) { + // Data is encrypted — ask the background script for decrypted config. + // The background has access to the Storage module which can decrypt. + try { + const response = await api.runtime.sendMessage({ type: 'get:decrypted-config' }); + if (response?.mappings) { + mappings = response.mappings; + identity = response.identity || {}; + settings = response.settings || { enabled: true }; + } else { + // Background couldn't decrypt (locked) — inject with empty config + // and wait for vault:unlocked message later + mappings = []; + identity = {}; + settings = result.ss_settings || { enabled: true }; + } + } catch { + mappings = []; + identity = {}; + settings = result.ss_settings || { enabled: true }; + } + } else { + // Data is plaintext — read directly + mappings = result.ss_mappings || []; + const identityData = result.ss_identity || {}; + identity = mergeProfiles(identityData); + settings = result.ss_settings || { enabled: true }; + } - // Merge active profiles into a flat identity object for the content script - const identity = mergeProfiles(identityData); + // Ensure identity is merged if it came from background + if (identity.profiles) { + identity = mergeProfiles(identity); + } // Inject the main interception script into the page's world const script = document.createElement('script'); @@ -91,8 +118,6 @@ // Also log directly from the injector (content script world) // in case the background worker is asleep const replacements = event.data.replacements || []; - const settingsResult = await api.storage.local.get('ss_settings'); - const maxLog = settingsResult.ss_settings?.maxLogEntries || 100; for (const r of replacements) { const log = (await api.storage.local.get('ss_activity_log')).ss_activity_log || []; log.unshift({ @@ -106,8 +131,8 @@ pattern: r.pattern || '', url: location.href, }); - // Trim to user-configured max - if (log.length > maxLog) log.length = maxLog; + // Trim + if (log.length > 200) log.length = 200; await api.storage.local.set({ ss_activity_log: log }); } } @@ -127,10 +152,7 @@ const val = changes.ss_identity.newValue; if (!val?._ssLocalEncrypted) msg.identity = mergeProfiles(val); } - if (changes.ss_settings) { - const val = changes.ss_settings.newValue; - if (!val?._ssLocalEncrypted) msg.settings = val; - } + if (changes.ss_settings) msg.settings = changes.ss_settings.newValue; // Only post if we have something meaningful to send if (msg.mappings || msg.identity || msg.settings) { diff --git a/src/options/options.html b/src/options/options.html index 32f9b0b..3e52028 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -629,7 +629,7 @@