import type { RakazoDesktopBrowserBounds } from "@rakazo/contracts"; /** Only plain http(s) pages load in the in-app browser; no files, schemes or URL credentials. */ export function previewUrl(value: unknown): string | null { if (typeof value !== "string" || value.length > 8192) return null; try { const url = new URL(value); if (url.protocol !== "http:" && url.protocol !== "https:") return null; if (url.username || url.password) return null; return url.toString(); } catch { return null; } } /** * Renderer-reported CSS-pixel bounds, converted to window DIPs and clamped to the content area * so a compromised renderer cannot place the view outside the panel it reserved. */ export function previewBounds( value: unknown, content: { width: number; height: number }, zoomFactor = 1, ): RakazoDesktopBrowserBounds | null { if (!value || typeof value !== "object") return null; const raw = value as Record; const numbers = [raw.x, raw.y, raw.width, raw.height]; if (!numbers.every((n) => typeof n === "number" && Number.isFinite(n))) return null; const scale = Number.isFinite(zoomFactor) && zoomFactor > 0 ? zoomFactor : 1; const [x, y, width, height] = (numbers as number[]).map((n) => Math.round(n * scale)) as [ number, number, number, number, ]; const left = Math.min(Math.max(0, x), content.width); const top = Math.min(Math.max(0, y), content.height); const right = Math.min(Math.max(left, x + width), content.width); const bottom = Math.min(Math.max(top, y + height), content.height); if (right - left < 1 || bottom - top < 1) return null; return { x: left, y: top, width: right - left, height: bottom - top }; } export type PreviewShortcut = "reload" | "hard-reload" | "back" | "forward" | "devtools"; /** Browser keyboard shortcuts while focus is inside the page. */ export function previewShortcut( input: { type: string; key: string; control: boolean; meta: boolean; alt: boolean; shift: boolean; }, platform: string, ): PreviewShortcut | null { if (input.type !== "keyDown") return null; const mod = platform === "darwin" ? input.meta : input.control; const key = input.key.length === 1 ? input.key.toLowerCase() : input.key; if (key === "F5") return input.shift ? "hard-reload" : "reload"; if (key === "F12") return "devtools"; if (mod && key === "r") return input.shift ? "hard-reload" : "reload"; if (mod && input.alt && key === "i") return "devtools"; if (platform === "darwin" ? input.meta && !input.alt : input.alt && !input.control) { if (key === (platform === "darwin" ? "[" : "ArrowLeft")) return "back"; if (key === (platform === "darwin" ? "]" : "ArrowRight")) return "forward"; } return null; } /** Load failures worth showing; an aborted load (-3) is a superseded navigation, not an error. */ export function previewLoadError(errorCode: number, description: string): string | null { if (errorCode === -3 || errorCode === 0) return null; return description || `Load failed (${errorCode})`; }