Skip to content

Commit a476865

Browse files
committed
WIP: load initialState from localStorage
1 parent 78cebc1 commit a476865

File tree

10 files changed

+294
-104
lines changed

10 files changed

+294
-104
lines changed

packages/botonic-plugin-flow-builder/src/api.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ export class FlowBuilderApi {
5757
headers: { Authorization: `Bearer ${token}` },
5858
})
5959
this.request.session.organization_id = response.data.organization_id
60-
this.request.session.bot.id = response.data.bot_id
60+
this.request.session.bot = {
61+
...this.request.session.bot,
62+
id: response.data.bot_id,
63+
}
6164
}
6265

6366
getNodeByFlowId(id: string): HtNodeWithContent {

packages/botonic-react/src/index-types.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
WebchatSettingsProps,
1919
} from './components'
2020
import { CloseWebviewOptions } from './contexts'
21+
import { DevSettings } from './webchat/context/types'
2122
import { UseWebchat } from './webchat/context/use-webchat'
2223
import {
2324
CoverComponentOptions,
@@ -90,7 +91,7 @@ export interface WebchatArgs {
9091
defaultTyping?: number
9192
storage?: Storage | null
9293
storageKey?: string
93-
onInit?: (app: WebchatApp, args: any) => void
94+
onInit?: (app: WebchatApp, args: any) => Promise<void>
9495
onOpen?: (app: WebchatApp, args: any) => void
9596
onClose?: (app: WebchatApp, args: any) => void
9697
onMessage?: (app: WebchatApp, message: WebchatMessage) => void
@@ -102,18 +103,18 @@ export interface WebchatArgs {
102103
}
103104

104105
export interface WebchatProps {
105-
webchatHooks?: UseWebchat
106+
webchatHooks: UseWebchat
106107
initialSession?: any
107-
initialDevSettings?: any
108+
initialDevSettings?: DevSettings
108109
onStateChange: (args: OnStateChangeArgs) => void
109110

110111
shadowDOM?: any
111112
theme?: WebchatTheme
112-
storage?: Storage | null
113-
storageKey?: string | (() => string)
113+
storage: Storage
114+
storageKey: string
114115
defaultDelay?: number
115116
defaultTyping?: number
116-
onInit?: (args?: any) => void
117+
onInit?: (args?: any) => Promise<void>
117118
onOpen?: (args?: any) => void
118119
onClose?: (args?: any) => void
119120
onUserInput(args: OnUserInputArgs): Promise<void>

packages/botonic-react/src/util/webchat.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function getSystemLocale(user: Partial<ClientUser>) {
9090
export const shouldKeepSessionOnReload = ({
9191
initialDevSettings,
9292
devSettings,
93-
}) => !initialDevSettings || (devSettings && devSettings.keepSessionOnReload)
93+
}) => !initialDevSettings || devSettings?.keepSessionOnReload
9494

9595
//TODO: Review param serverConfig if is of type ServerConfig this never have errorMessage
9696
export const getServerErrorMessage = serverConfig => {

packages/botonic-react/src/webchat-app.tsx

+31-6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
WebchatTheme,
2828
} from './webchat/theme/types'
2929
import { Webchat } from './webchat/webchat'
30+
import { WebchatHooks } from './webchat/webchat-hooks'
3031

3132
export class WebchatApp {
3233
public theme?: Partial<WebchatTheme>
@@ -41,9 +42,9 @@ export class WebchatApp {
4142
public shadowDOM?: boolean | (() => boolean)
4243
public defaultDelay?: number
4344
public defaultTyping?: number
44-
public storage?: Storage | null
45+
public storage: Storage
4546
public storageKey: string
46-
public onInit?: (app: WebchatApp, args: any) => void
47+
public onInit?: (app: WebchatApp, args: any) => Promise<void>
4748
public onOpen?: (app: WebchatApp, args: any) => void
4849
public onClose?: (app: WebchatApp, args: any) => void
4950
public onMessage?: (app: WebchatApp, message: WebchatMessage) => void
@@ -107,7 +108,7 @@ export class WebchatApp {
107108
this.hostId = hostId || WEBCHAT.DEFAULTS.HOST_ID
108109
this.defaultDelay = defaultDelay
109110
this.defaultTyping = defaultTyping
110-
this.storage = storage === undefined ? localStorage : storage
111+
this.storage = !storage ? localStorage : storage
111112
this.storageKey = storageKey || WEBCHAT.DEFAULTS.STORAGE_KEY
112113
this.onInit = onInit
113114
this.onOpen = onOpen
@@ -175,8 +176,8 @@ export class WebchatApp {
175176
return node
176177
}
177178

178-
onInitWebchat(...args: [any]) {
179-
this.onInit && this.onInit(this, ...args)
179+
async onInitWebchat(...args: [any]) {
180+
this.onInit && (await this.onInit(this, ...args))
180181
}
181182

182183
onOpenWebchat(...args: [any]) {
@@ -485,7 +486,31 @@ export class WebchatApp {
485486
this.createRootElement(host)
486487

487488
return (
488-
<Webchat
489+
// <Webchat
490+
// {...webchatOptions}
491+
// ref={this.webchatRef}
492+
// host={this.host}
493+
// shadowDOM={this.shadowDOM}
494+
// theme={theme as WebchatTheme}
495+
// storage={this.storage}
496+
// storageKey={this.storageKey}
497+
// defaultDelay={defaultDelay}
498+
// defaultTyping={defaultTyping}
499+
// onInit={(...args: [any]) => this.onInitWebchat(...args)}
500+
// onOpen={(...args: [any]) => this.onOpenWebchat(...args)}
501+
// onClose={(...args: [any]) => this.onCloseWebchat(...args)}
502+
// onUserInput={(...args: [any]) => this.onUserInput(...args)}
503+
// onStateChange={(args: OnStateChangeArgs) => {
504+
// this.onStateChange(args)
505+
// }}
506+
// onTrackEvent={(
507+
// request: ActionRequest,
508+
// eventName: string,
509+
// args?: EventArgs
510+
// ) => this.onTrackEventWebchat(request, eventName, args)}
511+
// server={server}
512+
// />
513+
<WebchatHooks
489514
{...webchatOptions}
490515
ref={this.webchatRef}
491516
host={this.host}

packages/botonic-react/src/webchat/context/use-webchat.ts

+61-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import { Session } from '@botonic/core'
2-
import { useReducer, useRef } from 'react'
2+
import merge from 'lodash.merge'
3+
import { useEffect, useReducer, useRef } from 'react'
34

45
import { Reply } from '../../components'
56
import { Webview } from '../../components/index-types'
67
import { WebchatMessage } from '../../index-types'
8+
import { msgToBotonic } from '../../msg-to-botonic'
9+
import { initSession, shouldKeepSessionOnReload } from '../../util'
710
import { defaultTheme } from '../theme/default-theme'
811
import { WebchatTheme } from '../theme/types'
12+
import { BotonicStorage } from '../use-botonic-storage'
913
import { WebchatAction } from './actions'
1014
import { ClientInput, DevSettings, ErrorMessage, WebchatState } from './types'
1115
import { webchatReducer } from './webchat-reducer'
1216

13-
function getWebchatInitialState(initialTheme: WebchatTheme): WebchatState {
17+
function getWebchatInitialState(
18+
initialTheme: WebchatTheme,
19+
botonicStorage?: BotonicStorage,
20+
initialSession?: Partial<Session>,
21+
devSettings?: DevSettings
22+
): WebchatState {
23+
const initialStorageSession = initSession(botonicStorage?.session)
24+
const session = merge(initialSession, initialStorageSession)
25+
const lastMessageUpdate = botonicStorage?.lastMessageUpdate
26+
const themeUpdates = botonicStorage?.themeUpdates || {}
27+
const lastRoutePath = botonicStorage?.lastRoutePath
28+
console.log('getWebchatInitialState devSettings', devSettings)
29+
1430
return {
1531
replies: [],
1632
messagesJSON: [],
@@ -19,20 +35,20 @@ function getWebchatInitialState(initialTheme: WebchatTheme): WebchatState {
1935
typing: false,
2036
webview: null,
2137
webviewParams: null,
22-
session: { user: undefined },
23-
lastRoutePath: undefined,
38+
session: session as Partial<Session>,
39+
lastRoutePath: lastRoutePath,
2440
handoff: false,
2541
theme: initialTheme,
26-
themeUpdates: {},
42+
themeUpdates: themeUpdates,
2743
error: {},
2844
online: true,
29-
devSettings: { keepSessionOnReload: false },
45+
devSettings: devSettings || { keepSessionOnReload: false },
3046
isWebchatOpen: false,
3147
isEmojiPickerOpen: false,
3248
isPersistentMenuOpen: false,
3349
isCoverComponentOpen: false,
3450
isCustomComponentRendered: false,
35-
lastMessageUpdate: undefined,
51+
lastMessageUpdate: lastMessageUpdate,
3652
currentAttachment: undefined,
3753
numUnreadMessages: 0,
3854
isLastMessageVisible: true,
@@ -77,9 +93,20 @@ export interface UseWebchat {
7793
inputPanelRef: React.MutableRefObject<HTMLDivElement | null>
7894
}
7995

80-
export function useWebchat(theme?: WebchatTheme): UseWebchat {
96+
export function useWebchat(
97+
theme?: WebchatTheme,
98+
botonicStorage?: BotonicStorage,
99+
initialSession?: Partial<Session>,
100+
devSettings?: DevSettings
101+
): UseWebchat {
81102
const initialTheme = theme || defaultTheme
82-
const webchatInitialState = getWebchatInitialState(initialTheme)
103+
console.log('useWebchat devSettings', devSettings)
104+
const webchatInitialState = getWebchatInitialState(
105+
initialTheme,
106+
botonicStorage,
107+
initialSession,
108+
devSettings
109+
)
83110

84111
const [webchatState, webchatDispatch] = useReducer(
85112
webchatReducer,
@@ -245,6 +272,31 @@ export function useWebchat(theme?: WebchatTheme): UseWebchat {
245272
})
246273
}
247274

275+
useEffect(() => {
276+
// const keepSessionOnReload = shouldKeepSessionOnReload({
277+
// devSettings,
278+
// initialDevSettings,
279+
// })
280+
281+
const keepSessionOnReload = devSettings?.keepSessionOnReload
282+
console.log('useWebchat useEffect keepSessionOnReload', keepSessionOnReload)
283+
if (keepSessionOnReload) {
284+
const messagesJSON = botonicStorage?.messages || []
285+
for (const message of messagesJSON) {
286+
addMessage(message)
287+
288+
const newMessageComponent = msgToBotonic(
289+
{ ...message, delay: 0, typing: 0 },
290+
initialTheme.message?.customTypes
291+
)
292+
if (newMessageComponent) {
293+
//@ts-ignore
294+
addMessageComponent(newMessageComponent)
295+
}
296+
}
297+
}
298+
}, [])
299+
248300
return {
249301
addMessage,
250302
addMessageComponent,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Session } from 'node:inspector'
2+
3+
import { WebchatMessage } from '../index-types'
4+
import { stringifyWithRegexs } from '../util/regexs'
5+
import { DevSettings } from './context/types'
6+
import { WebchatTheme } from './theme/types'
7+
8+
export interface BotonicStorage {
9+
messages: WebchatMessage[]
10+
session: Session
11+
lastRoutePath: string
12+
devSettings: DevSettings
13+
lastMessageUpdate: string
14+
themeUpdates: WebchatTheme
15+
}
16+
17+
export function useBotonicStorage(storage: Storage, storageKey: string) {
18+
const getBotonicStorage = (): BotonicStorage | undefined => {
19+
const botonicStorage = storage.getItem(storageKey)
20+
return botonicStorage ? JSON.parse(botonicStorage) : undefined
21+
}
22+
23+
const getBotonicStorageAttribute = <T = any>(key: string): T => {
24+
const botonicStorage = getBotonicStorage()
25+
return botonicStorage?.[key]
26+
}
27+
28+
const setBotonicStorage = (value: any) => {
29+
const stringValueJSON = JSON.stringify(
30+
JSON.parse(stringifyWithRegexs(value))
31+
)
32+
storage.setItem(storageKey, stringValueJSON)
33+
}
34+
35+
return {
36+
getBotonicStorage,
37+
setBotonicStorage,
38+
getBotonicStorageAttribute,
39+
}
40+
}

packages/botonic-react/src/webchat/use-storage-state-hook.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const IS_BROWSER =
66
typeof navigator !== 'undefined' &&
77
typeof document !== 'undefined'
88

9-
export function useStorageState(storage, key, defaultValue) {
9+
export function useStorageState(storage, key) {
1010
let evtTarget
1111

1212
try {
@@ -17,7 +17,7 @@ export function useStorageState(storage, key, defaultValue) {
1717

1818
const raw = storage?.getItem(key)
1919

20-
const [value, setValue] = useState(raw ? JSON.parse(raw) : defaultValue)
20+
const [value, setValue] = useState(raw ? JSON.parse(raw) : undefined)
2121

2222
const updater = useCallback(
2323
(updatedValue, remove = false) => {
@@ -33,8 +33,6 @@ export function useStorageState(storage, key, defaultValue) {
3333
[key]
3434
)
3535

36-
defaultValue != null && !raw && updater(defaultValue)
37-
3836
useEffect(() => {
3937
const listener = ({ detail }) => {
4038
if (detail.key === key) {
@@ -51,5 +49,5 @@ export function useStorageState(storage, key, defaultValue) {
5149
if (storage === null) {
5250
return [undefined, undefined]
5351
}
54-
return [value, updater, () => updater(null, true)]
52+
return [value, updater]
5553
}

packages/botonic-react/src/webchat/webchat-dev.jsx

+20-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import styled from 'styled-components'
66

77
import { useWebchat } from './context/use-webchat'
88
import { SessionView } from './session-view'
9+
import { useBotonicStorage } from './use-botonic-storage'
910
import { Webchat } from './webchat'
1011

1112
export const DebugTab = styled.div`
@@ -46,7 +47,24 @@ const initialSession = {
4647

4748
// eslint-disable-next-line react/display-name
4849
export const WebchatDev = forwardRef((props, ref) => {
49-
const webchatHooks = useWebchat()
50+
const { getBotonicStorage } = useBotonicStorage(
51+
props.storage,
52+
props.storageKey
53+
)
54+
const devSettings =
55+
getBotonicStorage()?.devSettings || props.initialDevSettings
56+
57+
console.log('devSettings', {
58+
keepSessionOnReload: devSettings?.keepSessionOnReload,
59+
showSessionView: devSettings?.showSessionView,
60+
})
61+
62+
const webchatHooks = useWebchat(
63+
undefined,
64+
getBotonicStorage(),
65+
initialSession,
66+
devSettings
67+
)
5068
const { webchatState, updateTheme } = webchatHooks
5169

5270
/* TODO: webchatState.theme should be included in the dependencies array
@@ -63,7 +81,7 @@ export const WebchatDev = forwardRef((props, ref) => {
6381
{...props}
6482
ref={ref}
6583
webchatHooks={webchatHooks}
66-
initialSession={initialSession}
84+
// initialSession={initialSession}
6785
initialDevSettings={{
6886
keepSessionOnReload: webchatState.devSettings.keepSessionOnReload,
6987
showSessionView: webchatState.devSettings.showSessionView,

0 commit comments

Comments
 (0)