import { ASTRALVIEW_SCHEMA_VERSION, type Screen } from './schema'; /** * Placeholder for future schema migrations. * Keep it pure and versioned. */ function toNumber(v: unknown, fallback: number): number { return typeof v === 'number' && Number.isFinite(v) ? v : fallback; } function asString(v: unknown): string { return typeof v === 'string' ? v : ''; } function sanitizeScreenV1(input: Screen): Screen { const allowedTypes = new Set(['text', 'image', 'iframe', 'video']); const nodes = Array.isArray(input.nodes) ? input.nodes : []; const sanitizedNodes = nodes .filter((n): n is Screen['nodes'][number] => !!n && typeof n === 'object') .filter((n) => allowedTypes.has((n as { type?: unknown }).type as string)) .map((n, i) => { const anyNode = n as unknown as Record; const rect = (anyNode.rect ?? {}) as Record; const base = { ...n, id: asString(anyNode.id) || `import_${Date.now()}_${i}`, rect: { x: toNumber(rect.x, 0), y: toNumber(rect.y, 0), w: toNumber(rect.w, 100), h: toNumber(rect.h, 60), }, }; // Ensure required props exist for media widgets to prevent runtime crashes. const props = (base as unknown as { props?: unknown }).props; const propsObj = (props && typeof props === 'object' ? (props as Record) : {}) as Record< string, unknown >; if (base.type === 'image') { return { ...base, props: { ...propsObj, src: asString(propsObj.src) } }; } if (base.type === 'iframe') { return { ...base, props: { ...propsObj, src: asString(propsObj.src) } }; } if (base.type === 'video') { return { ...base, props: { ...propsObj, src: asString(propsObj.src) } }; } if (base.type === 'text') { return { ...base, props: { ...propsObj, text: asString(propsObj.text) || '' } }; } return base; }); return { ...input, id: input.id || `av_${Date.now()}`, name: input.name || 'Untitled Screen', width: toNumber(input.width, 1920), height: toNumber(input.height, 1080), nodes: sanitizedNodes, }; } /** * Placeholder for future schema migrations. * Keep it pure and versioned. */ export function migrateScreen(input: unknown): Screen { const s = input as Partial; if (!s || typeof s !== 'object') { throw new Error('Invalid screen: not an object'); } const version = (s as Screen).version; if (version === ASTRALVIEW_SCHEMA_VERSION) { // Even for the current version, we defensively sanitize to avoid runtime // "assertNever" crashes when users import malformed JSON. return sanitizeScreenV1(s as Screen); } // Future: apply incremental migrations. throw new Error(`Unsupported screen version: ${String(version)}`); }