From f481dee81321b4351af1cef315e3bd5243ede9ed Mon Sep 17 00:00:00 2001
From: PhilReact
Date: Mon, 16 Dec 2024 03:39:41 +0200
Subject: [PATCH] public group chats
---
src/App-styles.ts | 41 +++
src/App.tsx | 150 ++++++++--
src/MessageQueueContext.tsx | 50 ++--
src/background.ts | 6 +-
src/components/Apps/AppInfoSnippet.tsx | 6 +-
src/components/Apps/AppsCategory.tsx | 12 +-
src/components/Apps/AppsLibrary.tsx | 41 ++-
src/components/Chat/ChatDirect.tsx | 10 +-
src/components/Chat/ChatGroup.tsx | 95 ++++---
src/components/Chat/ChatList.tsx | 10 +-
src/components/Chat/ChatOptions.tsx | 309 ++++++++++-----------
src/components/Chat/MessageDisplay.tsx | 2 +-
src/components/Chat/MessageItem.tsx | 11 +-
src/components/Chat/styles.css | 2 +-
src/components/Group/Group.tsx | 131 +++++++--
src/qdn/encryption/group-encryption.ts | 48 +++-
src/utils/generateWallet/generateWallet.ts | 7 +-
17 files changed, 638 insertions(+), 293 deletions(-)
diff --git a/src/App-styles.ts b/src/App-styles.ts
index bf3a146..a522d2f 100644
--- a/src/App-styles.ts
+++ b/src/App-styles.ts
@@ -100,6 +100,47 @@ transition: all 0.2s;
`
+interface CustomButtonProps {
+ bgColor?: string;
+ color?: string;
+}
+export const CustomButtonAccept = styled(Box)(
+ ({ bgColor, color }) => ({
+ boxSizing: "border-box",
+ padding: "15px 20px",
+ gap: "10px",
+ border: "0.5px solid rgba(255, 255, 255, 0.5)",
+ filter: "drop-shadow(1px 4px 10.5px rgba(0,0,0,0.3))",
+ borderRadius: 5,
+ display: "inline-flex",
+ justifyContent: "center",
+ alignItems: "center",
+ width: "fit-content",
+ transition: "all 0.2s",
+ minWidth: 160,
+ cursor: "pointer",
+ fontWeight: 600,
+ fontFamily: "Inter",
+ textAlign: "center",
+ opacity: 0.7,
+ // Use the passed-in props or fallback defaults
+ backgroundColor: bgColor || "transparent",
+ color: color || "white",
+
+ "&:hover": {
+ opacity: 1,
+ backgroundColor: bgColor
+ ? bgColor
+ : "rgba(41, 41, 43, 1)", // fallback hover bg
+ color: color || "white",
+ svg: {
+ path: {
+ fill: color || "white",
+ },
+ },
+ },
+ })
+);
export const CustomButton = styled(Box)`
/* Authenticate */
diff --git a/src/App.tsx b/src/App.tsx
index 2d9a709..f477e48 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -62,6 +62,7 @@ import {
AuthenticatedContainerInnerLeft,
AuthenticatedContainerInnerRight,
CustomButton,
+ CustomButtonAccept,
CustomInput,
CustomLabel,
TextItalic,
@@ -436,6 +437,8 @@ function App() {
const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false);
const [rootHeight, setRootHeight] = useState("100%");
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
+ const [showSeed, setShowSeed] = useState(false)
+ const [creationStep, setCreationStep] = useState(1)
const qortalRequestCheckbox1Ref = useRef(null);
useRetrieveDataLocalStorage();
useQortalGetSaveSettings(userInfo?.name);
@@ -1095,6 +1098,8 @@ function App() {
setExtstate("authenticated");
setIsOpenSendQort(false);
setIsOpenSendQortSuccess(false);
+ setShowSeed(false)
+ setCreationStep(1)
};
const resetAllStates = () => {
@@ -1124,6 +1129,8 @@ function App() {
setTxList([]);
setMemberGroups([]);
resetAllRecoil();
+ setShowSeed(false)
+ setCreationStep(1)
};
function roundUpToDecimals(number, decimals = 8) {
@@ -2534,7 +2541,15 @@ await showInfo({
cursor: "pointer",
}}
onClick={() => {
+ if(creationStep === 2){
+ setCreationStep(1)
+ return
+ }
setExtstate("not-authenticated");
+ setShowSeed(false)
+ setCreationStep(1)
+ setWalletToBeDownloadedPasswordConfirm('')
+ setWalletToBeDownloadedPassword('')
}}
src={Return}
/>
@@ -2567,32 +2582,110 @@ await showInfo({
padding: '10px'
}}>
Your seedphrase
+ }}>
+ A ‘ {
+ setShowSeed(true)
+ }} style={{
+ fontSize: '14px',
+ color: 'steelblue',
+ cursor: 'pointer'
+ }}>SEEDPHRASE ’ has been randomly generated in the background.
+
+
+
Only shown once! Please copy and keep safe!
+ fontSize: '14px',
+ marginTop: '5px'
+ }}>
+ If you wish to VIEW THE SEEDPHRASE, click the word 'SEEDPHRASE' in this text. Seedphrases are used to generate the private key for your Qortal account. For security by default, seedphrases are NOT displayed unless specifically chosen.
+
+
+ Create your Qortal account by clicking NEXT below.
+
+
+
+ {
+ setCreationStep(2)
+ }}>
+ Next
+
+
+
+
+
+
+
Wallet Password
@@ -2622,6 +2715,7 @@ await showInfo({
Create Account
+
{walletToBeDownloadedError}
>
)}
@@ -2776,11 +2870,29 @@ await showInfo({
-
@@ -3051,22 +3163,26 @@ await showInfo({
gap: "14px",
}}
>
- onOkQortalRequestExtension("accepted")}
>
accept
-
-
+ onCancelQortalRequestExtension()}
>
decline
-
+
{sendPaymentError}
diff --git a/src/MessageQueueContext.tsx b/src/MessageQueueContext.tsx
index 3727b03..7104520 100644
--- a/src/MessageQueueContext.tsx
+++ b/src/MessageQueueContext.tsx
@@ -92,21 +92,6 @@ export const MessageQueueProvider = ({ children }) => {
// Remove the message from the queue after successful sending
messageQueueRef.current.shift();
- // Remove the message from queueChats
- setQueueChats((prev) => {
- const updatedChats = { ...prev };
- if (updatedChats[groupDirectId]) {
- updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
- (item) => item.identifier !== identifier
- );
-
- // If no more chats for this group, delete the groupDirectId entry
- if (updatedChats[groupDirectId].length === 0) {
- delete updatedChats[groupDirectId];
- }
- }
- return updatedChats;
- });
} catch (error) {
console.error('Message sending failed', error);
@@ -142,15 +127,25 @@ export const MessageQueueProvider = ({ children }) => {
// Method to process with new messages and groupDirectId
const processWithNewMessages = (newMessages, groupDirectId) => {
+ let updatedNewMessages = newMessages
if (newMessages.length > 0) {
- messageQueueRef.current = messageQueueRef.current.filter((msg) => {
- return !newMessages.some(newMsg => newMsg?.specialId === msg?.specialId);
- });
-
// Remove corresponding entries in queueChats for the provided groupDirectId
setQueueChats((prev) => {
const updatedChats = { ...prev };
if (updatedChats[groupDirectId]) {
+
+ updatedNewMessages = newMessages?.map((msg)=> {
+ const findTempMsg = updatedChats[groupDirectId]?.find((msg2)=> msg2?.message?.specialId === msg?.specialId)
+ if(findTempMsg){
+ return {
+ ...msg,
+ tempSignature: findTempMsg?.signature
+ }
+ }
+ return msg
+ })
+
+
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter((chat) => {
return !newMessages.some(newMsg => newMsg?.specialId === chat?.message?.specialId);
});
@@ -167,8 +162,23 @@ export const MessageQueueProvider = ({ children }) => {
}
return updatedChats;
});
+
}
-
+ setTimeout(() => {
+ if(!messageQueueRef.current.find((msg) => msg?.groupDirectId === groupDirectId)){
+ setQueueChats((prev) => {
+ const updatedChats = { ...prev };
+ if (updatedChats[groupDirectId]) {
+ delete updatedChats[groupDirectId]
+ }
+
+ return updatedChats
+ }
+ )
+ }
+ }, 300);
+
+ return updatedNewMessages
};
return (
diff --git a/src/background.ts b/src/background.ts
index d2ea6ca..255335c 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -608,8 +608,7 @@ const handleNotification = async (groups) => {
const data = groups.filter(
(group) =>
group?.sender !== address &&
- !mutedGroups.includes(group.groupId) &&
- !isUpdateMsg(group?.data)
+ !mutedGroups.includes(group.groupId)
);
const dataWithUpdates = groups.filter(
(group) => group?.sender !== address && !mutedGroups.includes(group.groupId)
@@ -657,8 +656,7 @@ const handleNotification = async (groups) => {
Date.now() - lastGroupNotification >= 120000
) {
if (
- !newestLatestTimestamp?.data ||
- !isExtMsg(newestLatestTimestamp?.data)
+ !newestLatestTimestamp?.data
) return;
const notificationId = generateId()
diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx
index 01a7083..f141222 100644
--- a/src/components/Apps/AppInfoSnippet.tsx
+++ b/src/components/Apps/AppInfoSnippet.tsx
@@ -22,7 +22,7 @@ import { useRecoilState, useSetRecoilState } from "recoil";
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global";
import { saveToLocalStorage } from "./AppsNavBar";
-export const AppInfoSnippet = ({ app, myName, isFromCategory }) => {
+export const AppInfoSnippet = ({ app, myName, isFromCategory, parentStyles = {} }) => {
const isInstalled = app?.status?.status === 'READY'
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
@@ -30,7 +30,9 @@ export const AppInfoSnippet = ({ app, myName, isFromCategory }) => {
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service)
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
return (
-
+
const categoryList = useMemo(() => {
+ if(category?.id === 'all') return availableQapps
+
return availableQapps.filter(
(app) =>
app?.metadata?.category === category?.id
@@ -99,7 +101,11 @@ export const AppsCategory = ({ availableQapps, myName, category, isShow }) =>
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 350);
-
+ setTimeout(() => {
+ virtuosoRef.current.scrollToIndex({
+ index: 0
+ });
+ }, 500);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
@@ -111,14 +117,14 @@ export const AppsCategory = ({ availableQapps, myName, category, isShow }) =>
const searchedList = useMemo(() => {
if (!debouncedValue) return categoryList
return categoryList.filter((app) =>
- app.name.toLowerCase().includes(debouncedValue.toLowerCase())
+ app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
);
}, [debouncedValue, categoryList]);
const rowRenderer = (index) => {
let app = searchedList[index];
- return ;
+ return ;
};
diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx
index 6961a61..a172c11 100644
--- a/src/components/Apps/AppsLibrary.tsx
+++ b/src/components/Apps/AppsLibrary.tsx
@@ -17,7 +17,7 @@ import {
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
-import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
+import { Avatar, Box, ButtonBase, InputBase, Typography, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
@@ -101,7 +101,11 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 350);
-
+ setTimeout(() => {
+ virtuosoRef.current.scrollToIndex({
+ index: 0
+ });
+ }, 500);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
@@ -113,7 +117,7 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
const searchedList = useMemo(() => {
if (!debouncedValue) return [];
return availableQapps.filter((app) =>
- app.name.toLowerCase().includes(debouncedValue.toLowerCase())
+ app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
);
}, [debouncedValue]);
@@ -214,6 +218,10 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
/>
+ ) : searchedList?.length === 0 && debouncedValue ? (
+
+ No results
+
) : (
<>
@@ -313,6 +321,33 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
}}>
+ {
+ executeEvent("selectedCategory", {
+ data: {
+ id: 'all',
+ name: 'All'
+ },
+ });
+ }}
+ >
+
+ All
+
+
{categories?.map((category)=> {
return (
{
diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx
index 9fafcff..bfe00ee 100644
--- a/src/components/Chat/ChatDirect.tsx
+++ b/src/components/Chat/ChatDirect.tsx
@@ -116,9 +116,9 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
data: encryptedMessages,
involvingAddress: selectedDirect?.address,
})
- .then((response) => {
- if (!response?.error) {
- processWithNewMessages(response, selectedDirect?.address);
+ .then((decryptResponse) => {
+ if (!decryptResponse?.error) {
+ const response = processWithNewMessages(decryptResponse, selectedDirect?.address);
res(response);
if (isInitiated) {
@@ -366,7 +366,7 @@ useEffect(() => {
const htmlContent = editorRef?.current.getHTML();
const stringified = JSON.stringify(htmlContent);
const size = new Blob([stringified]).size;
- setMessageSize(size + 100);
+ setMessageSize(size + 200);
};
// Add a listener for the editorRef?.current's content updates
@@ -381,7 +381,7 @@ useEffect(() => {
const sendMessage = async ()=> {
try {
-
+ if(messageSize > 4000) return
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
if(isSending) return
diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx
index bfda8a3..57bf908 100644
--- a/src/components/Chat/ChatGroup.tsx
+++ b/src/components/Chat/ChatGroup.tsx
@@ -31,7 +31,7 @@ import { throttle } from 'lodash'
const uid = new ShortUniqueId({ length: 5 });
-export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent}) => {
+export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent, isPrivate}) => {
const [messages, setMessages] = useState([])
const [chatReferences, setChatReferences] = useState({})
const [isSending, setIsSending] = useState(false)
@@ -191,7 +191,6 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
try {
if(!secretKeyRef.current){
checkForFirstSecretKeyNotification(encryptedMessages)
- return
}
return new Promise((res, rej)=> {
window.sendMessage("decryptSingle", {
@@ -203,9 +202,9 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const filterUIMessages = encryptedMessages.filter((item) => !isExtMsg(item.data));
const decodedUIMessages = decodeBase64ForUIChatMessages(filterUIMessages);
- const combineUIAndExtensionMsgs = [...decodedUIMessages, ...response];
- processWithNewMessages(
- combineUIAndExtensionMsgs.map((item) => ({
+ const combineUIAndExtensionMsgsBefore = [...decodedUIMessages, ...response];
+ const combineUIAndExtensionMsgs = processWithNewMessages(
+ combineUIAndExtensionMsgsBefore.map((item) => ({
...item,
...(item?.decryptedData || {}),
})),
@@ -233,7 +232,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
setChatReferences((prev) => {
const organizedChatReferences = { ...prev };
combineUIAndExtensionMsgs
- .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit"))
+ .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.type === "reaction"))
.forEach((item) => {
try {
if(item.decryptedData?.type === "edit"){
@@ -241,11 +240,16 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
...(organizedChatReferences[item.chatReference] || {}),
edit: item.decryptedData,
};
+ } else if(item?.type === "edit"){
+ organizedChatReferences[item.chatReference] = {
+ ...(organizedChatReferences[item.chatReference] || {}),
+ edit: item,
+ };
} else {
- const content = item.decryptedData?.content;
+ const content = item?.content || item.decryptedData?.content;
const sender = item.sender;
const newTimestamp = item.timestamp;
- const contentState = item.decryptedData?.contentState;
+ const contentState = item?.contentState || item.decryptedData?.contentState;
if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) {
console.warn("Invalid content, sender, or timestamp in reaction data", item);
@@ -316,7 +320,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const organizedChatReferences = { ...prev };
combineUIAndExtensionMsgs
- .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit"))
+ .filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.type === "reaction"))
.forEach((item) => {
try {
if(item.decryptedData?.type === "edit"){
@@ -324,11 +328,16 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
...(organizedChatReferences[item.chatReference] || {}),
edit: item.decryptedData,
};
+ } else if(item?.type === "edit"){
+ organizedChatReferences[item.chatReference] = {
+ ...(organizedChatReferences[item.chatReference] || {}),
+ edit: item,
+ };
} else {
- const content = item.decryptedData?.content;
+ const content = item?.content || item.decryptedData?.content;
const sender = item.sender;
const newTimestamp = item.timestamp;
- const contentState = item.decryptedData?.contentState;
+ const contentState = item?.contentState || item.decryptedData?.contentState;
if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) {
console.warn("Invalid content, sender, or timestamp in reaction data", item);
@@ -463,10 +472,11 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
setIsLoading(true)
initWebsocketMessageGroup()
}
- }, [triedToFetchSecretKey, secretKey])
+ }, [triedToFetchSecretKey, secretKey, isPrivate])
useEffect(()=> {
- if(!secretKey || hasInitializedWebsocket.current) return
+ if(isPrivate === null) return
+ if(isPrivate === false || !secretKey || hasInitializedWebsocket.current) return
forceCloseWebSocket()
setMessages([])
setIsLoading(true)
@@ -476,17 +486,32 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
}, 6000);
initWebsocketMessageGroup()
hasInitializedWebsocket.current = true
- }, [secretKey])
+ }, [secretKey, isPrivate])
useEffect(() => {
if (!editorRef?.current) return;
- handleUpdateRef.current = throttle(() => {
- const htmlContent = editorRef.current.getHTML();
- const size = new TextEncoder().encode(htmlContent).length;
- setMessageSize(size + 100);
+ handleUpdateRef.current = throttle(async () => {
+ try {
+ if(isPrivate){
+ const htmlContent = editorRef.current.getHTML();
+ const message64 = await objectToBase64(JSON.stringify(htmlContent))
+ const secretKeyObject = await getSecretKey(false, true)
+ const encryptSingle = await encryptChatMessage(message64, secretKeyObject)
+ setMessageSize((encryptSingle?.length || 0) + 200);
+ } else {
+ const htmlContent = editorRef.current.getJSON();
+ const message = JSON.stringify(htmlContent)
+ const size = new Blob([message]).size
+ setMessageSize(size + 300);
+ }
+
+ } catch (error) {
+ // calc size error
+ }
}, 1200);
+
const currentEditor = editorRef.current;
@@ -495,7 +520,9 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
return () => {
currentEditor.off("update", handleUpdateRef.current);
};
- }, [editorRef, setMessageSize]);
+ }, [editorRef, setMessageSize, isFocusedParent, isPrivate]);
+
+
useEffect(()=> {
@@ -587,6 +614,8 @@ const clearEditorContent = () => {
const sendMessage = async ()=> {
try {
+ if(messageSize > 4000) return
+ if(isPrivate === null) throw new Error('Unable to determine if group is private')
if(isSending) return
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
pauseAllQueues()
@@ -594,8 +623,10 @@ const sendMessage = async ()=> {
const htmlContent = editorRef.current.getHTML();
if(!htmlContent?.trim() || htmlContent?.trim() === '') return
+
+
setIsSending(true)
- const message = htmlContent
+ const message = isPrivate === false ? editorRef.current.getJSON() : htmlContent
const secretKeyObject = await getSecretKey(false, true)
let repliedTo = replyMessage?.signature
@@ -605,19 +636,24 @@ const sendMessage = async ()=> {
}
let chatReference = onEditMessage?.signature
+ const publicData = isPrivate ? {} : {
+ isEdited : chatReference ? true : false,
+ }
const otherData = {
repliedTo,
...(onEditMessage?.decryptedData || {}),
type: chatReference ? 'edit' : '',
specialId: uid.rnd(),
+ ...publicData
}
const objectMessage = {
...(otherData || {}),
- message
+ [isPrivate ? 'message' : 'messageText']: message,
+ version: 3
}
const message64: any = await objectToBase64(objectMessage)
- const encryptSingle = await encryptChatMessage(message64, secretKeyObject)
+ const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject)
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
const sendMessageFunc = async () => {
@@ -627,7 +663,7 @@ const sendMessage = async ()=> {
// Add the function to the queue
const messageObj = {
message: {
- text: message,
+ text: htmlContent,
timestamp: Date.now(),
senderName: myName,
sender: myAddress,
@@ -687,10 +723,8 @@ const sendMessage = async ()=> {
setReplyMessage(null)
setIsFocusedParent(true);
setTimeout(() => {
- editorRef.current.chain().focus().setContent(message?.text).run();
-
- }, 250);
-
+ editorRef.current.chain().focus().setContent(message?.messageText || message?.text).run();
+ }, 250);
}, [])
const handleReaction = useCallback(async (reaction, chatMessage, reactionState = true)=> {
@@ -718,7 +752,7 @@ const sendMessage = async ()=> {
}
const message64: any = await objectToBase64(objectMessage)
const reactiontypeNumber = RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS
- const encryptSingle = await encryptChatMessage(message64, secretKeyObject, reactiontypeNumber)
+ const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject, reactiontypeNumber)
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
const sendMessageFunc = async () => {
@@ -770,7 +804,7 @@ const sendMessage = async ()=> {
left: hide && '-100000px',
}}>
-
+
{
- {}} members={members} myName={myName} selectedGroup={selectedGroup}/>
@@ -878,7 +912,6 @@ const sendMessage = async ()=> {
{isFocusedParent && (
{
- if(messageSize > 4000) return
if(isSending) return
sendMessage()
}}
diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx
index 2023b88..143f738 100644
--- a/src/components/Chat/ChatList.tsx
+++ b/src/components/Chat/ChatList.tsx
@@ -6,7 +6,7 @@ import { useInView } from 'react-intersection-observer'
import { Typography } from '@mui/material';
import ErrorBoundary from '../../common/ErrorBoundary';
-export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply, handleReaction, chatReferences, tempChatReferences, onEdit
+export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply, handleReaction, chatReferences, tempChatReferences, isPrivate, onEdit
}) => {
const parentRef = useRef();
const [messages, setMessages] = useState(initialMessages);
@@ -20,7 +20,7 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
// Initialize the virtualizer
const rowVirtualizer = useVirtualizer({
count: messages.length,
- getItemKey: (index) => messages[index].signature,
+ getItemKey: (index) => messages[index]?.tempSignature || messages[index].signature,
getScrollElement: () => parentRef?.current,
estimateSize: useCallback(() => 80, []), // Provide an estimated height of items, adjust this as needed
overscan: 10, // Number of items to render outside the visible area to improve smoothness
@@ -264,7 +264,10 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
message.text = chatReferences[message.signature]?.edit?.message;
message.isEdit = true
}
-
+ if (chatReferences[message.signature]?.edit?.messageText && message?.messageText) {
+ message.messageText = chatReferences[message.signature]?.edit?.messageText;
+ message.isEdit = true
+ }
}
@@ -348,6 +351,7 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
handleReaction={handleReaction}
reactions={reactions}
isUpdating={isUpdating}
+ isPrivate={isPrivate}
/>
diff --git a/src/components/Chat/ChatOptions.tsx b/src/components/Chat/ChatOptions.tsx
index 7f0eb87..6d96a2c 100644
--- a/src/components/Chat/ChatOptions.tsx
+++ b/src/components/Chat/ChatOptions.tsx
@@ -33,6 +33,12 @@ import { ContextMenuMentions } from "../ContextMenuMentions";
import { convert } from 'html-to-text';
import { executeEvent } from "../../utils/events";
import InsertLinkIcon from '@mui/icons-material/InsertLink';
+import Highlight from "@tiptap/extension-highlight";
+import Mention from "@tiptap/extension-mention";
+import StarterKit from "@tiptap/starter-kit";
+import Underline from "@tiptap/extension-underline";
+import { generateHTML } from "@tiptap/react";
+import ErrorBoundary from "../../common/ErrorBoundary";
const extractTextFromHTML = (htmlString = '') => {
return convert(htmlString, {
@@ -44,7 +50,7 @@ const cache = new CellMeasurerCache({
defaultHeight: 50,
});
-export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGroup, openQManager }) => {
+export const ChatOptions = ({ messages : untransformedMessages, goToMessage, members, myName, selectedGroup, openQManager, isPrivate }) => {
const [mode, setMode] = useState("default");
const [searchValue, setSearchValue] = useState("");
const [selectedMember, setSelectedMember] = useState(0);
@@ -53,6 +59,27 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
const parentRefMentions = useRef();
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null)
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
+ const messages = useMemo(()=> {
+ return untransformedMessages?.map((item)=> {
+ if(item?.messageText){
+ let transformedMessage = item?.messageText
+ try {
+ transformedMessage = generateHTML(item?.messageText, [
+ StarterKit,
+ Underline,
+ Highlight,
+ Mention
+ ])
+ return {
+ ...item,
+ messageText: transformedMessage
+ }
+ } catch (error) {
+ // error
+ }
+ } else return item
+ })
+ }, [untransformedMessages])
const getTimestampMention = async () => {
try {
@@ -125,7 +152,7 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
.filter(
(message) =>
message?.senderName === selectedMember &&
- extractTextFromHTML(message?.decryptedData?.message)?.includes(
+ extractTextFromHTML(isPrivate ? message?.messageText : message?.decryptedData?.message)?.includes(
debouncedValue.toLowerCase()
)
)
@@ -133,20 +160,27 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
}
return messages
.filter((message) =>
- extractTextFromHTML(message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase())
+ extractTextFromHTML(isPrivate === false ? message?.messageText : message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase())
)
?.sort((a, b) => b?.timestamp - a?.timestamp);
- }, [debouncedValue, messages, selectedMember]);
+ }, [debouncedValue, messages, selectedMember, isPrivate]);
const mentionList = useMemo(() => {
if(!messages || messages.length === 0 || !myName) return []
-
+ if(isPrivate === false){
+ return messages
+ .filter((message) =>
+ extractTextFromHTML(message?.messageText)?.includes(`@${myName}`)
+ )
+ ?.sort((a, b) => b?.timestamp - a?.timestamp);
+
+ }
return messages
.filter((message) =>
extractTextFromHTML(message?.decryptedData?.message)?.includes(`@${myName}`)
)
?.sort((a, b) => b?.timestamp - a?.timestamp);
- }, [messages, myName]);
+ }, [messages, myName, isPrivate]);
const rowVirtualizer = useVirtualizer({
count: searchedList.length,
@@ -297,86 +331,7 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
gap: "5px",
}}
>
-
-
-
-
- {message?.senderName?.charAt(0)}
-
-
- {message?.senderName}
-
-
-
-
- {formatTimestamp(message.timestamp)}
- {
- const findMsgIndex = messages.findIndex(
- (item) =>
- item?.signature === message?.signature
- );
- if (findMsgIndex !== -1) {
- if(isMobile){
- setMode("default");
- executeEvent('goToMessage', {index: findMsgIndex})
-
- } else {
- goToMessage(findMsgIndex);
-
- }
- }
- }}
- >
-
"
- }
- />
-
-
+
);
})}
@@ -580,86 +535,15 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
gap: "5px",
}}
>
-
-
-
-
- {message?.senderName?.charAt(0)}
-
-
- {message?.senderName}
-
-
-
-
- {formatTimestamp(message.timestamp)}
- {
- const findMsgIndex = messages.findIndex(
- (item) =>
- item?.signature === message?.signature
- );
- if (findMsgIndex !== -1) {
- if(isMobile){
- setMode("default");
- executeEvent('goToMessage', {index: findMsgIndex})
-
- } else {
- goToMessage(findMsgIndex);
-
- }
- }
- }}
- >
- "
- }
- />
-
-
+
+ Error loading content: Invalid Data
+
+ }
+ >
+
+
);
})}
@@ -727,3 +611,96 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
);
};
+
+const ShowMessage = ({message, goToMessage, messages, setMode})=> {
+
+ return (
+
+
+
+
+ {message?.senderName?.charAt(0)}
+
+
+ {message?.senderName}
+
+
+
+
+ {formatTimestamp(message.timestamp)}
+ {
+ const findMsgIndex = messages.findIndex(
+ (item) =>
+ item?.signature === message?.signature
+ );
+ if (findMsgIndex !== -1) {
+ if(isMobile){
+ setMode("default");
+ executeEvent('goToMessage', {index: findMsgIndex})
+
+ } else {
+ goToMessage(findMsgIndex);
+
+ }
+ }
+ }}
+ >
+ {message?.messageText && (
+
+ )}
+ {message?.decryptedData?.message && (
+ "
+ }
+ />
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx
index 867bcb5..56b7dd1 100644
--- a/src/components/Chat/MessageDisplay.tsx
+++ b/src/components/Chat/MessageDisplay.tsx
@@ -108,7 +108,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
};
- const embedLink = htmlContent.match(/qortal:\/\/use-embed\/[^\s<>]+/);
+ const embedLink = htmlContent?.match(/qortal:\/\/use-embed\/[^\s<>]+/);
let embedData = null;
diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx
index b711116..35454fb 100644
--- a/src/components/Chat/MessageItem.tsx
+++ b/src/components/Chat/MessageItem.tsx
@@ -17,6 +17,7 @@ import { Spacer } from "../../common/Spacer";
import { ReactionPicker } from "../ReactionPicker";
import KeyOffIcon from '@mui/icons-material/KeyOff';
import EditIcon from '@mui/icons-material/Edit';
+import Mention from "@tiptap/extension-mention";
export const MessageItem = ({
message,
@@ -33,7 +34,8 @@ export const MessageItem = ({
reactions,
isUpdating,
lastSignature,
- onEdit
+ onEdit,
+ isPrivate
}) => {
const [anchorEl, setAnchorEl] = useState(null);
const [selectedReaction, setSelectedReaction] = useState(null);
@@ -133,7 +135,7 @@ export const MessageItem = ({
gap: '10px',
alignItems: 'center'
}}>
- {message?.sender === myAddress && !message?.isNotEncrypted && (
+ {message?.sender === myAddress && (!message?.isNotEncrypted || isPrivate === false) && (
{
onEdit(message);
@@ -202,6 +204,7 @@ export const MessageItem = ({
StarterKit,
Underline,
Highlight,
+ Mention
])}
/>
)}
@@ -220,6 +223,7 @@ export const MessageItem = ({
StarterKit,
Underline,
Highlight,
+ Mention
])}
/>
)}
@@ -336,7 +340,7 @@ export const MessageItem = ({
alignItems: 'center',
gap: '15px'
}}>
- {message?.isNotEncrypted && (
+ {message?.isNotEncrypted && isPrivate && (
{
StarterKit,
Underline,
Highlight,
+ Mention
])}
/>
)}
diff --git a/src/components/Chat/styles.css b/src/components/Chat/styles.css
index 161eb13..806ffdf 100644
--- a/src/components/Chat/styles.css
+++ b/src/components/Chat/styles.css
@@ -126,7 +126,7 @@
}
-.tiptap .mention {
+.tiptap [data-type="mention"] {
box-decoration-break: clone;
color: lightblue;
padding: 0.1rem 0.3rem;
diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx
index 1b656dc..588015f 100644
--- a/src/components/Group/Group.tsx
+++ b/src/components/Group/Group.tsx
@@ -34,7 +34,8 @@ import RefreshIcon from "@mui/icons-material/Refresh";
import AnnouncementsIcon from "@mui/icons-material/Notifications";
import GroupIcon from "@mui/icons-material/Group";
import PersonIcon from "@mui/icons-material/Person";
-
+import LockIcon from '@mui/icons-material/Lock';
+import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import {
AuthenticatedContainerInnerRight,
CustomButton,
@@ -119,6 +120,19 @@ import { sortArrayByTimestampAndGroupName } from "../../utils/time";
// }
// });
+function areKeysEqual(array1, array2) {
+ // If lengths differ, the arrays cannot be equal
+ if (array1?.length !== array2?.length) {
+ return false;
+ }
+
+ // Sort both arrays and compare their elements
+ const sortedArray1 = [...array1].sort();
+ const sortedArray2 = [...array2].sort();
+
+ return sortedArray1.every((key, index) => key === sortedArray2[index]);
+}
+
export const getPublishesFromAdmins = async (admins: string[], groupId) => {
// const validApi = await findUsableApi();
@@ -476,6 +490,16 @@ export const Group = ({
const [appsMode, setAppsMode] = useState('home')
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false)
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
+
+ const [groupsProperties, setGroupsProperties] = useState({})
+
+ const isPrivate = useMemo(()=> {
+ if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null
+ if(groupsProperties[selectedGroup?.groupId]?.isOpen === true) return false
+ if(groupsProperties[selectedGroup?.groupId]?.isOpen === false) return true
+ return null
+ }, [selectedGroup])
+
const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom)
const toggleSideViewDirects = ()=> {
if(isOpenSideViewGroups){
@@ -682,9 +706,8 @@ export const Group = ({
if (
group?.data &&
- isExtMsg(group?.data) &&
group?.sender !== myAddress &&
- group?.timestamp && (!isUpdateMsg(group?.data) || groupChatTimestamps[group?.groupId]) &&
+ group?.timestamp && groupChatTimestamps[group?.groupId] &&
((!timestampEnterData[group?.groupId] &&
Date.now() - group?.timestamp < timeDifferenceForNotificationChats) ||
timestampEnterData[group?.groupId] < group?.timestamp)
@@ -844,12 +867,19 @@ export const Group = ({
useEffect(() => {
- if (selectedGroup) {
- setTriedToFetchSecretKey(false);
- getSecretKey(true);
+ if (selectedGroup && isPrivate !== null) {
+ if(isPrivate){
+ setTriedToFetchSecretKey(false);
+ getSecretKey(true);
+ }
+
getGroupOwner(selectedGroup?.groupId);
}
- }, [selectedGroup]);
+ if(isPrivate === false){
+ setTriedToFetchSecretKey(true);
+
+ }
+ }, [selectedGroup, isPrivate]);
@@ -880,9 +910,8 @@ export const Group = ({
const groupData = {}
const getGroupData = groups.map(async(group)=> {
- const isUpdate = isUpdateMsg(group?.data)
if(!group.groupId || !group?.timestamp) return null
- if(isUpdate && (!groupData[group.groupId] || groupData[group.groupId] < group.timestamp)){
+ if((!groupData[group.groupId] || groupData[group.groupId] < group.timestamp)){
const hasMoreRecentMsg = await getCountNewMesg(group.groupId, timestampEnterDataRef.current[group?.groupId] || Date.now() - 24 * 60 * 60 * 1000)
if(hasMoreRecentMsg){
groupData[group.groupId] = hasMoreRecentMsg
@@ -899,6 +928,31 @@ export const Group = ({
}
}
+ const getGroupsProperties = useCallback(async(address)=> {
+ try {
+ const url = `${getBaseApiReact()}/groups/member/${address}`;
+ const response = await fetch(url);
+ if(!response.ok) throw new Error('Cannot get group properties')
+ let data = await response.json();
+ const transformToObject = data.reduce((result, item) => {
+
+ result[item.groupId] = item
+ return result;
+ }, {});
+ setGroupsProperties(transformToObject)
+ } catch (error) {
+ // error
+ }
+ }, [])
+
+
+ useEffect(()=> {
+ if(!myAddress) return
+ if(areKeysEqual(groups?.map((grp)=> grp?.groupId), Object.keys(groupsProperties))){
+ } else {
+ getGroupsProperties(myAddress)
+ }
+ }, [groups, myAddress])
useEffect(() => {
@@ -1089,9 +1143,9 @@ export const Group = ({
.filter((group) => group?.sender !== myAddress)
.find((gr) => gr?.groupId === selectedGroup?.groupId);
if (!findGroup) return false;
- if (!findGroup?.data || !isExtMsg(findGroup?.data)) return false;
+ if (!findGroup?.data) return false;
return (
- findGroup?.timestamp && (!isUpdateMsg(findGroup?.data) || groupChatTimestamps[findGroup?.groupId]) &&
+ findGroup?.timestamp && groupChatTimestamps[findGroup?.groupId] &&
((!timestampEnterData[selectedGroup?.groupId] &&
Date.now() - findGroup?.timestamp <
timeDifferenceForNotificationChats) ||
@@ -1930,16 +1984,37 @@ export const Group = ({
}}
>
-
- {group.groupName?.charAt(0)}
-
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ }}>
+
+
+ ): (
+
+
+
+
+ )}
+
)}
- {group?.data &&
- isExtMsg(group?.data) && (!isUpdateMsg(group?.data) || groupChatTimestamps[group?.groupId]) &&
+ {group?.data &&
+ groupChatTimestamps[group?.groupId] &&
group?.sender !== myAddress &&
group?.timestamp &&
((!timestampEnterData[group?.groupId] &&
@@ -2054,6 +2129,7 @@ export const Group = ({
{isMobile && (
{triedToFetchSecretKey && (
)}
- {firstSecretKeyInCreation &&
+ {isPrivate && firstSecretKeyInCreation &&
triedToFetchSecretKey &&
!secretKeyPublishDate && (
)}
- {!admins.includes(myAddress) &&
+ {isPrivate && !admins.includes(myAddress) &&
!secretKey &&
triedToFetchSecretKey ? (
<>
@@ -2404,7 +2481,7 @@ export const Group = ({
) : null}
>
) : admins.includes(myAddress) &&
- !secretKey &&
+ (!secretKey && isPrivate) &&
triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : (
<>
- {admins.includes(myAddress) &&
+ {isPrivate && admins.includes(myAddress) &&
shouldReEncrypt &&
triedToFetchSecretKey &&
!firstSecretKeyInCreation &&
diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts
index afe5c85..251eb2d 100644
--- a/src/qdn/encryption/group-encryption.ts
+++ b/src/qdn/encryption/group-encryption.ts
@@ -143,7 +143,7 @@ export const encryptDataGroup = ({ data64, publicKeys, privateKey, userPublicKey
}
}
-export const encryptSingle = async ({ data64, secretKeyObject, typeNumber = 1 }: any) => {
+export const encryptSingle = async ({ data64, secretKeyObject, typeNumber = 2 }: any) => {
// Find the highest key in the secretKeyObject
const highestKey = Math.max(...Object.keys(secretKeyObject).filter(item => !isNaN(+item)).map(Number));
const highestKeyObject = secretKeyObject[highestKey];
@@ -186,13 +186,29 @@ export const encryptSingle = async ({ data64, secretKeyObject, typeNumber = 1 }:
// Concatenate the highest key, type number, nonce, and encrypted data (new format)
const highestKeyStr = highestKey.toString().padStart(10, '0'); // Fixed length of 10 digits
- finalEncryptedData = btoa(highestKeyStr + typeNumberStr + nonceBase64 + encryptedDataBase64);
+
+ const highestKeyBytes = new TextEncoder().encode(highestKeyStr.padStart(10, '0'));
+const typeNumberBytes = new TextEncoder().encode(typeNumberStr.padStart(3, '0'));
+
+// Step 3: Concatenate all binary
+const combinedBinary = new Uint8Array(
+ highestKeyBytes.length + typeNumberBytes.length + nonce.length + encryptedData.length
+);
+ // finalEncryptedData = btoa(highestKeyStr) + btoa(typeNumberStr) + nonceBase64 + encryptedDataBase64;
+ combinedBinary.set(highestKeyBytes, 0);
+combinedBinary.set(typeNumberBytes, highestKeyBytes.length);
+combinedBinary.set(nonce, highestKeyBytes.length + typeNumberBytes.length);
+combinedBinary.set(encryptedData, highestKeyBytes.length + typeNumberBytes.length + nonce.length);
+
+// Step 4: Base64 encode once
+ finalEncryptedData = uint8ArrayToBase64(combinedBinary);
}
return finalEncryptedData;
};
+
export const decodeBase64ForUIChatMessages = (messages)=> {
let msgs = []
@@ -200,12 +216,12 @@ export const decodeBase64ForUIChatMessages = (messages)=> {
try {
const decoded = atob(msg?.data);
const parseDecoded =JSON.parse(decodeURIComponent(escape(decoded)))
- if(parseDecoded?.messageText){
+
msgs.push({
...msg,
...parseDecoded
})
- }
+
} catch (error) {
}
@@ -215,7 +231,7 @@ export const decodeBase64ForUIChatMessages = (messages)=> {
- export const decryptSingle = async ({ data64, secretKeyObject, skipDecodeBase64 }: any) => {
+export const decryptSingle = async ({ data64, secretKeyObject, skipDecodeBase64 }: any) => {
// First, decode the base64-encoded input (if skipDecodeBase64 is not set)
const decodedData = skipDecodeBase64 ? data64 : atob(data64);
@@ -247,6 +263,28 @@ export const decodeBase64ForUIChatMessages = (messages)=> {
encryptedDataBase64 = decodeForNumber.slice(10); // The remaining part is the encrypted data
} else {
if (hasTypeNumber) {
+ // const typeNumberStr = new TextDecoder().decode(typeNumberBytes);
+ if(decodeForNumber.slice(10, 13) !== '001'){
+ const decodedBinary = base64ToUint8Array(decodedData);
+ const highestKeyBytes = decodedBinary.slice(0, 10); // if ASCII digits only
+ const highestKeyStr = new TextDecoder().decode(highestKeyBytes);
+
+const nonce = decodedBinary.slice(13, 13 + 24);
+const encryptedData = decodedBinary.slice(13 + 24);
+const highestKey = parseInt(highestKeyStr, 10);
+
+const messageKey = base64ToUint8Array(secretKeyObject[+highestKey].messageKey);
+const decryptedBytes = nacl.secretbox.open(encryptedData, nonce, messageKey);
+
+ // Check if decryption was successful
+ if (!decryptedBytes) {
+ throw new Error("Decryption failed");
+ }
+
+ // Convert the decrypted Uint8Array back to a Base64 string
+ return uint8ArrayToBase64(decryptedBytes);
+
+ }
// New format: Extract type number and nonce
typeNumberStr = possibleTypeNumberStr; // Extract type number
nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number)
diff --git a/src/utils/generateWallet/generateWallet.ts b/src/utils/generateWallet/generateWallet.ts
index c3f90ae..0e5b8c0 100644
--- a/src/utils/generateWallet/generateWallet.ts
+++ b/src/utils/generateWallet/generateWallet.ts
@@ -7,6 +7,9 @@ import { mimeToExtensionMap } from '../memeTypes';
import PhraseWallet from './phrase-wallet';
import * as WORDLISTS from './wordlists';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
+import ShortUniqueId from "short-unique-id";
+const uid = new ShortUniqueId({ length: 8 });
+
export function generateRandomSentence(template = 'adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun', maxWordLength = 0, capitalize = true) {
const partsOfSpeechMap = {
'noun': 'nouns',
@@ -88,7 +91,7 @@ export const createAccount = async(generatedSeedPhrase)=> {
export const saveFileToDisk = async (data: any, qortAddress: string) => {
const dataString = JSON.stringify(data);
- const fileName = `qortal_backup_${qortAddress}.json`;
+ const fileName = `qortal_backup_${qortAddress}_${uid.rnd()}.json`;
// Write the file to the Filesystem
await Filesystem.writeFile({
@@ -102,7 +105,7 @@ export const createAccount = async(generatedSeedPhrase)=> {
export const saveSeedPhraseToDisk = async (data) => {
- const fileName = "qortal_seedphrase.txt"
+ const fileName = `qortal_seedphrase_${uid.rnd()}.txt`
await Filesystem.writeFile({
path: fileName,