Skip to content

Commit 88c0a99

Browse files
authored
fix: make the browser bridge actually connect (offscreen path + popup status) + e2e verified (#1)
1 parent b84cf1e commit 88c0a99

5 files changed

Lines changed: 43 additions & 7 deletions

File tree

packages/extension/src/background.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function ensureOffscreen(): Promise<void> {
2121
offscreenPromise = (async () => {
2222
if (!(await chrome.offscreen.hasDocument())) {
2323
await chrome.offscreen.createDocument({
24-
url: "offscreen.html",
24+
url: "src/offscreen.html",
2525
reasons: [chrome.offscreen.Reason.WORKERS],
2626
justification:
2727
"Maintain a persistent WebSocket connection to the local reins MCP server.",

packages/extension/src/lib/backoff.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ describe("nextBackoff", () => {
99
});
1010

1111
it("caps at the maximum delay", () => {
12-
expect(nextBackoff(20)).toBe(30_000);
12+
expect(nextBackoff(20)).toBe(5_000);
1313
});
1414
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/** Exponential backoff (ms) for the M1 WS reconnect loop. */
2-
export function nextBackoff(attempt: number, baseMs = 500, maxMs = 30_000): number {
2+
export function nextBackoff(attempt: number, baseMs = 500, maxMs = 5_000): number {
33
return Math.min(baseMs * 2 ** attempt, maxMs);
44
}

packages/extension/src/popup.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "./popup.css";
22
import { clearPairing, loadPairing, savePairing } from "./lib/pairing.js";
3+
import { normalizeStatus } from "./lib/status.js";
34

45
type Status = "idle" | "connecting" | "connected" | "error";
56

@@ -17,7 +18,13 @@ const STATUS_LABELS: Record<Status, string> = {
1718
error: "Auth failed",
1819
};
1920

20-
function setStatus(status: Status, label = STATUS_LABELS[status]): void {
21+
/** Idle means "not connected": label depends on whether a pairing is saved. */
22+
function labelFor(status: Status): string {
23+
if (status === "idle") return tokenInput.value.trim() ? "Paired" : "Not paired";
24+
return STATUS_LABELS[status];
25+
}
26+
27+
function setStatus(status: Status, label = labelFor(status)): void {
2128
statusEl.className = `reins__status reins__status--${status}`;
2229
statusLabel.textContent = label;
2330
}
@@ -31,19 +38,38 @@ function notifyBackground(type: string): void {
3138
}
3239
}
3340

41+
/** Ask the worker for the live connection status; falls back to idle. */
42+
async function queryStatus(): Promise<Status> {
43+
try {
44+
const res = (await chrome.runtime.sendMessage({ type: "reins:status" })) as
45+
| { status?: unknown }
46+
| undefined;
47+
return normalizeStatus(res?.status);
48+
} catch {
49+
return "idle";
50+
}
51+
}
52+
3453
async function refresh(): Promise<void> {
3554
const pairing = await loadPairing();
3655
if (pairing) {
3756
urlInput.value = pairing.url;
3857
tokenInput.value = pairing.token;
3958
disconnectBtn.hidden = false;
40-
setStatus("idle", "Paired");
59+
setStatus(await queryStatus());
4160
} else {
4261
disconnectBtn.hidden = true;
4362
setStatus("idle");
4463
}
4564
}
4665

66+
// Live-update the pill when the worker/offscreen reports a status change.
67+
chrome.runtime.onMessage.addListener((msg: unknown) => {
68+
if (!msg || typeof msg !== "object") return;
69+
const message = msg as Record<string, unknown>;
70+
if (message.type === "reins:status-update") setStatus(normalizeStatus(message.status));
71+
});
72+
4773
form.addEventListener("submit", async (event) => {
4874
event.preventDefault();
4975
const url = urlInput.value.trim();

packages/mcp/src/bridge.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export class BridgeHost implements BridgePort {
3333
start(): Promise<void> {
3434
return new Promise((resolve, reject) => {
3535
const wss = new WebSocketServer({ host: "127.0.0.1", port: this.#requestedPort });
36-
wss.on("listening", () => resolve());
36+
wss.on("listening", () => {
37+
process.stderr.write(`reins-mcp: bridge listening on 127.0.0.1:${this.port}\n`);
38+
resolve();
39+
});
3740
wss.on("error", (err) => {
3841
// Don't retain a server that never bound (e.g. EADDRINUSE) — avoids a leak on retry.
3942
this.#wss = undefined;
@@ -55,7 +58,9 @@ export class BridgeHost implements BridgePort {
5558
}
5659

5760
#onConnection(ws: WebSocket, origin: string | undefined): void {
61+
process.stderr.write(`reins-mcp: connection from origin=${origin}\n`);
5862
if (!origin?.startsWith(this.#originPrefix)) {
63+
process.stderr.write(`reins-mcp: rejected: origin not allowed (${origin})\n`);
5964
ws.close(4003, "origin not allowed");
6065
return;
6166
}
@@ -68,11 +73,15 @@ export class BridgeHost implements BridgePort {
6873
if (hello.success && hello.data.token === this.#token) {
6974
authed = true;
7075
if (this.#client && this.#client !== ws && this.#client.readyState === WebSocket.OPEN) {
76+
process.stderr.write("reins-mcp: client replaced by new connection\n");
7177
this.#client.close(4002, "replaced by a new connection");
7278
}
7379
this.#client = ws;
80+
const browser = hello.data.browser;
81+
process.stderr.write(`reins-mcp: authed${browser ? ` (browser=${browser})` : ""}\n`);
7482
ws.send(JSON.stringify(WelcomeFrame.parse({ type: "welcome", server: "reins" })));
7583
} else {
84+
process.stderr.write("reins-mcp: rejected: bad token\n");
7685
ws.close(4001, "bad token");
7786
}
7887
return;
@@ -82,7 +91,8 @@ export class BridgeHost implements BridgePort {
8291
this.#settle(response.data.id, response.data);
8392
}
8493
});
85-
ws.on("close", () => {
94+
ws.on("close", (code) => {
95+
process.stderr.write(`reins-mcp: connection closed (code=${code})\n`);
8696
if (this.#client === ws) {
8797
this.#client = undefined;
8898
// Spec §7: fail fast — don't leave in-flight requests hanging to timeout.

0 commit comments

Comments
 (0)