UNPKG

@habit.analytics/habit-smartlink-reactcomponent

Version:

A React component for Habit SmartLink integration.

263 lines (213 loc) 9 kB
/** * @file types.ts * @description Shared type system for iframe ↔ parent postMessage communication. * * Direction is documented per message type as: child → parent | parent → child | both */ /*================================= SMARTLINK MESSAGE MAP ==================================*/ export type SmartlinkMessageMap = { /** parent → child. Signals the smartlink to start. Sent in response to SMARTLINK_READY. */ SMARTLINK_INIT: undefined; /** child → parent. Signals the child is mounted and ready to receive messages. */ SMARTLINK_READY: undefined; /** child → parent. Signals parent to start the prepayment method flow. */ SMARTLINK_PREPAYMENT_METHOD: { quote_id: string }; /** parent → child. Signals the parent has completed the prepayment method flow. */ SMARTLINK_PREPAYMENT_METHOD_COMPLETE: { success: boolean; payment_id: string; error?: string; }; /** child → parent. Signals the current step completed successfully. */ SMARTLINK_STEP_COMPLETE: { success?: boolean; source?: "payments" | "uploader" | "consent" | string; payment_data?: string; consent_data?: string; uploader_data?: string; }; /** child → parent. Signals the user cancelled or closed the flow. */ SMARTLINK_CANCELLED: undefined; /** child → parent. Reports an error to the parent. */ SMARTLINK_ERROR: { details?: unknown; message?: string }; /** child → parent. Requests the parent to resize the iframe element. */ SMARTLINK_RESIZE: { height: number; width?: number }; }; /*======================================= BASE TYPES =======================================*/ export type AnyMessageMap = SmartlinkMessageMap; export type SmartlinkMessage< K extends keyof SmartlinkMessageMap = keyof SmartlinkMessageMap, > = { type: K; payload?: SmartlinkMessageMap[K]; requestId?: string; }; export type AnyIframeMessage = SmartlinkMessage; /*================================ REQUEST → RESPONSE MAP ==================================*/ /** * Maps child → parent request types to the expected parent → child response type. * Only message types with a known response are included — these are the only types * accepted by the `request` function on SmartlinkChildAPI. */ export type SmartlinkRequestResponseMap = { SMARTLINK_READY: SmartlinkMessage<"SMARTLINK_INIT">; SMARTLINK_PREPAYMENT_METHOD: SmartlinkMessage<"SMARTLINK_PREPAYMENT_METHOD_COMPLETE">; }; /*==================================== REQUEST ID HELPER ===================================*/ /** * Generates a unique requestId for correlating request/response message pairs. * Used internally by useSmartlinkMessaging — not typically called directly. */ export const createRequestId = (): string => `req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; /*===================================== FACTORY HELPER =====================================*/ export function createSmartlinkMessage<K extends keyof SmartlinkMessageMap>( type: K, payload?: SmartlinkMessageMap[K], requestId?: string, ): SmartlinkMessage<K> { return { type, ...(payload !== undefined ? { payload } : {}), ...(requestId !== undefined ? { requestId } : {}), }; } /*================================== MESSAGE REQUEST TYPES =================================*/ /** * Internal entry stored per in-flight request. * Keyed by requestId in the pending map inside useSmartlinkMessaging. */ export interface PendingRequest<TResponse> { resolve: (value: TResponse) => void; reject: (reason: Error) => void; timer: ReturnType<typeof setTimeout>; } /** Options passed to the `request` function. */ export interface MessageRequestOptions { /** * How long to wait for a response before rejecting. * Defaults to 5000ms. */ timeout?: number; } /*======================================= SUBSCRIBER =======================================*/ /** * isValid — true if the message passed origin check + selfcare revalidation. * False means the parent would have silently dropped it internally; it is exposed * here so the consumer can decide whether to act or log. */ export type SmartlinkSubscriber = ( message: SmartlinkMessage, event: MessageEvent<SmartlinkMessage>, isValid: boolean, ) => void; /*================================= PARENT HOOK OPTIONS ====================================*/ interface BaseIframeMessagingOptions { /** The full URL of the iframe src. Used to derive and validate the expected origin. */ pluginUrl: string; /** Ref to the iframe DOM element. Used for postMessage and resize. */ iframeRef: React.RefObject<HTMLIFrameElement>; /** * Called when the child requests a resize. * Receives the dimensions the child requested. Return overridden dimensions to clamp, * or void to apply the requested values as-is. */ onResize?: (requested: { height: number; width?: number; }) => { height: number; width?: number } | void; } export interface UseSelfcareParentMessagingOptions extends BaseIframeMessagingOptions { /** * Plugin ID used to look up and validate the plugin in the selfcare route registry. * Required for revalidation — without it all messages will fail the security check. */ id: string; } export interface UseSmartlinkParentMessagingOptions extends BaseIframeMessagingOptions {} /*================================ PARENT HOOK API — SMARTLINK =============================*/ export interface SmartlinkParentAPI { /** Send any smartlink-namespaced message to the child. */ sendMessage: (message: SmartlinkMessage) => void; /** Send SMARTLINK_INIT — signals the smartlink to start after SMARTLINK_READY. */ sendInit: (requestId?: string) => void; /** * Subscribe to all incoming smartlink messages from the child. * isValid indicates whether the message passed the origin check. * Returns unsubscribe fn. */ subscribe: (handler: SmartlinkSubscriber) => () => void; /** * Subscribe to a specific smartlink message type. * isValid is passed as the third argument to the handler. * Returns unsubscribe fn. */ subscribeTo: <K extends keyof SmartlinkMessageMap>( type: K, handler: ( message: SmartlinkMessage<K>, event: MessageEvent<SmartlinkMessage>, isValid: boolean, ) => void, ) => () => void; } /*================================ CHILD HOOK OPTIONS — SMARTLINK ==========================*/ export interface UseSmartlinkMessagingOptions { /** Called on every incoming message before subscribers are notified. */ onMessage?: (message: SmartlinkMessage) => void; /** * Origin to target when sending postMessage to the parent. * Defaults to '*'. Set to the known parent origin in production. */ targetOrigin?: string; /** * Origins from which to accept incoming messages. * Defaults to '*'. Pass an array to restrict. */ acceptOrigins?: "*" | string[]; } /*=============================== CHILD HOOK API — SMARTLINK ===============================*/ export interface SmartlinkChildAPI { /** Send a raw smartlink message to the parent. */ sendMessage: (message: SmartlinkMessage) => void; /** Send SMARTLINK_READY — call once on mount to trigger the init handshake. */ notifyReady: () => void; /** Send SMARTLINK_STEP_COMPLETE — signal the current step finished. */ completeStep: ( result?: SmartlinkMessageMap["SMARTLINK_STEP_COMPLETE"] | undefined, ) => void; /** Send SMARTLINK_CANCELLED — signal the user cancelled the flow. */ cancel: () => void; /** Send SMARTLINK_RESIZE — ask the parent to resize the iframe. */ resize: (height: number, width?: number) => void; /** * Send a typed request and await the correlated response. * Only message types present in SmartlinkRequestResponseMap are accepted. * Rejects if no response arrives within the timeout (default 5000ms). */ request: <K extends keyof SmartlinkRequestResponseMap>( type: K, payload?: SmartlinkMessageMap[K], options?: MessageRequestOptions, ) => Promise<SmartlinkRequestResponseMap[K]>; /** * Send SMARTLINK_PREPAYMENT_METHOD and await SMARTLINK_PREPAYMENT_METHOD_COMPLETE. * Resolves with the full response message including success and payment_id. */ requestPrePaymentMethod: ( quoteId: string, options?: MessageRequestOptions, ) => Promise<SmartlinkMessage<"SMARTLINK_PREPAYMENT_METHOD_COMPLETE">>; /** Subscribe to all incoming messages from the parent. Returns unsubscribe fn. */ subscribe: (handler: SmartlinkSubscriber) => () => void; /** Subscribe to a specific message type from the parent. Returns unsubscribe fn. */ subscribeTo: <K extends keyof SmartlinkMessageMap>( type: K, handler: ( message: SmartlinkMessage<K>, event: MessageEvent<SmartlinkMessage>, ) => void, ) => () => void; /** True once notifyReady() has been called. Reactive — triggers re-render. */ isReady: boolean; }