feat(browser): fixed Windows identity and per-site egress in umt-cookies plugin (BR-4)

- browser:launching hook generates a Windows fingerprint once (camoufox-js,
  os=windows), persists it to /root/.camofox/umt-identity.json and reuses it
  on every launch; Firefox/Camoufox upgrades only bump UA/version fields
- Windows fontconfig with absolute font dir (upstream path was broken)
- GET /umt/identity (public UA/OS/version) and PUT /umt/sessions/:id/proxy
  (per-context proxy; Firefox no longer uses container HTTP_PROXY env)
- adapter identity()/setEgress(), splitBrowserOnlyCookies for cf_clearance/__cf_bm

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 03:17:06 +08:00
1 parent 6663afce7e
commit ebd07c9230
8 files changed
+746 -5

No files matched your search

@@ -19,7 +19,9 @@ import type {
BrowserCapabilities,
BrowserControlLease,
BrowserCookie,
BrowserEgressProxy,
BrowserHealth,
BrowserIdentity,
BrowserSession,
BrowserSnapshot,
BrowserTab,
@@ -175,7 +177,7 @@ export class CamofoxBrowserProvider implements BrowserProvider {
}
private async request(
method: "GET" | "POST" | "DELETE",
method: "GET" | "POST" | "PUT" | "DELETE",
path: string,
opts: { query?: Record<string, string | undefined>; body?: Json; timeoutMs?: number; auth?: boolean } = {},
): Promise<Response> {
@@ -386,6 +388,54 @@ export class CamofoxBrowserProvider implements BrowserProvider {
return typeof v === "string" || typeof v === "number" ? String(v) : null;
}
/**
* 固定浏览器身份(BR-4):umt 插件 `GET /umt/identity`(UA、OS、版本、生成时间,不含指纹细节);
* 插件不在或身份关闭(404)返回 null。
*/
async identity(): Promise<BrowserIdentity | null> {
let body: Json;
try {
body = await this.json(await this.request("GET", "/umt/identity", { timeoutMs: 10_000 }), "/umt/identity");
} catch (e) {
if (e instanceof BrowserError && e.httpStatus === 404) return null;
throw e;
}
const userAgent = str(body.userAgent);
if (!userAgent) return null;
return {
userAgent,
os: str(body.os) ?? null,
platform: str(body.platform) ?? null,
firefoxVersion: str(body.firefoxVersion) ?? null,
camoufoxVersion: str(body.camoufoxVersion) ?? null,
fingerprintId: str(body.fingerprintId) ?? null,
revision: int(body.revision) ?? 1,
createdAt: str(body.createdAt) ?? null,
updatedAt: str(body.updatedAt) ?? null,
};
}
/**
* 该 profile 的出口(BR-4 出口 IP 按站点统一):umt 插件 `PUT /umt/sessions/{userId}/proxy`。
* proxy = null 直连;活动上下文出口不同时插件会先关掉它(落盘登录状态)。插件不在报 not_implemented。
*/
async setEgress(profileKey: string, proxy: BrowserEgressProxy | null): Promise<{ restarted: boolean }> {
try {
const body = await this.json(
await this.request("PUT", `/umt/sessions/${encodeURIComponent(profileKey)}/proxy`, {
body: { proxy } as unknown as Json,
timeoutMs: 15_000,
}),
"/umt/sessions/{userId}/proxy",
);
return { restarted: body.restarted === true };
} catch (e) {
if (e instanceof BrowserError && e.httpStatus === 404)
throw new BrowserError("not_implemented", "浏览器服务不支持按站点设出口(镜像未带新版 umt-cookies 插件)");
throw e;
}
}
/**
* 重建会话的上游上下文(BR-3):上游重启 / 回收后旧标签页都不在了。先(可选)注入 Cookie,再用同一 userId / sessionKey
* 开新标签页并导航到 url;旧的打开中标签页标 lost,旧元素引用作废。不检查控制者:接管中的会话重连后仍归原控制者。