ext: MV3 manifest + esbuild build; drop polyfill for Firefox's browser global

Firefox-only extension, so webextension-polyfill (a Chrome shim) is dead
weight and forces a bundler just to resolve one bare import. Use the native
`browser.*` global with @types/firefox-webext-browser instead.

  - manifest.json: MV3, event-page background (dist/background.js), the
    docs/05 §7 permission set (<all_urls> in host_permissions),
    strict_min_version 128.0, data_collection_permissions none.
  - scripts/build.mjs: esbuild bundle of src/background/index.ts -> dist/,
    esm, target firefox128. Wired to `prepare` so `npm ci` produces the
    bundle and CI's `web-ext lint` (which needs it to exist) passes with no
    added CI step. dist/ stays gitignored.
  - src/background/index.ts: event-page entry — brings the transport up,
    holds the shared reference. Capture surfaces attach here next.
  - transport/storage.ts, transport/index.ts: use the browser global.
  - tests/setup.ts: stub the browser global instead of mocking a module.

web-ext lint clean (0/0/0). typecheck clean. 38 tests still green.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
This commit is contained in:
2026-09-10 13:50:23 +04:00
co-authored by Claude Sonnet 5
parent 90e2580b05
commit c5d596d93b
10 changed files with 973 additions and 68 deletions
+26
View File
@@ -0,0 +1,26 @@
// Background event-page entry.
//
// Brings the transport up and holds the single shared reference to it. Capture, the
// popup relay, and context menus attach here as they land (docs/05 §6 build order).
import { createTransport, type TransportStatus, type VeloxTransport } from './transport/index.js';
let transport: VeloxTransport | undefined;
function onTransportState(status: TransportStatus): void {
const detail = status.fatal ?? (status.needsPairing ? 'needs pairing' : '');
console.debug(`[velox] transport ${status.state}${detail ? `${detail}` : ''}`);
}
async function start(): Promise<void> {
transport = await createTransport();
transport.onStateChange(onTransportState);
onTransportState(transport.status);
}
void start();
/** Exposed for the surfaces wired in later steps of the build order. */
export function getTransport(): VeloxTransport | undefined {
return transport;
}
+1 -3
View File
@@ -4,8 +4,6 @@
// upgrade that is only taken when its handshake actually succeeds, never on detection
// alone. The user can force one from Options (the override, persisted in storage).
import browser from 'webextension-polyfill';
import type { BackoffOptions } from './backoff.js';
import { WS_PORT_RANGE, type PortRange } from './discovery.js';
import { NativeTransport, type ConnectNative } from './native.js';
@@ -60,7 +58,7 @@ function wsDeps(opts: CreateTransportOptions): WebSocketTransportDeps {
function nativeDeps(opts: CreateTransportOptions): ConstructorParameters<typeof NativeTransport>[0] {
const connectNative =
opts.connectNative ?? (browser.runtime.connectNative.bind(browser.runtime) as ConnectNative);
opts.connectNative ?? (browser.runtime.connectNative.bind(browser.runtime) as unknown as ConnectNative);
return opts.backoff ? { connectNative, backoff: opts.backoff } : { connectNative };
}
@@ -1,8 +1,8 @@
// The few pieces of transport state that must survive a browser restart:
// the pairing token, the user's manual transport override, and a hint of which
// WebSocket port worked last time so the next scan starts there.
import browser from 'webextension-polyfill';
//
// Firefox exposes a promise-based `browser.*` natively, so there is no polyfill import.
import type { TransportKind } from './types.js';