Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/src/EmbeddedChatApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ export default class EmbeddedChatApi {
return await response.json();
} catch (err) {
console.error(err);
return { success: false, error: err };
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"json5": "^2.2.3",
"normalize.css": "^8.0.1",
"prop-types": "^15.8.1",
"react-icons": "^5.5.0",
"swiper": "^11.1.0",
"zustand": "^4.3.8"
}
Expand Down
84 changes: 75 additions & 9 deletions packages/react/src/store/messageStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,46 @@ import { create } from 'zustand';
import cloneArray from '../lib/cloneArray';
import { upsertMessage } from '../lib/messageListHelpers';

const PERSISTENCE_KEY = 'ec_offline_messages';

const getPersistedMessages = (rid) => {
try {
const saved = localStorage.getItem(PERSISTENCE_KEY);
const allOffline = saved ? JSON.parse(saved) : {};
return rid ? allOffline[rid] || [] : [];
} catch (e) {
console.error('Error loading persisted messages', e);
return [];
}
};

const savePersistedMessages = (rid, messages) => {
try {
const saved = localStorage.getItem(PERSISTENCE_KEY);
const allOffline = saved ? JSON.parse(saved) : {};
if (messages && messages.length > 0) {
allOffline[rid] = messages;
} else {
delete allOffline[rid];
}
localStorage.setItem(PERSISTENCE_KEY, JSON.stringify(allOffline));
} catch (e) {
console.error('Error saving persisted messages', e);
}
};

const clearOfflineMessages = () => {
try {
localStorage.removeItem(PERSISTENCE_KEY);
} catch (e) {
console.error('Error clearing persisted messages', e);
}
};

const useMessageStore = create((set, get) => ({
messages: [],
rid: null,
setRid: (rid) => set({ rid }),
isMessageLoaded: false,
threadMessages: [],
filtered: false,
Expand All @@ -25,16 +63,23 @@ const useMessageStore = create((set, get) => ({
set((state) => {
const allMessages = append
? [...state.messages, ...newMessages]
: newMessages;
: [...getPersistedMessages(state.rid), ...newMessages];

const uniqueMessages = Array.from(
new Map(allMessages.map((msg) => [msg._id, msg])).values()
);
).sort((a, b) => new Date(b.ts) - new Date(a.ts));
return {
messages: uniqueMessages,
isMessageLoaded: true,
};
}),
upsertMessage: (message, enableThreads = false) => {
if (message.isError) {
const offlineMessages = getPersistedMessages(message.rid);
const updatedOffline = upsertMessage(offlineMessages, message);
savePersistedMessages(message.rid, updatedOffline);
}

if (message.tmid && enableThreads) {
if (get().threadMainMessage?._id === message.tmid) {
set((state) => ({
Expand All @@ -43,13 +88,22 @@ const useMessageStore = create((set, get) => ({
}
} else {
set((state) => ({
messages: upsertMessage(state.messages, message),
messages: upsertMessage(state.messages, message).sort(
(a, b) => new Date(b.ts) - new Date(a.ts)
),
}));
}
},
removeMessage: (messageId) => {
const currentMessages = get().messages;
const targetMessage = currentMessages.find((m) => m._id === messageId);
if (targetMessage && targetMessage.isError) {
const offlineMessages = getPersistedMessages(targetMessage.rid);
const updatedOffline = offlineMessages.filter((m) => m._id !== messageId);
savePersistedMessages(targetMessage.rid, updatedOffline);
}

const threadMessage = get().threadMessages.find((m) => m._id === messageId);
const message = get().messages.find((m) => m._id === messageId);
if (threadMessage) {
return set((state) => ({
deletedMessage: threadMessage,
Expand All @@ -58,14 +112,23 @@ const useMessageStore = create((set, get) => ({
),
}));
}
if (message) {
if (targetMessage) {
return set((state) => ({
deletedMessage: message,
deletedMessage: targetMessage,
messages: cloneArray(state.messages).filter((m) => m._id !== messageId),
}));
}
},
replaceMessage: (oldMessageId, newMessage) => {
const offlineMessages = getPersistedMessages(newMessage.rid);
let updatedOffline;
if (newMessage.isError) {
updatedOffline = upsertMessage(offlineMessages, newMessage);
} else {
updatedOffline = offlineMessages.filter((m) => m._id !== oldMessageId);
}
savePersistedMessages(newMessage.rid, updatedOffline);

const threadMessage = get().threadMessages.find(
(m) => m._id === oldMessageId
);
Expand All @@ -79,9 +142,9 @@ const useMessageStore = create((set, get) => ({
}
if (message) {
return set((state) => ({
messages: cloneArray(state.messages).map((m) =>
m._id === oldMessageId ? newMessage : m
),
messages: cloneArray(state.messages)
.map((m) => (m._id === oldMessageId ? newMessage : m))
.sort((a, b) => new Date(b.ts) - new Date(a.ts)),
}));
}
},
Expand Down Expand Up @@ -135,6 +198,9 @@ const useMessageStore = create((set, get) => ({
set((state) => ({ ...state, forceDeleteMessageRoles })),
setThreadMessages: (messages) => set(() => ({ threadMessages: messages })),
setHeaderTitle: (title) => set(() => ({ headerTitle: title })),
clearOfflineMessages: () => {
clearOfflineMessages();
},
}));

export default useMessageStore;
6 changes: 6 additions & 0 deletions packages/react/src/views/ChatBody/ChatBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ const ChatBody = ({
});
}, [RCInstance, anonymousMode, getMessagesAndRoles]);

const setRid = useMessageStore((state) => state.setRid);

useEffect(() => {
setRid(RCInstance.rid);
}, [RCInstance.rid, setRid]);

useEffect(() => {
RCInstance.auth.onAuthChange((user) => {
if (user) {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/views/ChatHeader/ChatHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const ChatHeader = ({
setChannelInfo({});
setShowSidebar(false);
setUserAvatarUrl(null);
useMessageStore.getState().clearOfflineMessages();
useMessageStore.setState({ isMessageLoaded: false });
} catch (e) {
console.error(e);
Expand Down
19 changes: 18 additions & 1 deletion packages/react/src/views/ChatInput/ChatInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ const ChatInput = ({ scrollToBottom, clearUnreadDividerRef }) => {

upsertMessage(pendingMessage, ECOptions.enableThreads);

if (!navigator.onLine) {
const erroredMessage = {
...pendingMessage,
isError: true,
isPending: false,
};
replaceMessage(pendingMessage._id, erroredMessage);
return;
}

const res = await RCInstance.sendMessage(
{
msg: pendingMessage.msg,
Expand All @@ -333,7 +343,14 @@ const ChatInput = ({ scrollToBottom, clearUnreadDividerRef }) => {

if (res.success) {
clearQuoteMessages();
replaceMessage(pendingMessage, res.message);
replaceMessage(pendingMessage._id, res.message);
} else {
const erroredMessage = {
...pendingMessage,
isError: true,
isPending: false,
};
replaceMessage(pendingMessage._id, erroredMessage);
}
};

Expand Down
Loading