mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 06:14:23 +00:00
fix(browser): tolerate brief extension relay disconnects on attached tabs
Keep extension relay tab metadata available across short extension worker drops and allow CDP clients to connect while waiting for reconnect. This prevents false "no tab connected" failures in environments where the extension worker disconnects transiently (e.g. WSLg/MV3).
This commit is contained in:
committed by
Peter Steinberger
parent
0eebae44f6
commit
f77f3fb839
@@ -392,6 +392,53 @@ describe("chrome extension relay server", () => {
|
||||
ext2.close();
|
||||
});
|
||||
|
||||
it("keeps /json/version websocket endpoint during short extension disconnects", async () => {
|
||||
const { port, ext } = await startRelayWithExtension();
|
||||
ext.send(
|
||||
JSON.stringify({
|
||||
method: "forwardCDPEvent",
|
||||
params: {
|
||||
method: "Target.attachedToTarget",
|
||||
params: {
|
||||
sessionId: "cb-tab-disconnect",
|
||||
targetInfo: {
|
||||
targetId: "t-disconnect",
|
||||
type: "page",
|
||||
title: "Disconnect test",
|
||||
url: "https://example.com",
|
||||
},
|
||||
waitingForDebugger: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await waitForListMatch(
|
||||
async () =>
|
||||
(await fetch(`${cdpUrl}/json/list`, {
|
||||
headers: relayAuthHeaders(cdpUrl),
|
||||
}).then((r) => r.json())) as Array<{ id?: string }>,
|
||||
(list) => list.some((entry) => entry.id === "t-disconnect"),
|
||||
);
|
||||
|
||||
const extClosed = waitForClose(ext, 2_000);
|
||||
ext.close();
|
||||
await extClosed;
|
||||
|
||||
const version = (await fetch(`${cdpUrl}/json/version`, {
|
||||
headers: relayAuthHeaders(cdpUrl),
|
||||
}).then((r) => r.json())) as {
|
||||
webSocketDebuggerUrl?: string;
|
||||
};
|
||||
expect(String(version.webSocketDebuggerUrl ?? "")).toContain("/cdp");
|
||||
|
||||
const cdp = new WebSocket(`ws://127.0.0.1:${port}/cdp`, {
|
||||
headers: relayAuthHeaders(`ws://127.0.0.1:${port}/cdp`),
|
||||
});
|
||||
await waitForOpen(cdp);
|
||||
cdp.close();
|
||||
});
|
||||
|
||||
it("waits briefly for extension reconnect before failing CDP commands", async () => {
|
||||
const { port, ext: ext1 } = await startRelayWithExtension();
|
||||
const cdp = new WebSocket(`ws://127.0.0.1:${port}/cdp`, {
|
||||
|
||||
@@ -82,7 +82,7 @@ type ConnectedTarget = {
|
||||
};
|
||||
|
||||
const RELAY_AUTH_HEADER = "x-openclaw-relay-token";
|
||||
const DEFAULT_EXTENSION_RECONNECT_GRACE_MS = 5_000;
|
||||
const DEFAULT_EXTENSION_RECONNECT_GRACE_MS = 20_000;
|
||||
const DEFAULT_EXTENSION_COMMAND_RECONNECT_WAIT_MS = 3_000;
|
||||
|
||||
function headerValue(value: string | string[] | undefined): string | undefined {
|
||||
@@ -256,6 +256,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
||||
const cdpClients = new Set<WebSocket>();
|
||||
const connectedTargets = new Map<string, ConnectedTarget>();
|
||||
const extensionConnected = () => extensionWs?.readyState === WebSocket.OPEN;
|
||||
const hasConnectedTargets = () => connectedTargets.size > 0;
|
||||
let extensionDisconnectCleanupTimer: NodeJS.Timeout | null = null;
|
||||
const extensionReconnectWaiters = new Set<(connected: boolean) => void>();
|
||||
|
||||
@@ -534,8 +535,9 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
||||
Browser: "OpenClaw/extension-relay",
|
||||
"Protocol-Version": "1.3",
|
||||
};
|
||||
// Only advertise the WS URL if a real extension is connected.
|
||||
if (extensionConnected()) {
|
||||
// Keep reporting CDP WS while attached targets are cached, so callers can
|
||||
// reconnect through brief MV3 worker disconnects.
|
||||
if (extensionConnected() || hasConnectedTargets()) {
|
||||
payload.webSocketDebuggerUrl = cdpWsUrl;
|
||||
}
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
@@ -658,10 +660,8 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
||||
rejectUpgrade(socket, 401, "Unauthorized");
|
||||
return;
|
||||
}
|
||||
if (!extensionConnected()) {
|
||||
rejectUpgrade(socket, 503, "Extension not connected");
|
||||
return;
|
||||
}
|
||||
// Allow CDP clients to connect even during brief extension worker drops.
|
||||
// Individual commands already wait briefly for extension reconnect.
|
||||
wssCdp.handleUpgrade(req, socket, head, (ws) => {
|
||||
wssCdp.emit("connection", ws, req);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user