Browse Source

fix get latest secretkey, multi-send group chat

master
PhilReact 1 week ago
parent
commit
82503efa8c
  1. 24
      src/App.tsx
  2. 138
      src/MessageQueueContext.tsx
  3. 93
      src/background.ts
  4. 7
      src/components/Chat/AnnouncementDiscussion.tsx
  5. 2
      src/components/Chat/ChatDirect.tsx
  6. 53
      src/components/Chat/ChatGroup.tsx
  7. 180
      src/components/Chat/ChatList.tsx
  8. 7
      src/components/Chat/GroupAnnouncements.tsx
  9. 25
      src/components/Chat/MessageItem.tsx
  10. 2
      src/components/Chat/TipTap.tsx
  11. 2
      src/components/Group/Forum/NewThread.tsx
  12. 238
      src/components/Group/Group.tsx
  13. 17
      src/components/Group/WebsocketActive.tsx
  14. 4
      src/main.tsx

24
src/App.tsx

@ -158,8 +158,28 @@ export const clearAllQueues = () => {
}); });
} }
export const pauseAllQueues = () => controlAllQueues('pause'); export const pauseAllQueues = () => {
export const resumeAllQueues = () => controlAllQueues('resume'); controlAllQueues('pause');
chrome.runtime.sendMessage(
{
action: "pauseAllQueues",
payload: {
},
}
);
}
export const resumeAllQueues = () => {
controlAllQueues('resume');
chrome.runtime.sendMessage(
{
action: "resumeAllQueues",
payload: {
},
}
);
}
export const MyContext = createContext<MyContextInterface>(defaultValues); export const MyContext = createContext<MyContextInterface>(defaultValues);

138
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 (
<MessageQueueContext.Provider value={{ addToQueue, queueChats, clearStatesMessageQueueProvider }}>
{children}
</MessageQueueContext.Provider>
);
};

93
src/background.ts

