Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full stack/refactor internal session vars - adapt frontend code to botState data model #1967

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/botonic-api/src/rest/routes/events.ts
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ export default function eventsRouter(args: any): Router {
})
await handlers.run('botExecutor', {
input: { ...message, userId }, // To identify user executing the input
session: updatedUser.session,
session: user.session,
botState: user.botState,
websocketId: user.websocketId,
})
7 changes: 4 additions & 3 deletions packages/botonic-core/src/handoff.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios'

import { PATH_PAYLOAD_IDENTIFIER } from './constants'
import { Session } from './models'
import { BotState, Session } from './models'

const HUBTYPE_API_URL = 'https://api.hubtype.com'

@@ -257,14 +257,15 @@ export async function getAgentVacationRanges(
}

export function cancelHandoff(
botState: any,
botState: BotState,
typification: string | null = null
): void {
let action = 'discard_case'
if (typification) action = `${action}:${JSON.stringify({ typification })}`
botState.botonicAction = action
botState.isHandoff = false // TODO: Review handoff functionalities
}

export function deleteUser(botState: any): void {
export function deleteUser(botState: BotState): void {
botState.botonicAction = `delete_user`
}
2 changes: 1 addition & 1 deletion packages/botonic-core/src/hubtype-service.ts
Original file line number Diff line number Diff line change
@@ -185,7 +185,7 @@ export class HubtypeService {
}

handleConnectionChange(online: boolean): void {
this.onPusherEvent({ action: 'connectionChange', online })
this.onPusherEvent({ action: 'connection_change', online })
}

onPusherEvent(event: any): void {
6 changes: 4 additions & 2 deletions packages/botonic-react/src/experimental/dev-app.jsx
Original file line number Diff line number Diff line change
@@ -102,8 +102,10 @@ export class DevApp extends WebchatApp {
enableAnimations={enableAnimations}
storage={storage}
storageKey={storageKey}
getString={(stringId, session) => this.bot.getString(stringId, session)}
setLocale={(locale, session) => this.bot.setLocale(locale, session)}
getString={(stringId, botState) =>
this.bot.getString(stringId, botState)
}
setLocale={(locale, botState) => this.bot.setLocale(locale, botState)}
onInit={(...args) => this.onInitWebchat(...args)}
onOpen={(...args) => this.onOpenWebchat(...args)}
onClose={(...args) => this.onCloseWebchat(...args)}
29 changes: 16 additions & 13 deletions packages/botonic-react/src/experimental/index.js
Original file line number Diff line number Diff line change
@@ -38,9 +38,9 @@ class WebsocketBackendService {
// On Event Received...
this.wsClient.addEventListener('message', event => {
console.log(event, this.onEvent)
const message = JSON.parse(decode(event.data))
const eventData = JSON.parse(decode(event.data))
if (this.onEvent && typeof this.onEvent === 'function')
this.onEvent({ message })
this.onEvent(eventData)
})
}
async doAuthAndUpdateJwt() {
@@ -72,7 +72,7 @@ class WebsocketBackendService {
`${REST_API_URL}events/`,
{
message,
sender: user,
sender: user, // TODO: Really needed or we should pass user information through JWT?
},
{ headers: { Authorization: 'Bearer ' + this.jwt } } // Note: Do not use string template as it will convert the token with commas, which will be invalid
)
@@ -85,22 +85,22 @@ class WebsocketBackendService {
if (hasErrors) {
// TODO: Handle rest of errors
await this.doAuthAndUpdateJwt()
await this.postMessage(user, message)
// await this.postMessage(user, message) // Temporary, avoid infinite events loop
}
}
}

export class FullstackProdApp extends WebchatApp {
async onUserInput({ user, input }) {
this.onMessage && this.onMessage(this, { from: 'user', message: input })
async onUserInput({ user, input, session, botState }) {
this.onMessage && this.onMessage(this, { from: input.from, message: input })
this.backendService.postMessage(user, input)
}

async doAuth({ userId }) {
return await this.backendService.doAuth({ userId })
}

onStateChange({ session: { user }, messagesJSON, jwt, updateJwt }) {
onStateChange({ user, messagesJSON, jwt, updateJwt }) {
if (!this.backendService && user) {
const lastMessage = messagesJSON[messagesJSON.length - 1]
this.backendService = new WebsocketBackendService({
@@ -121,8 +121,8 @@ export class FullstackDevApp extends DevApp {
console.log('FullstackDevApp ', args.playgroundCode)
}

async onUserInput({ user, input }) {
this.onMessage && this.onMessage(this, { from: 'user', message: input })
async onUserInput({ user, input, session, botState }) {
this.onMessage && this.onMessage(this, { from: input.from, message: input })
this.backendService && this.backendService.postMessage(user, input)
}

@@ -179,8 +179,10 @@ export class FullstackDevApp extends DevApp {
storageKey={storageKey}
playgroundCode={this.playgroundCode}
onStateChange={webchatState => this.onStateChange(webchatState)}
getString={(stringId, session) => this.bot.getString(stringId, session)}
setLocale={(locale, session) => this.bot.setLocale(locale, session)}
getString={(stringId, botState) =>
this.bot.getString(stringId, botState)
}
setLocale={(locale, botState) => this.bot.setLocale(locale, botState)}
onInit={(...args) => this.onInitWebchat(...args)}
onOpen={(...args) => this.onOpenWebchat(...args)}
onClose={(...args) => this.onCloseWebchat(...args)}
@@ -194,7 +196,7 @@ export class FullstackDevApp extends DevApp {
return await this.backendService.doAuth({ userId })
}

onStateChange({ session: { user }, messagesJSON, jwt, updateJwt }) {
onStateChange({ user, messagesJSON, jwt, updateJwt }) {
if (!this.backendService && user) {
const lastMessage = messagesJSON[messagesJSON.length - 1]
this.backendService = new WebsocketBackendService({
@@ -260,6 +262,7 @@ export class BrowserProdApp extends WebchatApp {
...botOptions,
})
}
// TODO: Review how this be done for only browser versions
async onUserInput({ input, session, lastRoutePath }) {
this.onMessage && this.onMessage(this, { from: 'user', message: input })
const resp = await this.bot.input({ input, session, lastRoutePath })
@@ -280,10 +283,10 @@ export { ShareButton } from '../components/share-button'
export { Subtitle } from '../components/subtitle'
export { Title } from '../components/title'
export { WebchatSettings } from '../components/webchat-settings'
export { RequestContext, WebchatContext } from '../contexts'
export { staticAsset } from '../util/environment'
export { getBotonicApp } from '../webchat'
export { WebviewApp } from '../webview'
export { RequestContext, WebchatContext } from './contexts'
// Experimental
export { Audio } from './components/audio'
export { Carousel } from './components/carousel'
13 changes: 7 additions & 6 deletions packages/botonic-react/src/experimental/util/webchat.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PROVIDER } from '@botonic/core'
import merge from 'lodash.merge'
import UAParser from 'ua-parser-js'
import { v4 as uuidv4 } from 'uuid'
@@ -34,14 +35,14 @@ export const createUser = () => {
return {
id: uuidv4(),
name,
channel: PROVIDER.DEV,
}
}
export const initSession = session => {
if (!session) session = {}
const hasUserId = session.user && session.user.id !== undefined
if (!session.user || Object.keys(session.user).length === 0 || !hasUserId)
session.user = !hasUserId ? merge(session.user, createUser()) : createUser()
return session

export const initUser = user => {
if (!user) return createUser()
if (user && !user.id) return merge(user, createUser())
return user
}

export const shouldKeepSessionOnReload = ({
41 changes: 27 additions & 14 deletions packages/botonic-react/src/experimental/webchat-app.jsx
Original file line number Diff line number Diff line change
@@ -139,29 +139,42 @@ export class WebchatApp {
}

onServiceEvent(event) {
if (event.action === 'connectionChange')
const { action, ...eventData } = event
if (action === 'connection_change')
this.webchatRef.current.setOnline(event.online)
// TODO: Temporary solution, decide how we will send these events in next iterations
else if (event.message.action === 'update_message_info') {
const { message } = event.message
this.updateMessageInfo(message.id, message)
} else if (event.action === 'update_message_info')
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 === 'typing_on')
else {
else if (action === 'update_message_info') {
this.updateMessageInfo(eventData.id, eventData)
} else if (action === 'update_user') {
this.updateUser(eventData)
} else if (action === 'update_session') {
this.updateSession(eventData)
} else if (action === 'update_bot_state') {
this.updateBotState(eventData)
}
// TODO: Discuss how this updates to be done
else if (eventData.type === 'update_webchat_settings')
this.updateWebchatSettings(event.data)
else if (eventData.type === 'sender_action')
this.setTyping(event.data === 'typing_on')
else if (eventData.eventType === 'message') {
this.onMessage &&
this.onMessage(this, { from: SENDERS.bot, message: event.message })
this.addBotMessage(event.message)
this.onMessage(this, { from: SENDERS.bot, message: eventData })
this.addBotMessage(eventData)
}
}

updateUser(user) {
this.webchatRef.current.updateUser(user)
}

updateSession(session) {
this.webchatRef.current.updateSession(session)
}

updateBotState(botState) {
this.webchatRef.current.updateBotState(botState)
}

addBotMessage(message) {
this.webchatRef.current.addBotResponse({
response: msgToBotonic(
2 changes: 2 additions & 0 deletions packages/botonic-react/src/experimental/webchat/actions.jsx
Original file line number Diff line number Diff line change
@@ -20,3 +20,5 @@ export const UPDATE_LAST_MESSAGE_DATE = 'updateLastMessageDate'
export const SET_CURRENT_ATTACHMENT = 'setCurrentAttachment'
export const SET_ONLINE = 'setOnline'
export const UPDATE_JWT = 'updateJwt'
export const UPDATE_USER = 'updateUser'
export const UPDATE_BOT_STATE = 'updateBotState'
50 changes: 44 additions & 6 deletions packages/botonic-react/src/experimental/webchat/hooks.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import {
TOGGLE_EMOJI_PICKER,
TOGGLE_PERSISTENT_MENU,
TOGGLE_WEBCHAT,
UPDATE_BOT_STATE,
UPDATE_DEV_SETTINGS,
UPDATE_HANDOFF,
UPDATE_JWT,
@@ -24,10 +25,31 @@ import {
UPDATE_SESSION,
UPDATE_THEME,
UPDATE_TYPING,
UPDATE_USER,
UPDATE_WEBVIEW,
} from './actions'
import { webchatReducer } from './webchat-reducer'

export const initialUser = {
id: undefined,
name: undefined,
userName: undefined,
channel: undefined,
idFromChannel: undefined,
isOnline: true,
}

const initialBotState = {
botId: undefined,
lastRoutePath: null,
isFirstInteraction: true,
retries: 0,
isHandoff: false,
isShadowing: false,
}

const initialSession = {}

export const webchatInitialState = {
width: WEBCHAT.DEFAULTS.WIDTH,
height: WEBCHAT.DEFAULTS.HEIGHT,
@@ -38,9 +60,9 @@ export const webchatInitialState = {
typing: false,
webview: null,
webviewParams: null,
session: { user: null },
lastRoutePath: null,
handoff: false,
// session: { user: null },
// lastRoutePath: null,
// handoff: false,
theme: {
headerTitle: WEBCHAT.DEFAULTS.TITLE,
brandColor: COLORS.BOTONIC_BLUE,
@@ -53,7 +75,7 @@ export const webchatInitialState = {
},
themeUpdates: {},
error: {},
online: true,
isWebchatOnline: true,
devSettings: { keepSessionOnReload: false },
isWebchatOpen: false,
isEmojiPickerOpen: false,
@@ -62,6 +84,9 @@ export const webchatInitialState = {
lastMessageUpdate: undefined,
currentAttachment: undefined,
jwt: null,
user: initialUser,
session: initialSession,
botState: initialBotState,
}

export function useWebchat() {
@@ -87,12 +112,23 @@ export function useWebchat() {
type: UPDATE_WEBVIEW,
payload: { webview, webviewParams: params },
})
const updateSession = session => {
const updateSession = session =>
webchatDispatch({
type: UPDATE_SESSION,
payload: session,
})
}

const updateUser = user =>
webchatDispatch({
type: UPDATE_USER,
payload: user,
})

const updateBotState = botState =>
webchatDispatch({
type: UPDATE_BOT_STATE,
payload: botState,
})

const updateLastRoutePath = path =>
webchatDispatch({
@@ -198,6 +234,8 @@ export function useWebchat() {
updateLastMessageDate,
setCurrentAttachment,
updateJwt,
updateBotState,
updateUser,
}
}

12 changes: 9 additions & 3 deletions packages/botonic-react/src/experimental/webchat/session-view.jsx
Original file line number Diff line number Diff line change
@@ -100,7 +100,9 @@ const KeepSessionContainer = styled.div`
export const SessionView = props => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { webchatState, updateDevSettings } = props.webchatHooks || useWebchat()
const { latestInput: input, session, lastRoutePath } = webchatState
const { latestInput: input, session, botState } = webchatState
const { type, id, ...latestInputData } = input

const toggleSessionView = () =>
updateDevSettings({
...webchatState.devSettings,
@@ -122,7 +124,7 @@ export const SessionView = props => {
label='INPUT:'
value={
input && Object.keys(input).length
? `[${input.type}] ${input.data || ''}`
? `[${type}] ${JSON.stringify(latestInputData) || ''}`
: ''
}
/>
@@ -137,8 +139,12 @@ export const SessionView = props => {
/>
<SessionViewAttribute
label='PATH:'
value={lastRoutePath ? `/${lastRoutePath}` : '/'}
value={botState.lastRoutePath ? `/${botState.lastRoutePath}` : '/'}
/>
<SessionViewAttribute label='BOT STATE:' />
<SessionContainer>
<JSONTree data={botState} hideRoot={true} />
</SessionContainer>
<SessionViewAttribute label='SESSION:' />
<SessionContainer>
<JSONTree data={session} hideRoot={true} />
Loading