@botonic/react
Version:
Build Chatbots using React
405 lines • 16.3 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, 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