Popup and Options are separate documents from the background page and can't reach its live VeloxTransport directly, so background/bridge.ts relays it over one browser.runtime.connect port per document (call/subscribe/getStatus/reconnect/pair/unpair/setOverride in; result/event/status/pairError out). shared/panel-client.ts is the client side both surfaces use. Popup (src/popup/): status dot + text, active-downloads list driven by event.task.progress/added/state/removed (repaints ride the event's own <=4 Hz cap rather than adding a second timer), pause/resume buttons, "Start it" wired to a reconnect request. State lives in a DOM-free store.ts for unit testing; rendering uses createElement, not innerHTML (web-ext lint flags the latter). Options (src/options/): transport override select, pairing (code entry + pair/unpair, backed by two new WebSocketTransport methods, pairWithCode/unpair), and the daemon's capture policy mirrored via capture.getRules. The capture-policy form is editable only when the active transport is native messaging (uds) -- settings.set and rules.upsert are privileged, uds-only methods per shared/protocol METHODS, so WebSocket can't write them no matter what the page shows; CLAUDE.md section 2 rules out working around that locally. The bridge's status payload adds a kind field (which transport is live) for this to key off. Default category is the one piece of state that's genuinely the extension's own, not the daemon's, and lives in browser.storage.local via options/prefs.ts. Pure decisions (statusLine, pairingAvailable, captureRulesEditable) are split into view.ts for unit testing without a DOM. manifest.json registers the popup action and options_ui page (and, in the same edit, the content_scripts entry the next commit's media detection needs -- split by file, not by manifest line). build.mjs gains popup/options as further esbuild entry points, plus copying their static HTML/CSS into dist/. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ed8KEmAW48v4YHdxLtqsMB
70 lines
2.6 KiB
JavaScript
70 lines
2.6 KiB
JavaScript
// Bundles the extension's TypeScript entry points into dist/ for Firefox to load.
|
|
//
|
|
// Runs on `npm run build` and, via the `prepare` script, on every `npm ci` — so CI's
|
|
// `web-ext lint` (which needs the referenced bundles to exist) works without a separate
|
|
// build step. Firefox-only: no polyfill, native ESM, `browser.*` is a global.
|
|
|
|
import { build } from 'esbuild';
|
|
import { rm, mkdir, copyFile } from 'node:fs/promises';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const outdir = resolve(root, 'dist');
|
|
|
|
// The background page and the popup/options documents load as native ESM (manifest.json
|
|
// declares "type": "module" for the background script; the popup/options HTML load
|
|
// their script with type="module"). Content scripts registered via manifest.json's
|
|
// content_scripts have no such declaration and run as classic scripts, so content.js is
|
|
// built as an IIFE instead — an `export` left in by the esm build would be a syntax
|
|
// error there.
|
|
const esmEntryPoints = {
|
|
background: resolve(root, 'src/background/index.ts'),
|
|
popup: resolve(root, 'src/popup/popup.ts'),
|
|
options: resolve(root, 'src/options/options.ts'),
|
|
};
|
|
const iifeEntryPoints = {
|
|
content: resolve(root, 'src/content/index.ts'),
|
|
};
|
|
|
|
// Static HTML/CSS esbuild doesn't touch — copied straight to dist/ alongside their JS.
|
|
const staticFiles = [
|
|
['src/popup/popup.html', 'popup.html'],
|
|
['src/popup/popup.css', 'popup.css'],
|
|
['src/options/options.html', 'options.html'],
|
|
['src/options/options.css', 'options.css'],
|
|
];
|
|
|
|
await rm(outdir, { recursive: true, force: true });
|
|
await mkdir(outdir, { recursive: true });
|
|
await Promise.all(staticFiles.map(([src, dest]) => copyFile(resolve(root, src), resolve(outdir, dest))));
|
|
|
|
const watch = process.argv.includes('--watch');
|
|
const dev = watch || process.argv.includes('--dev');
|
|
|
|
const shared = {
|
|
outdir,
|
|
bundle: true,
|
|
target: ['firefox128'],
|
|
platform: 'browser',
|
|
sourcemap: dev ? 'inline' : 'linked',
|
|
minify: !dev,
|
|
logLevel: 'info',
|
|
// A bare `import ... from 'ws'` etc. must never reach a bundle — fail loud if one does.
|
|
external: [],
|
|
};
|
|
|
|
const buildConfigs = [
|
|
{ ...shared, entryPoints: esmEntryPoints, format: 'esm' },
|
|
{ ...shared, entryPoints: iifeEntryPoints, format: 'iife' },
|
|
];
|
|
|
|
if (watch) {
|
|
const { context } = await import('esbuild');
|
|
const contexts = await Promise.all(buildConfigs.map((cfg) => context(cfg)));
|
|
await Promise.all(contexts.map((ctx) => ctx.watch()));
|
|
console.log('esbuild: watching');
|
|
} else {
|
|
await Promise.all(buildConfigs.map((cfg) => build(cfg)));
|
|
}
|