Spike S1 — run on the target machine through real snap confinement
(apparmor snap.firefox.firefox enforced; web-ext's direct-exec of the inner
binary bypasses it, so runs were forced through `snap run firefox`):
- manifest in ~/.mozilla/native-messaging-hosts/ -> WORKS; host launched
unconfined with real $HOME and real $XDG_RUNTIME_DIR, bound a socket in
the real /run/user/<uid>. Corroborated by the machine's 1Password host.
- ~/snap/firefox/common/.mozilla/native-messaging-hosts/ -> not read
- /usr/lib/mozilla/native-messaging-hosts/ -> not read
- flatpak path -> N/A (snap Firefox)
Decision: WebSocket stays the default; native messaging is an opportunistic
upgrade taken only when its handshake succeeds. docs/05 §4 corrected in this
commit to point the snap manifest at ~/.mozilla and mark /usr/lib as
deb/tarball-only. ADR carries a self-contained reproduction; the scratch
harness has been removed.
transport/ (build order item 1):
- types.ts VeloxTransport interface + error taxonomy
- rpc.ts JSON-RPC id correlation, per-call deadline, AbortSignal
- backoff.ts exponential backoff with jitter
- discovery.ts 52000-52016 scan ordering (last-good port first)
- websocket.ts scan -> session.hello -> auto-pair (token in
storage.local) -> reconnect; -32001 fatal, refused/
rate-limited pairing latches needsPairing (no retry storm);
a mid-handshake drop aborts hello immediately
- native.ts connectNative(); distinguishes "not installed" (fatal,
lets the picker fall through) from a crash (reconnect)
- index.ts createTransport() runtime picker + persisted Options override
Toolchain: package.json / tsconfig (strict) / vitest; webextension-polyfill
mocked. 38 tests, incl. the WS suite against a real loopback ws server.
No manifest.json yet, so CI's extension-lint guard stays a no-op.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
32 lines
1.1 KiB
TypeScript
32 lines
1.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { Backoff } from '../../src/background/transport/backoff.js';
|
|
|
|
describe('Backoff', () => {
|
|
it('grows geometrically and caps, with no jitter', () => {
|
|
const b = new Backoff({ baseMs: 500, factor: 2, maxMs: 8000, jitter: 0 });
|
|
expect([b.next(), b.next(), b.next(), b.next(), b.next(), b.next()]).toEqual([
|
|
500, 1000, 2000, 4000, 8000, 8000,
|
|
]);
|
|
});
|
|
|
|
it('reset() returns to the base delay', () => {
|
|
const b = new Backoff({ baseMs: 100, factor: 3, jitter: 0 });
|
|
b.next();
|
|
b.next();
|
|
expect(b.attempts).toBe(2);
|
|
b.reset();
|
|
expect(b.attempts).toBe(0);
|
|
expect(b.next()).toBe(100);
|
|
});
|
|
|
|
it('keeps jittered delays within +/- the jitter fraction', () => {
|
|
const rand = [0, 0.5, 1];
|
|
let i = 0;
|
|
const b = new Backoff({ baseMs: 1000, factor: 1, maxMs: 1000, jitter: 0.2, random: () => rand[i++]! });
|
|
expect(b.next()).toBe(800); // random 0 -> raw - spread
|
|
expect(b.next()).toBe(1000); // random 0.5 -> raw
|
|
expect(b.next()).toBe(1200); // random 1 -> raw + spread
|
|
});
|
|
});
|