UNPKG

@botonic/react

Version:

Build Chatbots using React

391 lines 18.5 kB
import { __awaiter, __rest } from "tslib"; import { jsx as _jsx } from "react/jsx-runtime"; import { HubtypeService, INPUT } from '@botonic/core'; import merge from 'lodash.merge'; import { createRef } from 'react'; import { createRoot } from 'react-dom/client'; import { WEBCHAT } from './constants'; import { SENDERS, Typing, } from './index-types'; import { msgToBotonic } from './msg-to-botonic'; import { isShadowDOMSupported, onDOMLoaded } from './util/dom'; import { defaultTheme } from './webchat/theme/default-theme'; import { Webchat } from './webchat/webchat'; export class WebchatApp { constructor({ theme, persistentMenu, coverComponent, blockInputs, enableEmojiPicker, enableAttachments, enableUserInput, enableAnimations, hostId = WEBCHAT.DEFAULTS.HOST_ID, shadowDOM = false, defaultDelay, defaultTyping, storage, storageKey, onInit, onOpen, onClose, onMessage, onTrackEvent, onConnectionChange, appId, visibility, server, }) { 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 && !isShadowDOMSupported()) { console.warn('[botonic] ShadowDOM not supported on this browser'); this.shadowDOM = false; } this.hostId = hostId || WEBCHAT.DEFAULTS.HOST_ID; this.defaultDelay = defaultDelay; this.defaultTyping = defaultTyping; this.storage = storage === undefined ? localStorage : storage; this.storageKey = storageKey || 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.webchatRef = createRef(); this.appId = appId; this.host = null; this.reactRoot = null; } 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.onInit(this, ...args); } onOpenWebchat(...args) { this.onOpen && this.onOpen(this, ...args); } onCloseWebchat(...args) { this.onClose && this.onClose(this, ...args); } onUserInput({ user, input }) { return __awaiter(this, void 0, void 0, function* () { if (!user) return; this.onMessage && this.onMessage(this, Object.assign(Object.assign({}, input), { sentBy: SENDERS.user, isUnread: false })); this.hubtypeService.postMessage(user, Object.assign({}, input)); return; }); } onTrackEventWebchat(request, eventName, args) { return __awaiter(this, void 0, void 0, function* () { if (this.onTrackEvent) { yield this.onTrackEvent(request, eventName, args); } }); } onConnectionRegained() { return __awaiter(this, void 0, void 0, function* () { return this.hubtypeService.onConnectionRegained(); }); } onStateChange(args) { const { user, messagesJSON } = args; const lastMessage = messagesJSON[messagesJSON.length - 1]; const lastMessageId = lastMessage && lastMessage.id; const lastMessageUpdateDate = this.getLastMessageUpdate(); if (this.hubtypeService) { this.hubtypeService.lastMessageId = lastMessageId; this.hubtypeService.lastMessageUpdateDate = lastMessageUpdateDate; } if (!this.hubtypeService) { this.hubtypeService = new HubtypeService({ appId: this.appId, user, lastMessageId, lastMessageUpdateDate, onEvent: (event) => this.onServiceEvent(event), unsentInputs: () => { var _a; return ((_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.getMessages().filter(msg => msg.ack === 0 && msg.unsentInput)) || []; }, server: this.server, }); } } onServiceEvent(event) { var _a, _b, _c, _d; if (event.action === 'connectionChange') { this.onConnectionChange && this.onConnectionChange(this, event.online); (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.setOnline(event.online); } else if (event.action === 'update_message_info' && ((_b = event.message) === null || _b === void 0 ? void 0 : _b.id)) { this.updateMessageInfo(event.message.id, event.message); } else if (((_c = event.message) === null || _c === void 0 ? void 0 : _c.type) === 'update_webchat_settings') { this.updateWebchatSettings(event.message.data); } else if (((_d = event.message) === null || _d === void 0 ? void 0 : _d.type) === 'sender_action') { this.setTyping(event.message.data === Typing.On); } else { // TODO: onMessage function should receive a WebchatMessage // and message.type is typed as enum of INPUT // INPUT not contain 'update_webchat_settings' or 'sender_action' // so we need to cast it to unknown to avoid type error this.onMessage && this.onMessage(this, Object.assign({ sentBy: SENDERS.bot }, event.message)); this.addBotMessage(event.message); } } updateUser(user) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateUser(user); } setSystemLocale(locale) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateUser({ system_locale: locale }); } setUserLocale(locale) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateUser({ locale }); } setUserCountry(country) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateUser({ country }); } addBotMessage(message) { var _a, _b, _c, _d; message.ack = 0; message.isUnread = true; message.sentBy = (_a = message.sent_by) === null || _a === void 0 ? void 0 : _a.split('message_sent_by_')[1]; delete message.sent_by; const response = msgToBotonic(message, (_c = (_b = this.theme) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.customTypes); (_d = this.webchatRef.current) === null || _d === void 0 ? void 0 : _d.addBotResponse({ response, }); } addBotText(text) { this.addBotMessage({ type: INPUT.TEXT, data: text }); } addUserMessage(message) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.addUserMessage(message); } addUserText(text) { this.addUserMessage({ type: INPUT.TEXT, data: text }); } addUserPayload(payload) { this.addUserMessage({ type: INPUT.POSTBACK, payload }); } setTyping(typing) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.setTyping(typing); } open() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.openWebchat(); } close() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.closeWebchat(); } closeWebview(options) { var _a; return __awaiter(this, void 0, void 0, function* () { yield ((_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.closeWebview(options)); }); } // TODO: Remove this function because we have open and close functions toggle() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.toggleWebchat(); } openCoverComponent() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.openCoverComponent(); } closeCoverComponent() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.closeCoverComponent(); } renderCustomComponent(_customComponent) { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.renderCustomComponent(_customComponent); } unmountCustomComponent() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.unmountCustomComponent(); } // TODO: Remove this function because we have openCoverComponent and closeCoverComponent functions toggleCoverComponent() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.toggleCoverComponent(); } getMessages() { var _a; return (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.getMessages(); } clearMessages() { var _a; (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.clearMessages(); } getVisibility() { return __awaiter(this, void 0, void 0, function* () { return this.resolveWebchatVisibility({ appId: this.appId, visibility: this.visibility, }); }); } getLastMessageUpdate() { var _a; return (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.getLastMessageUpdate(); } updateMessageInfo(msgId, messageInfo) { var _a; return (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateMessageInfo(msgId, messageInfo); } updateWebchatSettings(settings) { var _a; return (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.updateWebchatSettings(settings); } createInitialTheme(optionsAtRuntime = {}) { var _a, _b, _c, _d; const theme = merge(defaultTheme, this.theme, optionsAtRuntime.theme); if (theme.animations === undefined) { theme.animations = {}; } theme.animations.enable = (_b = (_a = theme.animations.enable) !== null && _a !== void 0 ? _a : optionsAtRuntime.enableAnimations) !== null && _b !== void 0 ? _b : this.enableAnimations; theme.coverComponent = (_d = (_c = theme.coverComponent) !== null && _c !== void 0 ? _c : optionsAtRuntime.coverComponent) !== null && _d !== void 0 ? _d : this.coverComponent; theme.userInput = this.createInitialThemeUserInput(theme, optionsAtRuntime); return theme; } createInitialThemeUserInput(theme, optionsAtRuntime = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; if (!theme.userInput) theme.userInput = {}; // Set main userInput properties theme.userInput = Object.assign(Object.assign({}, theme.userInput), { enable: (_b = (_a = theme.userInput.enable) !== null && _a !== void 0 ? _a : optionsAtRuntime.enableUserInput) !== null && _b !== void 0 ? _b : this.enableUserInput, persistentMenu: (_d = (_c = theme.userInput.persistentMenu) !== null && _c !== void 0 ? _c : optionsAtRuntime.persistentMenu) !== null && _d !== void 0 ? _d : this.persistentMenu, blockInputs: (_f = (_e = theme.userInput.blockInputs) !== null && _e !== void 0 ? _e : optionsAtRuntime.blockInputs) !== null && _f !== void 0 ? _f : this.blockInputs }); // Set emoji picker properties theme.userInput.emojiPicker = Object.assign(Object.assign({}, (theme.userInput.emojiPicker || {})), { enable: (_j = (_h = (_g = theme.userInput.emojiPicker) === null || _g === void 0 ? void 0 : _g.enable) !== null && _h !== void 0 ? _h : optionsAtRuntime.enableEmojiPicker) !== null && _j !== void 0 ? _j : this.enableEmojiPicker }); // Set attachments properties theme.userInput.attachments = Object.assign(Object.assign({}, (theme.userInput.attachments || {})), { enable: (_m = (_l = (_k = theme.userInput.attachments) === null || _k === void 0 ? void 0 : _k.enable) !== null && _l !== void 0 ? _l : optionsAtRuntime.enableAttachments) !== null && _m !== void 0 ? _m : this.enableAttachments }); return theme.userInput; } getComponent(host, optionsAtRuntime = {}) { let { theme = {}, defaultDelay, defaultTyping, storage, storageKey, onInit, onOpen, onClose, onMessage, onConnectionChange, onTrackEvent, appId, visibility, server, hostId } = optionsAtRuntime, webchatOptions = __rest(optionsAtRuntime, ["theme", "defaultDelay", "defaultTyping", "storage", "storageKey", "onInit", "onOpen", "onClose", "onMessage", "onConnectionChange", "onTrackEvent", "appId", "visibility", "server", "hostId"]); 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.createRootElement(host); return (_jsx(Webchat, Object.assign({}, webchatOptions, { ref: this.webchatRef, host: this.host, shadowDOM: this.shadowDOM, theme: theme, storage: this.storage, storageKey: this.storageKey, defaultDelay: defaultDelay, defaultTyping: defaultTyping, 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 }))); } isWebchatVisible(appId) { return __awaiter(this, void 0, void 0, function* () { try { const { status } = yield HubtypeService.getWebchatVisibility(appId); return status === 200; } catch (e) { return false; } }); } isOnline() { var _a; return (_a = this.webchatRef.current) === null || _a === void 0 ? void 0 : _a.isOnline(); } resolveWebchatVisibility(optionsAtRuntime) { return __awaiter(this, void 0, void 0, function* () { 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' && (yield this.isWebchatVisible(appId))) { return true; } return false; }); } destroy() { var _a; if (this.hubtypeService) this.hubtypeService.destroyPusher(); (_a = this.reactRoot) === null || _a === void 0 ? void 0 : _a.unmount(); if (this.storage) this.storage.removeItem(this.storageKey); } render(dest, optionsAtRuntime) { return __awaiter(this, void 0, void 0, function* () { onDOMLoaded(() => __awaiter(this, void 0, void 0, function* () { const isVisible = yield this.resolveWebchatVisibility(optionsAtRuntime); if (isVisible) { const webchatComponent = this.getComponent(dest, optionsAtRuntime); const container = this.getReactMountNode(dest); this.reactRoot = createRoot(container); this.reactRoot.render(webchatComponent); } })); }); } } //# sourceMappingURL=webchat-app.js.map