-
+
+
+ setSelectedAnnouncement(null)}
+ sx={{
+ cursor: 'pointer',
+ }}
+ />
+
-
- setSelectedAnnouncement(null)} sx={{
- cursor: 'pointer'
- }} />
-
-
+
{}}
+ setSelectedAnnouncement={() => {}}
disableComment
showLoadMore={comments.length > 0 && comments.length % 20 === 0}
loadMore={loadMore}
myName={myName}
-
/>
+
-
- {isFocusedParent && (
- {
- if(isSending) return
- setIsFocusedParent(false)
- clearEditorContent()
- // Unfocus the editor
- }}
- style={{
- marginTop: 'auto',
- alignSelf: 'center',
- cursor: isSending ? 'default' : 'pointer',
- flexShrink: 0,
- padding: isMobile && '5px',
- fontSize: isMobile && '14px',
- background: 'red',
- }}
- >
-
- {` Close`}
-
-
- )}
- {
- if (isSending) return;
- publishComment();
- }}
- style={{
- marginTop: "auto",
- alignSelf: "center",
- cursor: isSending ? "default" : "pointer",
- background: isSending && "rgba(0, 0, 0, 0.8)",
+
+
- {isSending && (
- {
+ if (isSending) return;
+ setIsFocusedParent(false);
+ clearEditorContent();
+ // TODO Unfocus the editor
}}
- />
+ style={{
+ alignSelf: 'center',
+ background: 'red',
+ cursor: isSending ? 'default' : 'pointer',
+ flexShrink: 0,
+ fontSize: '14px',
+ marginTop: 'auto',
+ padding: '5px',
+ }}
+ >
+ {t('core:action.close', { postProcess: 'capitalizeFirstChar' })}
+
)}
- {` Publish Comment`}
-
-
-
+
{
+ if (isSending) return;
+ publishComment();
+ }}
+ style={{
+ alignSelf: 'center',
+ background: theme.palette.background.default,
+ cursor: isSending ? 'default' : 'pointer',
+ flexShrink: 0,
+ fontSize: '14px',
+ marginTop: 'auto',
+ padding: '5px',
+ }}
+ >
+ {isSending && (
+
+ )}
+ {t('core:action.publish_comment', {
+ postProcess: 'capitalizeFirstChar',
+ })}
+
+
-
+
diff --git a/src/components/Chat/AnnouncementItem.tsx b/src/components/Chat/AnnouncementItem.tsx
index 758a451..a20f58d 100644
--- a/src/components/Chat/AnnouncementItem.tsx
+++ b/src/components/Chat/AnnouncementItem.tsx
@@ -1,173 +1,218 @@
-import { Message } from "@chatscope/chat-ui-kit-react";
-import React, { useEffect, useState } from "react";
-import { useInView } from "react-intersection-observer";
-import { MessageDisplay } from "./MessageDisplay";
-import { Avatar, Box, Typography } from "@mui/material";
-import { formatTimestamp } from "../../utils/time";
+import { useCallback, useEffect, useState } from 'react';
+import { MessageDisplay } from './MessageDisplay';
+import { Avatar, Box, Typography, useTheme } from '@mui/material';
+import { formatTimestamp } from '../../utils/time';
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
-import { getBaseApi } from "../../background";
-import { requestQueueCommentCount } from "./GroupAnnouncements";
-import { CustomLoader } from "../../common/CustomLoader";
-import { getArbitraryEndpointReact, getBaseApiReact } from "../../App";
-import { WrapperUserAction } from "../WrapperUserAction";
-export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment, myName }) => {
+import { requestQueueCommentCount } from './GroupAnnouncements';
+import { CustomLoader } from '../../common/CustomLoader';
+import { getArbitraryEndpointReact, getBaseApiReact } from '../../App';
+import { WrapperUserAction } from '../WrapperUserAction';
+import { useTranslation } from 'react-i18next';
- const [commentLength, setCommentLength] = useState(0)
- const getNumberOfComments = React.useCallback(
- async () => {
- try {
- const offset = 0;
+export const AnnouncementItem = ({
+ message,
+ messageData,
+ setSelectedAnnouncement,
+ disableComment,
+ myName,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation([
+ 'auth',
+ 'core',
+ 'group',
+ 'question',
+ 'tutorial',
+ ]);
+ const [commentLength, setCommentLength] = useState(0);
- // dispatch(setIsLoadingGlobal(true))
- const identifier = `cm-${message.identifier}`;
- const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
-
- const response = await requestQueueCommentCount.enqueue(() => {
- return fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
- })
- const responseData = await response.json();
+ const getNumberOfComments = useCallback(async () => {
+ try {
+ const offset = 0;
+ const identifier = `cm-${message.identifier}`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
+
+ const response = await requestQueueCommentCount.enqueue(() => {
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ });
+ const responseData = await response.json();
+
+ setCommentLength(responseData?.length);
+ } catch (error) {
+ console.log(error);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (disableComment) return;
+ getNumberOfComments();
+ }, []);
- setCommentLength(responseData?.length);
-
- } catch (error) {
- } finally {
- // dispatch(setIsLoadingGlobal(false))
- }
- },
- []
- );
- useEffect(()=> {
- if(disableComment) return
- getNumberOfComments()
- }, [])
return (
-
-
-
- {message?.name?.charAt(0)}
-
-
-
-
+
+ {message?.name?.charAt(0)}
+
+
+
+
- {message?.name}
-
-
- {!messageData?.decryptedData && (
-
-
-
- )}
- {messageData?.decryptedData?.message && (
- <>
- {messageData?.type === "notification" ? (
-
- ) : (
-
- )}
- >
- )}
+
+
+ {message?.name}
+
+
-
-
- {formatTimestamp(message.created)}
+ {!messageData?.decryptedData && (
+
+
+
+ )}
+
+ {messageData?.decryptedData?.message && (
+ <>
+ {messageData?.type === 'notification' ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+
+
+ {formatTimestamp(message.created)}
+
+
-
- {!disableComment && (
- setSelectedAnnouncement(message)}>
-
-
-
- {commentLength ? (
- {`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}
- ) : (
- Leave comment
- )}
-
+
+ {!disableComment && (
+ setSelectedAnnouncement(message)}
+ >
+
+
+ {commentLength ? (
+ {`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}
+ ) : (
+
+ {t('core:action.leave_comment', {
+ postProcess: 'capitalizeFirstChar',
+ })}
+
+ )}
+
+
+
-
-
- )}
-
+ )}
);
};
diff --git a/src/components/Chat/AnnouncementList.tsx b/src/components/Chat/AnnouncementList.tsx
index b55ebb5..eea6c83 100644
--- a/src/components/Chat/AnnouncementList.tsx
+++ b/src/components/Chat/AnnouncementList.tsx
@@ -1,13 +1,9 @@
-import React, { useCallback, useState, useEffect, useRef } from "react";
-import {
- List,
- AutoSizer,
- CellMeasurerCache,
- CellMeasurer,
-} from "react-virtualized";
-import { AnnouncementItem } from "./AnnouncementItem";
-import { Box } from "@mui/material";
-import { CustomButton } from "../../App-styles";
+import { useState, useEffect, useRef } from 'react';
+import { CellMeasurerCache } from 'react-virtualized';
+import { AnnouncementItem } from './AnnouncementItem';
+import { Box } from '@mui/material';
+import { CustomButton } from '../../styles/App-styles';
+import { useTranslation } from 'react-i18next';
const cache = new CellMeasurerCache({
fixedWidth: true,
@@ -21,11 +17,16 @@ export const AnnouncementList = ({
disableComment,
showLoadMore,
loadMore,
- myName
+ myName,
}) => {
-
- const listRef = useRef();
const [messages, setMessages] = useState(initialMessages);
+ const { t } = useTranslation([
+ 'auth',
+ 'core',
+ 'group',
+ 'question',
+ 'tutorial',
+ ]);
useEffect(() => {
cache.clearAll();
@@ -35,64 +36,63 @@ export const AnnouncementList = ({
setMessages(initialMessages);
}, [initialMessages]);
-
return (
{messages.map((message) => {
- const messageData = message?.tempData ? {
- decryptedData: message?.tempData
- } : announcementData[`${message.identifier}-${message.name}`];
+ const messageData = message?.tempData
+ ? {
+ decryptedData: message?.tempData,
+ }
+ : announcementData[`${message.identifier}-${message.name}`];
return (
-
-
-
+
);
})}
- {/*
- {({ height, width }) => (
-
+
+
+ {showLoadMore && (
+
+ {t('core:action.load_announcements', {
+ postProcess: 'capitalizeFirstChar',
+ })}
+
)}
- */}
-
- {showLoadMore && (
- Load older announcements
- )}
-
+
);
};
diff --git a/src/components/Chat/ChatContainer.tsx b/src/components/Chat/ChatContainer.tsx
deleted file mode 100644
index 399e5a4..0000000
--- a/src/components/Chat/ChatContainer.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { useState } from "react";
-import InfiniteScroll from "react-infinite-scroller";
-import {
- MainContainer,
- ChatContainer,
- MessageList,
- Message,
- MessageInput,
- Avatar
-} from "@chatscope/chat-ui-kit-react";
-import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
-
-export const ChatContainerComp = ({messages}) => {
- // const [messages, setMessages] = useState([
- // { id: 1, text: "Hello! How are you?", sender: "Joe"},
- // { id: 2, text: "I'm good, thank you!", sender: "Me" }
- // ]);
-
- // const loadMoreMessages = () => {
- // // Simulate loading more messages (you could fetch these from an API)
- // const moreMessages = [
- // { id: 3, text: "What about you?", sender: "Joe", direction: "incoming" },
- // { id: 4, text: "I'm great, thanks!", sender: "Me", direction: "outgoing" }
- // ];
- // setMessages((prevMessages) => [...moreMessages, ...prevMessages]);
- // };
-
- return (
-
-
-
-
- {messages.map((msg) => (
-
- {msg.direction === "incoming" && }
-
- ))}
-
-
-
-
-
-
- );
-};
-
-
diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx
index d23f026..5b5ac1f 100644
--- a/src/components/Chat/ChatDirect.tsx
+++ b/src/components/Chat/ChatDirect.tsx
@@ -1,96 +1,120 @@
-import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
-
-import { objectToBase64 } from '../../qdn/encryption/group-encryption'
-import { ChatList } from './ChatList'
-import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
-import Tiptap from './TipTap'
-import { CustomButton } from '../../App-styles'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { ChatList } from './ChatList';
+import Tiptap from './TipTap';
+import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress';
-import { Box, ButtonBase, Input, Typography } from '@mui/material';
+import { Box, ButtonBase, Input, Typography, useTheme } from '@mui/material';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getNameInfo } from '../Group/Group';
import { Spacer } from '../../common/Spacer';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
-import { getBaseApiReact, getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App';
-import { getPublicKey } from '../../background';
-import { useMessageQueue } from '../../MessageQueueContext';
-import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
+import {
+ getBaseApiReact,
+ getBaseApiReactSocket,
+ pauseAllQueues,
+ resumeAllQueues,
+} from '../../App';
+import { getPublicKey } from '../../background/background.ts';
+import { useMessageQueue } from '../../messaging/MessageQueueContext.tsx';
+import {
+ executeEvent,
+ subscribeToEvent,
+ unsubscribeFromEvent,
+} from '../../utils/events';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
-import ShortUniqueId from "short-unique-id";
-import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
+import ShortUniqueId from 'short-unique-id';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
-import { MessageItem, ReplyPreview } from './MessageItem';
-
+import { ReplyPreview } from './MessageItem';
+import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 5 });
-
-export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDirect, setNewChat, getTimestampEnterChat, myName, balance, close, setMobileViewModeKeepOpen}) => {
- const { queueChats, addToQueue, processWithNewMessages} = useMessageQueue();
- const [isFocusedParent, setIsFocusedParent] = useState(false);
- const [onEditMessage, setOnEditMessage] = useState(null)
-
- const [messages, setMessages] = useState([])
- const [isSending, setIsSending] = useState(false)
- const [directToValue, setDirectToValue] = useState('')
- const hasInitialized = useRef(false)
- const [isLoading, setIsLoading] = useState(false)
- const [openSnack, setOpenSnack] = React.useState(false);
- const [infoSnack, setInfoSnack] = React.useState(null);
- const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState("")
- const hasInitializedWebsocket = useRef(false)
- const [chatReferences, setChatReferences] = useState({})
-
+export const ChatDirect = ({
+ myAddress,
+ isNewChat,
+ selectedDirect,
+ setSelectedDirect,
+ setNewChat,
+ getTimestampEnterChat,
+ myName,
+ balance,
+ close,
+ setMobileViewModeKeepOpen,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation([
+ 'auth',
+ 'core',
+ 'group',
+ 'question',
+ 'tutorial',
+ ]);
+ const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
+ const [isFocusedParent, setIsFocusedParent] = useState(false);
+ const [onEditMessage, setOnEditMessage] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [isSending, setIsSending] = useState(false);
+ const [directToValue, setDirectToValue] = useState('');
+ const hasInitialized = useRef(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [openSnack, setOpenSnack] = useState(false);
+ const [infoSnack, setInfoSnack] = useState(null);
+ const [publicKeyOfRecipient, setPublicKeyOfRecipient] = useState('');
+ const hasInitializedWebsocket = useRef(false);
+ const [chatReferences, setChatReferences] = useState({});
const editorRef = useRef(null);
const socketRef = useRef(null);
const timeoutIdRef = useRef(null);
- const [messageSize, setMessageSize] = useState(0)
+ const [messageSize, setMessageSize] = useState(0);
const groupSocketTimeoutRef = useRef(null);
- const [replyMessage, setReplyMessage] = useState(null)
+ const [replyMessage, setReplyMessage] = useState(null);
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
+ const publicKeyOfRecipientRef = useRef(null);
- const triggerRerender = () => {
- forceUpdate(); // Trigger re-render by updating the state
- };
- const publicKeyOfRecipientRef = useRef(null)
- const getPublicKeyFunc = async (address)=> {
+ const getPublicKeyFunc = async (address) => {
try {
- const publicKey = await getPublicKey(address)
- if(publicKeyOfRecipientRef.current !== selectedDirect?.address) return
- setPublicKeyOfRecipient(publicKey)
+ const publicKey = await getPublicKey(address);
+ if (publicKeyOfRecipientRef.current !== selectedDirect?.address) return;
+ setPublicKeyOfRecipient(publicKey);
} catch (error) {
-
+ console.log(error);
}
- }
+ };
- const tempMessages = useMemo(()=> {
- if(!selectedDirect?.address) return []
- if(queueChats[selectedDirect?.address]){
- return queueChats[selectedDirect?.address]?.filter((item)=> !item?.chatReference)
+ const tempMessages = useMemo(() => {
+ if (!selectedDirect?.address) return [];
+ if (queueChats[selectedDirect?.address]) {
+ return queueChats[selectedDirect?.address]?.filter(
+ (item) => !item?.chatReference
+ );
}
- return []
- }, [selectedDirect?.address, queueChats])
+ return [];
+ }, [selectedDirect?.address, queueChats]);
- const tempChatReferences = useMemo(()=> {
- if(!selectedDirect?.address) return []
- if(queueChats[selectedDirect?.address]){
- return queueChats[selectedDirect?.address]?.filter((item)=> !!item?.chatReference)
+ const tempChatReferences = useMemo(() => {
+ if (!selectedDirect?.address) return [];
+ if (queueChats[selectedDirect?.address]) {
+ return queueChats[selectedDirect?.address]?.filter(
+ (item) => !!item?.chatReference
+ );
}
- return []
- }, [selectedDirect?.address, queueChats])
+ return [];
+ }, [selectedDirect?.address, queueChats]);
- useEffect(()=> {
- if(selectedDirect?.address){
- publicKeyOfRecipientRef.current = selectedDirect?.address
- getPublicKeyFunc(publicKeyOfRecipientRef.current)
+ useEffect(() => {
+ if (selectedDirect?.address) {
+ publicKeyOfRecipientRef.current = selectedDirect?.address;
+ getPublicKeyFunc(publicKeyOfRecipientRef.current);
}
- }, [selectedDirect?.address])
-
+ }, [selectedDirect?.address]);
- const middletierFunc = async (data: any, selectedDirectAddress: string, myAddress: string) => {
+ const middletierFunc = async (
+ data: any,
+ selectedDirectAddress: string,
+ myAddress: string
+ ) => {
try {
if (hasInitialized.current) {
decryptMessages(data, true);
@@ -99,9 +123,9 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
hasInitialized.current = true;
const url = `${getBaseApiReact()}/chat/messages?involving=${selectedDirectAddress}&involving=${myAddress}&encoding=BASE64&limit=0&reverse=false`;
const response = await fetch(url, {
- method: "GET",
+ method: 'GET',
headers: {
- "Content-Type": "application/json",
+ 'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@@ -109,609 +133,656 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
} catch (error) {
console.error(error);
}
- }
+ };
- const decryptMessages = (encryptedMessages: any[], isInitiated: boolean)=> {
- try {
- return new Promise((res, rej)=> {
- window.sendMessage("decryptDirect", {
+ const decryptMessages = (encryptedMessages: any[], isInitiated: boolean) => {
+ try {
+ return new Promise((res, rej) => {
+ window
+ .sendMessage('decryptDirect', {
data: encryptedMessages,
involvingAddress: selectedDirect?.address,
})
- .then((decryptResponse) => {
- if (!decryptResponse?.error) {
- const response = processWithNewMessages(decryptResponse, selectedDirect?.address);
- res(response);
-
- if (isInitiated) {
- const formatted = response.filter((rawItem) => !rawItem?.chatReference).map((item) => ({
+ .then((decryptResponse) => {
+ if (!decryptResponse?.error) {
+ const response = processWithNewMessages(
+ decryptResponse,
+ selectedDirect?.address
+ );
+ res(response);
+
+ if (isInitiated) {
+ const formatted = response
+ .filter((rawItem) => !rawItem?.chatReference)
+ .map((item) => ({
...item,
id: item.signature,
text: item.message,
unread: item?.sender === myAddress ? false : true,
}));
- setMessages((prev) => [...prev, ...formatted]);
- setChatReferences((prev) => {
- const organizedChatReferences = { ...prev };
- response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => {
- try {
- organizedChatReferences[item.chatReference] = {
- ...(organizedChatReferences[item.chatReference] || {}),
- edit: item
- };
- } catch(error){
+ setMessages((prev) => [...prev, ...formatted]);
+ setChatReferences((prev) => {
+ const organizedChatReferences = { ...prev };
- }
- })
- return organizedChatReferences
- })
- } else {
- hasInitialized.current = true;
- const formatted = response.filter((rawItem) => !rawItem?.chatReference)
+ response
+ .filter(
+ (rawItem) =>
+ !!rawItem?.chatReference && rawItem?.type === 'edit'
+ )
+ .forEach((item) => {
+ try {
+ organizedChatReferences[item.chatReference] = {
+ ...(organizedChatReferences[item.chatReference] ||
+ {}),
+ edit: item,
+ };
+ } catch (error) {
+ console.log(error);
+ }
+ });
+ return organizedChatReferences;
+ });
+ } else {
+ hasInitialized.current = true;
+ const formatted = response
+ .filter((rawItem) => !rawItem?.chatReference)
.map((item) => ({
...item,
id: item.signature,
text: item.message,
unread: false,
}));
- setMessages(formatted);
+ setMessages(formatted);
- setChatReferences((prev) => {
- const organizedChatReferences = { ...prev };
+ setChatReferences((prev) => {
+ const organizedChatReferences = { ...prev };
- response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => {
- try {
- organizedChatReferences[item.chatReference] = {
- ...(organizedChatReferences[item.chatReference] || {}),
- edit: item
- };
- } catch(error){
-
- }
- })
- return organizedChatReferences
- })
- }
- return;
+ response
+ .filter(
+ (rawItem) =>
+ !!rawItem?.chatReference && rawItem?.type === 'edit'
+ )
+ .forEach((item) => {
+ try {
+ organizedChatReferences[item.chatReference] = {
+ ...(organizedChatReferences[item.chatReference] ||
+ {}),
+ edit: item,
+ };
+ } catch (error) {
+ console.log(error);
+ }
+ });
+ return organizedChatReferences;
+ });
}
- rej(response.error);
- })
- .catch((error) => {
- rej(error.message || "An error occurred");
- });
-
- })
- } catch (error) {
-
- }
+ return;
+ }
+ rej(response.error);
+ })
+ .catch((error) => {
+ rej(
+ error.message ||
+ t('core:message.error.generic', {
+ postProcess: 'capitalizeFirstChar',
+ })
+ );
+ });
+ });
+ } catch (error) {
+ console.log(error);
}
+ };
- const forceCloseWebSocket = () => {
+ const forceCloseWebSocket = () => {
+ if (socketRef.current) {
+ clearTimeout(timeoutIdRef.current);
+ clearTimeout(groupSocketTimeoutRef.current);
+ socketRef.current.close(1000, 'forced');
+ socketRef.current = null;
+ }
+ };
+
+ const pingWebSocket = () => {
+ try {
+ if (socketRef.current?.readyState === WebSocket.OPEN) {
+ socketRef.current.send('ping');
+ timeoutIdRef.current = setTimeout(() => {
+ if (socketRef.current) {
+ socketRef.current.close();
+ clearTimeout(groupSocketTimeoutRef.current);
+ }
+ }, 5000); // Close if no pong in 5 seconds
+ }
+ } catch (error) {
+ console.error('Error during ping:', error);
+ }
+ };
+
+ const initWebsocketMessageGroup = () => {
+ forceCloseWebSocket(); // Close any existing connection
+
+ if (!selectedDirect?.address || !myAddress) return;
+
+ const socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`;
+ socketRef.current = new WebSocket(socketLink);
+
+ socketRef.current.onopen = () => {
+ setTimeout(pingWebSocket, 50); // Initial ping
+ };
+
+ socketRef.current.onmessage = (e) => {
+ try {
+ if (e.data === 'pong') {
+ clearTimeout(timeoutIdRef.current);
+ groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds
+ } else {
+ middletierFunc(
+ JSON.parse(e.data),
+ selectedDirect?.address,
+ myAddress
+ );
+
+ setIsLoading(false);
+ }
+ } catch (error) {
+ console.error('Error handling WebSocket message:', error);
+ }
+ };
+
+ socketRef.current.onclose = (event) => {
+ clearTimeout(groupSocketTimeoutRef.current);
+ clearTimeout(timeoutIdRef.current);
+ console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`);
+ if (event.reason !== 'forced' && event.code !== 1000) {
+ setTimeout(() => initWebsocketMessageGroup(), 10000); // Retry after 10 seconds
+ }
+ };
+
+ socketRef.current.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ clearTimeout(groupSocketTimeoutRef.current);
+ clearTimeout(timeoutIdRef.current);
if (socketRef.current) {
- clearTimeout(timeoutIdRef.current);
- clearTimeout(groupSocketTimeoutRef.current);
- socketRef.current.close(1000, 'forced');
- socketRef.current = null;
+ socketRef.current.close();
}
};
-
- const pingWebSocket = () => {
- try {
- if (socketRef.current?.readyState === WebSocket.OPEN) {
- socketRef.current.send('ping');
- timeoutIdRef.current = setTimeout(() => {
- if (socketRef.current) {
- socketRef.current.close();
- clearTimeout(groupSocketTimeoutRef.current);
- }
- }, 5000); // Close if no pong in 5 seconds
- }
- } catch (error) {
- console.error('Error during ping:', error);
- }
+ };
+
+ const setDirectChatValueFunc = async (e) => {
+ setDirectToValue(e.detail.directToValue);
+ };
+ useEffect(() => {
+ subscribeToEvent('setDirectToValueNewChat', setDirectChatValueFunc);
+
+ return () => {
+ unsubscribeFromEvent('setDirectToValueNewChat', setDirectChatValueFunc);
};
-
+ }, []);
- const initWebsocketMessageGroup = () => {
- forceCloseWebSocket(); // Close any existing connection
-
- if (!selectedDirect?.address || !myAddress) return;
-
- const socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?involving=${selectedDirect?.address}&involving=${myAddress}&encoding=BASE64&limit=100`;
- socketRef.current = new WebSocket(socketLink);
-
- socketRef.current.onopen = () => {
- setTimeout(pingWebSocket, 50); // Initial ping
- };
-
- socketRef.current.onmessage = (e) => {
- try {
- if (e.data === 'pong') {
- clearTimeout(timeoutIdRef.current);
- groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds
- } else {
- middletierFunc(JSON.parse(e.data), selectedDirect?.address, myAddress)
+ useEffect(() => {
+ if (hasInitializedWebsocket.current || isNewChat) return;
+ setIsLoading(true);
+ initWebsocketMessageGroup();
+ hasInitializedWebsocket.current = true;
- setIsLoading(false);
- }
- } catch (error) {
- console.error('Error handling WebSocket message:', error);
- }
- };
-
- socketRef.current.onclose = (event) => {
- clearTimeout(groupSocketTimeoutRef.current);
- clearTimeout(timeoutIdRef.current);
- console.warn(`WebSocket closed: ${event.reason || 'unknown reason'}`);
- if (event.reason !== 'forced' && event.code !== 1000) {
- setTimeout(() => initWebsocketMessageGroup(), 10000); // Retry after 10 seconds
- }
- };
-
- socketRef.current.onerror = (error) => {
- console.error('WebSocket error:', error);
- clearTimeout(groupSocketTimeoutRef.current);
- clearTimeout(timeoutIdRef.current);
- if (socketRef.current) {
- socketRef.current.close();
- }
- };
+ return () => {
+ forceCloseWebSocket(); // Clean up WebSocket on component unmount
};
+ }, [selectedDirect?.address, myAddress, isNewChat]);
- const setDirectChatValueFunc = async (e)=> {
- setDirectToValue(e.detail.directToValue)
- }
- useEffect(() => {
- subscribeToEvent("setDirectToValueNewChat", setDirectChatValueFunc);
-
- return () => {
- unsubscribeFromEvent("setDirectToValueNewChat", setDirectChatValueFunc);
- };
- }, []);
-
- useEffect(() => {
- if (hasInitializedWebsocket.current || isNewChat) return;
- setIsLoading(true);
- initWebsocketMessageGroup();
- hasInitializedWebsocket.current = true;
-
- return () => {
- forceCloseWebSocket(); // Clean up WebSocket on component unmount
- };
- }, [selectedDirect?.address, myAddress, isNewChat]);
+ const sendChatDirect = async (
+ { chatReference = undefined, messageText, otherData }: any,
+ address,
+ publicKeyOfRecipient,
+ isNewChatVar
+ ) => {
+ try {
+ const directTo = isNewChatVar ? directToValue : address;
+ if (!directTo) return;
+ return new Promise((res, rej) => {
+ window
+ .sendMessage(
+ 'sendChatDirect',
+ {
+ directTo,
+ chatReference,
+ messageText,
+ otherData,
+ publicKeyOfRecipient,
+ address: directTo,
+ },
+ 120000
+ )
+ .then(async (response) => {
+ if (!response?.error) {
+ if (isNewChatVar) {
+ let getRecipientName = null;
+ try {
+ getRecipientName = await getNameInfo(response.recipient);
+ } catch (error) {
+ console.error('Error fetching recipient name:', error);
+ }
+ setSelectedDirect({
+ address: response.recipient,
+ name: getRecipientName,
+ timestamp: Date.now(),
+ sender: myAddress,
+ senderName: myName,
+ });
+ setNewChat(null);
+ window
+ .sendMessage('addTimestampEnterChat', {
+ timestamp: Date.now(),
+ groupId: response.recipient,
+ })
+ .catch((error) => {
+ console.error(
+ 'Failed to add timestamp:',
+ error.message || 'An error occurred'
+ );
+ });
-
-const sendChatDirect = async ({ chatReference = undefined, messageText, otherData}: any, address, publicKeyOfRecipient, isNewChatVar)=> {
- try {
- const directTo = isNewChatVar ? directToValue : address
-
- if(!directTo) return
- return new Promise((res, rej)=> {
- window.sendMessage("sendChatDirect", {
- directTo,
- chatReference,
- messageText,
- otherData,
- publicKeyOfRecipient,
- address: directTo,
- }, 120000)
- .then(async (response) => {
- if (!response?.error) {
- if (isNewChatVar) {
- let getRecipientName = null;
- try {
- getRecipientName = await getNameInfo(response.recipient);
- } catch (error) {
- console.error("Error fetching recipient name:", error);
+ setTimeout(() => {
+ getTimestampEnterChat();
+ }, 400);
}
- setSelectedDirect({
- address: response.recipient,
- name: getRecipientName,
- timestamp: Date.now(),
- sender: myAddress,
- senderName: myName,
- });
- setNewChat(null);
-
- window.sendMessage("addTimestampEnterChat", {
- timestamp: Date.now(),
- groupId: response.recipient,
- }).catch((error) => {
- console.error("Failed to add timestamp:", error.message || "An error occurred");
- });
-
- setTimeout(() => {
- getTimestampEnterChat();
- }, 400);
+ res(response);
+ return;
}
- res(response);
- return;
- }
- rej(response.error);
- })
- .catch((error) => {
- rej(error.message || "An error occurred");
- });
-
- })
- } catch (error) {
- throw new Error(error)
- } finally {
- }
-}
-const clearEditorContent = () => {
- if (editorRef.current) {
- setMessageSize(0)
- editorRef.current.chain().focus().clearContent().run();
- if(isMobile){
- setTimeout(() => {
- editorRef.current?.chain().blur().run();
- setIsFocusedParent(false)
- executeEvent("sent-new-message-group", {})
- setTimeout(() => {
- triggerRerender();
- }, 300);
- }, 200);
+ rej(response.error);
+ })
+ .catch((error) => {
+ rej(
+ error.message ||
+ t('core:message.error.generic', {
+ postProcess: 'capitalizeFirstChar',
+ })
+ );
+ });
+ });
+ } catch (error) {
+ if (error instanceof Error) {
+ throw new Error(error.message);
+ } else {
+ throw new Error(String(error));
+ }
}
- }
-};
-useEffect(() => {
- if (!editorRef?.current) return;
- const handleUpdate = () => {
- const htmlContent = editorRef?.current.getHTML();
- const stringified = JSON.stringify(htmlContent);
- const size = new Blob([stringified]).size;
- setMessageSize(size + 200);
};
-
- // Add a listener for the editorRef?.current's content updates
- editorRef?.current.on('update', handleUpdate);
-
- // Cleanup the listener on unmount
- return () => {
- editorRef?.current.off('update', handleUpdate);
+ const clearEditorContent = () => {
+ if (editorRef.current) {
+ setMessageSize(0);
+ editorRef.current.chain().focus().clearContent().run();
+ }
};
-}, [editorRef?.current]);
+ useEffect(() => {
+ if (!editorRef?.current) return;
+ const handleUpdate = () => {
+ const htmlContent = editorRef?.current.getHTML();
+ const stringified = JSON.stringify(htmlContent);
+ const size = new Blob([stringified]).size;
+ setMessageSize(size + 200);
+ };
- const sendMessage = async ()=> {
- try {
- if(messageSize > 4000) return
+ // Add a listener for the editorRef?.current's content updates
+ editorRef?.current.on('update', handleUpdate);
-
- if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
- if(isSending) return
- if (editorRef.current) {
- const htmlContent = editorRef.current.getHTML();
-
- if(!htmlContent?.trim() || htmlContent?.trim() === '
') return
- setIsSending(true)
- pauseAllQueues()
- const message = JSON.stringify(htmlContent)
-
-
- if(isNewChat){
- await sendChatDirect({ messageText: htmlContent}, null, null, true)
- return
+ // Cleanup the listener on unmount
+ return () => {
+ editorRef?.current.off('update', handleUpdate);
+ };
+ }, [editorRef?.current]);
+
+ const sendMessage = async () => {
+ try {
+ if (messageSize > 4000) return;
+
+ // TODO set magic number in a proper file
+ if (+balance < 4)
+ throw new Error(
+ t('group:message.error.qortals_required', {
+ quantity: 4,
+ postProcess: 'capitalizeFirstChar',
+ })
+ );
+ if (isSending) return;
+ if (editorRef.current) {
+ const htmlContent = editorRef.current.getHTML();
+
+ if (!htmlContent?.trim() || htmlContent?.trim() === '
') return;
+ setIsSending(true);
+ pauseAllQueues();
+ const message = JSON.stringify(htmlContent);
+
+ if (isNewChat) {
+ await sendChatDirect({ messageText: htmlContent }, null, null, true);
+ return;
}
- let repliedTo = replyMessage?.signature
+ let repliedTo = replyMessage?.signature;
- if (replyMessage?.chatReference) {
- repliedTo = replyMessage?.chatReference
- }
- let chatReference = onEditMessage?.signature
+ if (replyMessage?.chatReference) {
+ repliedTo = replyMessage?.chatReference;
+ }
+ let chatReference = onEditMessage?.signature;
const otherData = {
...(onEditMessage?.decryptedData || {}),
specialId: uid.rnd(),
repliedTo: onEditMessage ? onEditMessage?.repliedTo : repliedTo,
- type: chatReference ? 'edit' : ''
- }
+ type: chatReference ? 'edit' : '',
+ };
const sendMessageFunc = async () => {
- return await sendChatDirect({ chatReference, messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false)
+ return await sendChatDirect(
+ { chatReference, messageText: htmlContent, otherData },
+ selectedDirect?.address,
+ publicKeyOfRecipient,
+ false
+ );
};
-
-
// Add the function to the queue
const messageObj = {
message: {
timestamp: Date.now(),
- senderName: myName,
- sender: myAddress,
- ...(otherData || {}),
- text: htmlContent,
+ senderName: myName,
+ sender: myAddress,
+ ...(otherData || {}),
+ text: htmlContent,
},
- chatReference
- }
- addToQueue(sendMessageFunc, messageObj, 'chat-direct',
- selectedDirect?.address );
+ chatReference,
+ };
+ addToQueue(
+ sendMessageFunc,
+ messageObj,
+ 'chat-direct',
+ selectedDirect?.address
+ );
setTimeout(() => {
- executeEvent("sent-new-message-group", {})
+ executeEvent('sent-new-message-group', {});
}, 150);
- clearEditorContent()
- setReplyMessage(null)
- setOnEditMessage(null)
-
- }
- // send chat message
- } catch (error) {
- const errorMsg = error?.message || error
- setInfoSnack({
- type: "error",
- message: errorMsg === 'invalid signature' ? 'You need at least 4 QORT to send a message' : errorMsg,
- });
- setOpenSnack(true);
- console.error(error)
- } finally {
- setIsSending(false)
- resumeAllQueues()
+ clearEditorContent();
+ setReplyMessage(null);
+ setOnEditMessage(null);
}
+ // send chat message
+ } catch (error) {
+ const errorMsg = error?.message || error;
+ setInfoSnack({
+ type: 'error',
+ message:
+ errorMsg === 'invalid signature'
+ ? t('group:message.error.qortals_required', {
+ quantity: 4,
+ postProcess: 'capitalizeFirstChar',
+ })
+ : errorMsg,
+ });
+ setOpenSnack(true);
+ console.error(error);
+ } finally {
+ setIsSending(false);
+ resumeAllQueues();
}
+ };
- const onReply = useCallback((message)=> {
- if(onEditMessage){
- clearEditorContent()
+ const onReply = useCallback(
+ (message) => {
+ if (onEditMessage) {
+ clearEditorContent();
}
- setReplyMessage(message)
- setOnEditMessage(null)
- editorRef?.current?.chain().focus()
- }, [onEditMessage])
-
-
- const onEdit = useCallback((message)=> {
- setOnEditMessage(message)
- setReplyMessage(null)
- editorRef.current.chain().focus().setContent(message?.text).run();
-
- }, [])
-
+ setReplyMessage(message);
+ setOnEditMessage(null);
+ editorRef?.current?.chain().focus();
+ },
+ [onEditMessage]
+ );
+
+ const onEdit = useCallback((message) => {
+ setOnEditMessage(message);
+ setReplyMessage(null);
+ editorRef.current.chain().focus().setContent(message?.text).run();
+ }, []);
+
return (
-
- {!isMobile && (
-
+
-
- Close Direct Chat
-
- )}
- {isMobile && (
-
-
-
- {
- close()
- }}
- >
-
-
-
-
- {isNewChat ? '' : selectedDirect?.name || (selectedDirect?.address?.slice(0,10) + '...')}
-
-
- {
- setSelectedDirect(null)
- setMobileViewModeKeepOpen('')
- setNewChat(false)
- }}
- >
-
-
-
-
-
- )}
+ }}
+ >
+
+
+ {t('core:action.close_chat', { postProcess: 'capitalizeFirstChar' })}
+
+
+
{isNewChat && (
<>
-
-
setDirectToValue(e.target.value)} />
+
+
setDirectToValue(e.target.value)}
+ />
>
)}
-
-
-
-
-
+
+
+
- {replyMessage && (
-
-
-
- {
- setReplyMessage(null)
- setOnEditMessage(null)
-
- }}
- >
-
-
-
- )}
- {onEditMessage && (
-
-
-
- {
- setReplyMessage(null)
- setOnEditMessage(null)
-
- clearEditorContent()
-
-
- }}
- >
-
-
-
- )}
-
-
- {messageSize > 750 && (
-
- 4000 ? 'var(--danger)' : 'unset'
- }}>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`}
-
-
- )}
-
-
-
-
- {
-
- if(isSending) return
- sendMessage()
- }}
- style={{
- marginTop: 'auto',
- alignSelf: 'center',
- cursor: isSending ? 'default' : 'pointer',
- background: isSending && 'rgba(0, 0, 0, 0.8)',
- flexShrink: 0,
- padding: '5px',
- width: '100px',
- minWidth: 'auto'
+ }}
+ >
+ {replyMessage && (
+
- {isSending && (
-
+
+ {
+ setReplyMessage(null);
+ setOnEditMessage(null);
+ }}
+ >
+
+
+
+ )}
+ {onEditMessage && (
+
+
+
+ {
+ setReplyMessage(null);
+ setOnEditMessage(null);
+ clearEditorContent();
+ }}
+ >
+
+
+
+ )}
+
+
+ {messageSize > 750 && (
+
+ 4000 ? theme.palette.other.danger : 'unset',
+ }}
+ >
+ {t('core:message.error.message_size', {
+ maximum: 4000,
+ size: messageSize,
+ postProcess: 'capitalizeFirstChar',
+ })}
+
+
+ )}
+
+
+
+ {
+ if (isSending) return;
+ sendMessage();
+ }}
+ style={{
+ alignSelf: 'center',
+ background: isSending
+ ? theme.palette.background.default
+ : theme.palette.background.paper,
+ cursor: isSending ? 'default' : 'pointer',
+ flexShrink: 0,
+ marginTop: 'auto',
+ minWidth: 'auto',
+ padding: '5px',
+ width: '100px',
+ }}
+ >
+ {isSending && (
+
- )}
- {` Send`}
-
-
-
-
-
-
-
- )
-}
+ )}
+ {` Send`}
+
+