export type ErrorCode = | "invalidOrigin" | "iframeError" | "timeout" | "internal"; export type ErrorSeverity = "debug" | "info" | "warn" | "error"; export interface WidgetError { code?: ErrorCode; retryable?: boolean; severity?: ErrorSeverity; cause?: unknown; context?: Record; } const ERROR_CODE_SET: Record = { invalidOrigin: true, iframeError: true, timeout: true, internal: true, }; const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null; export const isErrorCode = (value: unknown): value is ErrorCode => typeof value === "string" && value in ERROR_CODE_SET; const readRawCode = (value: unknown): string | undefined => { if (!isRecord(value)) return; if (typeof value.code === "string") { return value.code; } if (isRecord(value.cause) && typeof value.cause.code === "string") { return value.cause.code; } return; }; export const readErrorCode = (value: unknown): ErrorCode | undefined => { const raw = readRawCode(value); return isErrorCode(raw) ? raw : undefined; }; export const isWidgetError = (value: unknown): value is WidgetError => isRecord(value) && ("context" in value || "code" in value || "retryable" in value); /** * Wrap any error into a lightweight WidgetError with optional code and context. * - Preserves the original error in `cause` * - Adds `context` for observability (e.g., { domain, op }) * - Derives `retryable` for timeouts by default */ export function wrapError( err: unknown, context: Record, options?: { code?: ErrorCode; retryable?: boolean; severity?: ErrorSeverity }, ): WidgetError { const baseObj: Record = isRecord(err) ? { ...err } : {}; const code = options?.code ?? readErrorCode(baseObj); const retryable = options?.retryable ?? (code === "timeout" ? true : undefined); const severity = options?.severity; return { code, retryable, severity, cause: err, context, }; } /** * Classify an unknown error into the public failure shape used by the protocol. */ export function classifyError(err: unknown): { code: ErrorCode; detail: unknown; } { const raw = readRawCode(err); if (raw === "invalidOrigin") return { code: "invalidOrigin", detail: err }; if (raw === "timeout") return { code: "timeout", detail: err }; if (raw === "iframeError") return { code: "iframeError", detail: err }; // If a non-standard code is present, treat as internal if (raw != null) return { code: "internal", detail: err }; // Default to iframeError for all other cases return { code: "iframeError", detail: err }; }