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

@@ -126,6 +126,22 @@ export type MockCamofox = MockHttp & {
restart(opts?: { loseProfiles?: boolean }): void;
/** 当前实例标识 */
readonly instanceId: string;
/** 各 profile 的出口(`PUT /umt/sessions/:userId/proxy`,BR-4):null = 直连 */
egress: Map<string, { server: string; username?: string; password?: string } | null>;
};
/** mock 的固定浏览器身份(`GET /umt/identity`,BR-4) */
export const MOCK_CAMOFOX_IDENTITY = {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0",
os: "windows",
platform: "Win32",
firefoxVersion: "152",
camoufoxVersion: "152.0.4-mock",
fingerprintId: "mockfp000001",
revision: 1,
schema: 1,
createdAt: "2026-09-30T00:00:00.000Z",
updatedAt: "2026-09-30T00:00:00.000Z",
};
/** 1x1 透明 PNG */
@@ -331,6 +347,7 @@ export async function createMockCamofox(opts: MockCamofoxOptions = {}): Promise<
const actions: MockCamofoxAction[] = [];
const loggedIn = new Set<string>();
const cookieJars = new Map<string, MockCamofoxCookie[]>();
const egress = new Map<string, { server: string; username?: string; password?: string } | null>();
const cookieApi = opts.cookieApi ?? "plugin";
let instanceId = `inst-${crypto.randomUUID()}`;
/** 重启前存在过的 tabId:之后访问返回 410 */
@@ -795,6 +812,21 @@ export async function createMockCamofox(opts: MockCamofoxOptions = {}): Promise<
},
],
[/^\/umt\/instance$/, () => (cookieApi === "plugin" ? { json: { instanceId } } : err(404, "no route"))],
[
/^\/umt\/identity$/,
() => (cookieApi === "plugin" ? { json: { ...MOCK_CAMOFOX_IDENTITY } } : err(404, "no route")),
],
[
/^\/umt\/sessions\/([^/]+)\/proxy$/,
(m, _q, req, b) => {
if (cookieApi !== "plugin" || req.method !== "PUT") return err(404, "no route");
const userId = decodeURIComponent(m[1]!);
const p = b.proxy as { server?: string } | null | undefined;
if (p !== null && (!p || typeof p.server !== "string")) return err(400, "proxy must be {server} or null");
egress.set(userId, p === null ? null : { ...(p as { server: string }) });
return { json: { ok: true, userId, egress: p ? "proxy" : "direct", mode: "site", restarted: false } };
},
],
[
/^\/umt\/sessions\/([^/]+)\/cookies$/,
(m, q, req, b) => {
@@ -858,6 +890,7 @@ export async function createMockCamofox(opts: MockCamofoxOptions = {}): Promise<
actions,
loggedIn,
cookieJars,
egress,
expireLogin(userId?: string) {
if (userId) loggedIn.delete(userId);
else loggedIn.clear();