import * as z from "zod/mini"; import { appEventPayloadSchema } from "./app-event.js"; import { clientMetaSchema } from "./client-meta.js"; import { HANDLE_INVALIDATED_REASON_CODES, HANDLE_INVALIDATED_SOURCES, } from "./host/host-event-contract.js"; import { widgetFrameAppearanceSchema, widgetLayoutTransitionScheduleSchema, } from "./host/host-event-schemas.js"; import { publicCommandPayloadSchema } from "./widget-commands.js"; const nonEmptyStringSchema = z.string().check(z.trim(), z.minLength(1)); const finiteNumberSchema = z.number().check( z.refine(Number.isFinite, { message: "Expected a finite number.", }), ); const cubicBezierXControlPointSchema = finiteNumberSchema.check( z.minimum(0), z.maximum(1), ); const transportMessageIdSchema = nonEmptyStringSchema; const commandErrorSchema = z.strictObject({ message: nonEmptyStringSchema, code: z.optional(nonEmptyStringSchema), }); export const webViewCommandEnvelopeSchema = z.object({ version: z.literal("1"), instanceId: nonEmptyStringSchema, requestId: nonEmptyStringSchema, idempotencyKey: nonEmptyStringSchema, clientMeta: z.optional(clientMetaSchema), command: publicCommandPayloadSchema, }); const commandSettledDetailBase = { requestId: nonEmptyStringSchema, instanceId: z.nullable(nonEmptyStringSchema), kind: nonEmptyStringSchema, } as const; const commandSettledDetailSchema = z.discriminatedUnion("ok", [ z.strictObject({ ...commandSettledDetailBase, ok: z.literal(true), result: z.optional(z.unknown()), }), z.strictObject({ ...commandSettledDetailBase, ok: z.literal(false), error: commandErrorSchema, }), ]); const flowRenderStateFields = { isOpen: z.boolean(), isLoading: z.boolean(), width: z.optional(z.number()), height: z.optional(z.number()), } as const; const flowTransitionFields = { transitionPolicy: z.optional( z.discriminatedUnion("kind", [ z.strictObject({ kind: z.literal("instant"), durationMs: z.literal(0), }), z.strictObject({ kind: z.literal("timed"), durationMs: finiteNumberSchema.check(z.positive()), easing: z.tuple([ cubicBezierXControlPointSchema, finiteNumberSchema, cubicBezierXControlPointSchema, finiteNumberSchema, ]), }), ]), ), } as const; const flowStateBase = { instanceId: nonEmptyStringSchema, ...flowRenderStateFields, ...flowTransitionFields, } as const; const flowStateChangedDetailSchema = z.strictObject({ ...flowStateBase, flowHandleId: nonEmptyStringSchema, }); const instanceFlowStateChangedDetailSchema = z.strictObject({ ...flowStateBase, shouldRender: z.optional(z.boolean()), flowHandleId: z.optional(nonEmptyStringSchema), }); const viewHostFlowRunSnapshotSchema = z.strictObject({ ...flowRenderStateFields, ...flowTransitionFields, frameAppearance: z.optional(widgetFrameAppearanceSchema), transitionSchedule: z.optional(widgetLayoutTransitionScheduleSchema), kind: z.literal("flowRun"), flowRunId: nonEmptyStringSchema, flowHandleId: z.optional(nonEmptyStringSchema), }); const viewHostPendingFlowHandleSnapshotSchema = z.strictObject({ ...flowRenderStateFields, ...flowTransitionFields, frameAppearance: z.optional(widgetFrameAppearanceSchema), transitionSchedule: z.optional(widgetLayoutTransitionScheduleSchema), kind: z.literal("pendingFlowHandle"), flowHandleId: nonEmptyStringSchema, }); const viewHostSnapshotSchema = z.discriminatedUnion("kind", [ viewHostFlowRunSnapshotSchema, viewHostPendingFlowHandleSnapshotSchema, ]); const instanceViewHostSnapshotsChangedDetailSchema = z.strictObject({ instanceId: nonEmptyStringSchema, snapshots: z.array(viewHostSnapshotSchema), }); const openRequestedDetailSchema = z.strictObject({ instanceId: nonEmptyStringSchema, source: z.enum(["command", "targeting"]), flowId: nonEmptyStringSchema, flowHandleId: z.optional(nonEmptyStringSchema), hideCloseButton: z.optional(z.boolean()), }); const handleInvalidatedDetailSchema = z.strictObject({ instanceId: nonEmptyStringSchema, handleKind: nonEmptyStringSchema, handleId: nonEmptyStringSchema, reasonCode: z.enum(HANDLE_INVALIDATED_REASON_CODES), reasonMessage: z.optional(nonEmptyStringSchema), relatedRequestId: z.optional(nonEmptyStringSchema), source: z.enum(HANDLE_INVALIDATED_SOURCES), at: z.number(), }); const unsupportedCommandDetailSchema = z.strictObject({ requestId: nonEmptyStringSchema, instanceId: z.nullable(nonEmptyStringSchema), kind: nonEmptyStringSchema, code: z.literal("UNSUPPORTED_COMMAND"), message: nonEmptyStringSchema, issues: z.optional(z.array(z.string())), }); const instanceErrorDetailSchema = z.strictObject({ instanceId: nonEmptyStringSchema, code: nonEmptyStringSchema, details: z.optional(z.unknown()), recovery: z.optional(z.unknown()), at: z.number(), }); const loaderErrorDetailSchema = z.strictObject({ code: nonEmptyStringSchema, details: z.optional(z.unknown()), recovery: z.optional(z.unknown()), at: z.number(), }); const appEventDetailSchema = z.strictObject({ instanceId: nonEmptyStringSchema, payload: appEventPayloadSchema, }); export const webViewTransportNativeMessageSchema = z.discriminatedUnion( "kind", [ z.strictObject({ kind: z.literal("enqueueCommand"), id: transportMessageIdSchema, envelope: webViewCommandEnvelopeSchema, }), ], ); export const webViewTransportHostEventSchema = z.discriminatedUnion("name", [ z.strictObject({ name: z.literal("instance:command:settled"), detail: commandSettledDetailSchema, }), z.strictObject({ name: z.literal("instance:flow:state-changed"), detail: flowStateChangedDetailSchema, }), z.strictObject({ name: z.literal("instance:flow:state-changed:instance"), detail: instanceFlowStateChangedDetailSchema, }), z.strictObject({ name: z.literal("instance:view-host:snapshots-changed"), detail: instanceViewHostSnapshotsChangedDetailSchema, }), z.strictObject({ name: z.literal("instance:open:requested"), detail: openRequestedDetailSchema, }), z.strictObject({ name: z.literal("instance:handle:invalidated"), detail: handleInvalidatedDetailSchema, }), z.strictObject({ name: z.literal("instance:command:unsupported"), detail: unsupportedCommandDetailSchema, }), z.strictObject({ name: z.literal("instance:error"), detail: instanceErrorDetailSchema, }), z.strictObject({ name: z.literal("instance:app-event"), detail: appEventDetailSchema, }), z.strictObject({ name: z.literal("loader:error"), detail: loaderErrorDetailSchema, }), ]); export const webViewTransportWebMessageSchema = z.discriminatedUnion("kind", [ z.strictObject({ kind: z.literal("ready"), instanceId: z.optional(nonEmptyStringSchema), }), z.strictObject({ kind: z.literal("hostEvent"), event: webViewTransportHostEventSchema, }), z.strictObject({ kind: z.literal("error"), error: commandErrorSchema, }), ]); export type WebViewCommandEnvelope = z.output< typeof webViewCommandEnvelopeSchema >; export type WebViewTransportNativeMessage = z.output< typeof webViewTransportNativeMessageSchema >; export type WebViewTransportHostEvent = z.output< typeof webViewTransportHostEventSchema >; export type WebViewTransportWebMessage = z.output< typeof webViewTransportWebMessageSchema >; export function parseWebViewTransportNativeMessage( input: unknown, ): WebViewTransportNativeMessage { return webViewTransportNativeMessageSchema.parse(input); } export function parseWebViewTransportWebMessage( input: unknown, ): WebViewTransportWebMessage { return webViewTransportWebMessageSchema.parse(input); }