chore: rename project from idfoto to relicario
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>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user