expect(created.targetId).toBe("TARGET_WS_DIRECT"); // /json/version should NOT have been called — direct WS skips HTTP discovery
expect(fetchSpy).not.toHaveBeenCalled();
} finally {
fetchSpy.mockRestore();
}
});
it("preserves query params when connecting via direct WebSocket URL", async () => {
let receivedHeaders: Record<string, string> = {}; const wsPort = await startWsServer(); if (!wsServer) { thrownew Error("ws server not initialized");
}
wsServer.on("headers", (headers, req) => {
receivedHeaders = Object.fromEntries(
Object.entries(req.headers).map(([k, v]) => [k, String(v)]),
);
});
wsServer.on("connection", (socket) => {
socket.on("message", (data) => { const msg = JSON.parse(rawDataToString(data)) as { id?: number; method?: string }; if (msg.method === "Target.createTarget") {
socket.send(JSON.stringify({ id: msg.id, result: { targetId: "T_QP" } }));
}
});
});
const created = await createTargetViaCdp({
cdpUrl: `ws://127.0.0.1:${wsPort}/devtools/browser/TEST?apiKey=secret123`,
url: "https://example.com",
});
expect(created.targetId).toBe("T_QP"); // The WebSocket upgrade request should have been made to the URL with the query param
expect(receivedHeaders.host).toBe(`127.0.0.1:${wsPort}`);
});
it("enforces SSRF policy on the navigation target URL before any CDP connection attempt", async () => { const fetchSpy = vi.spyOn(globalThis, "fetch"); try {
await expect(
createTargetViaCdp({
cdpUrl: "ws://127.0.0.1:9222",
url: "http://127.0.0.1:8080",
}),
).rejects.toBeInstanceOf(SsrFBlockedError); // SSRF check happens before any connection attempt
expect(fetchSpy).not.toHaveBeenCalled();
} finally {
fetchSpy.mockRestore();
}
});
it("falls back to direct WS connection when /json/version is unavailable for a bare ws:// cdpUrl", async () => { // Simulates a Browserless/Browserbase-style provider: the cdpUrl IS a // WebSocket root (no /devtools/ path) but there is no HTTP /json/version // endpoint. The WS server accepts Target.createTarget directly. const wsPort = await startWsServerWithMessages((msg, socket) => { if (msg.method === "Target.createTarget") {
socket.send(JSON.stringify({ id: msg.id, result: { targetId: "WS_FALLBACK" } }));
}
}); // No HTTP server on this port — discovery will fail, triggering the fallback. const created = await createTargetViaCdp({
cdpUrl: `ws://127.0.0.1:${wsPort}`,
url: "https://example.com",
});
expect(created.targetId).toBe("WS_FALLBACK");
});
it("returns false for invalid or non-URL strings", () => {
expect(isWebSocketUrl("not-a-url")).toBe(false);
expect(isWebSocketUrl("")).toBe(false);
expect(isWebSocketUrl("ftp://example.com")).toBe(false);
});
});
describe("isDirectCdpWebSocketEndpoint", () => {
it("returns true for ws/wss URLs with a /devtools/<kind>/<id> path", () => {
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/devtools/browser/ABC")).toBe(true);
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/devtools/page/42")).toBe(true);
expect(isDirectCdpWebSocketEndpoint("wss://connect.example.com/devtools/browser/xyz")).toBe( true,
);
expect(
isDirectCdpWebSocketEndpoint("wss://connect.example.com/devtools/browser/xyz?token=secret"),
).toBe(true);
});
it("returns false for bare ws/wss URLs without a /devtools/ path (needs discovery)", () => { // Reproduces the configuration shape reported in #68027.
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("wss://browserless.example")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("wss://browserless.example/?token=abc")).toBe(false);
});
it("returns false for ws URLs whose path is not /devtools/*", () => {
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/json/version")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/devtools")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/devtools/")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("ws://127.0.0.1:9222/other/path")).toBe(false);
});
it("returns false for http/https URLs, invalid URLs, and empty strings", () => {
expect(isDirectCdpWebSocketEndpoint("http://127.0.0.1:9222/devtools/browser/ABC")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("https://host/devtools/browser/ABC")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("not-a-url")).toBe(false);
expect(isDirectCdpWebSocketEndpoint("")).toBe(false);
});
});
describe("parseHttpUrl with WebSocket protocols", () => {
it("accepts wss:// URLs and defaults to port 443", () => { const result = parseHttpUrl("wss://connect.example.com?apiKey=abc", "test");
expect(result.parsed.protocol).toBe("wss:");
expect(result.port).toBe(443);
expect(result.normalized).toContain("wss://connect.example.com");
});
it("accepts ws:// URLs and defaults to port 80", () => { const result = parseHttpUrl("ws://127.0.0.1/devtools", "test");
expect(result.parsed.protocol).toBe("ws:");
expect(result.port).toBe(80);
});
it("preserves explicit ports in wss:// URLs", () => { const result = parseHttpUrl("wss://connect.example.com:8443/path", "test");
expect(result.port).toBe(8443);
});
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.