From f2d3a5bbd419068ccc22e77a24f44e3821f41c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Fri, 5 Aug 2022 14:30:02 +0000 Subject: [PATCH] [IMP] im_livechat, *: remove Promise.reject in LivechatButton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *: website_livechat closes odoo/odoo#98160 Signed-off-by: Alexandre Kühn (aku) --- .../src/legacy/public_livechat_chatbot.js | 58 ++-- .../src/legacy/public_livechat_chatbot.xml | 4 +- .../src/legacy/widgets/livechat_button.js | 11 +- .../im_livechat/static/src/models/chatbot.js | 14 +- .../static/src/models/livechat_button_view.js | 268 +++--------------- .../src/models/public_livechat_global.js | 202 ++++++++++++- .../src/services/public_livechat_service.js | 11 +- .../website_livechat_chatbot_test_script.js | 6 +- .../static/src/models/livechat_button_view.js | 28 +- .../src/models/public_livechat_global.js | 32 +++ .../tours/website_livechat_chatbot_flow.js | 2 +- 11 files changed, 332 insertions(+), 304 deletions(-) create mode 100644 addons/website_livechat/static/src/models/public_livechat_global.js diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js index a6be8250b6e..1b4560d5bcb 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js @@ -66,14 +66,14 @@ const _t = core._t; */ _chatbotAwaitUserInput() { if (this._isLastMessageFromCustomer()) { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + if (this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) { this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } else { this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay, + this.messaging.publicLivechatGlobal.chatbot.messageDelay, ) }); } @@ -103,7 +103,7 @@ const _t = core._t; const postedWelcomeMessages = await session.rpc('/chatbot/post_welcome_steps', { channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid, - chatbot_script_id: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId, + chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId, }); const welcomeMessagesIds = welcomeMessages.map(welcomeMessage => welcomeMessage.id); @@ -154,7 +154,7 @@ const _t = core._t; $composerTextField.off('keydown', this.chatbotInputKeyDownHandler); } - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { + if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { this.chatbotInputKeyDownHandler = this._onChatbotInputKeyDown.bind(this); $composerTextField.on('keydown', this.chatbotInputKeyDownHandler); } @@ -187,19 +187,19 @@ const _t = core._t; * @private */ _chatbotProcessStep() { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + if (this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) { this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); - } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' - && this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found) { + } else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' + && this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_operator_found) { this._chatbotEnableInput(); - } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.isExpectingUserInput) { + } else if (this.messaging.publicLivechatGlobal.chatbot.isExpectingUserInput) { if (this._isLastMessageFromCustomer()) { // user has already typed a message in -> trigger next step this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay, + this.messaging.publicLivechatGlobal.chatbot.messageDelay, ), }); } else { @@ -207,7 +207,7 @@ const _t = core._t; } } else { let triggerNextStep = true; - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_selection') { + if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'question_selection') { if (!this._isLastMessageFromCustomer()) { // if there is no last message or if the last message is from the bot // -> don't trigger the next step, we are waiting for the user to pick an option @@ -216,7 +216,7 @@ const _t = core._t; } if (triggerNextStep) { - let nextStepDelay = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay; + let nextStepDelay = this.messaging.publicLivechatGlobal.chatbot.messageDelay; if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_typing').length !== 0) { // special case where we already have a "is typing" message displayed // can happen when the previous step did not trigger any message posted from the bot @@ -236,7 +236,7 @@ const _t = core._t; } } - if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton) { + if (!this.messaging.publicLivechatGlobal.chatbot.hasRestartButton) { this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); } }, @@ -257,7 +257,7 @@ const _t = core._t; }); if (emailValidResult.success) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.is_email_valid = true; + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.is_email_valid = true; this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSaveSession(); return true; @@ -280,9 +280,9 @@ const _t = core._t; async _chatbotTriggerNextStep() { let triggerNextStep = true; if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_email' + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'question_email' ) { triggerNextStep = await this._chatbotValidateEmail(); } @@ -293,7 +293,7 @@ const _t = core._t; const nextStep = await session.rpc('/chatbot/step/trigger', { channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid, - chatbot_script_id: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId, + chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId, }); if (nextStep) { @@ -301,12 +301,12 @@ const _t = core._t; this._chatbotAddMessage(nextStep.chatbot_posted_message); } - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: nextStep.chatbot_step }) }); + this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: nextStep.chatbot_step }) }); this._chatbotProcessStep(); } else { // did not find next step -> end the script - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last = true; + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_is_last = true; this._renderMessages(); this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } @@ -348,7 +348,7 @@ const _t = core._t; const parameters = this._super(...arguments); if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) { - parameters.chatbot_script_id = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId; + parameters.chatbot_script_id = this.messaging.publicLivechatGlobal.chatbot.scriptId; } return parameters; @@ -385,7 +385,7 @@ const _t = core._t; if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) { this._sendWelcomeChatbotMessage( 0, - this.messaging.publicLivechatGlobal.livechatButtonView.chatbotState === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay, + this.messaging.publicLivechatGlobal.chatbotState === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.chatbot.messageDelay, ); } else { this._super(...arguments); @@ -413,8 +413,8 @@ const _t = core._t; * @private */ _sendWelcomeChatbotMessage(stepIndex, welcomeMessageDelay) { - const chatbotStep = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps[stepIndex]; - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: chatbotStep }) }); + const chatbotStep = this.messaging.publicLivechatGlobal.chatbot.welcomeSteps[stepIndex]; + this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: chatbotStep }) }); if (chatbotStep.chatbot_step_message) { this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({ @@ -438,7 +438,7 @@ const _t = core._t; }); } - if (stepIndex + 1 < this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps.length) { + if (stepIndex + 1 < this.messaging.publicLivechatGlobal.chatbot.welcomeSteps.length) { if (welcomeMessageDelay !== 0) { this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(true); } @@ -450,7 +450,7 @@ const _t = core._t; }, welcomeMessageDelay), }); } else { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator') { + if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator') { // special case when the last welcome message is a forward to an operator // we need to save the welcome messages before continuing the script // indeed, if there are no operator available, the script will continue @@ -470,9 +470,9 @@ const _t = core._t; _onChatbotInputKeyDown() { if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi' + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi' ) { this._debouncedChatbotAwaitUserInput(); } @@ -517,7 +517,7 @@ const _t = core._t; } const messageId = stepMessage.id; stepMessage.legacyPublicLivechatMessage.setChatbotStepAnswerId(selectedAnswer); - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer; + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer; this._renderMessages(); this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSaveSession(); diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml index 528f4c34291..b8b04411556 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml @@ -20,12 +20,12 @@ - - diff --git a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js index 7f7ef46f88f..6b0d9add7c2 100644 --- a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js +++ b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js @@ -23,22 +23,21 @@ const LivechatButton = Widget.extend({ }, async willStart() { this.messaging.publicLivechatGlobal.livechatButtonView.update({ widget: this }); - return this.messaging.publicLivechatGlobal.livechatButtonView.willStart(); }, start() { this.$el.text(this.messaging.publicLivechatGlobal.livechatButtonView.buttonText); - if (this.messaging.publicLivechatGlobal.livechatButtonView.history) { - for (const m of this.messaging.publicLivechatGlobal.livechatButtonView.history) { + if (this.messaging.publicLivechatGlobal.history) { + for (const m of this.messaging.publicLivechatGlobal.history) { this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(m); } this.messaging.publicLivechatGlobal.livechatButtonView.openChat(); - } else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.livechatButtonView.rule.action === 'auto_popup') { + } else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.rule.action === 'auto_popup') { const autoPopupCookie = utils.get_cookie('im_livechat_auto_popup'); if (!autoPopupCookie || JSON.parse(autoPopupCookie)) { this.messaging.publicLivechatGlobal.livechatButtonView.update({ autoOpenChatTimeout: setTimeout( this.messaging.publicLivechatGlobal.livechatButtonView.openChat, - this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000, + this.messaging.publicLivechatGlobal.rule.auto_popup_timer * 1000, ), }); } @@ -86,7 +85,7 @@ const LivechatButton = Widget.extend({ */ _prepareGetSessionParameters() { return { - channel_id: this.messaging.publicLivechatGlobal.livechatButtonView.channelId, + channel_id: this.messaging.publicLivechatGlobal.channelId, anonymous_name: this.messaging.publicLivechatGlobal.livechatButtonView.defaultUsername, previous_operator_id: this._get_previous_operator_id(), }; diff --git a/addons/im_livechat/static/src/models/chatbot.js b/addons/im_livechat/static/src/models/chatbot.js index 19e3865ddc2..637f20f9717 100644 --- a/addons/im_livechat/static/src/models/chatbot.js +++ b/addons/im_livechat/static/src/models/chatbot.js @@ -6,7 +6,7 @@ import { clear } from '@mail/model/model_field_command'; registerModel({ name: 'Chatbot', - identifyingFields: ['livechatButtonViewOwner'], + identifyingFields: ['publicLivechatGlobalOwner'], recordMethods: { /** * @private @@ -63,7 +63,7 @@ registerModel({ * @returns {integer|FieldCommand} */ _computeMessageDelay() { - if (this.livechatButtonViewOwner.isWebsiteLivechatChatbotFlow) { + if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) { return 100; } return clear(); @@ -149,11 +149,6 @@ registerModel({ lastWelcomeStep: attr({ compute: '_computeLastWelcomeStep', }), - livechatButtonViewOwner: one('LivechatButtonView', { - inverse: 'chatbot', - readonly: true, - required: true, - }), name: attr({ compute: '_computeName', }), @@ -161,6 +156,11 @@ registerModel({ compute: '_computeMessageDelay', default: 3500, // in milliseconds }), + publicLivechatGlobalOwner: one('PublicLivechatGlobal', { + inverse: 'chatbot', + readonly: true, + required: true, + }), scriptId: attr({ compute: '_computeScriptId', }), diff --git a/addons/im_livechat/static/src/models/livechat_button_view.js b/addons/im_livechat/static/src/models/livechat_button_view.js index c8384513037..efadb3e9f85 100644 --- a/addons/im_livechat/static/src/models/livechat_button_view.js +++ b/addons/im_livechat/static/src/models/livechat_button_view.js @@ -7,11 +7,19 @@ import { attr, one } from '@mail/model/model_field'; import { clear, insertAndReplace, replace } from '@mail/model/model_field_command'; import { qweb } from 'web.core'; -import { get_cookie, Markup, set_cookie, unaccent } from 'web.utils'; +import { get_cookie, set_cookie, unaccent } from 'web.utils'; registerModel({ name: 'LivechatButtonView', identifyingFields: ['publicLivechatGlobalOwner'], + lifecycleHooks: { + _created() { + this.update({ widget: this.env.services.public_livechat_service.mountLivechatButton() }); + }, + _willDelete() { + this.widget.destroy(); + }, + }, recordMethods: { /** * @param {Object} data @@ -54,11 +62,11 @@ registerModel({ * unnecessary and irrelevant (restart / end messages, any text field values, ...). */ if ( - this.chatbot && - this.chatbot.currentStep && - this.chatbot.currentStep.data + this.messaging.publicLivechatGlobal.chatbot && + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data ) { - this.chatbot.currentStep.data.conversation_closed = true; + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed = true; this.chatbotSaveSession(); } this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); @@ -73,9 +81,9 @@ registerModel({ */ chatbotEndScript() { if ( - this.chatbot.currentStep && - this.chatbot.currentStep.data && - this.chatbot.currentStep.data.conversation_closed + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed ) { // don't touch anything if the user has closed the conversation, let the chat window // handle the display @@ -94,8 +102,8 @@ registerModel({ */ chatbotSaveSession() { localStorage.setItem('im_livechat.chatbot.state.uuid_' + this.messaging.publicLivechatGlobal.publicLivechat.uuid, JSON.stringify({ - '_chatbot': this.chatbot.data, - '_chatbotCurrentStep': this.chatbot.currentStep.data, + '_chatbot': this.messaging.publicLivechatGlobal.chatbot.data, + '_chatbotCurrentStep': this.messaging.publicLivechatGlobal.chatbot.currentStep.data, })); }, /** @@ -118,7 +126,7 @@ registerModel({ route: '/chatbot/restart', params: { channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid, - chatbot_script_id: this.chatbot.scriptId, + chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId, }, }); @@ -126,12 +134,12 @@ registerModel({ this.widget._chatbotAddMessage(postedMessage); } - this.chatbot.update({ currentStep: clear() }); + this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: clear() }); this.chatbotSetIsTyping(); this.update({ chatbotNextStepTimeout: setTimeout( this.widget._chatbotTriggerNextStep.bind(this.widget), - this.chatbot.messageDelay, + this.messaging.publicLivechatGlobal.chatbot.messageDelay, ), }); }, @@ -150,14 +158,14 @@ registerModel({ if (browserLocalStorage && browserLocalStorage.length) { for (let i = 0; i < browserLocalStorage.length; i++) { const key = browserLocalStorage.key(i); - if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.chatbotSessionCookieKey) { + if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.messaging.publicLivechatGlobal.chatbotSessionCookieKey) { browserLocalStorage.removeItem(key); } } } - const chatbotState = localStorage.getItem(this.chatbotSessionCookieKey); + const chatbotState = localStorage.getItem(this.messaging.publicLivechatGlobal.chatbotSessionCookieKey); if (chatbotState) { - this.chatbot.update({ currentStep: insertAndReplace({ data: this.localStorageChatbotState._chatbotCurrentStep }) }); + this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: this.messaging.publicLivechatGlobal.localStorageChatbotState._chatbotCurrentStep }) }); } }, closeChat() { @@ -210,13 +218,13 @@ registerModel({ 'chatbotImageSrc': `/im_livechat/operator/${ this.messaging.publicLivechatGlobal.publicLivechat.operator.id }/avatar`, - 'chatbotName': this.chatbot.name, + 'chatbotName': this.messaging.publicLivechatGlobal.chatbot.name, 'isWelcomeMessage': isWelcomeMessage, })) ); this.chatWindow.publicLivechatView.widget.scrollToBottom(); }, - this.chatbot.messageDelay / 3, + this.messaging.publicLivechatGlobal.chatbot.messageDelay / 3, ), }); }, @@ -228,13 +236,6 @@ registerModel({ await this._sendMessage(message); this._sendMessageChatbotAfter(); }, - async willStart() { - try { - await this._willStart(); - } finally { - await this._willStartChatbot(); - } - }, /** * @private * @returns {string} @@ -257,55 +258,16 @@ registerModel({ _computeButtonTextColor() { return this.messaging.publicLivechatGlobal.options.button_text_color; }, - /** - * @private - * @returns {integer} - */ - _computeChannelId() { - return this.messaging.publicLivechatGlobal.options.channel_id; - }, - /** - * @private - * @returns {FieldCommand} - */ - _computeChatbot() { - if (this.isTestChatbot) { - return insertAndReplace({ data: this.testChatbotData.chatbot }); - } - if (this.chatbotState === 'init') { - return insertAndReplace({ data: this.rule.chatbot }); - } - if (this.chatbotState === 'welcome') { - return insertAndReplace({ data: this.livechatInit.rule.chatbot }); - } - if (this.chatbotState === 'restore_session' && this.localStorageChatbotState) { - return insertAndReplace({ data: this.localStorageChatbotState._chatbot }); - } - return clear(); - }, /** * @private * @returns {integer|FieldCommand} */ _computeChatbotMessageDelay() { - if (this.isWebsiteLivechatChatbotFlow) { + if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) { return 100; } return clear(); }, - /** - * @private - * @returns {string|FieldCommand} - */ - _computeChatbotState() { - if (this.rule && !!this.rule.chatbot) { - return 'init'; - } - if (this.livechatInit && this.livechatInit.rule.chatbot) { - return 'welcome'; - } - return clear(); - }, /** * @private * @returns {integer} @@ -363,19 +325,19 @@ registerModel({ * @returns {boolean} */ _computeIsChatbot() { - if (this.isTestChatbot) { + if (this.messaging.publicLivechatGlobal.isTestChatbot) { return true; } - if (this.rule && this.rule.chatbot) { + if (this.messaging.publicLivechatGlobal.rule && this.messaging.publicLivechatGlobal.rule.chatbot) { return true; } - if (this.livechatInit && this.livechatInit.rule.chatbot) { + if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) { return true; } - if (this.chatbotState === 'welcome') { + if (this.messaging.publicLivechatGlobal.chatbotState === 'welcome') { return true; } - if (this.localStorageChatbotState) { + if (this.messaging.publicLivechatGlobal.localStorageChatbotState) { return true; } return clear(); @@ -387,20 +349,6 @@ registerModel({ _computeIsOpenChatDebounced() { return clear(); }, - /** - * @private - * @returns {FieldCommand} - */ - _computeLocalStorageChatbotState() { - if (!this.sessionCookie) { - return clear(); - } - const data = localStorage.getItem(this.chatbotSessionCookieKey); - if (!data) { - return clear(); - } - return JSON.parse(data); - }, /** * @private * @returns {_.debounce} @@ -418,15 +366,6 @@ registerModel({ } return this.messaging.publicLivechatGlobal.serverUrl; }, - /** - * @returns {string|FieldCommand} - */ - _computeChatbotSessionCookieKey() { - if (!this.sessionCookie) { - return clear(); - } - return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.sessionCookie).uuid; - }, /** * @private * @returns {string} @@ -477,7 +416,7 @@ registerModel({ publicLivechat: insertAndReplace({ data: livechatData }), }); return this.openChatWindow().then(() => { - if (!this.history) { + if (!this.messaging.publicLivechatGlobal.history) { this.widget._sendWelcomeMessage(); } this.widget._renderMessages(); @@ -517,9 +456,9 @@ registerModel({ }); if ( - this.chatbot && - this.chatbot.currentStep && - this.chatbot.currentStep.data && + this.messaging.publicLivechatGlobal.chatbot && + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data && this.messaging.publicLivechatGlobal.messages && this.messaging.publicLivechatGlobal.messages.length !== 0 ) { @@ -566,22 +505,22 @@ registerModel({ } if ( this.isChatbot && - this.chatbot.currentStep && - this.chatbot.currentStep.data + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data ) { if ( - this.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' && - this.chatbot.currentStep.data.chatbot_operator_found + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_operator_found ) { return; // operator has taken over the conversation, let them speak - } else if (this.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { + } else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { this.widget._debouncedChatbotAwaitUserInput(); - } else if (!this.chatbot.shouldEndScript) { + } else if (!this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) { this.chatbotSetIsTyping(); this.update({ chatbotNextStepTimeout: setTimeout( this.widget._chatbotTriggerNextStep.bind(this.widget), - this.chatbot.messageDelay, + this.messaging.publicLivechatGlobal.chatbot.messageDelay, ), }); } else { @@ -611,100 +550,12 @@ registerModel({ async _sendMessageChatbotBefore() { if ( this.isChatbot && - this.chatbot.currentStep && - this.chatbot.currentStep.data + this.messaging.publicLivechatGlobal.chatbot.currentStep && + this.messaging.publicLivechatGlobal.chatbot.currentStep.data ) { await this.widget._chatbotPostWelcomeMessages(); } }, - async _willStart() { - const cookie = get_cookie('im_livechat_session'); - if (cookie) { - const channel = JSON.parse(cookie); - const history = await this.messaging.rpc({ - route: '/mail/chat_history', - params: { uuid: channel.uuid, limit: 100 }, - }); - history.reverse(); - this.update({ history }); - for (const message of this.history) { - message.body = Markup(message.body); - } - } else { - const result = await this.messaging.rpc({ - route: '/im_livechat/init', - params: { channel_id: this.channelId }, - }); - if (!result.available_for_me) { - return Promise.reject(); - } - this.update({ rule: result.rule }); - } - return this.messaging.publicLivechatGlobal.loadQWebTemplate(); - }, - /** - * This override handles the following use cases: - * - * - If the chat is started for the first time (first visit of a visitor) - * We register the chatbot configuration and the rest of the behavior is triggered by various - * method overrides ('sendWelcomeMessage', 'sendMessage', ...) - * - * - If the chat has been started before, but the user did not interact with the bot - * The default behavior is to open an empty chat window, without any messages. - * In addition, we fetch the configuration (with a '/init' call), to see if we have a bot - * configured. - * Indeed we want to trigger the bot script on every page where the associated rule is matched. - * - * - If we have a non-empty chat history, resume the chat script where the end-user left it by - * fetching the necessary information from the local storage. - * - * @override - */ - async _willStartChatbot() { - if (this.rule && !!this.chatbot) { - // noop - } else if (this.history !== null && this.history.length === 0) { - this.update({ - livechatInit: await this.messaging.rpc({ - route: '/im_livechat/init', - params: { channel_id: this.channelId }, - }), - }); - } else if (this.history !== null && this.history.length !== 0) { - const sessionCookie = get_cookie('im_livechat_session'); - if (sessionCookie) { - this.update({ sessionCookie }); - if (localStorage.getItem(this.chatbotSessionCookieKey)) { - this.update({ chatbotState: 'restore_session' }); - } - } - } - - if (this.chatbotState === 'init') { - // we landed on a website page where a channel rule is configured to run a chatbot.script - // -> initialize necessary state - if (this.rule.chatbot_welcome_steps && this.rule.chatbot_welcome_steps.length !== 0) { - this.chatbot.update({ - currentStep: insertAndReplace({ - data: this.chatbot.lastWelcomeStep, - }), - }); - } - } else if (this.chatbotState === 'welcome') { - // we landed on a website page and a chatbot script was initialized on a previous one - // however the end-user did not interact with the bot ( :( ) - // -> remove cookie to force opening the popup again - // -> initialize necessary state - // -> batch welcome message (see '_sendWelcomeChatbotMessage') - set_cookie('im_livechat_auto_popup', '', -1); - this.update({ history: clear() }); - this.update({ rule: this.livechatInit.rule }); - } else if (this.chatbotState === 'restore_session') { - // we landed on a website page and a chatbot script is currently running - // -> restore the user's session (see 'chatbotRestoreSession') - this.chatbotRestoreSession(); - } - }, }, fields: { autoOpenChatTimeout: attr(), @@ -717,21 +568,7 @@ registerModel({ buttonTextColor: attr({ compute: '_computeButtonTextColor', }), - channelId: attr({ - compute: '_computeChannelId', - }), - chatbot: one('Chatbot', { - compute: '_computeChatbot', - inverse: 'livechatButtonViewOwner', - isCausal: true, - }), chatbotNextStepTimeout: attr(), - chatbotSessionCookieKey: attr({ - compute: '_computeChatbotSessionCookieKey', - }), - chatbotState: attr({ - compute: '_computeChatbotState', - }), chatbotWelcomeMessageTimeout: attr(), chatWindow: one('PublicLivechatWindow', { inverse: 'livechatButtonViewOwner', @@ -749,9 +586,6 @@ registerModel({ headerBackgroundColor: attr({ compute: '_computeHeaderBackgroundColor', }), - history: attr({ - default: null, - }), inputPlaceholder: attr({ compute: '_computeInputPlaceholder', default: '', @@ -770,17 +604,7 @@ registerModel({ isOpeningChat: attr({ default: false, }), - isTestChatbot: attr({ - default: false, - }), isTypingTimeout: attr(), - isWebsiteLivechatChatbotFlow: attr({ - default: false, - }), - livechatInit: attr(), - localStorageChatbotState: attr({ - compute: '_computeLocalStorageChatbotState', - }), openChatDebounced: attr({ compute: '_computeOpenChatDebounced', }), @@ -789,11 +613,9 @@ registerModel({ readonly: true, required: true, }), - rule: attr(), serverUrl: attr({ compute: '_computeServerUrl', }), - sessionCookie: attr(), testChatbotData: attr(), titleColor: attr({ compute: '_computeTitleColor', diff --git a/addons/im_livechat/static/src/models/public_livechat_global.js b/addons/im_livechat/static/src/models/public_livechat_global.js index 8de81a3200f..b35ed49d4e5 100644 --- a/addons/im_livechat/static/src/models/public_livechat_global.js +++ b/addons/im_livechat/static/src/models/public_livechat_global.js @@ -5,7 +5,8 @@ import { attr, many, one } from '@mail/model/model_field'; import { clear, insertAndReplace, replace } from '@mail/model/model_field_command'; import { qweb } from 'web.core'; -import utils from 'web.utils'; + +import { get_cookie, Markup, set_cookie } from 'web.utils'; registerModel({ name: 'PublicLivechatGlobal', @@ -14,7 +15,7 @@ registerModel({ _created() { // History tracking const page = window.location.href.replace(/^.*\/\/[^/]+/, ''); - const pageHistory = utils.get_cookie(this.LIVECHAT_COOKIE_HISTORY); + const pageHistory = get_cookie(this.LIVECHAT_COOKIE_HISTORY); let urlHistory = []; if (pageHistory) { urlHistory = JSON.parse(pageHistory) || []; @@ -24,8 +25,9 @@ registerModel({ while (urlHistory.length > this.HISTORY_LIMIT) { urlHistory.shift(); } - utils.set_cookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24); // 1 day cookie + set_cookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24); // 1 day cookie } + this.willStart(); }, }, recordMethods: { @@ -34,6 +36,60 @@ registerModel({ for (const template of templates) { qweb.add_template(template); } + this.update({ hasLoadedQWebTemplate: true }); + }, + async willStart() { + await this._willStart(); + await this._willStartChatbot(); + }, + /** + * @private + * @returns {integer} + */ + _computeChannelId() { + return this.options.channel_id; + }, + /** + * @private + * @returns {FieldCommand} + */ + _computeChatbot() { + if (this.isTestChatbot) { + return insertAndReplace({ data: this.testChatbotData.chatbot }); + } + if (this.chatbotState === 'init') { + return insertAndReplace({ data: this.rule.chatbot }); + } + if (this.chatbotState === 'welcome') { + return insertAndReplace({ data: this.livechatInit.rule.chatbot }); + } + if (this.chatbotState === 'restore_session' && this.localStorageChatbotState) { + return insertAndReplace({ data: this.localStorageChatbotState._chatbot }); + } + return clear(); + }, + /** + * @private + * @returns {string|FieldCommand} + */ + _computeChatbotSessionCookieKey() { + if (!this.sessionCookie) { + return clear(); + } + return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.sessionCookie).uuid; + }, + /** + * @private + * @returns {string|FieldCommand} + */ + _computeChatbotState() { + if (this.rule && !!this.rule.chatbot) { + return 'init'; + } + if (this.livechatInit && this.livechatInit.rule.chatbot) { + return 'welcome'; + } + return clear(); }, /** * @private @@ -50,11 +106,114 @@ registerModel({ * @returns {FieldCommand} */ _computeLivechatButtonView() { - if (this.isAvailable) { + if (this.isAvailable && this.isAvailableForMe && this.hasLoadedQWebTemplate && this.env.services.public_livechat_service) { return insertAndReplace(); } return clear(); }, + /** + * @private + * @returns {FieldCommand} + */ + _computeLocalStorageChatbotState() { + if (!this.sessionCookie) { + return clear(); + } + const data = localStorage.getItem(this.chatbotSessionCookieKey); + if (!data) { + return clear(); + } + return JSON.parse(data); + }, + async _willStart() { + const cookie = get_cookie('im_livechat_session'); + if (cookie) { + const channel = JSON.parse(cookie); + const history = await this.messaging.rpc({ + route: '/mail/chat_history', + params: { uuid: channel.uuid, limit: 100 }, + }); + history.reverse(); + this.update({ history }); + for (const message of this.history) { + message.body = Markup(message.body); + } + this.update({ isAvailableForMe: true }); + } else { + const result = await this.messaging.rpc({ + route: '/im_livechat/init', + params: { channel_id: this.channelId }, + }); + if (result.available_for_me) { + this.update({ isAvailableForMe: true }); + } + this.update({ rule: result.rule }); + } + return this.loadQWebTemplate(); + }, + /** + * This override handles the following use cases: + * + * - If the chat is started for the first time (first visit of a visitor) + * We register the chatbot configuration and the rest of the behavior is triggered by various + * method overrides ('sendWelcomeMessage', 'sendMessage', ...) + * + * - If the chat has been started before, but the user did not interact with the bot + * The default behavior is to open an empty chat window, without any messages. + * In addition, we fetch the configuration (with a '/init' call), to see if we have a bot + * configured. + * Indeed we want to trigger the bot script on every page where the associated rule is matched. + * + * - If we have a non-empty chat history, resume the chat script where the end-user left it by + * fetching the necessary information from the local storage. + * + * @override + */ + async _willStartChatbot() { + if (this.rule && !!this.chatbot) { + // noop + } else if (this.history !== null && this.history.length === 0) { + this.update({ + livechatInit: await this.messaging.rpc({ + route: '/im_livechat/init', + params: { channel_id: this.channelId }, + }), + }); + } else if (this.history !== null && this.history.length !== 0) { + const sessionCookie = get_cookie('im_livechat_session'); + if (sessionCookie) { + this.update({ sessionCookie }); + if (localStorage.getItem(this.chatbotSessionCookieKey)) { + this.update({ chatbotState: 'restore_session' }); + } + } + } + + if (this.chatbotState === 'init') { + // we landed on a website page where a channel rule is configured to run a chatbot.script + // -> initialize necessary state + if (this.rule.chatbot_welcome_steps && this.rule.chatbot_welcome_steps.length !== 0) { + this.chatbot.update({ + currentStep: insertAndReplace({ + data: this.chatbot.lastWelcomeStep, + }), + }); + } + } else if (this.chatbotState === 'welcome') { + // we landed on a website page and a chatbot script was initialized on a previous one + // however the end-user did not interact with the bot ( :( ) + // -> remove cookie to force opening the popup again + // -> initialize necessary state + // -> batch welcome message (see '_sendWelcomeChatbotMessage') + set_cookie('im_livechat_auto_popup', '', -1); + this.update({ history: clear() }); + this.update({ rule: this.livechatInit.rule }); + } else if (this.chatbotState === 'restore_session') { + // we landed on a website page and a chatbot script is currently running + // -> restore the user's session (see 'chatbotRestoreSession') + this.chatbotRestoreSession(); + } + }, }, fields: { HISTORY_LIMIT: attr({ @@ -70,14 +229,43 @@ registerModel({ 1: "😞", }, }), + channelId: attr({ + compute: '_computeChannelId', + }), + chatbot: one('Chatbot', { + compute: '_computeChatbot', + inverse: 'publicLivechatGlobalOwner', + isCausal: true, + }), chatbotServerUrl: attr(), + chatbotSessionCookieKey: attr({ + compute: '_computeChatbotSessionCookieKey', + }), + chatbotState: attr({ + compute: '_computeChatbotState', + }), feedbackView: one('PublicLivechatFeedbackView', { inverse: 'publicLivechatGlobalOwner', isCausal: true, }), + hasLoadedQWebTemplate: attr({ + default: false, + }), + history: attr({ + default: null, + }), isAvailable: attr({ default: false, }), + isAvailableForMe: attr({ + default: false, + }), + isTestChatbot: attr({ + default: false, + }), + isWebsiteLivechatChatbotFlow: attr({ + default: false, + }), lastMessage: one('PublicLivechatMessage', { compute: '_computeLastMessage', }), @@ -86,6 +274,10 @@ registerModel({ inverse: 'publicLivechatGlobalOwner', isCausal: true, }), + livechatInit: attr(), + localStorageChatbotState: attr({ + compute: '_computeLocalStorageChatbotState', + }), messages: many('PublicLivechatMessage'), notificationHandler: one('PublicLivechatGlobalNotificationHandler', { inverse: 'publicLivechatGlobalOwner', @@ -98,8 +290,10 @@ registerModel({ inverse: 'publicLivechatGlobalOwner', isCausal: true, }), + rule: attr(), serverUrl: attr({ default: '', }), + sessionCookie: attr(), }, }); diff --git a/addons/im_livechat/static/src/services/public_livechat_service.js b/addons/im_livechat/static/src/services/public_livechat_service.js index f614a7af63f..181d0aa08cc 100644 --- a/addons/im_livechat/static/src/services/public_livechat_service.js +++ b/addons/im_livechat/static/src/services/public_livechat_service.js @@ -8,9 +8,12 @@ export const publicLivechatService = { dependencies: ['messaging'], async start(env, { messaging: messagingService }) { const messaging = await messagingService.get(); - if (messaging.publicLivechatGlobal.livechatButtonView) { - const livechatButton = new LivechatButton(rootWidget, messaging); - livechatButton.appendTo(document.body); - } + return { + mountLivechatButton() { + const livechatButton = new LivechatButton(rootWidget, messaging); + livechatButton.appendTo(document.body); + return livechatButton; + }, + }; }, }; diff --git a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js index a539b01b5ab..cc4a5677a0e 100644 --- a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js +++ b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js @@ -27,11 +27,11 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ 'auto_popup_timer': 0, }, }); - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isTestChatbot: true }); + this.messaging.publicLivechatGlobal.update({ isTestChatbot: true }); this.messaging.publicLivechatGlobal.livechatButtonView.update({ testChatbotData: chatbotData.chatbot }); - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ + this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ - data: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.lastWelcomeStep, + data: this.messaging.publicLivechatGlobal.chatbot.lastWelcomeStep, }), }); }, diff --git a/addons/website_livechat/static/src/models/livechat_button_view.js b/addons/website_livechat/static/src/models/livechat_button_view.js index 911619fd8d3..b252b4d8a8d 100644 --- a/addons/website_livechat/static/src/models/livechat_button_view.js +++ b/addons/website_livechat/static/src/models/livechat_button_view.js @@ -16,39 +16,17 @@ patchRecordMethods('LivechatButtonView', { * @override */ async onChatbotRestartScript(ev) { - if (this.isWebsiteLivechatChatbotFlow) { + if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) { this.messaging.publicLivechatGlobal.update({ messages: clear() }); this.widget._renderMessages(); } return this._super(ev); }, - /** - * Check if a chat request is opened for this visitor - * if yes, replace the session cookie and start the conversation immediately. - * Do this before calling super to have everything ready before executing existing start logic. - * This is used for chat request mechanism, when an operator send a chat request - * from backend to a website visitor. - * - * @override - */ - willStart() { - if (this.isTestChatbot) { - /** - * Overridden to avoid calling the "init" endpoint as it requires a im_livechat.channel linked - * to work properly. - */ - return this.messaging.publicLivechatGlobal.loadQWebTemplate(); - } - if (this.messaging.publicLivechatGlobal.options.chat_request_session) { - set_cookie('im_livechat_session', JSON.stringify(this.messaging.publicLivechatGlobal.options.chat_request_session), 60 * 60); - } - return this._super(); - }, /** * @override */ _computeIsOpenChatDebounced() { - if (this.isTestChatbot) { + if (this.messaging.publicLivechatGlobal.isTestChatbot) { return false; } return this._super(); @@ -63,7 +41,7 @@ patchRecordMethods('LivechatButtonView', { * @override */ async _openChat() { - if (!this.isTestChatbot) { + if (!this.messaging.publicLivechatGlobal.isTestChatbot) { return this._super(); } this.messaging.publicLivechatGlobal.update({ diff --git a/addons/website_livechat/static/src/models/public_livechat_global.js b/addons/website_livechat/static/src/models/public_livechat_global.js new file mode 100644 index 00000000000..7dfffe67f15 --- /dev/null +++ b/addons/website_livechat/static/src/models/public_livechat_global.js @@ -0,0 +1,32 @@ +/** @odoo-module **/ + +import { patchRecordMethods } from '@mail/model/model_core'; +// ensure that the model definition is loaded before the patch +import '@im_livechat/models/public_livechat_global'; + +import { set_cookie } from 'web.utils'; + +patchRecordMethods('PublicLivechatGlobal', { + /** + * Check if a chat request is opened for this visitor + * if yes, replace the session cookie and start the conversation immediately. + * Do this before calling super to have everything ready before executing existing start logic. + * This is used for chat request mechanism, when an operator send a chat request + * from backend to a website visitor. + * + * @override + */ + willStart() { + if (this.isTestChatbot) { + /** + * Overridden to avoid calling the "init" endpoint as it requires a im_livechat.channel linked + * to work properly. + */ + return this.loadQWebTemplate(); + } + if (this.options.chat_request_session) { + set_cookie('im_livechat_session', JSON.stringify(this.options.chat_request_session), 60 * 60); + } + return this._super(); + }, +}); diff --git a/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js b/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js index e003d824ce0..90766eeff78 100644 --- a/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js +++ b/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js @@ -9,7 +9,7 @@ LivechatButton.include({ * Let us also debounce waiting for more user inputs for only 500ms. */ start: function () { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isWebsiteLivechatChatbotFlow: true }); + this.messaging.publicLivechatGlobal.update({ isWebsiteLivechatChatbotFlow: true }); this._debouncedChatbotAwaitUserInput = _.debounce( this._chatbotAwaitUserInput.bind(this),