- contracts/browser.ts: provider kind, per-capability {supported, verified} flags,
session/tab/control-lease rows, NavigationBudget, redacted BrowserSnapshot,
DownloadArtifact with blob/secret refs, BrowserErrorKind
- adapters/browser: BrowserProvider interface, camofox client (health, capabilities
static + /openapi.json probe, openSession/navigate/snapshot/screenshot; rest
not_implemented for M2), none provider, redactSnapshot, profile serial lock,
domain allowlist budget; Bearer access key via BrowserSecretResolver
- testkit/browser: mock camofox server (auth, fixtures with secrets, faults)
- db 0023: browser_provider/session/tab/control_lease with one-active-lease and
one-live-session-per-profile partial unique indexes; types regenerated
- docs/design/browser-provider.md: contract, camofox OpenAPI 1.17.0 mapping,
unverified items and M2 prototype checklist; Playwright is not a product browser
Co-Authored-By: Claude Opus 5.5 <[email protected]>
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
import type { NavigationBudget } from "@umt/contracts";
|
|
import { BrowserError } from "./errors.ts";
|
|
import { redactBrowserUrl } from "./redact.ts";
|
|
|
|
/** 主机是否在白名单内:精确匹配或为白名单项的子域。空白名单 = 全部拒绝。 */
|
|
export function hostAllowed(host: string, allowlist: readonly string[]): boolean {
|
|
const h = host.toLowerCase().replace(/\.$/, "");
|
|
return allowlist.some((raw) => {
|
|
const d = raw.toLowerCase().replace(/^\*\./, "").replace(/\.$/, "");
|
|
return d !== "" && (h === d || h.endsWith(`.${d}`));
|
|
});
|
|
}
|
|
|
|
/** 导航前检查:只允许 http(s) 且主机在白名单内;违规抛 domain_blocked(信息里只含脱敏 URL) */
|
|
export function assertNavigable(url: string, budget: NavigationBudget): URL {
|
|
let u: URL;
|
|
try {
|
|
u = new URL(url);
|
|
} catch {
|
|
throw new BrowserError("bad_request", "非法 URL");
|
|
}
|
|
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
throw new BrowserError("domain_blocked", `不允许的协议 ${u.protocol}`);
|
|
if (!hostAllowed(u.hostname, budget.domainAllowlist))
|
|
throw new BrowserError("domain_blocked", `域名不在白名单:${redactBrowserUrl(u.origin)}`);
|
|
return u;
|
|
}
|