Files
relicario/docs/superpowers/plans/2026-04-12-relicario-firefox-extension.md
adlee-was-taken 519a6f0e36 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>
2026-04-19 16:47:02 -04:00

8.9 KiB

relicario Firefox Extension Port Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Port the Chrome extension to Firefox with shared TypeScript source, a Firefox-specific manifest, and a separate webpack build target.

Architecture: All TypeScript source is shared. The only code change is an environment check in index.ts for WASM loading (service worker vs background script). A second webpack config produces dist-firefox/ with the Firefox manifest.

Tech Stack: TypeScript, webpack, Firefox WebExtensions MV3

Spec: docs/superpowers/specs/2026-04-12-relicario-firefox-extension-design.md


File Structure

New files

extension/manifest.firefox.json       # Firefox-specific manifest
extension/webpack.firefox.config.js    # Webpack config for Firefox build

Modified files

extension/src/service-worker/index.ts  # Environment-aware WASM loading
extension/package.json                 # Add Firefox build scripts
.gitignore                             # Add extension/dist-firefox/

Task 1: Firefox Manifest and Webpack Config

Files:

  • Create: extension/manifest.firefox.json

  • Create: extension/webpack.firefox.config.js

  • Modify: extension/package.json

  • Modify: .gitignore

  • Step 1: Create Firefox manifest

Create extension/manifest.firefox.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"
      ]
    }
  ]
}
  • Step 2: Create Firefox webpack config

Create extension/webpack.firefox.config.js:

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: {
    'service-worker': './src/service-worker/index.ts',
    popup: './src/popup/popup.ts',
    content: './src/content/detector.ts',
    setup: './src/setup/setup.ts',
  },
  output: {
    path: path.resolve(__dirname, 'dist-firefox'),
    filename: '[name].js',
    clean: true,
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  module: {
    rules: [{ test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/ }],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'manifest.firefox.json', to: 'manifest.json' },
        { from: 'src/popup/index.html', to: 'popup.html' },
        { from: 'src/popup/styles.css', to: 'styles.css' },
        { from: 'setup.html', to: '.' },
        { from: 'icons', to: 'icons' },
        { from: 'wasm/relicario_wasm_bg.wasm', to: '.' },
        { from: 'wasm/relicario_wasm.js', to: '.' },
      ],
    }),
  ],
  experiments: { asyncWebAssembly: true },
};
  • Step 3: Add Firefox build scripts to package.json

In extension/package.json, update the scripts section:

{
  "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"
  }
}
  • Step 4: Add dist-firefox/ to .gitignore

Append to the root .gitignore:

extension/dist-firefox/
  • Step 5: Commit
git add extension/manifest.firefox.json extension/webpack.firefox.config.js extension/package.json .gitignore
git commit -m "feat: add Firefox manifest and webpack config"

Task 2: Environment-Aware WASM Loading

Files:

  • Modify: extension/src/service-worker/index.ts

  • Step 1: Update the WASM init function

In extension/src/service-worker/index.ts, replace the current initWasm function and its surrounding comments (lines 23-49) with:

// --- WASM initialization ---

// Chrome MV3 uses service workers which do NOT support dynamic import().
// Firefox MV3 uses background scripts which DO support dynamic import().
// We detect the environment at runtime and use the appropriate loading strategy.
//
// The JS glue is imported statically so webpack bundles it. Both initSync
// (Chrome) and the default export (Firefox) are available.

// @ts-ignore TS2307 — resolved by webpack alias / copy
import initDefault, { initSync } from '../../wasm/relicario_wasm.js';
// @ts-ignore TS2307
import * as wasmBindings from '../../wasm/relicario_wasm.js';

type WasmModule = typeof wasmBindings;
let wasm: WasmModule | null = null;

async function initWasm(): Promise<WasmModule> {
  if (wasm) return wasm;

  const isServiceWorker = typeof ServiceWorkerGlobalScope !== 'undefined'
    && self instanceof ServiceWorkerGlobalScope;

  if (isServiceWorker) {
    // Chrome: fetch WASM binary and instantiate synchronously
    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 — dynamic init works
    const wasmUrl = chrome.runtime.getURL('relicario_wasm_bg.wasm');
    await initDefault(wasmUrl);
  }

  vault.setWasm(wasmBindings);
  wasm = wasmBindings;
  wasmReady = true;
  return wasm;
}
  • Step 2: Update the module doc comment

Change the doc comment at the top of the file (line 1) from:

/// Service worker entry point for the relicario Chrome extension.

To:

/// Background script entry point for the relicario browser extension.
///
/// In Chrome this runs as a service worker (MV3). In Firefox this runs
/// as a persistent background script. WASM loading adapts automatically.
  • Step 3: Build both targets
cd extension && bun run build && bun run build:firefox

Expected: Both builds succeed with 0 errors.

  • Step 4: Commit
git add extension/src/service-worker/index.ts
git commit -m "feat: add environment-aware WASM loading for Chrome/Firefox"

Task 3: Build and Manual Test

Files: None (integration testing)

  • Step 1: Verify Chrome build still works
cd extension && bun run build

Expected: dist/ output, 0 errors. Reload in Chrome — unlock, list, autofill all work.

  • Step 2: Build Firefox
bun run build:firefox

Expected: dist-firefox/ output with manifest.json (Firefox version), all JS bundles, WASM files, icons.

  • Step 3: Verify Firefox manifest
cat dist-firefox/manifest.json | grep -E "gecko|background"

Expected: browser_specific_settings.gecko.id present, background.scripts (not service_worker).

  • Step 4: Load in Firefox
  1. Open Firefox
  2. Navigate to about:debugging#/runtime/this-firefox
  3. Click "Load Temporary Add-on..."
  4. Select extension/dist-firefox/manifest.json
  5. Extension icon appears in toolbar
  • Step 5: Test basic flow
  1. Click extension icon — popup opens, shows setup prompt (or unlock if already configured)
  2. Open setup.html via the setup button — full-page wizard loads
  3. Configure vault (or verify it's already configured from Chrome)
  4. Unlock with passphrase — entry list appears
  5. Navigate entries, check TOTP countdown works
  6. Visit a login page — field icon appears
  7. Test autofill
  8. If credential capture is enabled, test save prompt appears on form submit
  • Step 6: Fix any Firefox-specific issues

  • Step 7: Final commit

git add -A
git commit -m "feat: complete Firefox extension port"

Task Summary

Task Description Dependencies
1 Firefox manifest + webpack config + scripts None
2 Environment-aware WASM loading None
3 Build and manual test Tasks 1, 2

Tasks 1 and 2 are independent and can run in parallel.