import assert from "node:assert/strict"; import { readFile, writeFile } from "node:fs/promises"; // Use only an isolated candidate instance: this creates a synthetic browser profile. const base = process.env.BROWSER_VERIFY_URL ?? "http://127.0.0.1:19377"; const key = process.env.BROWSER_VERIFY_KEY ?? "umt-isolated-upgrade-key"; const statePath = process.env.BROWSER_VERIFY_STATE ?? "/tmp/umt-browser-upgrade-state.json"; const mode = process.argv[2] ?? "before"; const userId = "umt-upgrade-isolated"; async function call(path, body, auth = true, method = body === undefined ? "GET" : "POST") { const response = await fetch(base + path, { method, headers: { ...(auth ? { authorization: `Bearer ${key}` } : {}), ...(body === undefined ? {} : { "content-type": "application/json" }), }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(60_000), }); const data = await response.json(); return { status: response.status, data }; } assert.equal((await call("/health", undefined, false)).status, 200); assert.equal((await call("/umt/instance", undefined, false)).status, 401); assert.equal((await call("/tabs", { userId, sessionKey: "upgrade" }, false)).status, 401); const instance = await call("/umt/instance"); assert.equal(instance.status, 200); const identity = await call("/umt/identity"); assert.equal(identity.status, 200); const cookiesPath = `/umt/sessions/${userId}/cookies`; if (mode === "after") { const before = JSON.parse(await readFile(statePath, "utf8")); assert.notEqual(instance.data.instanceId, before.instanceId); assert.deepEqual(identity.data, before.identity); const persisted = await call(`${cookiesPath}?urls=https%3A%2F%2Fexample.com%2F`); assert.equal(persisted.status, 200); assert.equal(persisted.data.source, "persisted"); assert.ok(persisted.data.cookies.some((c) => c.name === "umt_upgrade" && c.value === "candidate-only")); } else { const imported = await call(cookiesPath, { cookies: [ { name: "umt_upgrade", value: "candidate-only", domain: "example.com", path: "/", secure: true, httpOnly: true, sameSite: "Lax", expires: Math.floor(Date.now() / 1000) + 3600, }, ], }); assert.equal(imported.status, 200); assert.equal(imported.data.count, 1); await writeFile(statePath, JSON.stringify({ instanceId: instance.data.instanceId, identity: identity.data }), { mode: 0o600, }); } const tab = await call("/tabs", { userId, sessionKey: "upgrade" }); assert.equal(tab.status, 200, JSON.stringify(tab.data)); assert.ok(tab.data.tabId); const tabPath = `/tabs/${tab.data.tabId}`; const evaluated = await call(`${tabPath}/evaluate`, { userId, expression: `(() => { document.body.innerHTML = ''; window.probeClicked = false; document.querySelector('#probe').addEventListener('click', () => { window.probeClicked = true; }); return { title: document.title, userAgent: navigator.userAgent }; })()`, }); assert.equal(evaluated.status, 200, JSON.stringify(evaluated.data)); assert.match(evaluated.data.result.userAgent, /Firefox\/152/); const snapshot = await call(`${tabPath}/snapshot?userId=${userId}`); assert.equal(snapshot.status, 200, JSON.stringify(snapshot.data)); assert.match(JSON.stringify(snapshot.data), /升级验证/); const screenshot = await fetch(`${base}${tabPath}/screenshot?userId=${userId}`, { headers: { authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(60_000), }); assert.equal(screenshot.status, 200); assert.match(screenshot.headers.get("content-type"), /image\/png/); assert.deepEqual( new Uint8Array(await screenshot.arrayBuffer()).slice(0, 8), new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), ); const clicked = await call(`${tabPath}/click`, { userId, selector: "#probe" }); assert.equal(clicked.status, 200, JSON.stringify(clicked.data)); const outcome = await call(`${tabPath}/evaluate`, { userId, expression: "window.probeClicked" }); assert.equal(outcome.data.result, true); const mouseProbe = await call(`${tabPath}/evaluate`, { userId, expression: "(() => { window.probeTrusted = false; const b = document.querySelector('#probe'); b.addEventListener('click', e => { window.probeTrusted = e.isTrusted; }); const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()", }); assert.equal(mouseProbe.status, 200); const mouse = await call(`/umt/tabs/${tab.data.tabId}/mouse-click`, { userId, ...mouseProbe.data.result }); assert.equal(mouse.status, 200, JSON.stringify(mouse.data)); const trusted = await call(`${tabPath}/evaluate`, { userId, expression: "window.probeTrusted" }); assert.equal(trusted.data.result, true); const wrongProfile = await call(`${tabPath}/evaluate`, { userId: "umt-upgrade-other-isolated", expression: "document.title", }); assert.notEqual(wrongProfile.status, 200); const cookies = await call(`${cookiesPath}?urls=https%3A%2F%2Fexample.com%2F`); assert.ok(cookies.data.cookies.some((c) => c.name === "umt_upgrade" && c.value === "candidate-only")); await call(`${tabPath}?userId=${userId}`, undefined, true, "DELETE"); console.log( JSON.stringify( { mode, ok: true, instanceId: instance.data.instanceId, identity: identity.data, checks: [ "health", "authorization", "fixed-identity", "cookies", "new-context", "new-page", "evaluate", "snapshot", "screenshot", "click", "trusted-mouse-click", "profile-isolation", ...(mode === "after" ? ["restart-persistence"] : []), ], }, null, 2, ), );