export const BROWSER_ACTIONS = ["open", "navigate", "snapshot", "extract", "links", "close"] as const; export type BrowserAction = (typeof BROWSER_ACTIONS)[number]; export interface BrowserResultDetails { action: BrowserAction; url?: string; finalUrl?: string; status?: number; statusText?: string; title?: string; links?: number; count?: number; textLength?: number; contentType?: string; } function sanitizeNonNegativeNumber(value: unknown): number | undefined { if (typeof value !== "number" || !Number.isFinite(value)) return undefined; return Math.max(0, Math.floor(value)); } function sanitizeString(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed ? trimmed : undefined; } export function createBrowserResultDetails( action: BrowserAction, data?: Partial, ): BrowserResultDetails { return { action, url: sanitizeString(data?.url), finalUrl: sanitizeString(data?.finalUrl), status: sanitizeNonNegativeNumber(data?.status), statusText: sanitizeString(data?.statusText), title: sanitizeString(data?.title), links: sanitizeNonNegativeNumber(data?.links), count: sanitizeNonNegativeNumber(data?.count), textLength: sanitizeNonNegativeNumber(data?.textLength), contentType: sanitizeString(data?.contentType), }; } export function isBrowserResultDetails(value: unknown): value is BrowserResultDetails { if (!value || typeof value !== "object") return false; const candidate = value as Partial; if (typeof candidate.action !== "string" || !(BROWSER_ACTIONS as readonly string[]).includes(candidate.action)) { return false; } const numericKeys: Array> = [ "status", "links", "count", "textLength", ]; for (const key of numericKeys) { const v = candidate[key]; if (v !== undefined && (typeof v !== "number" || !Number.isFinite(v))) { return false; } } const stringKeys: Array> = [ "url", "finalUrl", "statusText", "title", "contentType", ]; for (const key of stringKeys) { const v = candidate[key]; if (v !== undefined && typeof v !== "string") { return false; } } return true; }