























@@ -1,6 +1,9 @@
1+import type { SsrFPolicy } from "../../infra/net/ssrf.js";
12import { withCdpSocket } from "../cdp.helpers.js";
23import { getChromeWebSocketUrl } from "../chrome.js";
4+import { getPwAiModule } from "../pw-ai-module.js";
35import type { BrowserRouteContext } from "../server-context.js";
6+import type { ProfileContext } from "../server-context.js";
47import type { BrowserRouteRegistrar } from "./types.js";
58import {
69asyncBrowserRoute,
@@ -10,11 +13,22 @@ import {
1013toStringOrEmpty,
1114} from "./utils.js";
121516+const permissionRouteDeps = {
17+ getPwAiModule,
18+};
19+20+export const __testing = {
21+setDepsForTest(deps: { getPwAiModule?: typeof getPwAiModule } | null) {
22+permissionRouteDeps.getPwAiModule = deps?.getPwAiModule ?? getPwAiModule;
23+},
24+};
25+1326type GrantPermissionsBody = {
1427origin?: unknown;
1528permissions?: unknown;
1629optionalPermissions?: unknown;
1730timeoutMs?: unknown;
31+targetId?: unknown;
1832};
19332034function readOrigin(raw: unknown): string | null {
@@ -47,15 +61,45 @@ function readPermissions(raw: unknown): string[] | null {
4761}
48624963async function grantPermissions(params: {
64+profileCtx: ProfileContext;
65+targetId?: string;
5066wsUrl: string;
5167origin: string;
5268requiredPermissions: string[];
5369optionalPermissions: string[];
5470timeoutMs: number;
71+ssrfPolicy?: SsrFPolicy;
5572}) {
5673const allPermissions = [
5774 ...new Set([...params.requiredPermissions, ...params.optionalPermissions]),
5875];
76+const playwrightRequiredPermissions = params.requiredPermissions.map(toPlaywrightPermission);
77+const canUsePlaywright =
78+playwrightRequiredPermissions.every((value): value is string => Boolean(value)) &&
79+params.requiredPermissions.length > 0;
80+if (canUsePlaywright) {
81+const pw = await permissionRouteDeps.getPwAiModule({ mode: "soft" });
82+if (pw) {
83+try {
84+const page = await pw.getPageForTargetId({
85+cdpUrl: params.profileCtx.profile.cdpUrl,
86+targetId: params.targetId,
87+ssrfPolicy: params.ssrfPolicy,
88+});
89+await page.context().grantPermissions(playwrightRequiredPermissions, {
90+origin: params.origin,
91+});
92+return {
93+grantedPermissions: params.requiredPermissions,
94+unsupportedPermissions: params.optionalPermissions,
95+grantMethod: "playwright",
96+};
97+} catch {
98+// Fall back to the raw CDP browser command below. Some routes call this
99+// before a page exists, while attached browser profiles need Playwright.
100+}
101+}
102+}
59103let unsupportedPermissions: string[] = [];
60104await withCdpSocket(
61105params.wsUrl,
@@ -82,9 +126,21 @@ async function grantPermissions(params: {
82126return {
83127grantedPermissions: allPermissions.filter((value) => !unsupportedPermissions.includes(value)),
84128 unsupportedPermissions,
129+grantMethod: "cdp",
85130};
86131}
87132133+function toPlaywrightPermission(permission: string): string | undefined {
134+switch (permission) {
135+case "audioCapture":
136+return "microphone";
137+case "videoCapture":
138+return "camera";
139+default:
140+return undefined;
141+}
142+}
143+88144export function registerBrowserPermissionRoutes(
89145app: BrowserRouteRegistrar,
90146ctx: BrowserRouteContext,
@@ -107,6 +163,7 @@ export function registerBrowserPermissionRoutes(
107163return jsonError(res, 400, "permissions must be a non-empty string array");
108164}
109165const optionalPermissions = readPermissions(body.optionalPermissions ?? []) ?? [];
166+const targetId = toStringOrEmpty(body.targetId) || undefined;
110167const timeoutMs = Math.max(1_000, toNumber(body.timeoutMs) ?? 5_000);
111168112169try {
@@ -120,11 +177,14 @@ export function registerBrowserPermissionRoutes(
120177return jsonError(res, 409, "browser CDP WebSocket unavailable");
121178}
122179const granted = await grantPermissions({
180+ profileCtx,
181+ targetId,
123182 wsUrl,
124183 origin,
125184 requiredPermissions,
126185 optionalPermissions,
127186 timeoutMs,
187+ssrfPolicy: ctx.state().resolved.ssrfPolicy,
128188});
129189return res.json({ ok: true, origin, ...granted });
130190} catch (error) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。