UNPKG

@xapp/chat-widget

Version:
398 lines (397 loc) 14.1 kB
import 'rollup-plugin-inject-process-env'; import { WidgetEnv } from "@xapp/stentor-chat-widget"; import { FC } from "react"; import { MessageMiddleware } from "../../middlewares"; import { CONNECTION_STATUS_TYPE } from "../../store/ChatAction"; import "./ChatWidget.scss"; /** * Preview message structure for preview mode. * Allows rendering sample messages with suggestions and lists. * * **Important:** At least one of `displayText` or `html` should be provided for * the message to render content. Messages without both fields will render empty. */ export interface PreviewMessage { /** * The main text to display in the message bubble. * If neither displayText nor html is provided, the message will render empty. */ readonly displayText?: string; /** * HTML content to render in the message bubble. * Takes precedence over displayText if both are provided. * * **Security Warning:** This field renders raw HTML without sanitization. * Only use with trusted content. If preview messages may come from untrusted * sources (user input, external APIs), sanitize the HTML using a library * like DOMPurify before passing it to this field. * * @example * ```tsx * // Safe: hardcoded content * { html: "<strong>Welcome!</strong>" } * * // Unsafe without sanitization: external content * // import DOMPurify from 'dompurify'; * // { html: DOMPurify.sanitize(untrustedContent) } * ``` */ readonly html?: string; /** * Suggestion chips to display below the message. * Suggestions without URLs will use "#" as a fallback to prevent navigation. */ readonly suggestions?: ReadonlyArray<{ readonly title: string; /** * URL to navigate to when the suggestion is clicked. * Defaults to "#" if not provided. */ readonly url?: string; }>; /** * List or carousel to display with the message. * Note: The `description` field maps to `subTitle` in the internal representation. */ readonly list?: { readonly type: "LIST" | "CAROUSEL"; readonly title?: string; readonly items: ReadonlyArray<{ readonly title: string; /** * Description text shown below the title. * Internally mapped to `subTitle` in ChatServerListItem. */ readonly description?: string; readonly url?: string; readonly imageUrl?: string; /** * When true, hides the URL text and external link icon while * still allowing the item to be clickable. */ readonly hideUrl?: boolean; }>; }; } /** * Props for the ChatWidget component. * * This interface defines all available configuration options for the chat widget, * including display modes, configuration loading strategies, and event callbacks. */ export interface ChatWidgetProps { /** * Enable pre-chat form before starting the conversation. * When enabled, users must fill out a form (name, email, etc.) before accessing chat. * * @default false * @example * ```tsx * <ChatWidget preChatFormEnabled={true} config={myConfig} /> * ``` */ readonly preChatFormEnabled?: boolean; /** * Widget environment configuration containing server connection details, theme, and behavior settings. * * This is the primary configuration object that defines how the widget connects to your backend, * how it appears visually, and how it behaves. Can be provided directly or via the `getConfig` callback. * * **Note:** If both `config` and `getConfig` are provided, `getConfig` takes precedence. * * @see WidgetEnv for the full configuration schema * @example * ```tsx * const config: WidgetEnv = { * connection: { * serverUrl: 'wss://chat.example.com', * type: 'websocketraw' * }, * channelId: 'support-channel', * theme: { * header: { background: { color: '#007AFF' } } * } * }; * * <ChatWidget config={config} /> * ``` */ readonly config?: WidgetEnv; /** * Display mode for the widget. * * - **`'normal'`** (default): Floating chat button that expands into a chat window. * User can minimize/maximize. Best for desktop websites. * * - **`'docked'`**: Always visible, docked to the side or bottom of the screen. * Cannot be minimized. Best for mobile apps and WebViews. * * - **`'static'`**: Embedded inline in the page content, takes up fixed space. * Cannot be minimized or moved. Best for dedicated chat pages. * * @default 'normal' * @example * ```tsx * // Docked mode for mobile * <ChatWidget config={config} mode="docked" /> * * // Static mode for dedicated chat page * <ChatWidget config={config} mode="static" /> * ``` */ readonly mode?: ChatWidgetMode; /** * Enable preview mode for displaying static sample messages without server connections. * * When `true`, the widget renders in a read-only state with: * - No server connection attempts * - Static messages from `previewMessages` prop * - Disabled input and interactions * * Can be combined with any `mode` for different visual layouts: * - `mode="docked" preview` - Docked layout with preview data * - `mode="static" preview` - Static layout with preview data * - `mode="normal" preview` - Floating layout with preview data * * @default false * @example * ```tsx * // Docked preview for settings page * <ChatWidget * mode="docked" * preview * config={themeConfig} * previewMessages={sampleMessages} * /> * ``` */ readonly preview?: boolean; /** * Custom middleware for processing and transforming chat messages before rendering. * * Middleware allows you to intercept messages and render custom components for specific * message types or modify message content. Useful for adding custom UI elements, * formatting, or handling special message types. * * @see MessageMiddleware * @example * ```tsx * const customMiddleware = (next) => (props) => { * const { msg } = props; * * // Handle custom message type * if (msg.type === 'SPECIAL_CARD') { * return <CustomCard data={msg.data} />; * } * * // Pass to next middleware * return next(props); * }; * * <ChatWidget * config={config} * messageMiddleware={customMiddleware} * /> * ``` */ readonly messageMiddleware?: MessageMiddleware; /** * Callback fired when the connection status changes. * * Use this to monitor the widget's connection state and display custom UI or * handle connection issues. Connection status changes throughout the widget lifecycle * as it connects, disconnects, reconnects, or encounters errors. * * @param status - Current connection status * - `'online'`: Successfully connected to server * - `'offline'`: Disconnected from server * - `'pending'`: Connecting or reconnecting (loading state) * * @example * ```tsx * const [status, setStatus] = useState('pending'); * * <ChatWidget * config={config} * onConnectionStatusChange={(newStatus) => { * setStatus(newStatus); * if (newStatus === 'offline') { * alert('Chat is currently unavailable'); * } * }} * /> * ``` */ onConnectionStatusChange?(status: CONNECTION_STATUS_TYPE): void; /** * Optional async callback to load configuration dynamically. * * When provided, the widget will wait for this Promise to resolve before initializing. * This prevents race conditions where the widget attempts to connect before configuration * is available. Particularly useful for: * * - **React Native apps**: Where props may propagate with delays across the native bridge * - **API-based config**: When configuration needs to be fetched from a server * - **Dynamic config**: When config depends on user authentication or other async operations * - **Native modules**: When waiting for React Native native modules to initialize * * While the Promise is pending, the widget displays a loading spinner. If the Promise * rejects, an error message is shown to the user. * * **Important:** If both `config` and `getConfig` are provided, `getConfig` takes precedence * and the `config` prop is ignored. * * @returns Promise that resolves to the WidgetEnv configuration * @throws Error if configuration cannot be loaded (will be displayed to user) * * @example * ```tsx * // Basic usage - fetch from API * <ChatWidget * getConfig={async () => { * const response = await fetch('/api/chat-config'); * return response.json(); * }} * /> * ``` * * @example * ```tsx * // React Native - wait for native module * import { NativeModules } from 'react-native'; * * <ChatWidget * getConfig={async () => { * await NativeModules.ChatModule.initialize(); * return await NativeModules.ChatModule.getConfig(); * }} * /> * ``` * * @example * ```tsx * // Wait for authentication with error handling * <ChatWidget * getConfig={async () => { * // Wait for auth * while (!window.auth?.user) { * await new Promise(resolve => setTimeout(resolve, 100)); * } * * // Fetch config with user context * try { * const config = await fetchConfigForUser(window.auth.user); * return config; * } catch (error) { * throw new Error('Failed to load chat configuration. Please try again.'); * } * }} * /> * ``` * * @example * ```tsx * // With timeout protection * <ChatWidget * getConfig={async () => { * const timeout = new Promise((_, reject) => * setTimeout(() => reject(new Error('Config load timeout')), 10000) * ); * * const configPromise = fetch('/api/config').then(r => r.json()); * * return Promise.race([configPromise, timeout]); * }} * /> * ``` */ getConfig?(): Promise<WidgetEnv>; /** * Disable automatic scrolling behavior when messages change. * * When enabled, prevents the widget from scrolling to the bottom when new messages * arrive or when the component mounts. This is particularly useful for: * * - **Embedded preview contexts**: When the widget is used as a static preview in settings pages * - **Multi-widget pages**: When multiple widgets are on the same page * - **Custom scroll control**: When you want to manage scrolling behavior externally * * @default false * @example * ```tsx * // Use in an embedded preview * <StaticMessagesChatWidgetContainer * messages={previewMessages} * config={config} * disableAutoScroll={true} * /> * ``` */ readonly disableAutoScroll?: boolean; /** * Preview messages to display when mode="preview". * * Array of sample messages to show in preview mode. Each message can include * text, HTML, suggestion chips, and lists. Only applies when mode is set to "preview". * * @example * ```tsx * <ChatWidget * mode="preview" * config={widgetConfig} * previewMessages={[ * { * displayText: "Hello! How can I help you today?", * suggestions: [ * { title: "Get a Quote" }, * { title: "Schedule Service", url: "#schedule" } * ], * list: { * type: "LIST", * items: [ * { title: "Our Services", description: "View all services", url: "#services" }, * { title: "Contact Us", description: "Get in touch", url: "#contact" } * ] * } * } * ]} * /> * ``` */ readonly previewMessages?: readonly PreviewMessage[]; } /** * Props for ChatWidgetWrapper component. * Extends ChatWidgetProps with the same interface. */ export interface ChatWidgetWrapperProps extends ChatWidgetProps { } /** * Display modes for the chat widget (visual layout). * * - **`'normal'`**: Default floating button mode - chat button appears as a floating action button, * clicking it opens the chat window. User can minimize/maximize the window. * * - **`'docked'`**: Always-visible docked mode - widget is permanently visible and docked to the * side or bottom of the screen. Cannot be minimized. Ideal for mobile apps and WebViews. * * - **`'static'`**: Static embedded mode - widget is embedded inline in the page content as a * fixed element. Cannot be minimized or repositioned. Ideal for dedicated chat pages. * * - **`'preview'`**: (Deprecated) Shorthand for `mode="static" preview={true}`. * Prefer using the `preview` prop with any mode for clearer intent. * * @see {@link ChatWidgetProps.preview} for enabling preview mode with any layout. */ export type ChatWidgetMode = "normal" | "docked" | "static" | "preview"; /** * Inner ChatWidget component that renders the actual UI. * Exported for use by StaticChatWidgetContainer to avoid infinite loops. */ export declare const ChatWidgetUI: FC<ChatWidgetProps>; /** * Top-level wrapper that dispatches between preview mode and connected mode. * This separation ensures React hooks rules are not violated. */ declare const ChatWidgetWrapper: FC<ChatWidgetWrapperProps>; export default ChatWidgetWrapper;