跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

浏览器绑定 API

最后更新 查看 MarkdownAgent 设置

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) 命令。

src/index.jsjs
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);
		}
	},
};
src/index.tsts
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 属性将 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 视为凭据,因为它们包含访问令牌。

后续步骤

这篇文档对您有帮助吗?