mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-23 19:37:52 +00:00
fix get latest secretkey, multi-send group chat
This commit is contained in:
parent
09e690e43f
commit
82503efa8c
24
src/App.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
Normal file
138
src/MessageQueueContext.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -241,7 +241,7 @@ const clearEditorContent = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ChatList initialMessages={messages} myAddress={myAddress}/>
|
<ChatList initialMessages={messages} myAddress={myAddress} tempMessages={[]}/>
|
||||||
|
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
|
@ -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={{
|
||||||
|
@ -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 rowRenderer = ({ index, key, parent, style }) => {
|
||||||
const message = messages[index];
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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,102 +2022,19 @@ 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
|
|
||||||
open={isLoadingGroups}
|
|
||||||
info={{
|
|
||||||
message: "Loading groups... please wait.",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<LoadingSnackbar
|
<LoadingSnackbar
|
||||||
open={isLoadingGroup}
|
open={isLoadingGroup}
|
||||||
info={{
|
info={{
|
||||||
message: "Setting up group... please wait.",
|
message: "Setting up group... please wait.",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<LoadingSnackbar
|
||||||
|
open={isLoadingGroups}
|
||||||
|
info={{
|
||||||
|
message: "Setting up groups... please wait.",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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));
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user