Skip to content

Commit

Permalink
fix(channel-web): fix useSessionStorage config option (#5118)
Browse files Browse the repository at this point in the history
* fix(channel-web): fix useSessionStorage config option

* small fix

* merge master

* fix using session storage

* fix build

* reject with error when visutor id cannot be acquired

* added missing typings

* added automatic serialize and deserialize when using storage utils class

* callers now use storage own serialize + deserialize

* small fix

* pr comments

* update locale with config + setup socket only once on init

* chore(shared-lite): added unit tests for storage utils (#5405)

* chore(sharedLite): added unit tests for storage utils

* fix build

* updated unit tests

* removed ability to reconfigure useSessionStorage

* removed unecessary async/await

* better typings

* PR comments + bump studio v0.0.37

Co-authored-by: Samuel Massé <59894025+samuelmasse@users.noreply.github.com>
  • Loading branch information
laurentlp and samuelmasse committed Sep 16, 2021
1 parent f976538 commit edac7c6
Show file tree
Hide file tree
Showing 24 changed files with 347 additions and 82 deletions.
30 changes: 15 additions & 15 deletions modules/channel-web/src/views/lite/core/socket.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Config } from '../typings'
import { Config, StudioConnector } from '../typings'

export default class BpSocket {
private events: any
private userId: string
private userIdScope: string
private chatId: string | undefined

Expand All @@ -12,16 +11,12 @@ export default class BpSocket {
public onData: (event: any) => void
public onUserIdChanged: (userId: string) => void

constructor(bp, config: Config) {
constructor(bp: StudioConnector, config: Config) {
this.events = bp?.events
this.userIdScope = config.userIdScope
this.chatId = config.chatId
}

private isString(str: string | any): str is string {
return typeof str === 'string' && str !== 'undefined'
}

public setup() {
if (!this.events) {
return
Expand All @@ -39,7 +34,7 @@ export default class BpSocket {
this.events.onAny(this.postToParent)
}

public postToParent = (type: string, payload: any) => {
public postToParent = (_type: string, payload: any) => {
// we could filter on event type if necessary
window.parent?.postMessage({ ...payload, chatId: this.chatId }, '*')
}
Expand All @@ -51,23 +46,28 @@ export default class BpSocket {
}

/** Waits until the VISITOR ID and VISITOR SOCKET ID is set */
public waitForUserId(): Promise<void> {
public async waitForUserId(): Promise<void> {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (this.isString(window.__BP_VISITOR_ID) && this.isString(window.__BP_VISITOR_SOCKET_ID)) {
if (isString(window.__BP_VISITOR_ID) && isString(window.__BP_VISITOR_SOCKET_ID)) {
clearInterval(interval)

this.userId = window.__BP_VISITOR_ID
this.onUserIdChanged(this.userId)
this.postToParent('', { userId: this.userId })
const userId = window.__BP_VISITOR_ID
this.onUserIdChanged(userId)
this.postToParent('', { userId })

resolve()
}
}, 250)

setTimeout(() => {
clearInterval(interval)
reject()
}, 300000)
reject('Timeout to acquire VISITOR ID and VISITOR SOCKET ID exceeded.')
}, 30000)
})
}
}

const isString = (str: string | any): str is string => {
return typeof str === 'string' && str !== 'undefined'
}
24 changes: 13 additions & 11 deletions modules/channel-web/src/views/lite/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import constants from './core/constants'
import BpSocket from './core/socket'
import ChatIcon from './icons/Chat'
import { RootStore, StoreDef } from './store'
import { Config, Message, uuid } from './typings'
import { Config, Message, Overrides, uuid } from './typings'
import { checkLocationOrigin, initializeAnalytics, isIE, trackMessage, trackWebchatState } from './utils'

const _values = obj => Object.keys(obj).map(x => obj[x])
const _values = (obj: Overrides) => Object.keys(obj).map(x => obj[x])

class Web extends React.Component<MainProps> {
private config: Config
Expand Down Expand Up @@ -62,8 +62,10 @@ class Web extends React.Component<MainProps> {
}

componentDidUpdate() {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.initializeIfChatDisplayed()
if (this.config) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.initializeIfChatDisplayed()
}
}

async initializeIfChatDisplayed() {
Expand Down Expand Up @@ -110,7 +112,7 @@ class Web extends React.Component<MainProps> {
window.parent?.postMessage({ type, value, chatId: this.config.chatId }, '*')
}

extractConfig() {
extractConfig(): Config {
const decodeIfRequired = (options: string) => {
try {
return decodeURIComponent(options)
Expand All @@ -121,7 +123,7 @@ class Web extends React.Component<MainProps> {
const { options, ref } = queryString.parse(location.search)
const { config } = JSON.parse(decodeIfRequired(options || '{}'))

const userConfig = Object.assign({}, constants.DEFAULT_CONFIG, config)
const userConfig: Config = Object.assign({}, constants.DEFAULT_CONFIG, config)
userConfig.reference = config.ref || ref

this.props.updateConfig(userConfig, this.props.bp)
Expand All @@ -143,7 +145,7 @@ class Web extends React.Component<MainProps> {
await this.socket.waitForUserId()
}

loadOverrides(overrides) {
loadOverrides(overrides: Overrides) {
try {
for (const override of _values(overrides)) {
override.map(({ module }) => this.props.bp.loadModuleView(module, true))
Expand All @@ -159,8 +161,8 @@ class Web extends React.Component<MainProps> {
return
}

await this.socket.changeUserId(data.newValue)
await this.socket.setup()
this.socket.changeUserId(data.newValue)
this.socket.setup()
await this.socket.waitForUserId()
await this.props.initializeChat()
})
Expand Down Expand Up @@ -246,7 +248,7 @@ class Web extends React.Component<MainProps> {

if (!['session_reset'].includes(event.payload.type) && event.id !== this.lastMessageId) {
this.lastMessageId = event.id
this.props.store.loadEventInDebugger(event.id)
await this.props.store.loadEventInDebugger(event.id)
}
}

Expand All @@ -259,7 +261,7 @@ class Web extends React.Component<MainProps> {
await this.props.updateTyping(event)
}

handleDataMessage = event => {
handleDataMessage = (event: Message) => {
if (!event || !event.payload) {
return
}
Expand Down
7 changes: 2 additions & 5 deletions modules/channel-web/src/views/lite/store/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ComposerStore {
this.rootStore = rootStore

if (window.BP_STORAGE) {
this._sentHistory = JSON.parse(window.BP_STORAGE.get(SENT_HISTORY_KEY) || '[]')
this._sentHistory = window.BP_STORAGE.get<string[]>(SENT_HISTORY_KEY) || []
}
}

Expand All @@ -52,10 +52,7 @@ class ComposerStore {
this._sentHistoryIndex = 0

if (this.rootStore.config.enablePersistHistory) {
window.BP_STORAGE?.set(
SENT_HISTORY_KEY,
JSON.stringify(takeRight(this._sentHistory, constants.SENT_HISTORY_SIZE))
)
window.BP_STORAGE?.set(SENT_HISTORY_KEY, takeRight(this._sentHistory, constants.SENT_HISTORY_SIZE))
}
}
}
Expand Down
26 changes: 13 additions & 13 deletions modules/channel-web/src/views/lite/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RootStore {
public composer: ComposerStore
public view: ViewStore

private _typingInterval
private _typingInterval: ReturnType<typeof setInterval> | undefined
private api: WebchatApi

@observable
Expand Down Expand Up @@ -76,11 +76,10 @@ class RootStore {
constructor({ fullscreen }) {
this.composer = new ComposerStore(this)
this.view = new ViewStore(this, fullscreen)
this.updateBotUILanguage(chosenLocale)
}

@action.bound
setIntlProvider(provider) {
setIntlProvider(provider: InjectedIntl) {
this.intl = provider
}

Expand Down Expand Up @@ -140,7 +139,7 @@ class RootStore {
}

@action.bound
updateMessages(messages) {
updateMessages(messages: Message[]) {
this.currentConversation.messages = messages
}

Expand Down Expand Up @@ -427,20 +426,21 @@ class RootStore {
this.view.disableAnimations = this.config.disableAnimations
this.config.showPoweredBy ? this.view.showPoweredBy() : this.view.hidePoweredBy()

const locale = getUserLocale(this.config.locale)
this.config.locale && this.updateBotUILanguage(locale)
document.documentElement.setAttribute('lang', locale)

document.title = this.config.botName || 'Botpress Webchat'

try {
this.api.updateAxiosConfig({ botId: this.config.botId, externalAuthToken: this.config.externalAuthToken })
this.api.updateUserId(this.config.userId)

if (!this.isInitialized) {
window.USE_SESSION_STORAGE = this.config.useSessionStorage
} catch {
console.error('Could not set USE_SESSION_STORAGE')
} else if (window.USE_SESSION_STORAGE !== this.config.useSessionStorage) {
console.warn('[WebChat] "useSessionStorage" value cannot be altered once the webchat is initialized')
}

this.api.updateAxiosConfig({ botId: this.config.botId, externalAuthToken: this.config.externalAuthToken })
this.api.updateUserId(this.config.userId)
const locale = this.config.locale ? getUserLocale(this.config.locale) : chosenLocale
this.updateBotUILanguage(locale)
document.documentElement.setAttribute('lang', locale)

this.publishConfigChanged()
}

Expand Down
2 changes: 1 addition & 1 deletion modules/channel-web/src/views/lite/translations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const DEFAULT_LOCALE = 'en'
const STORAGE_KEY = 'bp/channel-web/user-lang'
const translations = { en, fr, pt, es, ar, ru, uk, de, it }

const cleanLanguageCode = str => str.split('-')[0]
const cleanLanguageCode = (str: string) => str.split('-')[0]
const getNavigatorLanguage = () => cleanLanguageCode(navigator.language || navigator['userLanguage'] || '')
const getStorageLanguage = () => cleanLanguageCode(window.BP_STORAGE?.get(STORAGE_KEY) || '')

Expand Down
3 changes: 2 additions & 1 deletion modules/channel-web/src/views/lite/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BPStorage } from '../../../../../packages/ui-shared-lite/utils/storage'
import { RootStore } from './store'

declare global {
Expand All @@ -16,7 +17,7 @@ declare global {
SEND_USAGE_STATS: boolean
SHOW_POWERED_BY: boolean
USE_SESSION_STORAGE: boolean
BP_STORAGE: any
BP_STORAGE: BPStorage
botpress: {
[moduleName: string]: any
}
Expand Down
3 changes: 3 additions & 0 deletions modules/hitlnext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"serialize-javascript": "^5.0.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@types/serialize-javascript": "^5.0.1"
},
"resolutions": {
"fstream": ">=1.0.12",
"lodash": ">=4.17.21"
Expand Down
11 changes: 0 additions & 11 deletions modules/hitlnext/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,6 @@ import * as sdk from 'botpress/sdk'
import { UserProfile } from 'common/typings'

// TODO fix this and use those from common/typings
declare global {
interface Window {
botpressWebChat: {
init: (config: any, containerSelector?: string) => void
sendEvent: (payload: any, webchatId?: string) => void
}
BOT_ID: string
BP_STORAGE: any
ROOT_PATH: string
}
}
export interface AuthRule {
res: string
op: string
Expand Down
2 changes: 1 addition & 1 deletion modules/hitlnext/src/views/full/app/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const get = (path: string, defaultValue = undefined) => {
return data ? deserialize(data) : defaultValue
}

const set = (path: string, data) => {
const set = (path: string, data: any) => {
window.BP_STORAGE.set(key(path), serialize(data))
}

Expand Down
13 changes: 13 additions & 0 deletions modules/hitlnext/src/views/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BPStorage } from '../../../../packages/ui-shared-lite/utils/storage'

declare global {
interface Window {
botpressWebChat: {
init: (config: any, containerSelector?: string) => void
sendEvent: (payload: any, webchatId?: string) => void
}
BOT_ID: string
BP_STORAGE: BPStorage
ROOT_PATH: string
}
}
5 changes: 5 additions & 0 deletions modules/hitlnext/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
resolved "https://registry.yarnpkg.com/@types/random-seed/-/random-seed-0.3.3.tgz#7741f7b0a4513198a9396ce4ad25832f799a6727"
integrity sha512-kHsCbIRHNXJo6EN5W8EA5b4i1hdT6jaZke5crBPLUcLqaLdZ0QBq8QVMbafHzhjFF83Cl9qlee2dChD18d/kPg==

"@types/serialize-javascript@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/serialize-javascript/-/serialize-javascript-5.0.1.tgz#2c32e0626734a02a83b94cb924699c4bdcb6fd94"
integrity sha512-QqgTcm7IgIt/oWNFQMlpVv5Z3saYtxWK9yFrAUkk3jxvjbqIG835xNNoOYq12mXKQMuWGc+PgOXwXy92eax5BA==

"@webscopeio/react-textarea-autocomplete@^4.7.2":
version "4.7.2"
resolved "https://registry.yarnpkg.com/@webscopeio/react-textarea-autocomplete/-/react-textarea-autocomplete-4.7.2.tgz#e8cf8349ec285739a58b965a9c3cd7b3b71a5f34"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"version": "0.1.6"
},
"studio": {
"version": "0.0.36"
"version": "0.0.37"
},
"messaging": {
"version": "0.1.13"
Expand All @@ -26,7 +26,7 @@
"build": "yarn cmd build",
"watch": "yarn cmd watch",
"package": "yarn cmd package",
"test": "cross-env NATIVE_EXTENSIONS_DIR=build/native-extensions cross-env jest -i --detectOpenHandles -c jest.config.js",
"test": "cross-env NATIVE_EXTENSIONS_DIR=build/native-extensions cross-env jest -i --detectOpenHandles -c jest.config.js && yarn workspace ui-shared-lite test",
"reference": "yarn cmd build:reference",
"itest": "yarn workspace botpress itest",
"commitmsg": "commitlint -e $GIT_PARAMS",
Expand Down
4 changes: 2 additions & 2 deletions packages/ui-lite/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// import { BPStorage } from '~/util/storage'
import { BPStorage } from '../../ui-shared-lite/utils/storage'

// TODO: remove when at least one typing is exported from this file
export interface test {}
Expand All @@ -22,7 +22,7 @@ declare global {
SOCKET_TRANSPORTS: string[]
ANALYTICS_ID: string
UUID: string
BP_STORAGE: any
BP_STORAGE: BPStorage
EXPERIMENTAL: boolean
USE_SESSION_STORAGE: boolean
USE_ONEFLOW: boolean
Expand Down
7 changes: 3 additions & 4 deletions packages/ui-shared-lite/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ export const TOKEN_KEY = 'bp/token'
const MIN_MS_LEFT_BEFORE_REFRESH = ms('5m')

export const getToken = (onlyToken: boolean = true): StoredToken | string | undefined => {
const token = storage.get(TOKEN_KEY)
const parsed = token && JSON.parse(token)
const parsedToken = storage.get<StoredToken>(TOKEN_KEY)

return onlyToken ? parsed && parsed.token : parsed
return onlyToken ? parsedToken && parsedToken.token : parsedToken
}

export const setToken = (token: Partial<TokenResponse>): void => {
Expand All @@ -29,7 +28,7 @@ export const setToken = (token: Partial<TokenResponse>): void => {
storedToken = { token: token.jwt, expiresAt: tokenUser.exp, issuedAt: tokenUser.iat! }
}

storage.set(TOKEN_KEY, JSON.stringify(storedToken))
storage.set(TOKEN_KEY, storedToken)
}

export const isTokenValid = (): boolean => {
Expand Down
16 changes: 16 additions & 0 deletions packages/ui-shared-lite/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const tsjPreset = require('ts-jest/presets')

module.exports = {
clearMocks: true,
preset: 'ts-jest',
testEnvironment: 'jsdom',
transform: {
...tsjPreset.transform
},
globals: {
'ts-jest': {
tsConfig: '<rootDir>../ui-shared/tsconfig.json',
diagnostics: false
}
}
}

0 comments on commit edac7c6

Please sign in to comment.