Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
node_modules
.parcel-cache

.env
.env.local
.env.*.local

# yarn
.pnp.*
Expand Down
15 changes: 14 additions & 1 deletion packages/api/src/EmbeddedChatApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,16 @@ export default class EmbeddedChatApi {

async channelInfo() {
try {
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const currentUser = await this.auth.getCurrentUser();
if (!currentUser || !currentUser.authToken || !currentUser.userId) {
return {
success: false,
error: "User not authenticated",
errorType: "unauthorized",
};
}

const { userId, authToken } = currentUser;
const response = await fetch(
`${this.host}/api/v1/rooms.info?roomId=${this.rid}`,
{
Expand All @@ -482,6 +491,10 @@ export default class EmbeddedChatApi {
return await response.json();
} catch (err) {
console.error(err);
return {
success: false,
error: err instanceof Error ? err.message : "Unknown error",
};
}
}

Expand Down
152 changes: 138 additions & 14 deletions packages/react/src/views/EmbeddedChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import {
import { ChatLayout } from './ChatLayout';
import { ChatHeader } from './ChatHeader';
import { RCInstanceProvider } from '../context/RCInstance';
import { useUserStore, useLoginStore, useMessageStore } from '../store';
import {
useUserStore,
useLoginStore,
useMessageStore,
useChannelStore,
} from '../store';
import DefaultTheme from '../theme/DefaultTheme';
import { getTokenStorage } from '../lib/auth';
import { styles } from './EmbeddedChat.styles';
Expand All @@ -27,9 +32,17 @@ import { overrideECProps } from '../lib/overrideECProps';

const EmbeddedChat = (props) => {
const [config, setConfig] = useState(() => props);
const [explicitRoomId, setExplicitRoomId] = useState(() => props.roomId);
const [resolvedRoomId, setResolvedRoomId] = useState(() => {
if (props.roomId) {
return props.roomId;
}
return props.channelName ? null : 'GENERAL';
});

useEffect(() => {
setConfig(props);
setExplicitRoomId(props.roomId);
}, [props]);

const {
Expand Down Expand Up @@ -61,6 +74,7 @@ const EmbeddedChat = (props) => {
} = config;

const hasMounted = useRef(false);
const previousResolvedRoomId = useRef(resolvedRoomId);
const { classNames, styleOverrides } = useComponentOverrides('EmbeddedChat');
const [fullScreen, setFullScreen] = useState(false);
const [isSynced, setIsSynced] = useState(!remoteOpt);
Expand All @@ -72,6 +86,7 @@ const EmbeddedChat = (props) => {
setUserId: setAuthenticatedUserId,
setName: setAuthenticatedName,
setRoles: setAuthenticatedUserRoles,
isUserAuthenticated,
} = useUserStore((state) => ({
isUserAuthenticated: state.isUserAuthenticated,
setIsUserAuthenticated: state.setIsUserAuthenticated,
Expand All @@ -90,34 +105,144 @@ const EmbeddedChat = (props) => {
}

const initializeRCInstance = useCallback(() => {
const newRCInstance = new EmbeddedChatApi(host, roomId, {
const roomIdToUse = resolvedRoomId || 'GENERAL';
const newRCInstance = new EmbeddedChatApi(host, roomIdToUse, {
getToken,
deleteToken,
saveToken,
});

return newRCInstance;
}, [host, roomId, getToken, deleteToken, saveToken]);
}, [host, resolvedRoomId, getToken, deleteToken, saveToken]);

const [RCInstance, setRCInstance] = useState(() => initializeRCInstance());
const [RCInstance, setRCInstance] = useState(() => {
const initialRoomId = resolvedRoomId || 'GENERAL';
return new EmbeddedChatApi(host, initialRoomId, {
getToken,
deleteToken,
saveToken,
});
});
const setMessages = useMessageStore((state) => state.setMessages);
const setChannelInfo = useChannelStore((state) => state.setChannelInfo);

useEffect(() => {
const reInstantiate = () => {
const resolveRoomId = async () => {
if (explicitRoomId) {
setResolvedRoomId(explicitRoomId);
return;
}

if (channelName) {
try {
if (!RCInstance) {
return;
}

if (!isUserAuthenticated) {
return;
}

const currentUser = await RCInstance.auth.getCurrentUser();
const authToken = currentUser?.authToken;
const userId = currentUser?.userId || currentUser?._id;

if (!authToken || !userId) {
return;
}

const response = await fetch(
`${host}/api/v1/rooms.info?roomName=${encodeURIComponent(
channelName
)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': authToken,
'X-User-Id': userId,
},
}
);

if (!response.ok) {
if (response.status === 401) {
return;
}
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
if (data?.success && data?.room?._id) {
setResolvedRoomId(data.room._id);
} else {
setResolvedRoomId('GENERAL');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to have this fallback ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set this If a user provides a channelName that doesn't exist or they lack access, falling back to 'GENERAL' hides the failure.

}
} catch (error) {
setResolvedRoomId('GENERAL');
}
} else {
setResolvedRoomId('GENERAL');
}
};

resolveRoomId();
}, [channelName, explicitRoomId, host, RCInstance, isUserAuthenticated]);

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain the use for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This useEffect resolves a channelName to a roomId (e.g., "GENERAL", "6956b4ced42fcf35") when only the channel name is provided.

const reInstantiate = async () => {
if (!hasMounted.current) {
hasMounted.current = true;
previousResolvedRoomId.current = resolvedRoomId;
if (resolvedRoomId === null) {
return;
}
return;
}

if (resolvedRoomId === null) {
return;
}

if (previousResolvedRoomId.current === resolvedRoomId) {
return;
}

previousResolvedRoomId.current = resolvedRoomId;

await RCInstance.close();

setMessages([], false);
setChannelInfo({});
useMessageStore.setState({
messages: [],
threadMessages: [],
filtered: false,
threadMainMessage: null,
deletedMessage: {},
quoteMessage: [],
editMessage: {},
messagesOffset: 0,
isMessageLoaded: false,
});

const newRCInstance = initializeRCInstance();
setRCInstance(newRCInstance);
};

if (!hasMounted.current) {
hasMounted.current = true;
return;
}

RCInstance.close().then(reInstantiate).catch(console.error);
reInstantiate().catch(console.error);

return () => {
RCInstance.close().catch(console.error);
};
}, [roomId, host, initializeRCInstance]);
}, [
resolvedRoomId,
host,
initializeRCInstance,
setMessages,
setChannelInfo,
RCInstance,
]);

useEffect(() => {
const autoLogin = async () => {
Expand All @@ -138,7 +263,6 @@ const EmbeddedChat = (props) => {
if (user) {
RCInstance.connect()
.then(() => {
console.log(`Connected to RocketChat ${RCInstance.host}`);
const { me } = user;
setAuthenticatedAvatarUrl(me.avatarUrl);
setAuthenticatedUsername(me.username);
Expand Down Expand Up @@ -189,7 +313,7 @@ const EmbeddedChat = (props) => {
width,
height,
host,
roomId,
roomId: resolvedRoomId,
channelName,
showName,
showRoles,
Expand Down