import { z } from 'zod'; export const AssetBase = z.object({ assetId: z.string().uuid().optional(), configuration: z.any().optional() }); // Definition for Catalog 2.0 export const NewCatalogBase = z.object({ itemId: z.string().uuid().optional(), variantId: z.string().optional(), variant: z.any().optional(), variantConfiguration: z.any().optional() }); // Defining it as a separate object to make it easier to merge with specific event types. export const CallToActionBase = z.object({ callToAction: z.boolean().optional() }); export const SessionEventBase = z .object({ orgId: z.string().uuid(), userId: z.string().uuid().optional(), sessionId: z.string().uuid(), componentId: z.string().uuid().optional(), stageName: z.string().optional(), clientUserId: z.string().uuid().optional(), clientTime: z.string().optional(), pageLoadOffset: z.number().optional(), metadata: z.any().optional(), eventVersion: z.string().optional(), pageUrl: z.string().optional(), platformHost: z.string().optional(), experienceName: z.string().optional(), experienceVersion: z.string().optional() }) .merge(AssetBase) .merge(NewCatalogBase); export enum SessionEventType { Start = 'start', End = 'end', Load = 'load', Error = 'error', Change = 'change', Query = 'query', ImageUpload = 'image_upload', PersonalizeText = 'personalize_text', ParametricValue = 'parametric_value', Stage = 'stage', ChatPrompt = 'chat_prompt', ChatResponse = 'chat_response', RephrasePrompt = 'rephrase_prompt', ConfidenceMessagePrompt = 'confidence_message_prompt', VisualInteraction = 'visual_interaction', OptionsShow = 'options_show', OptionInteraction = 'option_interaction', AR = 'ar', Custom = 'custom', Share = 'share', AddToCart = 'add_to_cart', Purchase = 'purchase', Quote = 'quote', Lead = 'lead' } export enum PlayerType { Classic3D = 'classic-3d', Classic2D = 'classic-2d', ARify = 'arify', ComposableR3F = 'composable-r3f', ComposableAR = 'composable-ar', ComposableConfigurator = 'composable-configurator', FastCompositor = 'fast-compositor', Discovery = 'discovery' } export const LoadEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.Load), loadDuration: z.number(), downloadDuration: z.number().optional(), parseDuration: z.number().optional(), loadType: z.string().optional(), prefetch: z.boolean().optional().default(false), loadOptions: z.string().optional(), // like user agent, it will vary based on the type of component loaded, and will have to be parsed on the server side to pull out useful data. downloadSize: z.number().optional(), partialLoad: z.boolean().optional(), playerType: z.nativeEnum(PlayerType).optional() }) ); export type LoadEvent = z.infer; export const ChangeEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.Change), changeDuration: z.number().optional(), downloadSize: z.number().optional() }) ); export type ChangeEvent = z.infer; export const ImageUploadEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.ImageUpload), imageUploadId: z.string(), imageUploadFileId: z.string().uuid() }) ); export type ImageUploadEvent = z.infer; export const PersonalizeTextEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.PersonalizeText), personalizeId: z.string(), personalizedText: z.string() }) ); export type PersonalizeTextEvent = z.infer; export const ParametricValueEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.ParametricValue), parametricId: z.string(), parametricValue: z.number() }) ); export type ParametricValueEvent = z.infer; export const ErrorEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.Error), errorType: z.string(), errorMessage: z.string().optional(), errorStack: z.string().optional(), errorDetails: z.string().optional() // use errorToString() to populate this field. }) ); export type ErrorEvent = z.infer; export const QueryEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.Query), queryName: z.string(), query: z.record(z.string(), z.string()) }) ); export type QueryEvent = z.infer; export enum VisualInteractionType { Zoom = 'zoom', Rotate = 'rotate', Pan = 'pan', Hover = 'hover', Click = 'click' } export const Vector3 = z.object({ x: z.number(), y: z.number(), z: z.number() }); export const VisualInteractionEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.VisualInteraction), visualInteractionType: z.nativeEnum(VisualInteractionType), zoomFactor: z.number().optional(), targetAssetId: z.string().uuid().optional(), targetName: z.string().optional(), targetRotation: Vector3.optional(), targetPosition: Vector3.optional(), cameraPosition: Vector3.optional(), cameraTarget: Vector3.optional() }) ); export type VisualInteractionEvent = z.infer; export const ChatPromptEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.ChatPrompt), promptId: z.string().optional(), promptText: z.string().optional() }) ); export type ChatPromptEvent = z.infer; export const ChatResponseEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.ChatResponse), promptId: z.string(), promptResponseText: z.string().optional() }) ); export type ChatResponseEvent = z.infer; export const RephrasePromptEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.RephrasePrompt), promptId: z.string(), promptText: z.string(), promptVersion: z.number(), userPrompt: z.string(), rephrasedPrompt: z.string() }) ); export type RephrasePromptEvent = z.infer; export const ConfidenceMessagePromptEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.ConfidenceMessagePrompt), promptId: z.string(), promptText: z.string(), promptVersion: z.number(), vectorizedString: z.string(), confidenceMessage: z.string() }) ); export type ConfidenceMessagePromptEvent = z.infer< typeof ConfidenceMessagePromptEvent >; export const StageEvent = SessionEventBase.merge( z.object({ eventType: z.literal(SessionEventType.Stage), previousStageName: z.string().optional(), previousStageDuration2: z.number().optional() // 2 because the initial previousStageDuration was incorrectly in milliseconds. }) ); export type StageEvent = z.infer; export enum OptionsType { Value = 'value', Asset = 'asset', Quiz = 'quiz', Variant = 'variant' } const OptionBase = z.object({ optionId: z.string(), optionPrice: z.number().optional() }); const ValueOption = OptionBase.merge( z.object({ optionName: z.string().optional(), optionValue: z.string() }) ); export type ValueOption = z.infer; const AssetOption = OptionBase.merge( z.object({ assetId: z.string().uuid(), configuration: z.any().optional() }) ); export type AssetOption = z.infer; const QuizOption = OptionBase.merge( z.object({ optionName: z.string().optional(), optionText: z.string().optional(), optionMetadata: z.any().optional() }) ); export type QuizOption = z.infer; const VariantOption = OptionBase.merge( z.object({ itemId: z.string().uuid(), variant: z.any(), // Currently variants can be hard deleted so we can't enforce a schema here. Which is why we're using z.any() index: z.number() // The position of the variant in the list of results }) ); export type VariantOption = z.infer; const Option = z.union([ValueOption, AssetOption, QuizOption, VariantOption]); const Context = z.record( z.string(), z.union([z.string(), z.number(), z.boolean()]) ); export type Context = z.infer; export const OptionsShowEvent = SessionEventBase.merge(AssetBase).merge( z.object({ eventType: z.literal(SessionEventType.OptionsShow), optionsType: z.nativeEnum(OptionsType), attributePath: z.string().optional(), // if this option display represents an attribute, this will be the path to the attribute options: z.array(Option), optionsSetId: z.string(), optionsSetName: z.string().optional(), defaultOptionId: z.string().optional(), context: Context.optional() }) ); export type OptionsShowEvent = z.infer; export enum OptionInteractionType { View = 'view', Hover = 'hover', Select = 'select' } export const OptionInteractionEvent = SessionEventBase.merge(AssetBase).merge( z.object({ eventType: z.literal(SessionEventType.OptionInteraction), optionsSetId: z.string().optional(), optionsSetDuration: z.number().optional(), optionId: z.string(), interactionType: z.nativeEnum(OptionInteractionType), context: Context.optional() }) ); export type OptionInteractionEvent = z.infer; export const CustomEvent = SessionEventBase.merge(AssetBase).merge( z.object({ eventType: z.literal(SessionEventType.Custom), customName: z.string(), customParameters: z.any().optional() }) ); export type CustomEvent = z.infer; export enum ShareType { Share = 'share', View = 'view' } export const ShareEvent = SessionEventBase.merge(AssetBase).merge( z.object({ eventType: z.literal(SessionEventType.Share), configurationId: z.string().optional(), orderId: z.string().optional(), shareType: z.nativeEnum(ShareType), sharePlatform: z.string().optional(), shareLink: z.string().optional() }) ); export type ShareEvent = z.infer; export const AddToCartEvent = SessionEventBase.merge( z .object({ eventType: z.literal(SessionEventType.AddToCart), configurationId: z.string().uuid().optional(), orderId: z.string().uuid().optional(), orderCustomId: z.string().optional(), orderDetails: z.any().optional(), orderPrice: z.number().optional(), itemName: z.string().optional(), itemCustomId: z.string().optional(), itemPrice: z.number().optional(), itemCount: z.number().optional(), cartCustomId: z.string().optional(), customerId: z.string().uuid().optional(), customerCustomId: z.string().optional(), customerDetails: z.any().optional() }) .merge(CallToActionBase) ); export type AddToCartEvent = z.infer; export const AnalyticsCartItem = z.object({ assetId: z.string().uuid().optional(), configuration: z.any().optional(), itemName: z.string().optional(), itemCustomId: z.string().optional(), itemPrice: z.number().optional(), itemCount: z.number() }); export type AnalyticsCartItem = z.infer; export const PurchaseEvent = SessionEventBase.merge( z .object({ eventType: z.literal(SessionEventType.Purchase), configurationId: z.string().uuid().optional(), purchaseCustomId: z.string().optional(), orderId: z.string().uuid().optional(), orderCustomId: z.string().optional(), orderDetails: z.any().optional(), orderPrice: z.number().optional(), cart: z.array(AnalyticsCartItem).optional(), customerId: z.string().uuid().optional(), customerCustomId: z.string().optional(), customerDetails: z.any().optional() }) .merge(CallToActionBase) ); export type PurchaseEvent = z.infer; export const QuoteEvent = SessionEventBase.merge( z .object({ eventType: z.literal(SessionEventType.Quote), configurationId: z.string().uuid().optional(), orderId: z.string().uuid().optional(), orderCustomId: z.string().optional(), orderDetails: z.any().optional(), orderPrice: z.number().optional(), cart: z.array(AnalyticsCartItem).optional(), quoteCustomId: z.string().optional(), customerId: z.string().uuid().optional(), customerCustomId: z.string().optional(), customerDetails: z.any().optional() }) .merge(CallToActionBase) ); export type QuoteEvent = z.infer; export const LeadEvent = SessionEventBase.merge( z .object({ eventType: z.literal(SessionEventType.Lead), configurationId: z.string().uuid().optional(), orderId: z.string().uuid().optional(), orderCustomId: z.string().optional(), orderDetails: z.any().optional(), orderPrice: z.number().optional(), leadCustomId: z.string().optional(), customerId: z.string().uuid().optional(), customerCustomId: z.string().optional(), customerDetails: z.any().optional() }) .merge(CallToActionBase) ); export type LeadEvent = z.infer; export enum ARStage { Offered = 'offered', Handoff = 'handoff', Launch = 'launch' } export const AREvent = SessionEventBase.merge(AssetBase).merge( z.object({ eventType: z.literal(SessionEventType.AR), arStage: z.nativeEnum(ARStage), arHandoffId: z.string().optional() }) ); export type AREvent = z.infer; export const sessionEventTypeToEventZod = { [SessionEventType.Load]: LoadEvent, [SessionEventType.Error]: ErrorEvent, [SessionEventType.Query]: QueryEvent, [SessionEventType.ImageUpload]: ImageUploadEvent, [SessionEventType.PersonalizeText]: PersonalizeTextEvent, [SessionEventType.ParametricValue]: ParametricValueEvent, [SessionEventType.VisualInteraction]: VisualInteractionEvent, [SessionEventType.Change]: ChangeEvent, [SessionEventType.ChatPrompt]: ChatPromptEvent, [SessionEventType.ChatResponse]: ChatResponseEvent, [SessionEventType.RephrasePrompt]: RephrasePromptEvent, [SessionEventType.ConfidenceMessagePrompt]: ConfidenceMessagePromptEvent, [SessionEventType.Stage]: StageEvent, [SessionEventType.OptionsShow]: OptionsShowEvent, [SessionEventType.OptionInteraction]: OptionInteractionEvent, [SessionEventType.Custom]: CustomEvent, [SessionEventType.Share]: ShareEvent, [SessionEventType.AddToCart]: AddToCartEvent, [SessionEventType.Purchase]: PurchaseEvent, [SessionEventType.Quote]: QuoteEvent, [SessionEventType.Lead]: LeadEvent, [SessionEventType.AR]: AREvent }; const SessionEvent = z.union([ LoadEvent, ErrorEvent, QueryEvent, ImageUploadEvent, PersonalizeTextEvent, ParametricValueEvent, VisualInteractionEvent, ChangeEvent, ChatPromptEvent, ChatResponseEvent, RephrasePromptEvent, ConfidenceMessagePromptEvent, StageEvent, OptionsShowEvent, OptionInteractionEvent, CustomEvent, ShareEvent, AddToCartEvent, PurchaseEvent, QuoteEvent, LeadEvent, AREvent ]); export type SessionEvent = z.infer;