Browser Run 绑定提供用于从 Cloudflare Worker 管理浏览器会话的带类型方法。使用这些方法获取会话、连接浏览器客户端、将浏览器请求路由到另一个 Worker,以及管理 DevTools target。
在 Wrangler 配置中配置浏览器绑定:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "browser-binding-example",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-11-14",
"compatibility_flags": [
"nodejs_compat"
],
"browser": {
"binding": "BROWSER"
}
}name = "browser-binding-example"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-11-14"
compatibility_flags = ["nodejs_compat"]
[browser]
binding = "BROWSER"| 方法 | 描述 |
|---|---|
acquire(options?) |
创建浏览器会话并返回其会话 ID。设置 targets: true 以包含会话的当前 target。 |
connectSession(sessionId, options?) |
返回连接对象,其中包含会话固定的 webSocket Fetcher,用于将 CDP 客户端连接到现有会话。 |
launch(options?) |
获取会话并返回与 connectSession() 相同的连接结果。 |
getLiveView(sessionId, options?) |
为会话或 target 创建 Live View URL。 |
listSessions(options?) |
列出账户中的活动会话。 |
history(options?) |
列出最近的活动和已关闭会话。 |
limits() |
返回当前会话限制和用量值。 |
getSession(sessionId) |
返回会话详情;会话不存在时返回 null。 |
closeSession(sessionId) |
关闭会话并返回 closing 或 closed。 |
devtools |
提供 DevTools JSON 端点的带类型方法。 |
acquire() 返回会话元数据。使用 connectSession() 获取会话固定的 Fetcher,然后打开 WebSocket 升级并发送 Chrome DevTools Protocol (CDP) 命令。
let nextCdpCommandId = 0;
function sendCdpCommand(socket, method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++nextCdpCommandId;
const timeout = setTimeout(() => {
cleanup();
reject(new Error(`CDP command "${method}" timed out`));
}, 30_000);
const cleanup = () => {
clearTimeout(timeout);
socket.removeEventListener("message", onMessage);
socket.removeEventListener("close", onClose);
};
const onClose = () => {
cleanup();
reject(new Error("CDP connection closed before the command completed"));
};
const onMessage = (event) => {
const message = JSON.parse(event.data);
if (message.id !== id) return;
cleanup();
if (message.error) {
reject(new Error(message.error.message));
} else {
resolve(message.result);
}
};
socket.addEventListener("message", onMessage);
socket.addEventListener("close", onClose);
try {
socket.send(JSON.stringify({ id, method, params }));
} catch (error) {
cleanup();
reject(error);
}
});
}
export default {
async fetch(request, env) {
const session = await env.BROWSER.acquire({ targets: true });
const connection = await env.BROWSER.connectSession(session.sessionId);
const response = await connection.webSocket.fetch(
"https://browser-binding.invalid",
{ headers: { Upgrade: "websocket" } },
);
if (!response.webSocket) {
throw new Error("Browser Run did not return a WebSocket");
}
const socket = response.webSocket;
socket.accept();
try {
await sendCdpCommand(socket, "Page.navigate", {
url: "https://example.com",
});
const version = await sendCdpCommand(socket, "Browser.getVersion");
return Response.json(version);
} finally {
socket.close();
await env.BROWSER.closeSession(session.sessionId);
}
},
};interface Env {
BROWSER: Fetcher;
}
type CdpResponse = {
id: number;
result?: unknown;
error?: { message: string };
};
let nextCdpCommandId = 0;
function sendCdpCommand(
socket: WebSocket,
method: string,
params: Record<string, unknown> = {},
): Promise<unknown> {
return new Promise((resolve, reject) => {
const id = ++nextCdpCommandId;
const timeout = setTimeout(() => {
cleanup();
reject(new Error(`CDP command "${method}" timed out`));
}, 30_000);
const cleanup = () => {
clearTimeout(timeout);
socket.removeEventListener("message", onMessage);
socket.removeEventListener("close", onClose);
};
const onClose = () => {
cleanup();
reject(new Error("CDP connection closed before the command completed"));
};
const onMessage = (event: MessageEvent<string>) => {
const message = JSON.parse(event.data) as CdpResponse;
if (message.id !== id) return;
cleanup();
if (message.error) {
reject(new Error(message.error.message));
} else {
resolve(message.result);
}
};
socket.addEventListener("message", onMessage);
socket.addEventListener("close", onClose);
try {
socket.send(JSON.stringify({ id, method, params }));
} catch (error) {
cleanup();
reject(error);
}
});
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const session = await env.BROWSER.acquire({ targets: true });
const connection = await env.BROWSER.connectSession(session.sessionId);
const response = await connection.webSocket.fetch(
"https://browser-binding.invalid",
{ headers: { Upgrade: "websocket" } },
);
if (!response.webSocket) {
throw new Error("Browser Run did not return a WebSocket");
}
const socket = response.webSocket;
socket.accept();
try {
await sendCdpCommand(socket, "Page.navigate", {
url: "https://example.com",
});
const version = await sendCdpCommand(socket, "Browser.getVersion");
return Response.json(version);
} finally {
socket.close();
await env.BROWSER.closeSession(session.sessionId);
}
},
};当你不需要将获取与连接分开时,使用 launch():
async function useLaunch(env) {
const session = await env.BROWSER.launch();
const response = await session.webSocket.fetch(
"https://browser-binding.invalid",
{ headers: { Upgrade: "websocket" } },
);
if (!response.webSocket) {
throw new Error("Browser Run did not return a WebSocket");
}
const socket = response.webSocket;
socket.accept();
try {
return await sendCdpCommand(socket, "Browser.getVersion");
} finally {
socket.close();
await env.BROWSER.closeSession(session.sessionId);
}
}interface Env {
BROWSER: Fetcher;
}
async function useLaunch(env: Env): Promise<unknown> {
const session = await env.BROWSER.launch();
const response = await session.webSocket.fetch(
"https://browser-binding.invalid",
{ headers: { Upgrade: "websocket" } },
);
if (!response.webSocket) {
throw new Error("Browser Run did not return a WebSocket");
}
const socket = response.webSocket;
socket.accept();
try {
return await sendCdpCommand(socket, "Browser.getVersion");
} finally {
socket.close();
await env.BROWSER.closeSession(session.sessionId);
}
}launch() 在一次调用中执行获取和连接能力步骤。两种方法都返回具有以下结构的连接对象:
| 属性 | 描述 |
|---|---|
sessionId |
Browser Run 会话 ID。 |
webSocket |
用于打开 CDP 连接的会话固定 Fetcher。 |
targets |
使用 targets: true 请求时的 target 列表。 |
acquire() 和 launch() 接受这些选项:
| 选项 | 类型 | 描述 |
|---|---|---|
keepAlive |
number |
会话不活动超时(毫秒)。取值必须在 10 秒到 20 分钟之间。 |
recording |
boolean |
录制会话以供稍后检查。 |
outboundByHost |
Record<string, Fetcher> |
将每个主机名的请求路由到 Worker Fetcher。请参阅通过 Outbound Worker 路由请求。 |
guardrails |
object |
限制浏览器会话可以访问的主机名。请参阅 Guardrails。 |
targets |
boolean |
在结果中包含会话的 DevTools target。 |
liveViewUrlExpiresInMs |
number |
当 targets 为 true 时,设置 target Live View URL 的过期时间。 |
绑定在其选项对象中使用 keepAlive。
devtools 属性将 DevTools JSON 端点公开为带类型方法。它是一个嵌套绑定目标,因此你可以从同一个 Browser Run 绑定调用这些方法:
| 方法 | 描述 |
|---|---|
getVersion(sessionId) |
返回浏览器版本信息。 |
getProtocol(sessionId) |
返回浏览器的 DevTools 协议描述。 |
listTargets(sessionId, options?) |
列出浏览器的 target。设置 liveViewUrlExpiresInMs 以控制生成的 Live View URL 过期时间。 |
getTarget(sessionId, targetId) |
返回一个 target。 |
newTarget(sessionId, url?, options?) |
打开新 target。如果省略 url,target 在 about:blank 打开。 |
activateTarget(sessionId, targetId) |
激活 target。 |
closeTarget(sessionId, targetId) |
关闭 target。 |
Target 对象包含 target ID、类型、URL、标题,以及在可用时包含 devtoolsFrontendUrl。
const targets = await env.BROWSER.devtools.listTargets(sessionId, {
liveViewUrlExpiresInMs: 300_000,
});
const page = targets.find((target) => target.type === "page");
if (!page) {
throw new Error("No page target found");
}
const liveView = await env.BROWSER.getLiveView(sessionId, {
targetId: page.id,
mode: "devtools",
});
console.log(liveView.devtoolsFrontendUrl);const targets = await env.BROWSER.devtools.listTargets(sessionId, {
liveViewUrlExpiresInMs: 300_000,
});
const page = targets.find((target) => target.type === "page");
if (!page) {
throw new Error("No page target found");
}
const liveView = await env.BROWSER.getLiveView(sessionId, {
targetId: page.id,
mode: "devtools",
});
console.log(liveView.devtoolsFrontendUrl);使用返回的 devtoolsFrontendUrl 打开 Live View。要将 CDP 客户端连接到特定 target,将其 ID 传递给 connectSession(sessionId, { targetId })。将 Live View URL 视为凭据,因为它们包含访问令牌。
- 了解 Live View。
- 查看使用 HTTP 进行会话管理。
- 使用 Chrome DevTools Protocol 直接控制浏览器。