mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 22:44:31 +00:00
Browser: reuse extension relay when relay port is already occupied (#20035)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: b310666d39
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { createServer } from "node:http";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import WebSocket from "ws";
|
||||
import {
|
||||
@@ -152,6 +153,23 @@ describe("chrome extension relay server", () => {
|
||||
ext.close();
|
||||
});
|
||||
|
||||
it("derives relay auth headers from gateway token for loopback URLs", async () => {
|
||||
const port = await getFreePort();
|
||||
const prev = process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = "test-gateway-token";
|
||||
try {
|
||||
const headers = getChromeExtensionRelayAuthHeaders(`http://127.0.0.1:${port}`);
|
||||
expect(Object.keys(headers)).toContain("x-openclaw-relay-token");
|
||||
expect((headers["x-openclaw-relay-token"] ?? "").length).toBeGreaterThan(20);
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
} else {
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = prev;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects CDP access without relay auth token", async () => {
|
||||
const port = await getFreePort();
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
@@ -349,4 +367,57 @@ describe("chrome extension relay server", () => {
|
||||
cdp.close();
|
||||
ext.close();
|
||||
});
|
||||
|
||||
it("reuses an already-bound relay port when another process owns it", async () => {
|
||||
const port = await getFreePort();
|
||||
const fakeRelay = createServer((req, res) => {
|
||||
if (req.url?.startsWith("/extension/status")) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ connected: false }));
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("OK");
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
fakeRelay.listen(port, "127.0.0.1", () => resolve());
|
||||
fakeRelay.once("error", reject);
|
||||
});
|
||||
|
||||
const prev = process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = "test-gateway-token";
|
||||
try {
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
const relay = await ensureChromeExtensionRelayServer({ cdpUrl });
|
||||
expect(relay.port).toBe(port);
|
||||
const status = (await fetch(`${cdpUrl}/extension/status`).then((r) => r.json())) as {
|
||||
connected?: boolean;
|
||||
};
|
||||
expect(status.connected).toBe(false);
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
} else {
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = prev;
|
||||
}
|
||||
await new Promise<void>((resolve) => fakeRelay.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
it("does not swallow EADDRINUSE when occupied port is not an openclaw relay", async () => {
|
||||
const port = await getFreePort();
|
||||
const blocker = createServer((_, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("not-relay");
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
blocker.listen(port, "127.0.0.1", () => resolve());
|
||||
blocker.once("error", reject);
|
||||
});
|
||||
const blockedUrl = `http://127.0.0.1:${port}`;
|
||||
await expect(ensureChromeExtensionRelayServer({ cdpUrl: blockedUrl })).rejects.toThrow(
|
||||
/EADDRINUSE/i,
|
||||
);
|
||||
await new Promise<void>((resolve) => blocker.close(() => resolve()));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user