@botonic/react
Version:
Build Chatbots using React
395 lines • 18.9 kB
JavaScript
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, }) {
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.webchatRef = (0, react_1.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 tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!user)
return;
this.onMessage &&
this.onMessage(this, Object.assign(Object.assign({}, input), { sentBy: index_types_1.SENDERS.user, isUnread: false }));
this.hubtypeService.postMessage(user, Object.assign({}, input));
return;
});
}
onTrackEventWebchat(request, eventName, args) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (this.onTrackEvent) {
yield this.onTrackEvent(request, eventName, args);
}
});
}
onConnectionRegained() {
return tslib_1.__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 core_1.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 === index_types_1.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: index_types_1.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 = (0, msg_to_botonic_1.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: core_1.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: core_1.INPUT.TEXT, data: text });
}
addUserPayload(payload) {
this.addUserMessage({ type: core_1.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 tslib_1.__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 tslib_1.__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 = (0, lodash_merge_1.default)(default_theme_1.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 = tslib_1.__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 ((0, jsx_runtime_1.jsx)(webchat_1.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 tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
const { status } = yield core_1.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 tslib_1.__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 tslib_1.__awaiter(this, void 0, void 0, function* () {
(0, dom_1.onDOMLoaded)(() => tslib_1.__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 = (0, client_1.createRoot)(container);
this.reactRoot.render(webchatComponent);
}
}));
});
}
}
exports.WebchatApp = WebchatApp;
//# sourceMappingURL=webchat-app.js.map
;