@ -21,6 +21,7 @@ import { createTransaction } from "./transactions/transactions";
import { decryptChatMessage } from "./utils/decryptChatMessage"; import { decryptChatMessage } from "./utils/decryptChatMessage";
import { decryptStoredWallet } from "./utils/decryptWallet"; import { decryptStoredWallet } from "./utils/decryptWallet";
import PhraseWallet from "./utils/generateWallet/phrase-wallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet";
import { RequestQueueWithPromise } from "./utils/queue/queue";
import { validateAddress } from "./utils/validateAddress"; import { validateAddress } from "./utils/validateAddress";
import { Sha256 } from "asmcrypto.js"; 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 groupApiLocal = "http://127.0.0.1:12391"
export const groupApiSocketLocal = "ws://127.0.0.1:12391" export const groupApiSocketLocal = "ws://127.0.0.1:12391"
const timeDifferenceForNotificationChatsBackground = 600000 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)=> { const checkDifference = (createdTimestamp)=> {
return (Date.now() - createdTimestamp) < timeDifferenceForNotificationChatsBackground return (Date.now() - createdTimestamp) < timeDifferenceForNotificationChatsBackground
} }
@ -612,33 +644,34 @@ const checkNewMessages =
if(!groups || groups?.length === 0) return if(!groups || groups?.length === 0) return
const savedtimestamp = await getTimestampGroupAnnouncement() const savedtimestamp = await getTimestampGroupAnnouncement()
for (const group of groups){ await Promise.all(groups.map(async (group) => {
try { try {
const identifier = `grp-${group.groupId}-anc-`; 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 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, { const response = await requestQueueAnnouncements.enqueue(()=> {
method: 'GET', return fetch(url, {
headers: { method: 'GET',
'Content-Type': 'application/json' headers: {
} 'Content-Type': 'application/json'
}) }
const responseData = await response.json() });
})
const latestMessage = responseData.filter((pub)=> pub?.name !== myName)[0] const responseData = await response.json();
const latestMessage = responseData.filter((pub) => pub?.name !== myName)[0];
if (!latestMessage) { if (!latestMessage) {
continue return; // continue to the next group
} }
if(checkDifference(latestMessage.created) && !savedtimestamp[group.groupId] || latestMessage.created > savedtimestamp?.[group.groupId]?.notification){ if (checkDifference(latestMessage.created) && (!savedtimestamp[group.groupId] || latestMessage.created > savedtimestamp?.[group.groupId]?.notification)) {
newAnnouncements.push(group) newAnnouncements.push(group);
await addTimestampGroupAnnouncement({groupId: group.groupId, timestamp: Date.now()}) await addTimestampGroupAnnouncement({ groupId: group.groupId, timestamp: Date.now() });
//save new timestamp // save new timestamp
} }
} catch (error) { } catch (error) {
console.error(error); // Handle error if needed
} }
} }));
if(newAnnouncements.length > 0){ if(newAnnouncements.length > 0){
const notificationId = 'chat_notification_' + Date.now() + '_type=group-announcement' + `_from=${newAnnouncements[0]?.groupId}`; const notificationId = 'chat_notification_' + Date.now() + '_type=group-announcement' + `_from=${newAnnouncements[0]?.groupId}`;
@ -1487,7 +1520,7 @@ const data = await objectToBase64({
secretKeyObject: secretKeyObject, secretKeyObject: secretKeyObject,
}) })
.then((res2) => { .then((res2) => {
pauseAllQueues()
sendChatGroup({ sendChatGroup({
groupId, groupId,
typeMessage: undefined, typeMessage: undefined,
@ -1497,7 +1530,9 @@ const data = await objectToBase64({
.then(() => {}) .then(() => {})
.catch((error) => { .catch((error) => {
console.error('1',error.message); console.error('1',error.message);
}); }).finally(()=> {
resumeAllQueues()
})
}) })
.catch((error) => { .catch((error) => {
console.error('2',error.message); console.error('2',error.message);
@ -3361,6 +3396,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
break; break;
} }
case "pauseAllQueues": {
pauseAllQueues()
sendResponse(res);
break;
break;
}
case "resumeAllQueues": {
resumeAllQueues()
sendResponse(res);
break;
}
case "decryptSingleForPublishes": { case "decryptSingleForPublishes": {
const { data, secretKeyObject, skipDecodeBase64 } = request.payload; const { data, secretKeyObject, skipDecodeBase64 } = request.payload;
@ -3472,6 +3520,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
try { try {
const logoutFunc = async()=> { const logoutFunc = async()=> {
forceCloseWebSocket() forceCloseWebSocket()
clearAllQueues()
if(interval){ if(interval){
// for announcement notification // for announcement notification
clearInterval(interval) clearInterval(interval)

7
src/components/Chat/AnnouncementDiscussion.tsx

@ -10,7 +10,7 @@ import { decryptPublishes, getTempPublish, saveTempPublish } from "./GroupAnnoun
import { AnnouncementList } from "./AnnouncementList"; import { AnnouncementList } from "./AnnouncementList";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { getBaseApiReact } from "../../App"; import { getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App";
const tempKey = 'accouncement-comment' const tempKey = 'accouncement-comment'
@ -107,6 +107,7 @@ export const AnnouncementDiscussion = ({
const publishComment = async () => { const publishComment = async () => {
try { try {
pauseAllQueues()
const fee = await getFee('ARBITRARY') const fee = await getFee('ARBITRARY')
await show({ await show({
message: "Would you like to perform a ARBITRARY transaction?" , message: "Would you like to perform a ARBITRARY transaction?" ,
@ -123,7 +124,7 @@ export const AnnouncementDiscussion = ({
extra: {}, extra: {},
message: htmlContent, message: htmlContent,
}; };
const secretKeyObject = await getSecretKey(); const secretKeyObject = await getSecretKey(false, true);
const message64: any = await objectToBase64(message); const message64: any = await objectToBase64(message);
const encryptSingle = await encryptChatMessage( const encryptSingle = await encryptChatMessage(
@ -154,6 +155,7 @@ export const AnnouncementDiscussion = ({
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
resumeAllQueues()
setIsSending(false); setIsSending(false);
} }
}; };
@ -161,6 +163,7 @@ export const AnnouncementDiscussion = ({
const getComments = React.useCallback( const getComments = React.useCallback(
async (selectedAnnouncement) => { async (selectedAnnouncement) => {
try { try {
setIsLoading(true); setIsLoading(true);
const offset = 0; const offset = 0;

2
src/components/Chat/ChatDirect.tsx

@ -241,7 +241,7 @@ const clearEditorContent = () => {
</> </>
)} )}
<ChatList initialMessages={messages} myAddress={myAddress}/> <ChatList initialMessages={messages} myAddress={myAddress} tempMessages={[]}/>
<div style={{ <div style={{

53
src/components/Chat/ChatGroup.tsx

@ -10,15 +10,16 @@ import Tiptap from './TipTap'
import { CustomButton } from '../../App-styles' import { CustomButton } from '../../App-styles'
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar' import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'
import { getBaseApiReactSocket } from '../../App' import { getBaseApiReactSocket, pauseAllQueues, resumeAllQueues } from '../../App'
import { CustomizedSnackbars } from '../Snackbar/Snackbar' import { CustomizedSnackbars } from '../Snackbar/Snackbar'
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes' import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'
import { useMessageQueue } from '../../MessageQueueContext'
export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey}) => { export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName}) => {
const [messages, setMessages] = useState([]) const [messages, setMessages] = useState([])
const [isSending, setIsSending] = useState(false) const [isSending, setIsSending] = useState(false)
const [isLoading, setIsLoading] = 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 timeoutIdRef = useRef(null); // Timeout ID reference
const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference
const editorRef = useRef(null); const editorRef = useRef(null);
const { queueChats, addToQueue, } = useMessageQueue();
const setEditorRef = (editorInstance) => { const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance; editorRef.current = editorInstance;
}; };
const tempMessages = useMemo(()=> {
if(!selectedGroup) return []
if(queueChats[selectedGroup]){
return queueChats[selectedGroup]
}
return []
}, [selectedGroup, queueChats])
const secretKeyRef = useRef(null) const secretKeyRef = useRef(null)
useEffect(()=> { useEffect(()=> {
@ -88,7 +98,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
...item, ...item,
id: item.signature, id: item.signature,
text: item.text, text: item.text,
unread: true unread: item?.sender === myAddress ? false : true
} }
} ) } )
setMessages((prev)=> [...prev, ...formatted]) setMessages((prev)=> [...prev, ...formatted])
@ -98,7 +108,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
...item, ...item,
id: item.signature, id: item.signature,
text: item.text, text: item.text,
unread: false unread: false
} }
} ) } )
setMessages(formatted) setMessages(formatted)
@ -199,6 +209,10 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
forceCloseWebSocket() forceCloseWebSocket()
setMessages([]) setMessages([])
setIsLoading(true) setIsLoading(true)
pauseAllQueues()
setTimeout(() => {
resumeAllQueues()
}, 6000);
initWebsocketMessageGroup() initWebsocketMessageGroup()
hasInitializedWebsocket.current = true hasInitializedWebsocket.current = true
}, [secretKey]) }, [secretKey])
@ -262,18 +276,35 @@ const clearEditorContent = () => {
const sendMessage = async ()=> { const sendMessage = async ()=> {
try { try {
if(isSending) return if(isSending) return
pauseAllQueues()
if (editorRef.current) { if (editorRef.current) {
const htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
setIsSending(true) setIsSending(true)
const message = htmlContent const message = htmlContent
const secretKeyObject = await getSecretKey() const secretKeyObject = await getSecretKey(false, true)
const message64: any = await objectToBase64(message) const message64: any = await objectToBase64(message)
const encryptSingle = await encryptChatMessage(message64, secretKeyObject) 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() clearEditorContent()
} }
// send chat message // send chat message
@ -286,6 +317,7 @@ const clearEditorContent = () => {
console.error(error) console.error(error)
} finally { } finally {
setIsSending(false) setIsSending(false)
resumeAllQueues()
} }
} }
@ -305,12 +337,11 @@ const clearEditorContent = () => {
flexDirection: 'column', flexDirection: 'column',
width: '100%', width: '100%',
opacity: hide ? 0 : 1, opacity: hide ? 0 : 1,
visibility: hide && 'hidden', position: hide ? 'absolute' : 'relative',
position: hide ? 'fixed' : 'relative', left: hide && '-100000px',
left: hide && '-1000px',
}}> }}>
<ChatList initialMessages={messages} myAddress={myAddress}/> <ChatList initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages}/>
<div style={{ <div style={{

180
src/components/Chat/ChatList.tsx

@ -1,4 +1,4 @@
import React, { useCallback, useState, useEffect, useRef } from 'react'; import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
import { List, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtualized'; import { List, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtualized';
import { MessageItem } from './MessageItem'; import { MessageItem } from './MessageItem';
@ -7,35 +7,34 @@ const cache = new CellMeasurerCache({
defaultHeight: 50, defaultHeight: 50,
}); });
export const ChatList = ({ initialMessages, myAddress }) => { export const ChatList = ({ initialMessages, myAddress, tempMessages }) => {
const hasLoadedInitialRef = useRef(false); const hasLoadedInitialRef = useRef(false);
const listRef = useRef(); const listRef = useRef();
const [messages, setMessages] = useState(initialMessages); const [messages, setMessages] = useState(initialMessages);
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
useEffect(()=> { useEffect(() => {
cache.clearAll(); cache.clearAll();
}, []) }, []);
const handleMessageSeen = useCallback((messageId) => {
const handleMessageSeen = useCallback(() => {
setMessages((prevMessages) => setMessages((prevMessages) =>
prevMessages.map((msg) => { prevMessages.map((msg) => ({
return { ...msg, unread: false } ...msg,
} unread: false,
}))
)
); );
}, []); }, []);
const handleScroll = ({ scrollTop, scrollHeight, clientHeight }) => { const handleScroll = ({ scrollTop, scrollHeight, clientHeight }) => {
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50; const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
const hasUnreadMessages = messages.some((msg) => msg.unread); const hasUnreadMessages = messages.some((msg) => msg.unread);
if(isAtBottom){
if (!isAtBottom && hasUnreadMessages) { handleMessageSeen()
setShowScrollButton(true);
} else {
setShowScrollButton(false);
} }
setShowScrollButton(!isAtBottom && hasUnreadMessages);
}; };
const debounce = (func, delay) => { const debounce = (func, delay) => {
@ -47,96 +46,113 @@ export const ChatList = ({ initialMessages, myAddress }) => {
}, delay); }, delay);
}; };
}; };
const handleScrollDebounced = debounce(handleScroll, 100); const handleScrollDebounced = debounce(handleScroll, 100);
const scrollToBottom = () => { const scrollToBottom = (initialmsgs) => {
if (listRef.current) { if (listRef.current) {
const msgs = initialmsgs?.length ? initialmsgs : messages
listRef.current?.recomputeRowHeights(); listRef.current?.recomputeRowHeights();
listRef.current.scrollToRow(messages.length - 1);
listRef.current.scrollToRow(msgs.length - 1);
setTimeout(() => { setTimeout(() => {
listRef.current?.recomputeRowHeights();
listRef.current.scrollToRow(messages.length - 1); listRef.current.scrollToRow(msgs.length - 1);
}, 100); }, 100);
setShowScrollButton(false); setShowScrollButton(false);
} }
}; };
const preserveScrollPosition = (callback) => { const recomputeListHeights = () => {
if (listRef.current) { if (listRef.current) {
const scrollContainer = listRef.current.Grid._scrollingContainer; listRef.current.recomputeRowHeights();
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);
} }
}; };
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 ( return (
<CellMeasurer <CellMeasurer
key={key} key={key}
cache={cache} cache={cache}
parent={parent} parent={parent}
columnIndex={0} columnIndex={0}
rowIndex={index} rowIndex={index}
>
{({ measure }) => (
<div style={style}>
<div
onLoad={() => preserveScrollPosition(measure)} // Prevent jumps while measuring
style={{
marginBottom: '10px',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
> >
<MessageItem isLast={index === messages.length - 1} message={message} onSeen={handleMessageSeen} /> {({ measure }) => (
</div> <div style={style}>
</div> <div
)} onLoad={() => {
</CellMeasurer> if (isLargeMessage) {
measure(); // Ensure large messages are properly measured
}
}}
style={{
marginBottom: '10px',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<MessageItem
isLast={index === messages.length - 1}
message={message}
onSeen={handleMessageSeen}
isTemp={!!message?.isTemp}
/>
</div>
</div>
)}
</CellMeasurer>
); );
}; };
useEffect(() => { 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(() => { setTimeout(() => {
if (listRef.current) { if (listRef.current) {
// Accessing scrollTop, scrollHeight, clientHeight from List's methods const { scrollTop, scrollHeight, clientHeight } = listRef.current.Grid._scrollingContainer;
const scrollTop = listRef.current.Grid._scrollingContainer.scrollTop; handleScroll({ scrollTop, scrollHeight, clientHeight });
const scrollHeight = listRef.current.Grid._scrollingContainer.scrollHeight; recomputeListHeights(); // Ensure heights are recomputed on message load
const clientHeight = listRef.current.Grid._scrollingContainer.clientHeight; setTimeout(() => {
if(!hasLoadedInitialRef.current){
handleScroll({ scrollTop, scrollHeight, clientHeight }); scrollToBottom(totalMessages);
hasLoadedInitialRef.current = true
}
}, 100);
} }
}, 100); }, 500);
}, [initialMessages]); }, [tempMessages, initialMessages]);
useEffect(() => { // useEffect(() => {
// Scroll to the bottom on initial load or when messages change // // Scroll to the bottom on initial load or when messages change
if (listRef.current && messages.length > 0 && hasLoadedInitialRef.current === false) { // if (listRef.current && messages.length > 0 && !hasLoadedInitialRef.current) {
scrollToBottom(); // scrollToBottom();
hasLoadedInitialRef.current = true; // hasLoadedInitialRef.current = true;
} else if (messages.length > 0 && messages[messages.length - 1].sender === myAddress) { // } else if (messages.length > 0 && messages[messages.length - 1].sender === myAddress) {
scrollToBottom(); // scrollToBottom();
} // }
}, [messages, myAddress]); // }, [messages, myAddress]);
return ( return (
<div style={{ position: 'relative', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}> <div style={{ position: 'relative', flexGrow: 1, width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
@ -148,6 +164,8 @@ export const ChatList = ({ initialMessages, myAddress }) => {
rowRenderer={rowRenderer} rowRenderer={rowRenderer}
onScroll={handleScrollDebounced} onScroll={handleScrollDebounced}
deferredMeasurementCache={cache} deferredMeasurementCache={cache}
onRowsRendered={recomputeListHeights} // Force recompute on render
overscanRowCount={10} // For performance: pre-render some rows
/> />
)} )}
</AutoSizer> </AutoSizer>
@ -168,7 +186,9 @@ export const ChatList = ({ initialMessages, myAddress }) => {
> >
Scroll to Unread Messages Scroll to Unread Messages
</button> </button>
)} )}
</div> </div>
); );
}; };

7
src/components/Chat/GroupAnnouncements.tsx

@ -28,7 +28,7 @@ const uid = new ShortUniqueId({ length: 8 });
import CampaignIcon from '@mui/icons-material/Campaign'; import CampaignIcon from '@mui/icons-material/Campaign';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { AnnouncementDiscussion } from "./AnnouncementDiscussion"; import { AnnouncementDiscussion } from "./AnnouncementDiscussion";
import { MyContext, getBaseApiReact } from "../../App"; import { MyContext, getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App";
import { RequestQueueWithPromise } from "../../utils/queue/queue"; import { RequestQueueWithPromise } from "../../utils/queue/queue";
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from "../Snackbar/Snackbar";
@ -248,6 +248,8 @@ export const GroupAnnouncements = ({
const publishAnnouncement = async () => { const publishAnnouncement = async () => {
try { try {
pauseAllQueues()
const fee = await getFee('ARBITRARY') const fee = await getFee('ARBITRARY')
await show({ await show({
message: "Would you like to perform a ARBITRARY transaction?" , message: "Would you like to perform a ARBITRARY transaction?" ,
@ -263,7 +265,7 @@ export const GroupAnnouncements = ({
extra: {}, extra: {},
message: htmlContent message: htmlContent
} }
const secretKeyObject = await getSecretKey(); const secretKeyObject = await getSecretKey(false, true);
const message64: any = await objectToBase64(message); const message64: any = await objectToBase64(message);
const encryptSingle = await encryptChatMessage( const encryptSingle = await encryptChatMessage(
message64, message64,
@ -295,6 +297,7 @@ export const GroupAnnouncements = ({
}); });
setOpenSnack(true) setOpenSnack(true)
} finally { } finally {
resumeAllQueues()
setIsSending(false); setIsSending(false);
} }
}; };

25
src/components/Chat/MessageItem.tsx

@ -7,7 +7,7 @@ import { formatTimestamp } from "../../utils/time";
import { getBaseApi } from "../../background"; import { getBaseApi } from "../../background";
import { getBaseApiReact } from "../../App"; import { getBaseApiReact } from "../../App";
export const MessageItem = ({ message, onSeen, isLast }) => { export const MessageItem = ({ message, onSeen, isLast, isTemp }) => {
const { ref, inView } = useInView({ const { ref, inView } = useInView({
threshold: 0.7, // Fully visible threshold: 0.7, // Fully visible
@ -30,6 +30,7 @@ export const MessageItem = ({ message, onSeen, isLast }) => {
width: "95%", width: "95%",
display: "flex", display: "flex",
gap: '7px', gap: '7px',
opacity: isTemp ? 0.5 : 1
}} }}
> >
<Avatar <Avatar
@ -67,13 +68,23 @@ export const MessageItem = ({ message, onSeen, isLast }) => {
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
width: '100%' width: '100%',
}}> }}>
<Typography sx={{ {isTemp ? (
fontSize: '14px', <Typography sx={{
color: 'gray', fontSize: '14px',
fontFamily: 'Inter' color: 'gray',
}}>{formatTimestamp(message.timestamp)}</Typography> fontFamily: 'Inter'
}}>Sending...</Typography>
): (
<Typography sx={{
fontSize: '14px',
color: 'gray',
fontFamily: 'Inter'
}}>{formatTimestamp(message.timestamp)}</Typography>
) }
</Box> </Box>
</Box> </Box>

2
src/components/Chat/TipTap.tsx

@ -269,7 +269,7 @@ const extensions = [
const content = ``; const content = ``;
export default ({ setEditorRef, onEnter, disableEnter, isChat }) => { export default ({ setEditorRef, onEnter, disableEnter, isChat }) => {
console.log('exte', extensions)
const extensionsFiltered = isChat ? extensions.filter((item)=> item?.name !== 'image') : extensions const extensionsFiltered = isChat ? extensions.filter((item)=> item?.name !== 'image') : extensions
return ( return (
<EditorProvider <EditorProvider

2
src/components/Group/Forum/NewThread.tsx

@ -244,7 +244,7 @@ export const NewThread = ({
reply, reply,
}; };
const secretKey = await getSecretKey(); const secretKey = await getSecretKey(false, true);
if (!secretKey) { if (!secretKey) {
throw new Error("Cannot get group secret key"); throw new Error("Cannot get group secret key");
} }

238
src/components/Group/Group.tsx

@ -39,7 +39,7 @@ import { Spacer } from "../../common/Spacer";
import PeopleIcon from "@mui/icons-material/People"; import PeopleIcon from "@mui/icons-material/People";
import { ManageMembers } from "./ManageMembers"; import { ManageMembers } from "./ManageMembers";
import MarkChatUnreadIcon from "@mui/icons-material/MarkChatUnread"; import MarkChatUnreadIcon from "@mui/icons-material/MarkChatUnread";
import { MyContext, clearAllQueues, getBaseApiReact } from "../../App"; import { MyContext, clearAllQueues, getBaseApiReact, pauseAllQueues, resumeAllQueues } from "../../App";
import { ChatDirect } from "../Chat/ChatDirect"; import { ChatDirect } from "../Chat/ChatDirect";
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { LoadingButton } from "@mui/lab"; import { LoadingButton } from "@mui/lab";
@ -60,6 +60,7 @@ import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched";
import { RequestQueueWithPromise } from "../../utils/queue/queue"; import { RequestQueueWithPromise } from "../../utils/queue/queue";
import { WebSocketActive } from "./WebsocketActive"; import { WebSocketActive } from "./WebsocketActive";
import { flushSync } from "react-dom"; import { flushSync } from "react-dom";
import { useMessageQueue } from "../../MessageQueueContext";
interface GroupProps { interface GroupProps {
myAddress: string; myAddress: string;
@ -278,6 +279,7 @@ export const Group = ({
}: GroupProps) => { }: GroupProps) => {
const [secretKey, setSecretKey] = useState(null); const [secretKey, setSecretKey] = useState(null);
const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null); const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null);
const lastFetchedSecretKey = useRef(null)
const [secretKeyDetails, setSecretKeyDetails] = useState(null); const [secretKeyDetails, setSecretKeyDetails] = useState(null);
const [newEncryptionNotification, setNewEncryptionNotification] = const [newEncryptionNotification, setNewEncryptionNotification] =
useState(null); useState(null);
@ -321,6 +323,7 @@ export const Group = ({
const isLoadingOpenSectionFromNotification = useRef(false); const isLoadingOpenSectionFromNotification = useRef(false);
const setupGroupWebsocketInterval = useRef(null); const setupGroupWebsocketInterval = useRef(null);
const settimeoutForRefetchSecretKey = useRef(null); const settimeoutForRefetchSecretKey = useRef(null);
const { clearStatesMessageQueueProvider } = useMessageQueue();
useEffect(() => { useEffect(() => {
@ -470,7 +473,7 @@ export const Group = ({
const queryString = admins.map((name) => `name=${name}`).join("&"); const queryString = admins.map((name) => `name=${name}`).join("&");
const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${ const url = `${getBaseApiReact()}/arbitrary/resources/search?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
selectedGroup?.groupId selectedGroup?.groupId
}&exactmatchnames=true&limit=10&reverse=true&${queryString}`; }&exactmatchnames=true&limit=0&reverse=true&${queryString}`;
const response = await fetch(url); const response = await fetch(url);
if(!response.ok){ if(!response.ok){
throw new Error('network error') throw new Error('network error')
@ -484,10 +487,22 @@ export const Group = ({
if (filterId?.length === 0) { if (filterId?.length === 0) {
return false; 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 { try {
pauseAllQueues()
if(secretKeyToPublish && secretKey && lastFetchedSecretKey.current && Date.now() - lastFetchedSecretKey.current < 1800000) return secretKey
if (loadingGroupParam) { if (loadingGroupParam) {
setIsLoadingGroup(true); setIsLoadingGroup(true);
} }
@ -500,6 +515,7 @@ export const Group = ({
const prevGroupId = selectedGroupRef.current.groupId; const prevGroupId = selectedGroupRef.current.groupId;
// const validApi = await findUsableApi(); // const validApi = await findUsableApi();
const groupAdmins = await getGroupAdimns(selectedGroup?.groupId); const groupAdmins = await getGroupAdimns(selectedGroup?.groupId);
setAdmins(groupAdmins)
if(!groupAdmins.length){ if(!groupAdmins.length){
throw new Error('Network error') throw new Error('Network error')
} }
@ -536,7 +552,7 @@ export const Group = ({
throw new Error("SecretKey is not valid"); throw new Error("SecretKey is not valid");
setSecretKeyDetails(publish); setSecretKeyDetails(publish);
setSecretKey(decryptedKeyToObject); setSecretKey(decryptedKeyToObject);
lastFetchedSecretKey.current = Date.now()
setMemberCountFromSecretKeyData(decryptedKey.count); setMemberCountFromSecretKeyData(decryptedKey.count);
if (decryptedKeyToObject) { if (decryptedKeyToObject) {
setTriedToFetchSecretKey(true); setTriedToFetchSecretKey(true);
@ -556,6 +572,12 @@ export const Group = ({
} finally { } finally {
setIsLoadingGroup(false); setIsLoadingGroup(false);
if(!secretKeyToPublish){
await getAdmins(selectedGroup?.groupId);
}
resumeAllQueues()
} }
}; };
@ -768,11 +790,13 @@ export const Group = ({
}; };
useEffect(() => { useEffect(() => {
if (selectedGroup?.groupId) { if (selectedGroup?.groupId) {
getAdmins(selectedGroup?.groupId); // getAdmins(selectedGroup?.groupId);
getMembers(selectedGroup?.groupId); getMembers(selectedGroup?.groupId);
} }
}, [selectedGroup?.groupId]); }, [selectedGroup?.groupId]);
const shouldReEncrypt = useMemo(() => { const shouldReEncrypt = useMemo(() => {
if (triedToFetchSecretKey && !secretKeyPublishDate) return true; if (triedToFetchSecretKey && !secretKeyPublishDate) return true;
if ( if (
@ -913,6 +937,7 @@ export const Group = ({
const resetAllStatesAndRefs = () => { const resetAllStatesAndRefs = () => {
// Reset all useState values to their initial states // Reset all useState values to their initial states
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setSecretKeyDetails(null); setSecretKeyDetails(null);
setNewEncryptionNotification(null); setNewEncryptionNotification(null);
@ -960,6 +985,7 @@ export const Group = ({
const logoutEventFunc = ()=> { const logoutEventFunc = ()=> {
resetAllStatesAndRefs() resetAllStatesAndRefs()
clearStatesMessageQueueProvider()
} }
useEffect(() => { useEffect(() => {
@ -987,6 +1013,7 @@ export const Group = ({
setNewChat(false); setNewChat(false);
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setAdmins([]); setAdmins([]);
setSecretKeyDetails(null); setSecretKeyDetails(null);
@ -1034,6 +1061,7 @@ export const Group = ({
setChatMode("groups"); setChatMode("groups");
setSelectedGroup(null); setSelectedGroup(null);
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setAdmins([]); setAdmins([]);
setSecretKeyDetails(null); setSecretKeyDetails(null);
@ -1090,6 +1118,7 @@ export const Group = ({
setChatMode("groups"); setChatMode("groups");
setSelectedGroup(null); setSelectedGroup(null);
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setAdmins([]); setAdmins([]);
setSecretKeyDetails(null); setSecretKeyDetails(null);
@ -1125,7 +1154,7 @@ export const Group = ({
return ( return (
<> <>
<WebSocketActive myAddress={myAddress} /> <WebSocketActive myAddress={myAddress} setIsLoadingGroups={setIsLoadingGroups} />
<CustomizedSnackbars <CustomizedSnackbars
open={openSnack} open={openSnack}
setOpen={setOpenSnack} setOpen={setOpenSnack}
@ -1321,6 +1350,7 @@ export const Group = ({
setNewChat(false); setNewChat(false);
setSelectedGroup(null); setSelectedGroup(null);
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setAdmins([]); setAdmins([]);
setSecretKeyDetails(null); setSecretKeyDetails(null);
@ -1511,6 +1541,7 @@ export const Group = ({
hide={groupSection !== "chat" || !secretKey} hide={groupSection !== "chat" || !secretKey}
handleSecretKeyCreationInProgress={handleSecretKeyCreationInProgress} handleSecretKeyCreationInProgress={handleSecretKeyCreationInProgress}
triedToFetchSecretKey={triedToFetchSecretKey} triedToFetchSecretKey={triedToFetchSecretKey}
myName={userInfo?.name}
/> />
)} )}
{firstSecretKeyInCreation && triedToFetchSecretKey && !secretKeyPublishDate && ( {firstSecretKeyInCreation && triedToFetchSecretKey && !secretKeyPublishDate && (
@ -1655,10 +1686,95 @@ export const Group = ({
/> />
)} )}
<AuthenticatedContainerInnerRight
</>
)}
{selectedDirect && !newChat && (
<>
<Box
sx={{
position: "relative",
flexGrow: 1,
display: "flex",
}}
>
<ChatDirect
myAddress={myAddress}
isNewChat={newChat}
selectedDirect={selectedDirect}
setSelectedDirect={setSelectedDirect}
setNewChat={setNewChat}
getTimestampEnterChat={getTimestampEnterChat}
/>
</Box>
</>
)}
{!selectedDirect &&
!selectedGroup &&
!newChat &&
groupSection === "home" && (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
gap: "20px",
}}
>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-start",
}}
>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={refreshHomeDataFunc}
sx={{
color: "white",
}}
>
Refresh home data
</Button>
</Box>
<Box
sx={{
display: "flex",
gap: "40px",
flexWrap: "wrap",
}}
>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
hasGroups={groups?.length !== 0}
/>
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
/>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
/>
<ListOfThreadPostsWatched />
</Box>
</Box>
)}
<AuthenticatedContainerInnerRight
sx={{ sx={{
marginLeft: "auto", marginLeft: "auto",
width: "auto", width: "135px",
padding: "5px", padding: "5px",
}} }}
> >
@ -1685,6 +1801,7 @@ export const Group = ({
setNewChat(false); setNewChat(false);
setSelectedDirect(null); setSelectedDirect(null);
setSecretKey(null); setSecretKey(null);
lastFetchedSecretKey.current = null
setSecretKeyPublishDate(null); setSecretKeyPublishDate(null);
setAdmins([]); setAdmins([]);
setSecretKeyDetails(null); setSecretKeyDetails(null);
@ -1713,8 +1830,9 @@ export const Group = ({
Home Home
</Typography> </Typography>
</Box> </Box>
{selectedGroup && (
<Spacer height="20px" /> <>
<Spacer height="20px" />
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -1894,6 +2012,9 @@ export const Group = ({
</Typography> </Typography>
</Box> </Box>
<Spacer height="20px" /> <Spacer height="20px" />
</>
)}
{/* <SettingsIcon {/* <SettingsIcon
sx={{ sx={{
cursor: "pointer", cursor: "pointer",
@ -1901,100 +2022,17 @@ export const Group = ({
}} }}
/> */} /> */}
</AuthenticatedContainerInnerRight> </AuthenticatedContainerInnerRight>
</>
)}
{selectedDirect && !newChat && (
<>
<Box
sx={{
position: "relative",
flexGrow: 1,
display: "flex",
}}
>
<ChatDirect
myAddress={myAddress}
isNewChat={newChat}
selectedDirect={selectedDirect}
setSelectedDirect={setSelectedDirect}
setNewChat={setNewChat}
getTimestampEnterChat={getTimestampEnterChat}
/>
</Box>
</>
)}
{!selectedDirect &&
!selectedGroup &&
!newChat &&
groupSection === "home" && (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
gap: "20px",
}}
>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-start",
}}
>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={refreshHomeDataFunc}
sx={{
color: "white",
}}
>
Refresh home data
</Button>
</Box>
<Box
sx={{
display: "flex",
gap: "40px",
flexWrap: "wrap",
}}
>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
hasGroups={groups?.length !== 0}
/>
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
/>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
/>
<ListOfThreadPostsWatched />
</Box>
</Box>
)}
<LoadingSnackbar <LoadingSnackbar
open={isLoadingGroups} open={isLoadingGroup}
info={{ info={{
message: "Loading groups... please wait.", message: "Setting up group... please wait.",
}} }}
/> />
<LoadingSnackbar
open={isLoadingGroup} <LoadingSnackbar
open={isLoadingGroups}
info={{ info={{
message: "Setting up group... please wait.", message: "Setting up groups... please wait.",
}} }}
/> />
</div> </div>

17
src/components/Group/WebsocketActive.tsx

@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react'; 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 socketRef = useRef(null); // WebSocket reference
const timeoutIdRef = useRef(null); // Timeout ID reference const timeoutIdRef = useRef(null); // Timeout ID reference
const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference
const initiateRef = useRef(null)
const forceCloseWebSocket = () => { const forceCloseWebSocket = () => {
if (socketRef.current) { if (socketRef.current) {
console.log('Force closing the WebSocket'); console.log('Force closing the WebSocket');
@ -41,6 +41,11 @@ export const WebSocketActive = ({ myAddress }) => {
const currentAddress = myAddress; const currentAddress = myAddress;
try { try {
if(!initiateRef.current) {
setIsLoadingGroups(true)
pauseAllQueues()
}
const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64`; const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64`;
socketRef.current = new WebSocket(socketLink); socketRef.current = new WebSocket(socketLink);
@ -55,6 +60,12 @@ export const WebSocketActive = ({ myAddress }) => {
clearTimeout(timeoutIdRef.current); clearTimeout(timeoutIdRef.current);
groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds
} else { } else {
if(!initiateRef.current) {
setIsLoadingGroups(false)
initiateRef.current = true
resumeAllQueues()
}
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
const filteredGroups = data.groups?.filter(item => item?.groupId !== 0) || []; const filteredGroups = data.groups?.filter(item => item?.groupId !== 0) || [];
const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));

4
src/main.tsx

@ -5,6 +5,7 @@ import './index.css'
import { ThemeProvider, createTheme } from '@mui/material/styles'; import { ThemeProvider, createTheme } from '@mui/material/styles';
import { CssBaseline } from '@mui/material'; import { CssBaseline } from '@mui/material';
import { MessageQueueProvider } from './MessageQueueContext.tsx';
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
@ -48,8 +49,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<MessageQueueProvider>
<App /> <App />
</MessageQueueProvider>
</ThemeProvider> </ThemeProvider>
</React.StrictMode>, </React.StrictMode>,
) )

Loading…
Cancel
Save