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
Show file tree
Hide file tree
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
Expand Up @@ -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,
})
Expand Down
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'

Expand Down Expand Up @@ -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
Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions packages/botonic-react/src/experimental/dev-app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down
29 changes: 16 additions & 13 deletions packages/botonic-react/src/experimental/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
)
Expand All @@ -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({
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)}
Expand All @@ -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({
Expand Down Expand Up @@ -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 })
Expand All @@ -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'
Expand Down
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'
Expand Down Expand Up @@ -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 = ({
Expand Down
41 changes: 27 additions & 14 deletions packages/botonic-react/src/experimental/webchat-app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions packages/botonic-react/src/experimental/webchat/actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -13,6 +13,7 @@ import {
TOGGLE_EMOJI_PICKER,
TOGGLE_PERSISTENT_MENU,
TOGGLE_WEBCHAT,
UPDATE_BOT_STATE,
UPDATE_DEV_SETTINGS,
UPDATE_HANDOFF,
UPDATE_JWT,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -53,7 +75,7 @@ export const webchatInitialState = {
},
themeUpdates: {},
error: {},
online: true,
isWebchatOnline: true,
devSettings: { keepSessionOnReload: false },
isWebchatOpen: false,
isEmojiPickerOpen: false,
Expand All @@ -62,6 +84,9 @@ export const webchatInitialState = {
lastMessageUpdate: undefined,
currentAttachment: undefined,
jwt: null,
user: initialUser,
session: initialSession,
botState: initialBotState,
}

export function useWebchat() {
Expand All @@ -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({
Expand Down Expand Up @@ -198,6 +234,8 @@ export function useWebchat() {
updateLastMessageDate,
setCurrentAttachment,
updateJwt,
updateBotState,
updateUser,
}
}

Expand Down
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
Expand Up @@ -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,
Expand All @@ -122,7 +124,7 @@ export const SessionView = props => {
label='INPUT:'
value={
input && Object.keys(input).length
? `[${input.type}] ${input.data || ''}`
? `[${type}] ${JSON.stringify(latestInputData) || ''}`
: ''
}
/>
Expand All @@ -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} />
Expand Down
Loading