import type { Viewport } from '#lib/engine/types.js'; export const GRAPH_EMBED_PROTOCOL_VERSION = 1 as const; /** Public external viewer API version. This is independent of the legacy docs protocol. */ export const GRAPH_EMBED_API_VERSION = 1 as const; /** Version of the small lifecycle-message schema used by `/embed/v1`. */ export const GRAPH_EMBED_V1_PROTOCOL_VERSION = 1 as const; export const GRAPH_EMBED_V1_INTERACTION = 'none' as const; export const GRAPH_EMBED_V1_ERROR_CODES = [ 'missing-source', 'duplicate-source', 'conflicting-source', 'unknown-fragment-parameter', 'invalid-fragment', 'fragment-too-large', 'unsupported-parameter', 'unsupported-interaction', 'at-uri-too-large', 'invalid-at-uri', 'unsupported-publication', 'publication-not-found', 'publication-unavailable', 'publication-invalid', 'publication-too-large', 'invalid-document', 'document-too-large', 'render-failed' ] as const; export type GraphEmbedV1ErrorCode = (typeof GRAPH_EMBED_V1_ERROR_CODES)[number]; /** * Output-only lifecycle messages for the public `/embed/v1` viewer. * * The API and protocol versions are deliberately separate: the route contract * can evolve independently from the message envelope. `interaction` reports * the effective policy; v1 currently has only the passive `none` policy. */ export type GraphEmbedV1LifecycleMessage = | { type: 'cartesium.embed.ready'; protocolVersion: typeof GRAPH_EMBED_V1_PROTOCOL_VERSION; embedApiVersion: typeof GRAPH_EMBED_API_VERSION; interaction: typeof GRAPH_EMBED_V1_INTERACTION; title: string; } | { type: 'cartesium.embed.error'; protocolVersion: typeof GRAPH_EMBED_V1_PROTOCOL_VERSION; embedApiVersion: typeof GRAPH_EMBED_API_VERSION; interaction: typeof GRAPH_EMBED_V1_INTERACTION; code: GraphEmbedV1ErrorCode; message: string; }; export type GraphEmbedViewport = Pick< Viewport, 'x_min' | 'x_max' | 'y_min' | 'y_max' >; export type GraphEmbedHostMessage = | { type: 'cartesium:embed:reset-viewport'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; } | { type: 'cartesium:embed:set-viewport'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; viewport: GraphEmbedViewport; } | { type: 'cartesium:embed:set-expanded'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; expanded: boolean; }; export type GraphEmbedViewerMessage = | { type: 'cartesium:embed:ready'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; title: string; } | { type: 'cartesium:embed:viewport-changed'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; viewport: GraphEmbedViewport; } | { type: 'cartesium:embed:error'; version: typeof GRAPH_EMBED_PROTOCOL_VERSION; message: string; }; export function isGraphEmbedHostMessage(value: unknown): value is GraphEmbedHostMessage { if (!isProtocolRecord(value)) return false; if (value.type === 'cartesium:embed:reset-viewport') return true; if (value.type === 'cartesium:embed:set-expanded') return typeof value.expanded === 'boolean'; return value.type === 'cartesium:embed:set-viewport' && isViewport(value.viewport); } export function isGraphEmbedViewerMessage(value: unknown): value is GraphEmbedViewerMessage { if (!isProtocolRecord(value)) return false; if (value.type === 'cartesium:embed:ready') return typeof value.title === 'string'; if (value.type === 'cartesium:embed:error') return typeof value.message === 'string'; return value.type === 'cartesium:embed:viewport-changed' && isViewport(value.viewport); } export function isGraphEmbedV1LifecycleMessage( value: unknown ): value is GraphEmbedV1LifecycleMessage { if (typeof value !== 'object' || value === null) return false; const message = value as Record; if ( message.protocolVersion !== GRAPH_EMBED_V1_PROTOCOL_VERSION || message.embedApiVersion !== GRAPH_EMBED_API_VERSION || message.interaction !== GRAPH_EMBED_V1_INTERACTION ) { return false; } if (message.type === 'cartesium.embed.ready') { return typeof message.title === 'string' && message.title.length <= 200; } if (message.type !== 'cartesium.embed.error') return false; return ( typeof message.message === 'string' && message.message.length <= 240 && isGraphEmbedV1ErrorCode(message.code) ); } function isGraphEmbedV1ErrorCode(value: unknown): value is GraphEmbedV1ErrorCode { return ( typeof value === 'string' && (GRAPH_EMBED_V1_ERROR_CODES as readonly string[]).includes(value) ); } function isProtocolRecord( value: unknown ): value is Record & { version: typeof GRAPH_EMBED_PROTOCOL_VERSION } { return ( typeof value === 'object' && value !== null && (value as Record).version === GRAPH_EMBED_PROTOCOL_VERSION ); } function isViewport(value: unknown): value is GraphEmbedViewport { if (typeof value !== 'object' || value === null) return false; const viewport = value as Record; return ( finite(viewport.x_min) && finite(viewport.x_max) && finite(viewport.y_min) && finite(viewport.y_max) && viewport.x_min < viewport.x_max && viewport.y_min < viewport.y_max ); } function finite(value: unknown): value is number { return typeof value === 'number' && Number.isFinite(value); }