UNPKG

@botonic/react

Version:

Build Chatbots using React

405 lines 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebchatApp = void 0; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const core_1 = require("@botonic/core"); const lodash_merge_1 = tslib_1.__importDefault(require("lodash.merge")); const react_1 = require("react"); const client_1 = require("react-dom/client"); const constants_1 = require("./constants"); const index_types_1 = require("./index-types"); const msg_to_botonic_1 = require("./msg-to-botonic"); const dom_1 = require("./util/dom"); const default_theme_1 = require("./webchat/theme/default-theme"); const webchat_1 = require("./webchat/webchat"); class WebchatApp { constructor({ theme, persistentMenu, coverComponent, blockInputs, enableEmojiPicker, enableAttachments, enableUserInput, enableAnimations, hostId = constants_1.WEBCHAT.DEFAULTS.HOST_ID, shadowDOM = false, defaultDelay, defaultTyping, storage, storageKey, onInit, onOpen, onClose, onMessage, onTrackEvent, onConnectionChange, appId, visibility, server, previewUtils, }) { this.reactRoot = null; this.host = null; this.theme = theme; this.persistentMenu = persistentMenu; this.coverComponent = coverComponent; this.blockInputs = blockInputs; this.enableEmojiPicker = enableEmojiPicker; this.enableAttachments = enableAttachments; this.enableUserInput = enableUserInput; this.enableAnimations = enableAnimations; this.shadowDOM = Boolean(typeof shadowDOM === 'function' ? shadowDOM() : shadowDOM); if (this.shadowDOM && !(0, dom_1.isShadowDOMSupported)()) { console.warn('[botonic] ShadowDOM not supported on this browser'); this.shadowDOM = false; } this.hostId = hostId || constants_1.WEBCHAT.DEFAULTS.HOST_ID; this.defaultDelay = defaultDelay; this.defaultTyping = defaultTyping; this.storage = storage === undefined ? localStorage : storage; this.storageKey = storageKey || constants_1.WEBCHAT.DEFAULTS.STORAGE_KEY; this.onInit = onInit; this.onOpen = onOpen; this.onClose = onClose; this.onMessage = onMessage; this.onTrackEvent = onTrackEvent; this.onConnectionChange = onConnectionChange; this.visibility = visibility; this.server = server; this.previewUtils = previewUtils; this.webchatRef = (0, react_1.createRef)(); this.appId = appId; this.host = null; this.reactRoot = null; } // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: createRootElement is a complex function that creates a root element for the webchat createRootElement(host) { // Create root element <div id='root'> if not exists // Create shadowDOM to root element if needed if (host) { if (host.id && this.hostId) { if (host.id !== this.hostId) { console.warn(`[botonic] Host ID "${host.id}" don't match 'hostId' option: ${this.hostId}. Using value: ${host.id}.`); this.hostId = host.id; } } else if (host.id) { this.hostId = host.id; } else if (this.hostId) { host.id = this.hostId; } } else { host = this.hostId ? document.getElementById(this.hostId) : null; } if (!host) { host = document.createElement('div'); host.id = this.hostId; if (document.body.firstChild) { document.body.insertBefore(host, document.body.firstChild); } else { document.body.appendChild(host); } } this.host = this.shadowDOM ? host.attachShadow({ mode: 'open' }) : host; } getReactMountNode(node) { if (!node) { node = this.host; } if (node === null) { throw new Error('Host element not found'); } // TODO: Review logic of ShadowRoot if ('shadowRoot' in node && node.shadowRoot !== null) { return node.shadowRoot; } return node; } onInitWebchat(...args) { this.onInit?.(this, ...args); } onOpenWebchat(...args) { this.onOpen?.(this, ...args); } onCloseWebchat(...args) { this.onClose?.(this, ...args); } async onUserInput({ user, input }) { if (!user) { return; } this.onMessage?.(this, { ...input, sentBy: index_types_1.SENDERS.user, isUnread: false, }); this.hubtypeService.postMessage(user, { ...input, // TODO: Review if this is correct add sent_by or this is added in backend // sent_by: 'message_sent_by_user', }); return; } async onTrackEventWebchat(request, eventName, args) { if (this.onTrackEvent) { await this.onTrackEvent(request, eventName, args); } } async onConnectionRegained() { return this.hubtypeService.onConnectionRegained(); } onStateChange(args) { const { user, messagesJSON } = args; const lastMessage = messagesJSON[messagesJSON.length - 1]; const lastMessageId = lastMessage?.id; const lastMessageUpdateDate = this.getLastMessageUpdate(); if (this.hubtypeService) { this.hubtypeService.lastMessageId = lastMessageId; this.hubtypeService.lastMessageUpdateDate = lastMessageUpdateDate; } if (!this.hubtypeService) { this.hubtypeService = new core_1.HubtypeService({ appId: this.appId, user, lastMessageId, lastMessageUpdateDate, onEvent: (event) => this.onServiceEvent(event), unsentInputs: () => this.webchatRef.current ?.getMessages() .filter(msg => msg.ack === 0 && msg.unsentInput) || [], server: this.server, }); } } onServiceEvent(event) { if (event.action === 'connectionChange') { this.onConnectionChange?.(this, event.online); this.webchatRef.current?.setOnline(event.online); } else if (event.action === 'update_message_info' && event.message?.id) { this.updateMessageInfo(event.message.id, event.message); } else if (event.message?.type === 'update_webchat_settings') { this.updateWebchatSettings(event.message.data); } else if (event.message?.type === 'sender_action') { this.setTyping(event.message.data === index_types_1.Typing.On); } else { const isSystemMessage = event.message?.sent_by === index_types_1.SENDERS.system; this.onMessage?.(this, { sentBy: isSystemMessage ? index_types_1.SENDERS.system : index_types_1.SENDERS.bot, ...event.message, }); if (isSystemMessage) { this.addSystemMessage(event.message); } else { this.addBotMessage(event.message); } } } updateUser(user) { this.webchatRef.current?.updateUser(user); } setSystemLocale(locale) { this.webchatRef.current?.updateUser({ system_locale: locale }); } setUserLocale(locale) { this.webchatRef.current?.updateUser({ locale }); } setUserCountry(country) { this.webchatRef.current?.updateUser({ country }); } addBotMessage(message) { message.ack = 0; message.isUnread = true; message.sentBy = message.sent_by?.split('message_sent_by_')[1]; delete message.sent_by; const response = (0, msg_to_botonic_1.msgToBotonic)(message, this.theme?.message?.customTypes); this.webchatRef.current?.addBotResponse({ response, }); } addSystemMessage(message) { message.ack = 0; message.isUnread = true; message.sentBy = index_types_1.SENDERS.system; delete message.sent_by; const response = (0, msg_to_botonic_1.msgToBotonic)(message, this.theme?.message?.customTypes); this.webchatRef.current?.addSystemResponse({ response, }); } addBotText(text) { this.addBotMessage({ type: core_1.INPUT.TEXT, data: text }); } addUserMessage(message) { this.webchatRef.current?.addUserMessage(message); } addUserText(text) { this.addUserMessage({ type: core_1.INPUT.TEXT, data: text }); } addUserPayload(payload) { this.addUserMessage({ type: core_1.INPUT.POSTBACK, payload }); } setTyping(typing) { this.webchatRef.current?.setTyping(typing); } open() { this.webchatRef.current?.openWebchat(); } close() { this.webchatRef.current?.closeWebchat(); } async closeWebview(options) { await this.webchatRef.current?.closeWebview(options); } // TODO: Remove this function because we have open and close functions toggle() { this.webchatRef.current?.toggleWebchat(); } openCoverComponent() { this.webchatRef.current?.openCoverComponent(); } closeCoverComponent() { this.webchatRef.current?.closeCoverComponent(); } renderCustomComponent(_customComponent) { this.webchatRef.current?.renderCustomComponent(_customComponent); } unmountCustomComponent() { this.webchatRef.current?.unmountCustomComponent(); } // TODO: Remove this function because we have openCoverComponent and closeCoverComponent functions toggleCoverComponent() { this.webchatRef.current?.toggleCoverComponent(); } getMessages() { return this.webchatRef.current?.getMessages(); } clearMessages() { this.webchatRef.current?.clearMessages(); } async getVisibility() { return this.resolveWebchatVisibility({ appId: this.appId, visibility: this.visibility, }); } getLastMessageUpdate() { return this.webchatRef.current?.getLastMessageUpdate(); } updateMessageInfo(msgId, messageInfo) { return this.webchatRef.current?.updateMessageInfo(msgId, messageInfo); } updateWebchatSettings(settings) { return this.webchatRef.current?.updateWebchatSettings(settings); } createInitialTheme(optionsAtRuntime = {}) { const theme = (0, lodash_merge_1.default)(default_theme_1.defaultTheme, this.theme, optionsAtRuntime.theme); if (theme.animations === undefined) { theme.animations = {}; } theme.animations.enable = theme.animations.enable ?? optionsAtRuntime.enableAnimations ?? this.enableAnimations; theme.coverComponent = theme.coverComponent ?? optionsAtRuntime.coverComponent ?? this.coverComponent; theme.userInput = this.createInitialThemeUserInput(theme, optionsAtRuntime); return theme; } createInitialThemeUserInput(theme, optionsAtRuntime = {}) { if (!theme.userInput) { theme.userInput = {}; } // Set main userInput properties theme.userInput = { ...theme.userInput, enable: theme.userInput.enable ?? optionsAtRuntime.enableUserInput ?? this.enableUserInput, persistentMenu: theme.userInput.persistentMenu ?? optionsAtRuntime.persistentMenu ?? this.persistentMenu, blockInputs: theme.userInput.blockInputs ?? optionsAtRuntime.blockInputs ?? this.blockInputs, }; // Set emoji picker properties theme.userInput.emojiPicker = { ...(theme.userInput.emojiPicker || {}), enable: theme.userInput.emojiPicker?.enable ?? optionsAtRuntime.enableEmojiPicker ?? this.enableEmojiPicker, }; // Set attachments properties theme.userInput.attachments = { ...(theme.userInput.attachments || {}), enable: theme.userInput.attachments?.enable ?? optionsAtRuntime.enableAttachments ?? this.enableAttachments, }; return theme.userInput; } getComponent(host, optionsAtRuntime = {}) { let { theme = {}, defaultDelay, defaultTyping, storage, storageKey, onInit, onOpen, onClose, onMessage, onConnectionChange, onTrackEvent, appId, visibility, server, hostId, previewUtils, ...webchatOptions } = optionsAtRuntime; theme = this.createInitialTheme(optionsAtRuntime); defaultDelay = defaultDelay || this.defaultDelay; defaultTyping = defaultTyping || this.defaultTyping; server = server || this.server; this.storage = storage || this.storage; this.storageKey = storageKey || this.storageKey; this.onInit = onInit || this.onInit; this.onOpen = onOpen || this.onOpen; this.onClose = onClose || this.onClose; this.onMessage = onMessage || this.onMessage; this.onTrackEvent = onTrackEvent || this.onTrackEvent; this.onConnectionChange = onConnectionChange || this.onConnectionChange; this.visibility = visibility || this.visibility; this.appId = appId || this.appId; this.hostId = hostId || this.hostId; this.previewUtils = previewUtils || this.previewUtils; this.createRootElement(host); return ((0, jsx_runtime_1.jsx)(webchat_1.Webchat, { ...webchatOptions, ref: this.webchatRef, host: this.host, shadowDOM: this.shadowDOM, theme: theme, storage: this.storage, storageKey: this.storageKey, defaultDelay: defaultDelay, defaultTyping: defaultTyping, previewUtils: this.previewUtils, onInit: (...args) => this.onInitWebchat(...args), onOpen: (...args) => this.onOpenWebchat(...args), onClose: (...args) => this.onCloseWebchat(...args), onUserInput: (...args) => this.onUserInput(...args), onStateChange: (args) => { this.onStateChange(args); }, onTrackEvent: (request, eventName, args) => this.onTrackEventWebchat(request, eventName, args), server: server })); } async isWebchatVisible(appId) { try { const { status } = await core_1.HubtypeService.getWebchatVisibility(appId); return status === 200; } catch (_e) { return false; } } isOnline() { return this.webchatRef.current?.isOnline(); } async resolveWebchatVisibility(optionsAtRuntime) { if (!optionsAtRuntime) { // If optionsAtRuntime is not provided, always render the webchat return true; } let { appId, visibility } = optionsAtRuntime; visibility = visibility || this.visibility; if (visibility === undefined || visibility === true) { return true; } if (typeof visibility === 'function' && visibility()) { return true; } if (appId && visibility === 'dynamic' && (await this.isWebchatVisible(appId))) { return true; } return false; } destroy() { if (this.hubtypeService) { this.hubtypeService.destroyPusher(); } this.reactRoot?.unmount(); if (this.storage) { this.storage.removeItem(this.storageKey); } } async render(dest, optionsAtRuntime) { (0, dom_1.onDOMLoaded)(async () => { const isVisible = await this.resolveWebchatVisibility(optionsAtRuntime); if (isVisible) { const webchatComponent = this.getComponent(dest, optionsAtRuntime); const container = this.getReactMountNode(dest); this.reactRoot = (0, client_1.createRoot)(container); this.reactRoot.render(webchatComponent); } }); } } exports.WebchatApp = WebchatApp; //# sourceMappingURL=webchat-app.js.map