From 82503efa8cd4f5d5abd59d91e4275506b8a59e56 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 11 Sep 2024 15:42:01 +0300 Subject: [PATCH] fix get latest secretkey, multi-send group chat --- src/App.tsx | 24 +- src/MessageQueueContext.tsx | 138 ++++++++++ src/background.ts | 93 +++++-- .../Chat/AnnouncementDiscussion.tsx | 7 +- src/components/Chat/ChatDirect.tsx | 2 +- src/components/Chat/ChatGroup.tsx | 53 +++- src/components/Chat/ChatList.tsx | 180 +++++++------ src/components/Chat/GroupAnnouncements.tsx | 7 +- src/components/Chat/MessageItem.tsx | 25 +- src/components/Chat/TipTap.tsx | 2 +- src/components/Group/Forum/NewThread.tsx | 2 +- src/components/Group/Group.tsx | 238 ++++++++++-------- src/components/Group/WebsocketActive.tsx | 17 +- src/main.tsx | 4 +- 14 files changed, 559 insertions(+), 233 deletions(-) create mode 100644 src/MessageQueueContext.tsx diff --git a/src/App.tsx b/src/App.tsx index 712cdf6..940fa7c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -158,8 +158,28 @@ export const clearAllQueues = () => { }); } -export const pauseAllQueues = () => controlAllQueues('pause'); -export const resumeAllQueues = () => controlAllQueues('resume'); +export const pauseAllQueues = () => { + controlAllQueues('pause'); + chrome.runtime.sendMessage( + { + action: "pauseAllQueues", + payload: { + + }, + } + ); +} +export const resumeAllQueues = () => { + controlAllQueues('resume'); + chrome.runtime.sendMessage( + { + action: "resumeAllQueues", + payload: { + + }, + } + ); +} export const MyContext = createContext(defaultValues); diff --git a/src/MessageQueueContext.tsx b/src/MessageQueueContext.tsx new file mode 100644 index 0000000..2c6493d --- /dev/null +++ b/src/MessageQueueContext.tsx @@ -0,0 +1,138 @@ +import React, { createContext, useContext, useState, useCallback, useRef } from 'react'; +import ShortUniqueId from "short-unique-id"; + +const MessageQueueContext = createContext(null); + +export const useMessageQueue = () => useContext(MessageQueueContext); + +const uid = new ShortUniqueId({ length: 8 }); +let messageQueue = []; // Global message queue + +export const MessageQueueProvider = ({ children }) => { + const [queueChats, setQueueChats] = useState({}); // Stores chats and status for display + const isProcessingRef = useRef(false); // To track if the queue is being processed + const maxRetries = 3; + + const clearStatesMessageQueueProvider = useCallback(() => { + setQueueChats({}) + messageQueue = [] + isProcessingRef.current = false + }, []) + + // Function to add a message to the queue + const addToQueue = useCallback((sendMessageFunc, messageObj, type, groupDirectId) => { + const tempId = uid.rnd(); + const chatData = { + ...messageObj, + type, + groupDirectId, + identifier: tempId, + retries: 0, // Retry count for display purposes + status: 'pending' // Initial status is 'pending' + }; + + // Add chat data to queueChats for status tracking + setQueueChats((prev) => ({ + ...prev, + [groupDirectId]: [...(prev[groupDirectId] || []), chatData] + })); + + // Add the message to the global messageQueue + messageQueue = [ + ...messageQueue, + { func: sendMessageFunc, identifier: tempId, groupDirectId } + ]; + + // Start processing the queue if not already processing + processQueue(); + }, []); + + // Function to process the messageQueue + // Function to process the messageQueue +const processQueue = useCallback(async () => { + // If currently processing or the queue is empty, return + if (isProcessingRef.current || messageQueue.length === 0) return; + + isProcessingRef.current = true; // Lock the queue for processing + + while (messageQueue.length > 0) { + const currentMessage = messageQueue[0]; // Get the first message in the queue + const { groupDirectId, identifier } = currentMessage; + + // Update the chat status to 'sending' + setQueueChats((prev) => { + const updatedChats = { ...prev }; + if (updatedChats[groupDirectId]) { + const chatIndex = updatedChats[groupDirectId].findIndex( + (item) => item.identifier === identifier + ); + if (chatIndex !== -1) { + updatedChats[groupDirectId][chatIndex].status = 'sending'; + } + } + return updatedChats; + }); + + try { + // Execute the function stored in the messageQueue + await currentMessage.func(); + + + // Remove the message from the messageQueue after successful sending + messageQueue = messageQueue.slice(1); + + // Remove the message from queueChats after success + setQueueChats((prev) => { + const updatedChats = { ...prev }; + updatedChats[groupDirectId] = updatedChats[groupDirectId].filter( + (item) => item.identifier !== identifier + ); + return updatedChats; + }); + } catch (error) { + console.error('Message sending failed', error); + + // Retry logic + setQueueChats((prev) => { + const updatedChats = { ...prev }; + const chatIndex = updatedChats[groupDirectId]?.findIndex( + (item) => item.identifier === identifier + ); + if (chatIndex !== -1) { + const retries = updatedChats[groupDirectId][chatIndex].retries; + if (retries < maxRetries) { + // Increment retry count and set status to 'failed' + updatedChats[groupDirectId][chatIndex].retries += 1; + updatedChats[groupDirectId][chatIndex].status = 'failed'; + } else { + // Max retries reached, set status to 'failed-permanent' + updatedChats[groupDirectId][chatIndex].status = 'failed-permanent'; + + // Remove the message from the messageQueue after max retries + messageQueue = messageQueue.slice(1); + + // Remove the message from queueChats after failure + updatedChats[groupDirectId] = updatedChats[groupDirectId].filter( + (item) => item.identifier !== identifier + ); + } + } + return updatedChats; + }); + } + + // Delay between processing each message to avoid overlap + await new Promise((res) => setTimeout(res, 3000)); + } + + // Reset the processing lock once all messages are processed + isProcessingRef.current = false; + }, []); + + + return ( + + {children} + + ); +}; diff --git a/src/background.ts b/src/background.ts index dae068c..9539e5b 100644 --- a/src/background.ts +++ b/src/background.ts @@ -21,6 +21,7 @@ import { createTransaction } from "./transactions/transactions"; import { decryptChatMessage } from "./utils/decryptChatMessage"; import { decryptStoredWallet } from "./utils/decryptWallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet"; +import { RequestQueueWithPromise } from "./utils/queue/queue"; import { validateAddress } from "./utils/validateAddress"; import { Sha256 } from "asmcrypto.js"; @@ -30,7 +31,38 @@ export const groupApiSocket = "wss://ext-node.qortal.link" export const groupApiLocal = "http://127.0.0.1:12391" export const groupApiSocketLocal = "ws://127.0.0.1:12391" const timeDifferenceForNotificationChatsBackground = 600000 +const requestQueueAnnouncements = new RequestQueueWithPromise(1) +const allQueues = { + requestQueueAnnouncements: requestQueueAnnouncements, +} + +const controlAllQueues = (action) => { + Object.keys(allQueues).forEach((key) => { + const val = allQueues[key]; + try { + if (typeof val[action] === 'function') { + val[action](); + } + } catch (error) { + console.error(error); + } + }); +}; + +export const clearAllQueues = () => { + Object.keys(allQueues).forEach((key) => { + const val = allQueues[key]; + try { + val.clear(); + } catch (error) { + console.error(error); + } + }); +} + + const pauseAllQueues = () => controlAllQueues('pause'); + const resumeAllQueues = () => controlAllQueues('resume'); const checkDifference = (createdTimestamp)=> { return (Date.now() - createdTimestamp) < timeDifferenceForNotificationChatsBackground } @@ -612,33 +644,34 @@ const checkNewMessages = if(!groups || groups?.length === 0) return const savedtimestamp = await getTimestampGroupAnnouncement() - for (const group of groups){ + await Promise.all(groups.map(async (group) => { try { - const identifier = `grp-${group.groupId}-anc-`; - const url = await createEndpoint(`/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - - const latestMessage = responseData.filter((pub)=> pub?.name !== myName)[0] + const url = await createEndpoint(`/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=0&reverse=true&prefix=true`); + const response = await requestQueueAnnouncements.enqueue(()=> { + return fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + }) + const responseData = await response.json(); + + const latestMessage = responseData.filter((pub) => pub?.name !== myName)[0]; if (!latestMessage) { - continue + return; // continue to the next group } - - if(checkDifference(latestMessage.created) && !savedtimestamp[group.groupId] || latestMessage.created > savedtimestamp?.[group.groupId]?.notification){ - newAnnouncements.push(group) - await addTimestampGroupAnnouncement({groupId: group.groupId, timestamp: Date.now()}) - //save new timestamp + + if (checkDifference(latestMessage.created) && (!savedtimestamp[group.groupId] || latestMessage.created > savedtimestamp?.[group.groupId]?.notification)) { + newAnnouncements.push(group); + await addTimestampGroupAnnouncement({ groupId: group.groupId, timestamp: Date.now() }); + // save new timestamp } } catch (error) { - + console.error(error); // Handle error if needed } - } + })); if(newAnnouncements.length > 0){ const notificationId = 'chat_notification_' + Date.now() + '_type=group-announcement' + `_from=${newAnnouncements[0]?.groupId}`; @@ -1487,7 +1520,7 @@ const data = await objectToBase64({ secretKeyObject: secretKeyObject, }) .then((res2) => { - + pauseAllQueues() sendChatGroup({ groupId, typeMessage: undefined, @@ -1497,7 +1530,9 @@ const data = await objectToBase64({ .then(() => {}) .catch((error) => { console.error('1',error.message); - }); + }).finally(()=> { + resumeAllQueues() + }) }) .catch((error) => { console.error('2',error.message); @@ -3361,6 +3396,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { break; } + case "pauseAllQueues": { + pauseAllQueues() + sendResponse(res); + + break; + break; + } + case "resumeAllQueues": { + resumeAllQueues() + sendResponse(res); + + break; + } case "decryptSingleForPublishes": { const { data, secretKeyObject, skipDecodeBase64 } = request.payload; @@ -3472,6 +3520,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { try { const logoutFunc = async()=> { forceCloseWebSocket() + clearAllQueues() if(interval){ // for announcement notification clearInterval(interval) diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index fd61ab2..7a4bb9c 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -10,7 +10,7 @@ import { decryptPublishes, getTempPublish, saveTempPublish } from "./GroupAnnoun import { AnnouncementList } from "./AnnouncementList"; import { Spacer } from "../../common/Spacer"; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import { getBaseApiReact } from "../../App"; +import { getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App"; const tempKey = 'accouncement-comment' @@ -107,6 +107,7 @@ export const AnnouncementDiscussion = ({ const publishComment = async () => { try { + pauseAllQueues() const fee = await getFee('ARBITRARY') await show({ message: "Would you like to perform a ARBITRARY transaction?" , @@ -123,7 +124,7 @@ export const AnnouncementDiscussion = ({ extra: {}, message: htmlContent, }; - const secretKeyObject = await getSecretKey(); + const secretKeyObject = await getSecretKey(false, true); const message64: any = await objectToBase64(message); const encryptSingle = await encryptChatMessage( @@ -154,6 +155,7 @@ export const AnnouncementDiscussion = ({ } catch (error) { console.error(error); } finally { + resumeAllQueues() setIsSending(false); } }; @@ -161,6 +163,7 @@ export const AnnouncementDiscussion = ({ const getComments = React.useCallback( async (selectedAnnouncement) => { try { + setIsLoading(true); const offset = 0; diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 24de83d..beaca3c 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -241,7 +241,7 @@ const clearEditorContent = () => { )} - +
{ +export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName}) => { const [messages, setMessages] = useState([]) const [isSending, setIsSending] = useState(false) const [isLoading, setIsLoading] = useState(false) @@ -31,11 +32,20 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, const timeoutIdRef = useRef(null); // Timeout ID reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference const editorRef = useRef(null); - + const { queueChats, addToQueue, } = useMessageQueue(); + const setEditorRef = (editorInstance) => { editorRef.current = editorInstance; }; + const tempMessages = useMemo(()=> { + if(!selectedGroup) return [] + if(queueChats[selectedGroup]){ + return queueChats[selectedGroup] + } + return [] + }, [selectedGroup, queueChats]) + const secretKeyRef = useRef(null) useEffect(()=> { @@ -88,7 +98,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, ...item, id: item.signature, text: item.text, - unread: true + unread: item?.sender === myAddress ? false : true } } ) setMessages((prev)=> [...prev, ...formatted]) @@ -98,7 +108,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, ...item, id: item.signature, text: item.text, - unread: false + unread: false } } ) setMessages(formatted) @@ -199,6 +209,10 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, forceCloseWebSocket() setMessages([]) setIsLoading(true) + pauseAllQueues() + setTimeout(() => { + resumeAllQueues() + }, 6000); initWebsocketMessageGroup() hasInitializedWebsocket.current = true }, [secretKey]) @@ -262,18 +276,35 @@ const clearEditorContent = () => { const sendMessage = async ()=> { try { if(isSending) return + pauseAllQueues() if (editorRef.current) { const htmlContent = editorRef.current.getHTML(); if(!htmlContent?.trim() || htmlContent?.trim() === '

') return setIsSending(true) const message = htmlContent - const secretKeyObject = await getSecretKey() + const secretKeyObject = await getSecretKey(false, true) const message64: any = await objectToBase64(message) const encryptSingle = await encryptChatMessage(message64, secretKeyObject) - const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) + // const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) + const sendMessageFunc = async () => { + await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle}) + }; + + // Add the function to the queue + const messageObj = { + message: { + text: message, + timestamp: Date.now(), + senderName: myName, + sender: myAddress + }, + + } + addToQueue(sendMessageFunc, messageObj, 'chat', + selectedGroup ); clearEditorContent() } // send chat message @@ -286,6 +317,7 @@ const clearEditorContent = () => { console.error(error) } finally { setIsSending(false) + resumeAllQueues() } } @@ -305,12 +337,11 @@ const clearEditorContent = () => { flexDirection: 'column', width: '100%', opacity: hide ? 0 : 1, - visibility: hide && 'hidden', - position: hide ? 'fixed' : 'relative', - left: hide && '-1000px', + position: hide ? 'absolute' : 'relative', + left: hide && '-100000px', }}> - +
{ +export const ChatList = ({ initialMessages, myAddress, tempMessages }) => { + const hasLoadedInitialRef = useRef(false); const listRef = useRef(); const [messages, setMessages] = useState(initialMessages); const [showScrollButton, setShowScrollButton] = useState(false); - - - useEffect(()=> { + + + useEffect(() => { cache.clearAll(); - }, []) - const handleMessageSeen = useCallback((messageId) => { + }, []); + + const handleMessageSeen = useCallback(() => { setMessages((prevMessages) => - prevMessages.map((msg) => { - return { ...msg, unread: false } - } - - ) + prevMessages.map((msg) => ({ + ...msg, + unread: false, + })) ); }, []); const handleScroll = ({ scrollTop, scrollHeight, clientHeight }) => { const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50; const hasUnreadMessages = messages.some((msg) => msg.unread); - - if (!isAtBottom && hasUnreadMessages) { - setShowScrollButton(true); - } else { - setShowScrollButton(false); + if(isAtBottom){ + handleMessageSeen() } + setShowScrollButton(!isAtBottom && hasUnreadMessages); }; const debounce = (func, delay) => { @@ -47,96 +46,113 @@ export const ChatList = ({ initialMessages, myAddress }) => { }, delay); }; }; - + const handleScrollDebounced = debounce(handleScroll, 100); - const scrollToBottom = () => { + const scrollToBottom = (initialmsgs) => { if (listRef.current) { + const msgs = initialmsgs?.length ? initialmsgs : messages + listRef.current?.recomputeRowHeights(); - listRef.current.scrollToRow(messages.length - 1); + + listRef.current.scrollToRow(msgs.length - 1); setTimeout(() => { - listRef.current?.recomputeRowHeights(); - listRef.current.scrollToRow(messages.length - 1); + + listRef.current.scrollToRow(msgs.length - 1); }, 100); - - setShowScrollButton(false); } }; - const preserveScrollPosition = (callback) => { + const recomputeListHeights = () => { if (listRef.current) { - const scrollContainer = listRef.current.Grid._scrollingContainer; - const currentScrollTop = scrollContainer.scrollTop; // Get current scroll position - - callback(); // Perform the action that could change the layout (e.g., recompute row heights) - - // Restore the scroll position after the layout change - setTimeout(() => { - scrollContainer.scrollTop = currentScrollTop; - }, 0); + listRef.current.recomputeRowHeights(); } }; - - const rowRenderer = ({ index, key, parent, style }) => { - const message = messages[index]; + + const rowRenderer = ({ index, key, parent, style }) => { + let message = messages[index]; + const isLargeMessage = message.text?.length > 200; // Adjust based on your message size threshold + + if(message?.message && message?.groupDirectId){ + message = { + ...(message?.message || {}), + isTemp: true, + unread: false + } + } return ( - {({ measure }) => ( -
-
preserveScrollPosition(measure)} // Prevent jumps while measuring - style={{ - marginBottom: '10px', - width: '100%', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - }} + key={key} + cache={cache} + parent={parent} + columnIndex={0} + rowIndex={index} > - -
-
- )} -
+ {({ measure }) => ( +
+
{ + if (isLargeMessage) { + measure(); // Ensure large messages are properly measured + } + }} + style={{ + marginBottom: '10px', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }} + > + +
+
+ )} + ); }; useEffect(() => { - setMessages(initialMessages); + + const totalMessages = [...initialMessages, ...(tempMessages || [])] + if(totalMessages.length === 0) return + setMessages(totalMessages); + // cache.clearAll(); // Clear cache so the list can properly re-render with new messages setTimeout(() => { if (listRef.current) { - // Accessing scrollTop, scrollHeight, clientHeight from List's methods - const scrollTop = listRef.current.Grid._scrollingContainer.scrollTop; - const scrollHeight = listRef.current.Grid._scrollingContainer.scrollHeight; - const clientHeight = listRef.current.Grid._scrollingContainer.clientHeight; - - handleScroll({ scrollTop, scrollHeight, clientHeight }); + const { scrollTop, scrollHeight, clientHeight } = listRef.current.Grid._scrollingContainer; + handleScroll({ scrollTop, scrollHeight, clientHeight }); + recomputeListHeights(); // Ensure heights are recomputed on message load + setTimeout(() => { + if(!hasLoadedInitialRef.current){ + scrollToBottom(totalMessages); + hasLoadedInitialRef.current = true + } + }, 100); } - }, 100); - }, [initialMessages]); - - useEffect(() => { - // Scroll to the bottom on initial load or when messages change - if (listRef.current && messages.length > 0 && hasLoadedInitialRef.current === false) { - scrollToBottom(); - hasLoadedInitialRef.current = true; - } else if (messages.length > 0 && messages[messages.length - 1].sender === myAddress) { - scrollToBottom(); - } - }, [messages, myAddress]); + }, 500); + }, [tempMessages, initialMessages]); + + // useEffect(() => { + // // Scroll to the bottom on initial load or when messages change + // if (listRef.current && messages.length > 0 && !hasLoadedInitialRef.current) { + // scrollToBottom(); + // hasLoadedInitialRef.current = true; + // } else if (messages.length > 0 && messages[messages.length - 1].sender === myAddress) { + // scrollToBottom(); + // } + // }, [messages, myAddress]); return (
- {({ height, width }) => ( { rowRenderer={rowRenderer} onScroll={handleScrollDebounced} deferredMeasurementCache={cache} + onRowsRendered={recomputeListHeights} // Force recompute on render + overscanRowCount={10} // For performance: pre-render some rows /> )} @@ -168,7 +186,9 @@ export const ChatList = ({ initialMessages, myAddress }) => { > Scroll to Unread Messages + )} +
); -}; +}; \ No newline at end of file diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 778e019..9f8b91f 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -28,7 +28,7 @@ const uid = new ShortUniqueId({ length: 8 }); import CampaignIcon from '@mui/icons-material/Campaign'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { AnnouncementDiscussion } from "./AnnouncementDiscussion"; -import { MyContext, getBaseApiReact } from "../../App"; +import { MyContext, getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App"; import { RequestQueueWithPromise } from "../../utils/queue/queue"; import { CustomizedSnackbars } from "../Snackbar/Snackbar"; @@ -248,6 +248,8 @@ export const GroupAnnouncements = ({ const publishAnnouncement = async () => { try { + + pauseAllQueues() const fee = await getFee('ARBITRARY') await show({ message: "Would you like to perform a ARBITRARY transaction?" , @@ -263,7 +265,7 @@ export const GroupAnnouncements = ({ extra: {}, message: htmlContent } - const secretKeyObject = await getSecretKey(); + const secretKeyObject = await getSecretKey(false, true); const message64: any = await objectToBase64(message); const encryptSingle = await encryptChatMessage( message64, @@ -295,6 +297,7 @@ export const GroupAnnouncements = ({ }); setOpenSnack(true) } finally { + resumeAllQueues() setIsSending(false); } }; diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 8928b26..b53ce75 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -7,7 +7,7 @@ import { formatTimestamp } from "../../utils/time"; import { getBaseApi } from "../../background"; import { getBaseApiReact } from "../../App"; -export const MessageItem = ({ message, onSeen, isLast }) => { +export const MessageItem = ({ message, onSeen, isLast, isTemp }) => { const { ref, inView } = useInView({ threshold: 0.7, // Fully visible @@ -30,6 +30,7 @@ export const MessageItem = ({ message, onSeen, isLast }) => { width: "95%", display: "flex", gap: '7px', + opacity: isTemp ? 0.5 : 1 }} > { - {formatTimestamp(message.timestamp)} + {isTemp ? ( + Sending... + ): ( + {formatTimestamp(message.timestamp)} + ) } + diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 0db64c6..4bdf3c6 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -269,7 +269,7 @@ const extensions = [ const content = ``; export default ({ setEditorRef, onEnter, disableEnter, isChat }) => { - console.log('exte', extensions) + const extensionsFiltered = isChat ? extensions.filter((item)=> item?.name !== 'image') : extensions return ( { const [secretKey, setSecretKey] = useState(null); const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null); + const lastFetchedSecretKey = useRef(null) const [secretKeyDetails, setSecretKeyDetails] = useState(null); const [newEncryptionNotification, setNewEncryptionNotification] = useState(null); @@ -321,6 +323,7 @@ export const Group = ({ const isLoadingOpenSectionFromNotification = useRef(false); const setupGroupWebsocketInterval = useRef(null); const settimeoutForRefetchSecretKey = useRef(null); + const { clearStatesMessageQueueProvider } = useMessageQueue(); useEffect(() => { @@ -470,7 +473,7 @@ export const Group = ({ const queryString = admins.map((name) => `name=${name}`).join("&"); const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${ selectedGroup?.groupId - }&exactmatchnames=true&limit=10&reverse=true&${queryString}`; + }&exactmatchnames=true&limit=0&reverse=true&${queryString}`; const response = await fetch(url); if(!response.ok){ throw new Error('network error') @@ -484,10 +487,22 @@ export const Group = ({ if (filterId?.length === 0) { return false; } - return filterId[0]; + const sortedData = filterId.sort((a: any, b: any) => { + // Get the most recent date for both a and b + const dateA = a.updated ? new Date(a.updated) : new Date(a.created); + const dateB = b.updated ? new Date(b.updated) : new Date(b.created); + + // Sort by most recent + return dateB.getTime() - dateA.getTime(); + }); + + return sortedData[0]; }; - const getSecretKey = async (loadingGroupParam?: boolean) => { + const getSecretKey = async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => { try { + pauseAllQueues() + + if(secretKeyToPublish && secretKey && lastFetchedSecretKey.current && Date.now() - lastFetchedSecretKey.current < 1800000) return secretKey if (loadingGroupParam) { setIsLoadingGroup(true); } @@ -500,6 +515,7 @@ export const Group = ({ const prevGroupId = selectedGroupRef.current.groupId; // const validApi = await findUsableApi(); const groupAdmins = await getGroupAdimns(selectedGroup?.groupId); + setAdmins(groupAdmins) if(!groupAdmins.length){ throw new Error('Network error') } @@ -536,7 +552,7 @@ export const Group = ({ throw new Error("SecretKey is not valid"); setSecretKeyDetails(publish); setSecretKey(decryptedKeyToObject); - + lastFetchedSecretKey.current = Date.now() setMemberCountFromSecretKeyData(decryptedKey.count); if (decryptedKeyToObject) { setTriedToFetchSecretKey(true); @@ -556,6 +572,12 @@ export const Group = ({ } finally { setIsLoadingGroup(false); + if(!secretKeyToPublish){ + await getAdmins(selectedGroup?.groupId); + + } + resumeAllQueues() + } }; @@ -768,11 +790,13 @@ export const Group = ({ }; useEffect(() => { if (selectedGroup?.groupId) { - getAdmins(selectedGroup?.groupId); + // getAdmins(selectedGroup?.groupId); getMembers(selectedGroup?.groupId); } }, [selectedGroup?.groupId]); + + const shouldReEncrypt = useMemo(() => { if (triedToFetchSecretKey && !secretKeyPublishDate) return true; if ( @@ -913,6 +937,7 @@ export const Group = ({ const resetAllStatesAndRefs = () => { // Reset all useState values to their initial states setSecretKey(null); + lastFetchedSecretKey.current = null setSecretKeyPublishDate(null); setSecretKeyDetails(null); setNewEncryptionNotification(null); @@ -960,6 +985,7 @@ export const Group = ({ const logoutEventFunc = ()=> { resetAllStatesAndRefs() + clearStatesMessageQueueProvider() } useEffect(() => { @@ -987,6 +1013,7 @@ export const Group = ({ setNewChat(false); setSecretKey(null); + lastFetchedSecretKey.current = null setSecretKeyPublishDate(null); setAdmins([]); setSecretKeyDetails(null); @@ -1034,6 +1061,7 @@ export const Group = ({ setChatMode("groups"); setSelectedGroup(null); setSecretKey(null); + lastFetchedSecretKey.current = null setSecretKeyPublishDate(null); setAdmins([]); setSecretKeyDetails(null); @@ -1090,6 +1118,7 @@ export const Group = ({ setChatMode("groups"); setSelectedGroup(null); setSecretKey(null); + lastFetchedSecretKey.current = null setSecretKeyPublishDate(null); setAdmins([]); setSecretKeyDetails(null); @@ -1125,7 +1154,7 @@ export const Group = ({ return ( <> - + )} {firstSecretKeyInCreation && triedToFetchSecretKey && !secretKeyPublishDate && ( @@ -1655,10 +1686,95 @@ export const Group = ({ /> )} - + )} + + {selectedDirect && !newChat && ( + <> + + + + + )} + {!selectedDirect && + !selectedGroup && + !newChat && + groupSection === "home" && ( + + + + + + + + + + + + + )} + @@ -1685,6 +1801,7 @@ export const Group = ({ setNewChat(false); setSelectedDirect(null); setSecretKey(null); + lastFetchedSecretKey.current = null setSecretKeyPublishDate(null); setAdmins([]); setSecretKeyDetails(null); @@ -1713,8 +1830,9 @@ export const Group = ({ Home - - + {selectedGroup && ( + <> + + + )} + {/* */} - - )} - - {selectedDirect && !newChat && ( - <> - - - - - )} - {!selectedDirect && - !selectedGroup && - !newChat && - groupSection === "home" && ( - - - - - - - - - - - - - )} -
diff --git a/src/components/Group/WebsocketActive.tsx b/src/components/Group/WebsocketActive.tsx index b09ff9f..055f590 100644 --- a/src/components/Group/WebsocketActive.tsx +++ b/src/components/Group/WebsocketActive.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useRef } from 'react'; -import { getBaseApiReactSocket } from '../../App'; +import { getBaseApiReactSocket, pauseAllQueues, resumeAllQueues } from '../../App'; -export const WebSocketActive = ({ myAddress }) => { +export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { const socketRef = useRef(null); // WebSocket reference const timeoutIdRef = useRef(null); // Timeout ID reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference - + const initiateRef = useRef(null) const forceCloseWebSocket = () => { if (socketRef.current) { console.log('Force closing the WebSocket'); @@ -41,6 +41,11 @@ export const WebSocketActive = ({ myAddress }) => { const currentAddress = myAddress; try { + if(!initiateRef.current) { + setIsLoadingGroups(true) + pauseAllQueues() + + } const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64`; socketRef.current = new WebSocket(socketLink); @@ -55,6 +60,12 @@ export const WebSocketActive = ({ myAddress }) => { clearTimeout(timeoutIdRef.current); groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds } else { + if(!initiateRef.current) { + setIsLoadingGroups(false) + initiateRef.current = true + resumeAllQueues() + + } const data = JSON.parse(e.data); const filteredGroups = data.groups?.filter(item => item?.groupId !== 0) || []; const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); diff --git a/src/main.tsx b/src/main.tsx index e763d2b..8a477a3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import './index.css' import { ThemeProvider, createTheme } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; +import { MessageQueueProvider } from './MessageQueueContext.tsx'; const theme = createTheme({ palette: { @@ -48,8 +49,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - + + , )