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
109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { WebSocket as WsClient } from 'ws';
|
|
|
|
import { createTransport } from '../../src/background/transport/index.js';
|
|
import type { WebSocketCtor } from '../../src/background/transport/websocket.js';
|
|
import type { VeloxTransport } from '../../src/background/transport/types.js';
|
|
import { FakeDaemon, FakeNativePort, nextPort, tick } from './helpers.js';
|
|
|
|
const CTOR = WsClient as unknown as WebSocketCtor;
|
|
|
|
let daemon: FakeDaemon | undefined;
|
|
let transport: VeloxTransport | undefined;
|
|
|
|
afterEach(async () => {
|
|
transport?.disconnect();
|
|
transport = undefined;
|
|
await daemon?.stop();
|
|
daemon = undefined;
|
|
});
|
|
|
|
describe('createTransport (runtime picker)', () => {
|
|
it('override "ws" uses the WebSocket transport and never touches native messaging', async () => {
|
|
const port = nextPort();
|
|
daemon = await FakeDaemon.start({ port, acceptToken: null });
|
|
let nativeTried = false;
|
|
|
|
transport = await createTransport({
|
|
override: 'ws',
|
|
webSocketCtor: CTOR,
|
|
portRange: { start: port, end: port },
|
|
connectNative: () => {
|
|
nativeTried = true;
|
|
return new FakeNativePort();
|
|
},
|
|
backoff: { baseMs: 20, jitter: 0 },
|
|
});
|
|
|
|
expect(transport.kind).toBe('ws');
|
|
expect(transport.state).toBe('connected');
|
|
expect(nativeTried).toBe(false);
|
|
});
|
|
|
|
it('"auto" prefers native messaging when its handshake succeeds', async () => {
|
|
transport = await createTransport({
|
|
override: 'auto',
|
|
connectNative: () => new FakeNativePort(),
|
|
webSocketCtor: CTOR,
|
|
portRange: { start: nextPort(), end: nextPort() },
|
|
});
|
|
expect(transport.kind).toBe('uds');
|
|
expect(transport.state).toBe('connected');
|
|
});
|
|
|
|
it('"auto" falls back to WebSocket when the native host is not installed', async () => {
|
|
const port = nextPort();
|
|
daemon = await FakeDaemon.start({ port, acceptToken: null });
|
|
|
|
transport = await createTransport({
|
|
override: 'auto',
|
|
webSocketCtor: CTOR,
|
|
portRange: { start: port, end: port },
|
|
connectNative: () => {
|
|
const fp = new FakeNativePort({ autoHello: false });
|
|
queueMicrotask(() => fp.fireDisconnect('No such native application com.velox.host'));
|
|
return fp;
|
|
},
|
|
backoff: { baseMs: 20, jitter: 0 },
|
|
});
|
|
|
|
expect(transport.kind).toBe('ws');
|
|
expect(transport.state).toBe('connected');
|
|
});
|
|
|
|
it('reads the override from storage when none is passed', async () => {
|
|
await browser.storage.local.set({ 'velox.transportOverride': 'ws' });
|
|
const port = nextPort();
|
|
daemon = await FakeDaemon.start({ port, acceptToken: null });
|
|
let nativeTried = false;
|
|
|
|
transport = await createTransport({
|
|
webSocketCtor: CTOR,
|
|
portRange: { start: port, end: port },
|
|
connectNative: () => {
|
|
nativeTried = true;
|
|
return new FakeNativePort();
|
|
},
|
|
backoff: { baseMs: 20, jitter: 0 },
|
|
});
|
|
|
|
expect(transport.kind).toBe('ws');
|
|
expect(nativeTried).toBe(false);
|
|
});
|
|
|
|
it('override "uds" surfaces a native-host failure to the caller', async () => {
|
|
await expect(
|
|
createTransport({
|
|
override: 'uds',
|
|
connectNative: () => {
|
|
const fp = new FakeNativePort({ autoHello: false });
|
|
queueMicrotask(() => fp.fireDisconnect('No such native application com.velox.host'));
|
|
return fp;
|
|
},
|
|
backoff: { baseMs: 10 },
|
|
}),
|
|
).rejects.toThrow(/not installed/);
|
|
await tick(30);
|
|
});
|
|
});
|