Skip to content

Commit

Permalink
fix(webchat): properly serialize message (#523)
Browse files Browse the repository at this point in the history
* fix(webchat): properly serialize message

* reference to iframe window instead or postmessage

* set selected message when message is received

* pull request comments

* more comment fix
  • Loading branch information
EFF authored Aug 26, 2022
1 parent 4d7010b commit 91b997f
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 22 deletions.
40 changes: 19 additions & 21 deletions packages/inject/src/inject.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Config, WebchatEvent, WebchatEventType } from '@botpress/webchat'

require('./inject.css')
import './inject.css'

interface WebchatRef {
postMessage: (message: any, targetOrigin: string) => void
iframeWindow: Window
eventListener: {
handler: (event: WebchatEvent) => void
handler: WebchatEventHandler
topics: WebchatEventHandlerTopics
}
}
Expand All @@ -19,26 +19,26 @@ const DEFAULT_IFRAME_ID = 'bp-widget'

const CHAT_REFS: { [chatId: string]: WebchatRef } = {}

function _getContainerId(chatId?: string) {
function _getContainerId(chatId?: string): string {
return chatId ? `${chatId}-container` : DEFAULT_CHAT_ID
}

function _getIframeId(chatId: string) {
function _getIframeId(chatId: string): string {
return chatId || DEFAULT_IFRAME_ID
}

function _injectDOMElement(
tagName: string,
tagName: keyof HTMLElementTagNameMap,
selector: string,
options: { [key: string]: string } = {}
): HTMLElement | void {
const element = document.createElement(tagName)
// @ts-ignore
Object.entries(options).forEach(([attrName, attrValue]) => (element[attrName] = attrValue))

const parent = document.querySelector(selector)
if (!parent) {
console.error(`No element correspond to ${selector}`)
return
throw new Error(`No element correspond to ${selector}`)
}
parent.appendChild(element)
return element
Expand Down Expand Up @@ -78,15 +78,15 @@ function _makeChatRefProxy(chatId: string, target: Partial<WebchatRef>): Webchat
return target[prop]
}

if (prop === 'postMessage') {
if (prop === 'iframeWindow') {
return () => {
console.warn(
`No webchat with id ${chatId} has been initialized. \n Please use window.botpressWebChat.init first.`
)
}
} else if (prop === 'eventListener') {
return {
handler: (evnt: WebchatEvent) => console.log('this is the default event handler', evnt),
handler: () => {},
types: []
}
}
Expand All @@ -110,32 +110,30 @@ function _getIframeElement(containerId: string, iframeId: string): HTMLIFrameEle

function sendEvent(payload: any, chatId?: string) {
const chatRef = _getChatRef(chatId)
chatRef.postMessage({ action: 'event', payload }, '*')
chatRef.iframeWindow.postMessage({ action: 'event', payload }, '*')
}

function sendPayload(payload: any, chatId?: string) {
const chatRef = _getChatRef(chatId)
chatRef.postMessage({ action: 'sendPayload', payload }, '*')
chatRef.iframeWindow.postMessage({ action: 'sendPayload', payload }, '*')
}

function configure(payload: Config, chatId?: string) {
const chatRef = _getChatRef(chatId)
chatRef.postMessage({ action: 'configure', payload }, '*')
chatRef.iframeWindow.postMessage({ action: 'configure', payload }, '*')
}

function mergeConfig(payload: Partial<Config>, chatId?: string) {
const chatRef = _getChatRef(chatId)
chatRef.postMessage({ action: 'mergeConfig', payload }, '*')
chatRef.iframeWindow.postMessage({ action: 'mergeConfig', payload }, '*')
}

function onEvent(handler: WebchatEventHandler, topics: WebchatEventHandlerTopics = [], chatId?: string) {
if (typeof handler !== 'function') {
console.error('eventHandler is not a function, please provide a function')
return
throw new Error('EventHandler is not a function, please provide a function')
}
if (!topics || typeof topics !== 'object' || !topics.length) {
console.error('topics should be an array of supported event types')
return
if (!Array.isArray(topics)) {
throw new Error('Topics should be an array of supported event types')
}

chatId = chatId || DEFAULT_CHAT_ID
Expand All @@ -150,7 +148,7 @@ function onEvent(handler: WebchatEventHandler, topics: WebchatEventHandlerTopics

/**
*
* @param {object} config Configuration object you want to apply to your webchat instance
* @param {Config} config Configuration object you want to apply to your webchat instance
* @param {string} targetSelector css selector under which you want your webchat to be rendered
*/
function init(config: Config, targetSelector: string) {
Expand All @@ -168,7 +166,7 @@ function init(config: Config, targetSelector: string) {
_injectDOMElement('div', targetSelector, { id: containerId, innerHTML: iframeHTML })

const iframeRef = _getIframeElement(containerId, iframeId)
const partialChatRef: Partial<WebchatRef> = { postMessage: iframeRef.contentWindow!.postMessage }
const partialChatRef: Partial<WebchatRef> = { iframeWindow: iframeRef.contentWindow! }

if (CHAT_REFS[config.chatId]) {
Object.assign(CHAT_REFS[config.chatId], partialChatRef)
Expand Down
3 changes: 2 additions & 1 deletion packages/webchat/src/components/messages/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReactMessageRenderer from '@botpress/messaging-components'
import classnames from 'classnames'
import { cloneDeep } from 'lodash'
import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'
import { WrappedComponentProps, injectIntl } from 'react-intl'
Expand Down Expand Up @@ -35,7 +36,7 @@ class Message extends Component<MessageProps> {
id: this.props.messageId,
conversationId: this.props.store?.currentConversationId,
sentOn: this.props.sentOn,
payload: { ...this.props.payload },
payload: cloneDeep(this.props.payload), //necessary because of potentially nested mobx proxy object isn't serializable
from: this.props.isBotMessage ? 'bot' : 'user'
},
this.props.config!.chatId
Expand Down
6 changes: 6 additions & 0 deletions packages/webchat/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ class Web extends React.Component<MainProps> {
if (this.props.currentConversation?.userId !== event.authorId) {
trackMessage('received')
postMessageToParent('MESSAGE.RECEIVED', event, this.props.config!.chatId)

// This is to handle a special case for the emulator, setting the selected css class to the last message group
// This needs a rethinking
if (event.id) {
this.props.store?.setSelectedMessage(event.id)
}
}

this.props.updateLastMessage!(event.conversationId, event)
Expand Down

0 comments on commit 91b997f

Please sign in to comment.