feat: add Firefox support with cross-browser compatibility

Add manifest.firefox.json for Firefox MV3 (gecko ID, background
scripts instead of service_worker, options_ui). Introduce
browser-polyfill.js shim so all modules use whichever API is
available (browser.* or chrome.*). Add build.sh to target
chrome, firefox, or both.

https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
This commit is contained in:
Claude
2026-03-25 23:36:59 +00:00
parent 4576597e64
commit 3ef5df7efb
8 changed files with 191 additions and 36 deletions
+20
View File
@@ -0,0 +1,20 @@
/**
* Minimal browser API compatibility layer.
*
* Firefox exposes `browser.*` (Promise-based) and polyfills `chrome.*`.
* Chrome only has `chrome.*` (callback-based, but storage/runtime are
* Promise-based in MV3). This normalizes to whichever is available.
*/
const api =
typeof browser !== 'undefined' && browser.runtime
? browser
: typeof chrome !== 'undefined'
? chrome
: null;
if (!api) {
console.error('[Silent Send] No WebExtension API found');
}
export default api;
+10 -8
View File
@@ -1,10 +1,12 @@
/**
* Silent Send - Storage Manager
*
* Wraps chrome.storage.local with typed helpers for mappings,
* Wraps browser/api.storage.local with typed helpers for mappings,
* activity log, and settings.
*/
import api from './browser-polyfill.js';
const KEYS = {
MAPPINGS: 'ss_mappings',
LOG: 'ss_activity_log',
@@ -23,12 +25,12 @@ const Storage = {
// --- Mappings ---
async getMappings() {
const result = await chrome.storage.local.get(KEYS.MAPPINGS);
const result = await api.storage.local.get(KEYS.MAPPINGS);
return result[KEYS.MAPPINGS] || [];
},
async saveMappings(mappings) {
await chrome.storage.local.set({ [KEYS.MAPPINGS]: mappings });
await api.storage.local.set({ [KEYS.MAPPINGS]: mappings });
},
async addMapping(mapping) {
@@ -65,7 +67,7 @@ const Storage = {
// --- Activity Log ---
async getLog() {
const result = await chrome.storage.local.get(KEYS.LOG);
const result = await api.storage.local.get(KEYS.LOG);
return result[KEYS.LOG] || [];
},
@@ -84,23 +86,23 @@ const Storage = {
log.length = settings.maxLogEntries;
}
await chrome.storage.local.set({ [KEYS.LOG]: log });
await api.storage.local.set({ [KEYS.LOG]: log });
},
async clearLog() {
await chrome.storage.local.set({ [KEYS.LOG]: [] });
await api.storage.local.set({ [KEYS.LOG]: [] });
},
// --- Settings ---
async getSettings() {
const result = await chrome.storage.local.get(KEYS.SETTINGS);
const result = await api.storage.local.get(KEYS.SETTINGS);
return { ...DEFAULT_SETTINGS, ...(result[KEYS.SETTINGS] || {}) };
},
async saveSettings(settings) {
const current = await this.getSettings();
await chrome.storage.local.set({
await api.storage.local.set({
[KEYS.SETTINGS]: { ...current, ...settings },
});
},