feat(browser): add managed instance agent capabilities

This commit is contained in:
go0p
2026-09-03 13:31:59 +08:00
parent 8af9bb777e
commit e00746d834
37 changed files with 608 additions and 281 deletions
+35 -32
View File
@@ -2,10 +2,12 @@ import { browser } from 'wxt/browser';
import type { BridgeGrant, BrowserTarget, CapabilityScope } from '@/types/models';
import { getFrameInventory } from '@/features/page-context/frames';
import { getTab, resolveDocumentTarget } from '@/platform/browser/targets';
import { assertBrowserAccessPolicy, getEnterprisePolicy } from '@/platform/policy/managed';
import { CONTROL_CAPABILITY_SCOPES } from '@/protocol/capabilities';
import { ExtensionError } from '@/shared/errors';
import { requireActiveGrant } from './lifecycle';
export type CapabilityEngineRequest = <T>(method: string, params: unknown) => Promise<T>;
export const PAIRED_BROWSER_INSTANCE_ACCESS_ID = 'paired-browser-instance';
export interface CapabilityRouteContext {
method: string;
@@ -20,8 +22,23 @@ export interface CapabilityDomainHandler {
handle(context: CapabilityRouteContext): Promise<unknown>;
}
export async function activeGrant(required: CapabilityScope): Promise<BridgeGrant> {
const grant = await requireActiveGrant();
export async function browserInstanceAccess(required: CapabilityScope): Promise<BridgeGrant> {
const policy = (await getEnterprisePolicy()).policy;
assertBrowserAccessPolicy(policy, {
programEval: required === 'browser.page.eval.program',
});
const scopes: CapabilityScope[] = [
...CONTROL_CAPABILITY_SCOPES,
...(policy.allowProgramEval === false ? [] : ['browser.page.eval.program' as const]),
];
const grant: BridgeGrant = {
id: PAIRED_BROWSER_INSTANCE_ACCESS_ID,
taskId: PAIRED_BROWSER_INSTANCE_ACCESS_ID,
targets: [],
scopes: [...scopes],
createdAt: 0,
expiresAt: Number.MAX_SAFE_INTEGER,
};
requireScope(grant, required);
return grant;
}
@@ -36,22 +53,15 @@ function originOf(url: string): string {
}
export async function allowedTarget(
grant: BridgeGrant,
_grant: BridgeGrant,
input: { tabId?: unknown; frameId?: unknown; documentId?: unknown },
resolveInPage = true,
): Promise<BrowserTarget> {
const requested = typeof input.tabId === 'number' ? input.tabId : grant.targets[0]?.tabId;
const requestedFrameId = typeof input.frameId === 'number' ? input.frameId : 0;
const target = grant.targets.find((item) => (
item.tabId === requested && item.frameId === requestedFrameId
));
if (!target) throw new ExtensionError('target_denied', '目标标签页不在本次共享会话中');
const currentTab = await getTab(target.tabId);
if (!currentTab.isolationContextId
|| currentTab.isolationContextId !== target.isolationContextId
|| currentTab.cookieStoreId !== target.cookieStoreId) {
throw new ExtensionError('isolation_stale', '目标标签页的身份隔离上下文已经变化,请重新共享页面');
}
const currentTab = await getTab(typeof input.tabId === 'number' ? input.tabId : undefined);
const target: BrowserTarget = {
tabId: currentTab.id,
frameId: typeof input.frameId === 'number' ? input.frameId : 0,
};
const currentFrame = await browser.webNavigation.getFrame({
tabId: target.tabId,
frameId: target.frameId,
@@ -62,27 +72,20 @@ export async function allowedTarget(
currentOrigin = (await getFrameInventory(target.tabId))
.find((frame) => frame.frameId === target.frameId)?.origin || '';
}
if (currentOrigin !== target.origin) {
throw new ExtensionError('origin_changed', '目标 frame 已经跨来源导航,请重新授权');
if (!currentOrigin) {
throw new ExtensionError('target_unavailable', '目标 frame 不是可访问的 HTTP(S) 页面');
}
if (target.documentId && currentFrame.documentId
&& target.documentId !== currentFrame.documentId) {
throw new ExtensionError('stale_document', '目标 frame 已经刷新或导航,请重新授权');
assertBrowserAccessPolicy((await getEnterprisePolicy()).policy, { origin: currentOrigin });
if (typeof input.documentId === 'string' && currentFrame.documentId
&& input.documentId !== currentFrame.documentId) {
throw new ExtensionError('stale_document', '请求的页面文档已经刷新或导航,请重新获取页面上下文');
}
if (typeof input.documentId === 'string' && target.documentId
&& input.documentId !== target.documentId) {
throw new ExtensionError('stale_document', '请求的页面文档已经失效,请重新授权');
}
if (!resolveInPage) return target;
const resolved = await resolveDocumentTarget(target);
if (target.documentId && resolved.documentId && target.documentId !== resolved.documentId) {
throw new ExtensionError('stale_document', '目标页面已经刷新或导航,请重新授权');
}
return resolved;
const currentTarget = { ...target, documentId: currentFrame.documentId };
return resolveInPage ? resolveDocumentTarget(currentTarget) : currentTarget;
}
export function requireScope(grant: BridgeGrant, scope: CapabilityScope): void {
if (!grant.scopes.includes(scope)) {
throw new ExtensionError('permission_denied', `共享会话未授权能力: ${scope}`);
throw new ExtensionError('permission_denied', `浏览器实例不允许能力: ${scope}`);
}
}