@botonic/react
Version:
Build Chatbots using React
391 lines • 18.5 kB
JavaScript
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