diff --git a/packages/htmlembed/CHANGELOG.md b/packages/htmlembed/CHANGELOG.md index 5253cf9f0..4888e510b 100644 --- a/packages/htmlembed/CHANGELOG.md +++ b/packages/htmlembed/CHANGELOG.md @@ -1,5 +1,12 @@ # @embeddedchat/htmlembed +## 0.0.3 + +### Patch Changes + +- Updated dependencies + - @embeddedchat/react@0.1.5 + ## 0.0.2 ### Patch Changes diff --git a/packages/htmlembed/package.json b/packages/htmlembed/package.json index b4eaa88ef..77d06ec61 100644 --- a/packages/htmlembed/package.json +++ b/packages/htmlembed/package.json @@ -1,7 +1,7 @@ { "name": "@embeddedchat/htmlembed", "description": "A lightweight and easy-to-use package that allows seamless integration of Embedded Chat into web applications using a simple HTML snippet. Ideal for quickly embedding chat functionalities without complex setup", - "version": "0.0.2", + "version": "0.0.3", "main": "index.js", "license": "MIT", "type": "module", @@ -11,7 +11,7 @@ "preview": "npm run build && vite preview --port=4001" }, "dependencies": { - "@embeddedchat/react": "0.1.4", + "@embeddedchat/react": "0.1.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index aaffd245f..3e5a5ab98 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,11 @@ # @embeddedchat/react +## 0.1.5 + +### Patch Changes + +- support @embeddedchat/react for Server Side Rendering + ## 0.1.0 ### Minor Changes diff --git a/packages/react/package.json b/packages/react/package.json index 8aed0a5c1..b782fb505 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@embeddedchat/react", - "version": "0.1.4", + "version": "0.1.7", "description": "React component library for embedding Rocket.Chat in React applications, providing customizable and feature-rich chat interfaces with easy React integration.", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", @@ -81,10 +81,11 @@ "schedule": "^0.4.0", "storybook": "^7.0.26" }, - "peerDependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, + "peerDependencies": { + "react": ">=17.0.2 <19.0.0", + "react-dom": ">=17.0.2 <19.0.0" + } + , "dependencies": { "@embeddedchat/api": "0.0.1", "@emotion/babel-preset-css-prop": "^11.11.0", diff --git a/packages/react/src/components/ChatHeader/ChatHeader.js b/packages/react/src/components/ChatHeader/ChatHeader.js index 51531ec41..0c3bb3d9a 100644 --- a/packages/react/src/components/ChatHeader/ChatHeader.js +++ b/packages/react/src/components/ChatHeader/ChatHeader.js @@ -1,8 +1,8 @@ -import React, { useCallback, useContext, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import stylesSheet from './ChatHeader.module.css'; -import RCContext, { useRCContext } from '../../context/RCInstance'; +import { useRCContext } from '../../context/RCInstance'; import { useUserStore, useMessageStore, diff --git a/packages/react/src/components/EmbeddedChat.js b/packages/react/src/components/EmbeddedChat.js new file mode 100644 index 000000000..3e7c63dfc --- /dev/null +++ b/packages/react/src/components/EmbeddedChat.js @@ -0,0 +1,245 @@ +import React, { memo, useEffect, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { css, ThemeProvider } from '@emotion/react'; +import { EmbeddedChatApi } from '@embeddedchat/api'; +import { ChatBody } from './ChatBody'; +import { ChatHeader } from './ChatHeader'; +import { ChatInput } from './ChatInput'; +import { Home } from './Home'; +import { RCInstanceProvider } from '../context/RCInstance'; +import { useToastStore, useUserStore } from '../store'; +import AttachmentWindow from './Attachments/AttachmentWindow'; +import useAttachmentWindowStore from '../store/attachmentwindow'; +import DefaultTheme from '../theme/DefaultTheme'; +import { deleteToken, getToken, saveToken } from '../lib/auth'; +import { Box } from './Box'; +import useComponentOverrides from '../theme/useComponentOverrides'; +import { ToastBarProvider } from './ToastBar'; + +const EmbeddedChat = ({ + isClosable = false, + setClosableState, + moreOpts = false, + width = '100%', + height = '50vh', + host = 'http://localhost:3000', + roomId = 'GENERAL', + channelName, + anonymousMode = false, + toastBarPosition = 'bottom right', + showRoles = false, + showAvatar = false, + enableThreads = false, + theme = null, + className = '', + style = {}, + hideHeader = false, + auth = { + flow: 'PASSWORD', + }, +}) => { + const { classNames, styleOverrides } = useComponentOverrides('EmbeddedChat'); + const [fullScreen, setFullScreen] = useState(false); + const setToastbarPosition = useToastStore((state) => state.setPosition); + const setShowAvatar = useUserStore((state) => state.setShowAvatar); + useEffect(() => { + setToastbarPosition(toastBarPosition); + setShowAvatar(showAvatar); + }, []); + + if (isClosable && !setClosableState) { + throw Error( + 'Please provide a setClosableState to props when isClosable = true' + ); + } + + const [RCInstance, setRCInstance] = useState(() => { + const newRCInstance = new EmbeddedChatApi(host, roomId, { + getToken, + deleteToken, + saveToken, + autoLogin: ['PASSWORD', 'OAUTH'].includes(auth.flow), + }); + if (auth.flow === 'TOKEN') { + newRCInstance.auth.loginWithOAuthServiceToken(auth.credentials); + } + return newRCInstance; + }); + + useEffect(() => { + const reInstantiate = () => { + const newRCInstance = new EmbeddedChatApi(host, roomId, { + getToken, + deleteToken, + saveToken, + autoLogin: ['PASSWORD', 'OAUTH'].includes(auth.flow), + }); + if (auth.flow === 'TOKEN') { + newRCInstance.auth.loginWithOAuthServiceToken(auth.credentials); + } + setRCInstance(newRCInstance); + }; + + if (RCInstance.rcClient.loggedIn()) { + RCInstance.close().then(reInstantiate).catch(console.error); + } else { + reInstantiate(); + } + }, [roomId, host, auth?.flow]); + + const isUserAuthenticated = useUserStore( + (state) => state.isUserAuthenticated + ); + const setIsUserAuthenticated = useUserStore( + (state) => state.setIsUserAuthenticated + ); + + const setAuthenticatedUserUsername = useUserStore( + (state) => state.setUsername + ); + const setAuthenticatedUserAvatarUrl = useUserStore( + (state) => state.setUserAvatarUrl + ); + const setAuthenticatedUserId = useUserStore((state) => state.setUserId); + const setAuthenticatedName = useUserStore((state) => state.setName); + + useEffect(() => { + RCInstance.auth.onAuthChange((user) => { + // getUserEssentials(); + if (user) { + RCInstance.connect() + .then(() => { + console.log(`Connected to RocketChat ${RCInstance.host}`); + }) + .catch(console.error); + console.log('reinstantiated'); + const { me } = user; + setAuthenticatedUserAvatarUrl(me.avatarUrl); + setAuthenticatedUserUsername(me.username); + setAuthenticatedUserId(me._id); + setAuthenticatedName(me.name); + setIsUserAuthenticated(true); + } else { + setIsUserAuthenticated(false); + } + }); + }, [ + RCInstance, + setAuthenticatedName, + setAuthenticatedUserAvatarUrl, + setAuthenticatedUserId, + setAuthenticatedUserUsername, + setIsUserAuthenticated, + ]); + + const attachmentWindowOpen = useAttachmentWindowStore((state) => state.open); + + const ECOptions = useMemo( + () => ({ + enableThreads, + authFlow: auth.flow, + width, + height, + host, + roomId, + channelName, + showRoles, + showAvatar, + hideHeader, + anonymousMode, + }), + [ + enableThreads, + auth.flow, + width, + height, + host, + roomId, + channelName, + showRoles, + showAvatar, + hideHeader, + anonymousMode, + ] + ); + + const RCContextValue = useMemo( + () => ({ RCInstance, ECOptions }), + [RCInstance, ECOptions] + ); + + return ( + + + + {attachmentWindowOpen ? : null} + + {hideHeader ? null : ( + + )} + {isUserAuthenticated || anonymousMode ? ( + + ) : ( + + )} + + + + + + ); +}; + +EmbeddedChat.propTypes = { + width: PropTypes.string, + height: PropTypes.string, + isClosable: PropTypes.bool, + setClosableState: PropTypes.func, + moreOpts: PropTypes.bool, + host: PropTypes.string, + roomId: PropTypes.string, + channelName: PropTypes.string, + anonymousMode: PropTypes.bool, + toastBarPosition: PropTypes.string, + showRoles: PropTypes.bool, + showAvatar: PropTypes.bool, + enableThreads: PropTypes.bool, + theme: PropTypes.object, + auth: PropTypes.oneOfType([ + PropTypes.shape({ flow: PropTypes.oneOf(['PASSWORD']) }), + PropTypes.shape({ flow: PropTypes.oneOf(['OAUTH']) }), + PropTypes.shape({ + flow: PropTypes.oneOf(['TOKEN']), + credentials: PropTypes.object, + }), + ]), + className: PropTypes.string, + style: PropTypes.object, + hideHeader: PropTypes.bool, +}; + +export default memo(EmbeddedChat); diff --git a/packages/react/src/components/index.js b/packages/react/src/components/index.js deleted file mode 100644 index cf565782b..000000000 --- a/packages/react/src/components/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { ChatBody } from './ChatBody'; -export { ChatHeader } from './ChatHeader'; -export { ChatInput } from './ChatInput'; -export { EmojiPicker } from './EmojiPicker'; -export { Home } from './Home'; diff --git a/packages/react/src/index.js b/packages/react/src/index.js index cf36c0c90..6f3263f72 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -1,240 +1 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { css, ThemeProvider } from '@emotion/react'; -import { EmbeddedChatApi } from '@embeddedchat/api'; -import { ChatBody, ChatHeader, ChatInput, Home } from './components'; -import { RCInstanceProvider } from './context/RCInstance'; -import { useToastStore, useUserStore } from './store'; -import AttachmentWindow from './components/Attachments/AttachmentWindow'; -import useAttachmentWindowStore from './store/attachmentwindow'; -import DefaultTheme from './theme/DefaultTheme'; -import { deleteToken, getToken, saveToken } from './lib/auth'; -import { Box } from './components/Box'; -import useComponentOverrides from './theme/useComponentOverrides'; -import { ToastBarProvider } from './components/ToastBar'; - -export const EmbeddedChat = ({ - isClosable = false, - setClosableState, - moreOpts = false, - width = '100%', - height = '50vh', - host = 'http://localhost:3000', - roomId = 'GENERAL', - channelName, - anonymousMode = false, - toastBarPosition = 'bottom right', - showRoles = false, - showAvatar = false, - enableThreads = false, - theme = null, - className = '', - style = {}, - hideHeader = false, - auth = { - flow: 'PASSWORD', - }, -}) => { - const { classNames, styleOverrides } = useComponentOverrides('EmbeddedChat'); - const [fullScreen, setFullScreen] = useState(false); - const setToastbarPosition = useToastStore((state) => state.setPosition); - const setShowAvatar = useUserStore((state) => state.setShowAvatar); - useEffect(() => { - setToastbarPosition(toastBarPosition); - setShowAvatar(showAvatar); - }, []); - - if (isClosable && !setClosableState) { - throw Error( - 'Please provide a setClosableState to props when isClosable = true' - ); - } - - const [RCInstance, setRCInstance] = useState(() => { - const newRCInstance = new EmbeddedChatApi(host, roomId, { - getToken, - deleteToken, - saveToken, - autoLogin: ['PASSWORD', 'OAUTH'].includes(auth.flow), - }); - if (auth.flow === 'TOKEN') { - newRCInstance.auth.loginWithOAuthServiceToken(auth.credentials); - } - return newRCInstance; - }); - - useEffect(() => { - const reInstantiate = () => { - const newRCInstance = new EmbeddedChatApi(host, roomId, { - getToken, - deleteToken, - saveToken, - autoLogin: ['PASSWORD', 'OAUTH'].includes(auth.flow), - }); - if (auth.flow === 'TOKEN') { - newRCInstance.auth.loginWithOAuthServiceToken(auth.credentials); - } - setRCInstance(newRCInstance); - }; - - if (RCInstance.rcClient.loggedIn()) { - RCInstance.close().then(reInstantiate).catch(console.error); - } else { - reInstantiate(); - } - }, [roomId, host, auth?.flow]); - - const isUserAuthenticated = useUserStore( - (state) => state.isUserAuthenticated - ); - const setIsUserAuthenticated = useUserStore( - (state) => state.setIsUserAuthenticated - ); - - const setAuthenticatedUserUsername = useUserStore( - (state) => state.setUsername - ); - const setAuthenticatedUserAvatarUrl = useUserStore( - (state) => state.setUserAvatarUrl - ); - const setAuthenticatedUserId = useUserStore((state) => state.setUserId); - const setAuthenticatedName = useUserStore((state) => state.setName); - - useEffect(() => { - RCInstance.auth.onAuthChange((user) => { - // getUserEssentials(); - if (user) { - RCInstance.connect() - .then(() => { - console.log(`Connected to RocketChat ${RCInstance.host}`); - }) - .catch(console.error); - console.log('reinstantiated'); - const { me } = user; - setAuthenticatedUserAvatarUrl(me.avatarUrl); - setAuthenticatedUserUsername(me.username); - setAuthenticatedUserId(me._id); - setAuthenticatedName(me.name); - setIsUserAuthenticated(true); - } else { - setIsUserAuthenticated(false); - } - }); - }, [ - RCInstance, - setAuthenticatedName, - setAuthenticatedUserAvatarUrl, - setAuthenticatedUserId, - setAuthenticatedUserUsername, - setIsUserAuthenticated, - ]); - - const attachmentWindowOpen = useAttachmentWindowStore((state) => state.open); - - const ECOptions = useMemo( - () => ({ - enableThreads, - authFlow: auth.flow, - width, - height, - host, - roomId, - channelName, - showRoles, - showAvatar, - hideHeader, - anonymousMode, - }), - [ - enableThreads, - auth.flow, - width, - height, - host, - roomId, - channelName, - showRoles, - showAvatar, - hideHeader, - anonymousMode, - ] - ); - - const RCContextValue = useMemo( - () => ({ RCInstance, ECOptions }), - [RCInstance, ECOptions] - ); - - return ( - - - - {attachmentWindowOpen ? : null} - - {hideHeader ? null : ( - - )} - {isUserAuthenticated || anonymousMode ? ( - - ) : ( - - )} - - - - - - ); -}; - -EmbeddedChat.propTypes = { - width: PropTypes.string, - height: PropTypes.string, - isClosable: PropTypes.bool, - setClosableState: PropTypes.func, - moreOpts: PropTypes.bool, - host: PropTypes.string, - roomId: PropTypes.string, - channelName: PropTypes.string, - anonymousMode: PropTypes.bool, - toastBarPosition: PropTypes.string, - showRoles: PropTypes.bool, - showAvatar: PropTypes.bool, - enableThreads: PropTypes.bool, - theme: PropTypes.object, - auth: PropTypes.oneOfType([ - PropTypes.shape({ flow: PropTypes.oneOf(['PASSWORD']) }), - PropTypes.shape({ flow: PropTypes.oneOf(['OAUTH']) }), - PropTypes.shape({ - flow: PropTypes.oneOf(['TOKEN']), - credentials: PropTypes.object, - }), - ]), - className: PropTypes.string, - style: PropTypes.object, - hideHeader: PropTypes.bool, -}; +export { default as EmbeddedChat } from './components/EmbeddedChat'; diff --git a/packages/react/src/lib/auth.js b/packages/react/src/lib/auth.js index 9f7230818..e9b04ab95 100644 --- a/packages/react/src/lib/auth.js +++ b/packages/react/src/lib/auth.js @@ -1,11 +1,18 @@ export async function saveToken(token) { - localStorage.setItem('ec_token', token); + if (typeof localStorage !== 'undefined') { + localStorage.setItem('ec_token', token); + } } export async function getToken() { - return localStorage.getItem('ec_token'); + if (typeof localStorage !== 'undefined') { + return localStorage.getItem('ec_token'); + } + return null; } export async function deleteToken() { - localStorage.removeItem('ec_token'); + if (typeof localStorage !== 'undefined') { + localStorage.removeItem('ec_token'); + } } diff --git a/packages/react/src/lib/createPendingMessage.js b/packages/react/src/lib/createPendingMessage.js index dc09ef134..6e46a2804 100644 --- a/packages/react/src/lib/createPendingMessage.js +++ b/packages/react/src/lib/createPendingMessage.js @@ -1,11 +1,13 @@ const createRandomId = () => { - if (window.crypto.randomUUID) { - return window.crypto.randomUUID().replaceAll('-', '').slice(0, 17); - } - if (window.crypto.getRandomValues) { - const array = new window.BigUint64Array(2); - window.crypto.getRandomValues(array); - return (array[0] * array[1]).toString(36).slice(0, 17); + if ( typeof window !== 'undefined' ) { + if (window.crypto.randomUUID) { + return window.crypto.randomUUID().replaceAll('-', '').slice(0, 17); + } + if (window.crypto.getRandomValues) { + const array = new window.BigUint64Array(2); + window.crypto.getRandomValues(array); + return (array[0] * array[1]).toString(36).slice(0, 17); + } } return ( Math.random().toString(36).replace('0.', '') + diff --git a/packages/react/src/store/attachmentwindow.js b/packages/react/src/store/attachmentwindow.js index d0ca3e8a0..2e8cb922d 100644 --- a/packages/react/src/store/attachmentwindow.js +++ b/packages/react/src/store/attachmentwindow.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useAttachmentWindowStore = create((set) => ({ open: false, diff --git a/packages/react/src/store/channelStore.js b/packages/react/src/store/channelStore.js index ad58233d1..6a500edac 100644 --- a/packages/react/src/store/channelStore.js +++ b/packages/react/src/store/channelStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useChannelStore = create((set) => ({ showChannelinfo: false, diff --git a/packages/react/src/store/inviteStore.js b/packages/react/src/store/inviteStore.js index afcba2988..92a85be9f 100644 --- a/packages/react/src/store/inviteStore.js +++ b/packages/react/src/store/inviteStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useInviteStore = create((set) => ({ showInvite: false, diff --git a/packages/react/src/store/loginmodalStore.js b/packages/react/src/store/loginmodalStore.js index 1ae7d2f90..3e12f97a9 100644 --- a/packages/react/src/store/loginmodalStore.js +++ b/packages/react/src/store/loginmodalStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const loginModalStore = create((set) => ({ isLoginModalOpen: false, diff --git a/packages/react/src/store/memberStore.js b/packages/react/src/store/memberStore.js index 997adfb7f..3dc614bc9 100644 --- a/packages/react/src/store/memberStore.js +++ b/packages/react/src/store/memberStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useMemberStore = create((set) => ({ members: [], diff --git a/packages/react/src/store/mentionmemberStore.js b/packages/react/src/store/mentionmemberStore.js index 4e9e1af23..3025c9248 100644 --- a/packages/react/src/store/mentionmemberStore.js +++ b/packages/react/src/store/mentionmemberStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const mentionmemberStore = create((set) => ({ roomMembers: {}, diff --git a/packages/react/src/store/messageStore.js b/packages/react/src/store/messageStore.js index aefc0dcbe..5a2c0ae3f 100644 --- a/packages/react/src/store/messageStore.js +++ b/packages/react/src/store/messageStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; import cloneArray from '../lib/cloneArray'; import { upsertMessage } from '../lib/messageListHelpers'; diff --git a/packages/react/src/store/searchMessageStore.js b/packages/react/src/store/searchMessageStore.js index 2e875a819..71980c9d5 100644 --- a/packages/react/src/store/searchMessageStore.js +++ b/packages/react/src/store/searchMessageStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useSearchMessageStore = create((set) => ({ showSearch: false, diff --git a/packages/react/src/store/toastStore.js b/packages/react/src/store/toastStore.js index 2c0b2a1d8..29a1d71cc 100644 --- a/packages/react/src/store/toastStore.js +++ b/packages/react/src/store/toastStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const useToastStore = create((set) => ({ position: 'top-start', diff --git a/packages/react/src/store/totpmodalStore.js b/packages/react/src/store/totpmodalStore.js index 305be640a..651e8e5e6 100644 --- a/packages/react/src/store/totpmodalStore.js +++ b/packages/react/src/store/totpmodalStore.js @@ -1,4 +1,4 @@ -import create from 'zustand'; +import { create } from 'zustand'; const totpModalStore = create((set) => ({ isModalOpen: false, diff --git a/packages/react/src/store/userStore.js b/packages/react/src/store/userStore.js index 6d57928d1..813d55643 100644 --- a/packages/react/src/store/userStore.js +++ b/packages/react/src/store/userStore.js @@ -1,31 +1,21 @@ -import create from 'zustand'; -import { - RC_LOCAL_USER_AVATAR_URL, - RC_LOCAL_USER_ID, - RC_LOCAL_USER_NAME, - RC_LOCAL_NAME, -} from '../lib/constant'; +import { create } from 'zustand'; const useUserStore = create((set) => ({ - userId: localStorage.getItem(RC_LOCAL_USER_ID) || '', + userId: '', setUserId: (userId) => { - localStorage.setItem(RC_LOCAL_USER_ID, userId); set({ userId }); }, - name: localStorage.getItem(RC_LOCAL_NAME) || '', + name: '', setName: (name) => { - localStorage.setItem(RC_LOCAL_NAME, name); set({ name }); }, - username: localStorage.getItem(RC_LOCAL_USER_NAME) || '', + username: '', setUsername: (username) => { - localStorage.setItem(RC_LOCAL_USER_NAME, username); set({ username }); }, - avatarUrl: localStorage.getItem(RC_LOCAL_USER_AVATAR_URL) || '', + avatarUrl: '', setUserAvatarUrl: (avatarUrl) => set(() => { - localStorage.setItem(RC_LOCAL_USER_AVATAR_URL, avatarUrl); return { avatarUrl, }; diff --git a/packages/react/src/stories/EmbeddedChatLazy.stories.js b/packages/react/src/stories/EmbeddedChatLazy.stories.js new file mode 100644 index 000000000..2775ecfa1 --- /dev/null +++ b/packages/react/src/stories/EmbeddedChatLazy.stories.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { EmbeddedChatLazy } from '..'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +export default { + title: 'EmbeddedChat/LazyLoad', + component: EmbeddedChatLazy, +}; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Simple = { + args: { + fallback:
Loading...
, + host: process.env.STORYBOOK_RC_HOST || 'http://localhost:3000', + roomId: 'GENERAL', + isClosable: true, + setClosableState: true, + moreOpts: true, + channelName: 'general', + anonymousMode: true, + headerColor: 'white', + toastBarPosition: 'bottom right', + showRoles: true, + showAvatar: true, + enableThreads: true, + auth: { + flow: 'PASSWORD', + }, + }, +};