Sweeping rename across crates, CLI binary, WASM bindings, extension, docs,
and vault metadata paths. Git remote updated to relicario.git.
- crates/idfoto-{core,cli,wasm} -> crates/relicario-{core,cli,wasm}
- IdfotoError -> RelicarioError
- IDFOTO_IMAGE env var -> RELICARIO_IMAGE
- ~/.config/idfoto -> ~/.config/relicario
- .idfoto/ vault metadata dir -> .relicario/ (breaking; pre-release)
- Binary name idfoto -> relicario
- Extension wasm module idfoto_wasm -> relicario_wasm
- Storage key idfotoSettings -> relicarioSettings
- All doc filenames and content references updated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
197 lines
6.2 KiB
Markdown
197 lines
6.2 KiB
Markdown
# relicario — Firefox Extension Port Design
|
|
|
|
Port the existing Chrome MV3 extension to Firefox. Shared TypeScript source, separate manifests, separate build outputs. No code changes to components, popup, or content script.
|
|
|
|
## Scope
|
|
|
|
- Firefox-specific `manifest.json`
|
|
- WASM loading compatibility (dynamic import for Firefox background script)
|
|
- Second webpack config for Firefox build target
|
|
- npm scripts for building both browsers
|
|
|
|
## What Stays the Same
|
|
|
|
All TypeScript source files are shared between Chrome and Firefox:
|
|
- `src/service-worker/` — all files (with one environment check for WASM loading)
|
|
- `src/popup/` — all components, styles, HTML
|
|
- `src/content/` — detector, fill, icon, capture
|
|
- `src/setup/` — setup wizard
|
|
- `src/shared/` — types, messages
|
|
- `setup.html` — init wizard
|
|
|
|
Firefox supports the `chrome.*` namespace for WebExtension APIs, so no `browser.*` polyfill is needed. All Chrome API calls (`chrome.runtime.sendMessage`, `chrome.storage.local`, `chrome.tabs`, etc.) work as-is.
|
|
|
|
## Manifest Differences
|
|
|
|
### Chrome (`manifest.json` — existing)
|
|
|
|
```json
|
|
{
|
|
"manifest_version": 3,
|
|
"background": {
|
|
"service_worker": "service-worker.js",
|
|
"type": "module"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Firefox (`manifest.firefox.json` — new)
|
|
|
|
```json
|
|
{
|
|
"manifest_version": 3,
|
|
"name": "relicario",
|
|
"version": "0.1.0",
|
|
"description": "Two-factor encrypted password manager",
|
|
"browser_specific_settings": {
|
|
"gecko": {
|
|
"id": "relicario@adlee.work",
|
|
"strict_min_version": "128.0"
|
|
}
|
|
},
|
|
"permissions": ["storage", "activeTab", "clipboardWrite"],
|
|
"host_permissions": ["<all_urls>"],
|
|
"background": {
|
|
"scripts": ["service-worker.js"]
|
|
},
|
|
"action": {
|
|
"default_popup": "popup.html",
|
|
"default_icon": {
|
|
"16": "icons/icon-16.png",
|
|
"48": "icons/icon-48.png",
|
|
"128": "icons/icon-128.png"
|
|
}
|
|
},
|
|
"content_scripts": [{
|
|
"matches": ["<all_urls>"],
|
|
"js": ["content.js"],
|
|
"run_at": "document_idle"
|
|
}],
|
|
"content_security_policy": {
|
|
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
|
},
|
|
"web_accessible_resources": [{
|
|
"resources": ["setup.html", "setup.js", "styles.css", "relicario_wasm_bg.wasm", "relicario_wasm.js"]
|
|
}]
|
|
}
|
|
```
|
|
|
|
Key differences from Chrome manifest:
|
|
- `browser_specific_settings.gecko.id` — required for Firefox, uses email-style ID
|
|
- `browser_specific_settings.gecko.strict_min_version` — Firefox 128+ for stable MV3 support
|
|
- `background.scripts` instead of `background.service_worker` + `type: module` — Firefox MV3 background scripts are NOT service workers, they're persistent scripts
|
|
- `web_accessible_resources` — Firefox doesn't use `matches` field in the resource entries
|
|
|
|
## WASM Loading
|
|
|
|
The service worker `index.ts` currently uses `initSync` with `chrome.runtime.getURL` because Chrome MV3 service workers don't support dynamic `import()`. Firefox background scripts DO support `import()`.
|
|
|
|
Add an environment check to `index.ts`:
|
|
|
|
```typescript
|
|
async function initWasm(): Promise<WasmModule> {
|
|
if (wasm) return wasm;
|
|
|
|
if (typeof ServiceWorkerGlobalScope !== 'undefined') {
|
|
// Chrome MV3: service worker context — use initSync
|
|
const wasmResponse = await fetch(chrome.runtime.getURL('relicario_wasm_bg.wasm'));
|
|
const wasmBytes = await wasmResponse.arrayBuffer();
|
|
initSync({ module: new WebAssembly.Module(wasmBytes) });
|
|
} else {
|
|
// Firefox: background script context — dynamic import works
|
|
const wasmUrl = chrome.runtime.getURL('relicario_wasm_bg.wasm');
|
|
await initDefault(wasmUrl);
|
|
}
|
|
|
|
vault.setWasm(wasmBindings);
|
|
wasm = wasmBindings;
|
|
wasmReady = true;
|
|
return wasm;
|
|
}
|
|
```
|
|
|
|
This uses the static import of `initSync` and the default export (`initDefault`) from the WASM glue, branching on runtime environment. Both paths end with the same `wasmBindings` module reference.
|
|
|
|
## Build Pipeline
|
|
|
|
### New file: `extension/webpack.firefox.config.js`
|
|
|
|
Identical to `webpack.config.js` except:
|
|
- Output directory: `dist-firefox/` instead of `dist/`
|
|
- CopyPlugin copies `manifest.firefox.json` as `manifest.json` (instead of `manifest.json`)
|
|
|
|
### Updated `extension/package.json` scripts
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "webpack --mode production",
|
|
"build:firefox": "webpack --config webpack.firefox.config.js --mode production",
|
|
"build:all": "npm run build:wasm && npm run build && npm run build:firefox",
|
|
"dev": "webpack --mode development --watch",
|
|
"dev:firefox": "webpack --config webpack.firefox.config.js --mode development --watch",
|
|
"build:wasm": "wasm-pack build ../crates/relicario-wasm --target web --out-dir ../../extension/wasm"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Output structure
|
|
|
|
```
|
|
extension/
|
|
├── dist/ # Chrome build (existing)
|
|
│ ├── service-worker.js
|
|
│ ├── popup.js
|
|
│ ├── content.js
|
|
│ ├── setup.js
|
|
│ ├── manifest.json # Chrome manifest
|
|
│ └── ...
|
|
├── dist-firefox/ # Firefox build (new)
|
|
│ ├── service-worker.js
|
|
│ ├── popup.js
|
|
│ ├── content.js
|
|
│ ├── setup.js
|
|
│ ├── manifest.json # Firefox manifest (copied from manifest.firefox.json)
|
|
│ └── ...
|
|
└── wasm/ # Shared WASM (same for both)
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Load in Firefox
|
|
|
|
1. Open `about:debugging#/runtime/this-firefox`
|
|
2. Click "Load Temporary Add-on..."
|
|
3. Select `extension/dist-firefox/manifest.json`
|
|
4. Extension appears in toolbar
|
|
|
|
### Test matrix
|
|
|
|
Same as Chrome — all features should work identically:
|
|
- Setup wizard (`setup.html`)
|
|
- Unlock with passphrase
|
|
- Entry list, search, group filtering
|
|
- Entry detail with TOTP countdown
|
|
- Add/edit/delete entries
|
|
- Autofill via field icon
|
|
- Credential capture (if enabled)
|
|
- Settings view
|
|
|
|
### Firefox-specific checks
|
|
|
|
- WASM loads correctly (background script, not service worker)
|
|
- master_key persists longer (background script stays alive)
|
|
- Popup dimensions render correctly
|
|
- Content script injection works on all pages
|
|
|
|
## .gitignore
|
|
|
|
Add `extension/dist-firefox/` to `.gitignore`.
|
|
|
|
## Non-Goals
|
|
|
|
- Firefox for Android (different extension API surface)
|
|
- Publishing to addons.mozilla.org (manual for now)
|
|
- Automated cross-browser testing
|
|
- Shared webpack config with conditional logic (two separate configs is clearer)
|