Skip to content

Commit

Permalink
Merge pull request #324 from abhinavkrin/fix/support-ssr
Browse files Browse the repository at this point in the history
Implement Token Handling Utilities Compatible with SSR
  • Loading branch information
abhinavkrin authored Nov 22, 2023
2 parents a4e78fc + 0846e94 commit 6499688
Show file tree
Hide file tree
Showing 22 changed files with 333 additions and 289 deletions.
7 changes: 7 additions & 0 deletions packages/htmlembed/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @embeddedchat/htmlembed

## 0.0.3

### Patch Changes

- Updated dependencies
- @embeddedchat/react@0.1.5

## 0.0.2

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/htmlembed/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @embeddedchat/react

## 0.1.5

### Patch Changes

- support @embeddedchat/react for Server Side Rendering

## 0.1.0

### Minor Changes
Expand Down
11 changes: 6 additions & 5 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/ChatHeader/ChatHeader.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
245 changes: 245 additions & 0 deletions packages/react/src/components/EmbeddedChat.js
Original file line number Diff line number Diff line change
@@ -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 (
<ThemeProvider theme={theme || DefaultTheme}>
<ToastBarProvider position={toastBarPosition}>
<RCInstanceProvider value={RCContextValue}>
{attachmentWindowOpen ? <AttachmentWindow /> : null}
<Box
css={css`
display: flex;
flex-direction: column;
width: ${width};
overflow: hidden;
max-height: 100vh;
height: ${height};
text-align: initial;
`}
className={`ec-embedded-chat ${className} ${classNames}`}
style={{ ...style, ...styleOverrides }}
>
{hideHeader ? null : (
<ChatHeader
channelName={channelName}
isClosable={isClosable}
setClosableState={setClosableState}
moreOpts={moreOpts}
fullScreen={fullScreen}
setFullScreen={setFullScreen}
/>
)}
{isUserAuthenticated || anonymousMode ? (
<ChatBody
height={!fullScreen ? height : '88vh'}
anonymousMode={anonymousMode}
showRoles={showRoles}
/>
) : (
<Home height={!fullScreen ? height : '88vh'} />
)}
<ChatInput />
</Box>
</RCInstanceProvider>
</ToastBarProvider>
</ThemeProvider>
);
};

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);
5 changes: 0 additions & 5 deletions packages/react/src/components/index.js

This file was deleted.

Loading

0 comments on commit 6499688

Please sign in to comment.