import { randomUUID } from "node:crypto"; import { ENGINE_VERSION, SCHEMA_VERSION, type GraphMode, type RuntimeContext, type TopologyGraph } from "./types.js"; import { shortHash } from "./hash.js"; const DEFAULT_VIEWPORT = { width: 1440, height: 1000 } as const; export function defaultViewport(): { width: number; height: number } { return { ...DEFAULT_VIEWPORT }; } export function buildRuntimeContext(storageState?: string): RuntimeContext { return { browserName: "chromium", viewport: defaultViewport(), locale: "en-US", timezoneId: null, authContext: storageState ? "storage_state" : "anonymous", }; } export function buildEmptyGraph(args: { inputUrl: string; entryUrl: string; mode: GraphMode; storageState?: string; }): TopologyGraph { const generatedAt = new Date().toISOString(); const crawlSessionId = `crawl_${randomUUID()}`; const inputFingerprint = `input_${shortHash([ args.inputUrl, args.mode, "risk-policy-v0.2", "normalization-v0.2", "locator-scoring-v0.2", ])}`; return { schemaVersion: SCHEMA_VERSION, metadata: { artifactVersion: "topology-graph-v0.2", inputUrl: args.inputUrl, entryUrl: args.entryUrl, generatedAt, engineVersion: ENGINE_VERSION, mode: args.mode, crawlSessionId, inputSeed: args.inputUrl, inputFingerprint, runtimeContext: buildRuntimeContext(args.storageState), site: { baseUrl: baseUrl(args.entryUrl), entryUrl: args.entryUrl, }, riskPolicyVersion: "risk-policy-v0.2", normalizationRuleVersion: "normalization-v0.2", locatorScoringVersion: "locator-scoring-v0.2", }, states: [], elements: [], edges: [], skippedActions: [], failedObservations: [], }; } function baseUrl(value: string): string { try { const url = new URL(value); if (url.protocol === "file:") return url.protocol; return `${url.protocol}//${url.host}`; } catch { return "unknown"; } }