From 170de54eb674a30b5e0a4b5c2e4a7b54d53edac8 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 19:28:40 +0200 Subject: [PATCH 01/20] Use const instead of let, remove unused vars --- src/qdn/encryption/group-encryption.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index 6f018ba..ebead90 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -77,7 +77,7 @@ export const encryptDataGroup = ({ userPublicKey, customSymmetricKey, }: any) => { - let combinedPublicKeys = [...publicKeys, userPublicKey]; + const combinedPublicKeys = [...publicKeys, userPublicKey]; const decodedPrivateKey = Base58.decode(privateKey); const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)]; const Uint8ArrayData = base64ToUint8Array(data64); @@ -114,7 +114,7 @@ export const encryptDataGroup = ({ const keyNonce = new Uint8Array(24); crypto.getRandomValues(keyNonce); // Encrypt the symmetric key for each recipient. - let encryptedKeys = []; + const encryptedKeys = []; publicKeysDuplicateFree.forEach((recipientPublicKey) => { const publicKeyUnit8Array = Base58.decode(recipientPublicKey); const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey); @@ -153,7 +153,7 @@ export const encryptDataGroup = ({ encryptedKeysSize += key.length; }); combinedDataSize += encryptedKeysSize; - let combinedData = new Uint8Array(combinedDataSize); + const combinedData = new Uint8Array(combinedDataSize); combinedData.set(strUint8Array); combinedData.set(nonce, strUint8Array.length); combinedData.set(keyNonce, strUint8Array.length + nonce.length); @@ -244,9 +244,6 @@ export const encryptSingle = async ({ encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey); encryptedDataBase64 = uint8ArrayToBase64(encryptedData); - // Convert the nonce to base64 - const nonceBase64 = uint8ArrayToBase64(nonce); - // Concatenate the highest key, type number, nonce, and encrypted data (new format) const highestKeyStr = highestKey.toString().padStart(10, '0'); // Fixed length of 10 digits @@ -281,7 +278,7 @@ export const encryptSingle = async ({ }; export const decodeBase64ForUIChatMessages = (messages) => { - let msgs = []; + const msgs = []; for (const msg of messages) { try { const decoded = atob(msg?.data); @@ -326,7 +323,7 @@ export const decryptSingle = async ({ const secretKeyEntry = secretKeyObject[highestKey]; - let typeNumberStr, nonceBase64, encryptedDataBase64; + let nonceBase64, encryptedDataBase64; // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits const possibleTypeNumberStr = decodeForNumber.slice(10, 13); @@ -370,7 +367,6 @@ export const decryptSingle = async ({ 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) encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data } else { @@ -424,14 +420,9 @@ export const decryptGroupEncryptionWithSharingKey = async ({ // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition; const keyNonceEndPosition = keyNonceStartPosition + 24; // Nonce is 24 bytes - const keyNonce = allCombined.slice( - keyNonceStartPosition, - keyNonceEndPosition - ); // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition; const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32; // Public keys are 32 bytes - // Calculate count first const countStartPosition = allCombined.length - 4; // 4 bytes before the end, since count is stored in Uint32 (4 bytes) const countArray = allCombined.slice( @@ -662,7 +653,6 @@ export function decryptDeprecatedSingle(uint8Array, publicKey, privateKey) { const str = 'qortalEncryptedData'; const strEncoder = new TextEncoder(); const strUint8Array = strEncoder.encode(str); - const strData = combinedData.slice(0, strUint8Array.length); const nonce = combinedData.slice( strUint8Array.length, strUint8Array.length + 24 From 7f7f76d40e899d9f3c2bba793b5555696cc74ff5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 19:35:52 +0200 Subject: [PATCH 02/20] Set default src as null --- src/components/Embeds/ImageEmbed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index ffa288e..5440862 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -197,7 +197,7 @@ export const ImageCard = ({ ); }; -export function ImageViewer({ src, alt = '' }) { +export function ImageViewer({ src = null, alt = '' }) { const [isFullscreen, setIsFullscreen] = useState(false); const handleOpenFullscreen = () => setIsFullscreen(true); From 298aa35425e8017696eb476204254513d3845cf1 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:10:29 +0200 Subject: [PATCH 03/20] Add optional --- src/qortal/qortal-requests.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qortal/qortal-requests.ts b/src/qortal/qortal-requests.ts index f67b922..ba04c96 100644 --- a/src/qortal/qortal-requests.ts +++ b/src/qortal/qortal-requests.ts @@ -152,7 +152,7 @@ function setupMessageListenerQortalRequest() { appInfo, skipAuth, }); - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -162,7 +162,7 @@ function setupMessageListenerQortalRequest() { event.origin ); } catch (error) { - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -236,7 +236,7 @@ function setupMessageListenerQortalRequest() { request.payload, event.source ); - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -246,7 +246,7 @@ function setupMessageListenerQortalRequest() { event.origin ); } catch (error) { - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, From c053122d634aaf89eaacdd5e29369354225d9441 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:11:53 +0200 Subject: [PATCH 04/20] Use const instead of let, add comment --- src/qortal/get.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/qortal/get.ts b/src/qortal/get.ts index 368bd50..1af583f 100644 --- a/src/qortal/get.ts +++ b/src/qortal/get.ts @@ -552,7 +552,7 @@ export const getUserAccount = async ({ export const encryptData = async (data, sender) => { let data64 = data.data64 || data.base64; - let publicKeys = data.publicKeys || []; + const publicKeys = data.publicKeys || []; if (data?.file || data?.blob) { data64 = await fileToBase64(data?.file || data?.blob); } @@ -587,8 +587,8 @@ export const encryptData = async (data, sender) => { export const encryptQortalGroupData = async (data, sender) => { let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; + const groupId = data?.groupId; + const isAdmins = data?.isAdmins; if (!groupId) { throw new Error( i18n.t('question:message.generic.provide_group_id', { @@ -613,7 +613,7 @@ export const encryptQortalGroupData = async (data, sender) => { groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[groupId].secretKeyObject; } @@ -659,7 +659,7 @@ export const encryptQortalGroupData = async (data, sender) => { groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; } @@ -717,9 +717,9 @@ export const encryptQortalGroupData = async (data, sender) => { }; export const decryptQortalGroupData = async (data, sender) => { - let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; + const data64 = data?.data64 || data?.base64; + const groupId = data?.groupId; + const isAdmins = data?.isAdmins; if (!groupId) { throw new Error( i18n.t('question:message.generic.provide_group_id', { @@ -742,7 +742,7 @@ export const decryptQortalGroupData = async (data, sender) => { groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[groupId].secretKeyObject; } @@ -785,7 +785,7 @@ export const decryptQortalGroupData = async (data, sender) => { groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 // TODO magic nummber ) { secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; } @@ -843,7 +843,7 @@ export const decryptQortalGroupData = async (data, sender) => { export const encryptDataWithSharingKey = async (data, sender) => { let data64 = data?.data64 || data?.base64; - let publicKeys = data.publicKeys || []; + const publicKeys = data.publicKeys || []; if (data?.file || data?.blob) { data64 = await fileToBase64(data?.file || data?.blob); } @@ -899,6 +899,7 @@ export const decryptDataWithSharingKey = async (data, sender) => { data64EncryptedData: encryptedData, key, }); + const base64ToObject = JSON.parse(atob(decryptedData)); if (!base64ToObject.data) From 1e50cf8cd80b380b0b5f24f6e7448fb6d8a9924c Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:12:57 +0200 Subject: [PATCH 05/20] Sort parames --- src/components/Chat/MessageItem.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index b16b07f..65d6f80 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -100,22 +100,22 @@ const UserBadge = memo(({ userInfo }) => { export const MessageItem = memo( ({ - message, - onSeen, + handleReaction, isLast, - isTemp, - myAddress, - onReply, + isPrivate, isShowingAsReply, + isTemp, + isUpdating, + lastSignature, + message, + myAddress, + onEdit, + onReply, + onSeen, + reactions, reply, replyIndex, scrollToItem, - handleReaction, - reactions, - isUpdating, - lastSignature, - onEdit, - isPrivate, }) => { const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); From 1e078e1324f20c6fbfffbb5c9d38dda278b3eb59 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:48:35 +0200 Subject: [PATCH 06/20] Pass null instead of empty string --- src/components/Embeds/Embed.tsx | 2 +- src/components/Embeds/ImageEmbed.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Embeds/Embed.tsx b/src/components/Embeds/Embed.tsx index 0038830..7aa2e39 100644 --- a/src/components/Embeds/Embed.tsx +++ b/src/components/Embeds/Embed.tsx @@ -59,7 +59,7 @@ export const Embed = ({ embedLink }) => { const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const [external, setExternal] = useState(null); - const [imageUrl, setImageUrl] = useState(''); + const [imageUrl, setImageUrl] = useState(null); const [parsedData, setParsedData] = useState(null); const setBlobs = useSetAtom(blobControllerAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom); diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index 5440862..13ec3e2 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -199,7 +199,6 @@ export const ImageCard = ({ export function ImageViewer({ src = null, alt = '' }) { const [isFullscreen, setIsFullscreen] = useState(false); - const handleOpenFullscreen = () => setIsFullscreen(true); const handleCloseFullscreen = () => setIsFullscreen(false); const theme = useTheme(); From 5444bacdd0657b53a5e33d656113ebfd97229252 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 21:42:33 +0200 Subject: [PATCH 07/20] Add check condition --- src/background/background.ts | 31 ++-- src/components/Group/Group.tsx | 18 +-- src/qdn/encryption/group-encryption.ts | 190 +++++++++++++------------ 3 files changed, 127 insertions(+), 112 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index 2057914..fce3d83 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -440,7 +440,7 @@ const handleNotificationDirect = async (directs) => { let isFocused; const wallet = await getSaveWallet(); const address = wallet.address0; - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; const dataDirects = directs.filter((direct) => direct?.sender !== address); try { @@ -1734,7 +1734,7 @@ export async function decryptSingleFunc({ secretKeyObject, skipDecodeBase64, }) { - let holdMessages = []; + const holdMessages = []; for (const message of messages) { try { @@ -1744,9 +1744,11 @@ export async function decryptSingleFunc({ skipDecodeBase64, }); - const decryptToUnit8Array = base64ToUint8Array(res); - const responseData = uint8ArrayToObject(decryptToUnit8Array); - holdMessages.push({ ...message, decryptedData: responseData }); + if (res) { + const decryptToUnit8Array = base64ToUint8Array(res); + const responseData = uint8ArrayToObject(decryptToUnit8Array); + holdMessages.push({ ...message, decryptedData: responseData }); + } } catch (error) { console.error(error); } @@ -1758,7 +1760,7 @@ export async function decryptSingleForPublishes({ secretKeyObject, skipDecodeBase64, }) { - let holdMessages = []; + const holdMessages = []; for (const message of messages) { try { @@ -2888,6 +2890,7 @@ export async function getTimestampEnterChat() { return {}; } } + export async function getTimestampMention() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -2900,6 +2903,7 @@ export async function getTimestampMention() { return {}; } } + export async function getTimestampGroupAnnouncement() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -2996,6 +3000,7 @@ async function getGroupData() { return {}; } } + export async function getGroupDataSingle(groupId) { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -3266,6 +3271,7 @@ function setupMessageListener() { break; case 'updateThreadActivity': updateThreadActivityCase(request, event); + break; case 'decryptGroupEncryption': decryptGroupEncryptionCase(request, event); break; @@ -3411,7 +3417,7 @@ export const checkNewMessages = async () => { myName = userData.name; } - let newAnnouncements = []; + const newAnnouncements = []; const activeData = (await getStoredData('active-groups-directs')) || { groups: [], directs: [], @@ -3463,7 +3469,8 @@ export const checkNewMessages = async () => { } }) ); - let isDisableNotifications = + + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; if ( @@ -3611,8 +3618,8 @@ export const checkThreads = async (bringBack) => { if (userData?.name) { myName = userData.name; } - let newAnnouncements = []; - let dataToBringBack = []; + const newAnnouncements = []; + const dataToBringBack = []; const threadActivity = await getThreadActivity(); if (!threadActivity) return null; @@ -3627,7 +3634,6 @@ export const checkThreads = async (bringBack) => { for (const thread of selectedThreads) { try { const identifier = `thmsg-${thread?.threadId}`; - const name = thread?.qortalName; const endpoint = await getArbitraryEndpoint(); const url = await createEndpoint( `${endpoint}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true` @@ -3643,7 +3649,6 @@ export const checkThreads = async (bringBack) => { const latestMessage = responseData.filter( (pub) => pub?.name !== myName )[0]; - // const latestMessage = responseData[0] if (!latestMessage) { continue; @@ -3717,7 +3722,7 @@ export const checkThreads = async (bringBack) => { '_type=thread-post' + `_data=${JSON.stringify(newAnnouncements[0])}` ); - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; if (!isDisableNotifications) { // Check user settings to see if notifications are disabled diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index f4091f2..dc8209d 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -276,15 +276,13 @@ export async function getNameInfo(address: string) { } export const getGroupAdmins = async (groupNumber: number) => { - // const validApi = await findUsableApi(); - const response = await fetch( `${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true` ); const groupData = await response.json(); - let members: any = []; - let membersAddresses = []; - let both = []; + const members: any = []; + const membersAddresses = []; + const both = []; const getMemNames = groupData?.members?.map(async (member) => { if (member?.member) { @@ -601,9 +599,11 @@ export const Group = ({ const getGroupOwner = async (groupId) => { try { + if (groupId == 0) return; + const url = `${getBaseApiReact()}/groups/${groupId}`; const response = await fetch(url); - let data = await response.json(); + const data = await response.json(); const name = await getNameInfo(data?.owner); if (name) { @@ -742,7 +742,7 @@ export const Group = ({ data = await res.text(); } - const decryptedKey: any = await decryptResource(data); + const decryptedKey: any = await decryptResource(data, null); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -877,6 +877,8 @@ export const Group = ({ }; const getOwnerNameForGroup = async (owner: string, groupId: string) => { + if (groupId == '0') return; + try { if (!owner) return; if (groupsOwnerNamesRef.current[groupId]) return; @@ -899,7 +901,7 @@ export const Group = ({ 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 data = await response.json(); const transformToObject = data.reduce((result, item) => { result[item.groupId] = item; return result; diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index ebead90..904413c 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -303,106 +303,114 @@ export const decryptSingle = async ({ // First, decode the base64-encoded input (if skipDecodeBase64 is not set) const decodedData = skipDecodeBase64 ? data64 : atob(data64); - // Then, decode it again for the specific format (if double encoding is used) - const decodeForNumber = atob(decodedData); + if (secretKeyObject) { + // Then, decode it again for the specific format (if double encoding is used) + const decodeForNumber = atob(decodedData); - // Extract the key (assuming it's always the first 10 characters) - const keyStr = decodeForNumber.slice(0, 10); + // Extract the key (assuming it's always the first 10 characters) + const keyStr = decodeForNumber.slice(0, 10); - // Convert the key string back to a number - const highestKey = parseInt(keyStr, 10); + // Convert the key string back to a number + const highestKey = parseInt(keyStr, 10); - // Check if we have a valid secret key for the extracted highestKey - if (!secretKeyObject[highestKey]) { - throw new Error( - i18n.t('auth:message.error.find_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const secretKeyEntry = secretKeyObject[highestKey]; - - let nonceBase64, encryptedDataBase64; - - // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits - const possibleTypeNumberStr = decodeForNumber.slice(10, 13); - const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr); // Check if next 3 characters are digits - - if (secretKeyEntry.nonce) { - // Old format: nonce is present in the secretKeyObject, so no type number exists - nonceBase64 = secretKeyEntry.nonce; - 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( - i18n.t('question:message.error.decryption_failed', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - // Convert the decrypted Uint8Array back to a Base64 string - return uint8ArrayToBase64(decryptedBytes); - } - // New format: Extract type number and nonce - nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number) - encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data - } else { - // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) - nonceBase64 = decodeForNumber.slice(10, 42); // First 32 characters for the nonce - encryptedDataBase64 = decodeForNumber.slice(42); // The remaining part is the encrypted data + // Check if we have a valid secret key for the extracted highestKey + if (!secretKeyObject[highestKey]) { + throw new Error( + i18n.t('auth:message.error.find_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); } - } - // Convert Base64 strings to Uint8Array - const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64); - const nonce = base64ToUint8Array(nonceBase64); - const messageKey = base64ToUint8Array(secretKeyEntry.messageKey); + const secretKeyEntry = secretKeyObject[highestKey]; - if (!(Uint8ArrayData instanceof Uint8Array)) { - throw new Error( - i18n.t('auth:message.error.invalid_uint8', { - postProcess: 'capitalizeFirstChar', - }) + let nonceBase64, encryptedDataBase64; + + // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits + const possibleTypeNumberStr = decodeForNumber.slice(10, 13); + const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr); // Check if next 3 characters are digits + + if (secretKeyEntry.nonce) { + // Old format: nonce is present in the secretKeyObject, so no type number exists + nonceBase64 = secretKeyEntry.nonce; + 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( + i18n.t('question:message.error.decryption_failed', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Convert the decrypted Uint8Array back to a Base64 string + return uint8ArrayToBase64(decryptedBytes); + } + // New format: Extract type number and nonce + nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number) + encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data + } else { + // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) + nonceBase64 = decodeForNumber.slice(10, 42); // First 32 characters for the nonce + encryptedDataBase64 = decodeForNumber.slice(42); // The remaining part is the encrypted data + } + } + + // Convert Base64 strings to Uint8Array + const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64); + const nonce = base64ToUint8Array(nonceBase64); + const messageKey = base64ToUint8Array(secretKeyEntry.messageKey); + + if (!(Uint8ArrayData instanceof Uint8Array)) { + throw new Error( + i18n.t('auth:message.error.invalid_uint8', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Decrypt the data using the nonce and messageKey + const decryptedData = nacl.secretbox.open( + Uint8ArrayData, + nonce, + messageKey ); + + // Check if decryption was successful + if (!decryptedData) { + throw new Error( + i18n.t('question:message.error.decryption_failed', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Convert the decrypted Uint8Array back to a Base64 string + return uint8ArrayToBase64(decryptedData); } - // Decrypt the data using the nonce and messageKey - const decryptedData = nacl.secretbox.open(Uint8ArrayData, nonce, messageKey); - - // Check if decryption was successful - if (!decryptedData) { - throw new Error( - i18n.t('question:message.error.decryption_failed', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - // Convert the decrypted Uint8Array back to a Base64 string - return uint8ArrayToBase64(decryptedData); + return; }; export const decryptGroupEncryptionWithSharingKey = async ({ From a3b5778da17be4630e30844aae1da687c8c0a188 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 06:32:41 +0200 Subject: [PATCH 08/20] Add type --- src/components/Chat/MessageItem.tsx | 932 ++++++++++++++-------------- 1 file changed, 477 insertions(+), 455 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 65d6f80..c129408 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -98,533 +98,555 @@ const UserBadge = memo(({ userInfo }) => { ); }); -export const MessageItem = memo( - ({ - handleReaction, - isLast, - isPrivate, - isShowingAsReply, - isTemp, - isUpdating, - lastSignature, - message, - myAddress, - onEdit, - onReply, - onSeen, - reactions, - reply, - replyIndex, - scrollToItem, - }) => { - const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); - const [anchorEl, setAnchorEl] = useState(null); - const [selectedReaction, setSelectedReaction] = useState(null); - const [userInfo, setUserInfo] = useState(null); +type MessageItemProps = { + handleReaction: (reaction: string, messageId: string) => void; + isLast: boolean; + isPrivate: boolean; + isShowingAsReply?: boolean; + isTemp: boolean; + isUpdating: boolean; + lastSignature: any; + message: any; + myAddress: any; + onEdit: (messageId: string) => void; + onReply: (messageId: string) => void; + onSeen: () => void; + reactions: any; // could be null, or type it more strictly + reply: any; // same here + replyIndex: number; + scrollToItem: (index: number) => void; +}; - useEffect(() => { - const getInfo = async () => { - if (!message?.sender) return; - try { - const res = await getIndividualUserInfo(message?.sender); - if (!res) return null; - setUserInfo(res); - } catch (error) { - // - } - }; +export const MessageItemComponent = ({ + handleReaction, + isLast, + isPrivate, + isShowingAsReply, + isTemp, + isUpdating, + lastSignature, + message, + myAddress, + onEdit, + onReply, + onSeen, + reactions, + reply, + replyIndex, + scrollToItem, +}: MessageItemProps) => { + const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); + const [anchorEl, setAnchorEl] = useState(null); + const [selectedReaction, setSelectedReaction] = useState(null); + const [userInfo, setUserInfo] = useState(null); - getInfo(); - }, [message?.sender, getIndividualUserInfo]); - - const htmlText = useMemo(() => { - if (message?.messageText) { - const isHtml = isHtmlString(message?.messageText); - if (isHtml) return message?.messageText; - return generateHTML(message?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); + useEffect(() => { + const getInfo = async () => { + if (!message?.sender) return; + try { + const res = await getIndividualUserInfo(message?.sender); + if (!res) return null; + setUserInfo(res); + } catch (error) { + // } - }, [message?.editTimestamp]); + }; - const htmlReply = useMemo(() => { - if (reply?.messageText) { - const isHtml = isHtmlString(reply?.messageText); - if (isHtml) return reply?.messageText; - return generateHTML(reply?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); - } - }, [reply?.editTimestamp]); + getInfo(); + }, [message?.sender, getIndividualUserInfo]); - const userAvatarUrl = useMemo(() => { - return message?.senderName - ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ - message?.senderName - }/qortal_avatar?async=true` - : ''; - }, []); + const htmlText = useMemo(() => { + if (message?.messageText) { + const isHtml = isHtmlString(message?.messageText); + if (isHtml) return message?.messageText; + return generateHTML(message?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [message?.editTimestamp]); - const onSeenFunc = useCallback(() => { - onSeen(message.id); - }, [message?.id]); + const htmlReply = useMemo(() => { + if (reply?.messageText) { + const isHtml = isHtmlString(reply?.messageText); + if (isHtml) return reply?.messageText; + return generateHTML(reply?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [reply?.editTimestamp]); - const theme = useTheme(); - const { t } = useTranslation([ - 'auth', - 'core', - 'group', - 'question', - 'tutorial', - ]); + const userAvatarUrl = useMemo(() => { + return message?.senderName + ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ + message?.senderName + }/qortal_avatar?async=true` + : ''; + }, []); - return ( - <> - {message?.divide && ( -
- {t('core:message.generic.unread_messages', { - postProcess: 'capitalizeFirstChar', - })} -
- )} + const onSeenFunc = useCallback(() => { + onSeen(message.id); + }, [message?.id]); - + {message?.divide && ( +
+ {t('core:message.generic.unread_messages', { + postProcess: 'capitalizeFirstChar', + })} +
+ )} + + +
-
+ ) : ( + + + + {message?.senderName?.charAt(0)} + + + + + )} + + - {isShowingAsReply ? ( - - ) : ( + + + + {message?.senderName || message?.sender} + + + - - { + onEdit(message); + }} + > + + + )} + + {!isShowingAsReply && ( + { + onReply(message); }} - alt={message?.senderName} - src={userAvatarUrl} > - {message?.senderName?.charAt(0)} - - - + + + )} + + {!isShowingAsReply && handleReaction && ( + { + if ( + reactions && + reactions[val] && + reactions[val]?.find( + (item) => item?.sender === myAddress + ) + ) { + handleReaction(val, message, false); + } else { + handleReaction(val, message, true); + } + }} + /> + )} + + + {reply && ( + <> + + + { + scrollToItem(replyIndex); + }} + > + + + + + {t('core:message.generic.replied_to', { + person: reply?.senderName || reply?.senderAddress, + postProcess: 'capitalizeFirstChar', + })} + + + {reply?.messageText && ( + + )} + + {reply?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + + + + )} + + {htmlText && } + + {message?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + {message?.images && messageHasImage(message) && ( + )} - - - {message?.senderName || message?.sender} - - - - - {message?.sender === myAddress && - (!message?.isNotEncrypted || isPrivate === false) && ( + {reactions && + Object.keys(reactions).map((reaction) => { + const numberOfReactions = reactions[reaction]?.length; + if (numberOfReactions === 0) return null; + return ( { - onEdit(message); + key={reaction} + sx={{ + background: theme.palette.background.surface, + borderRadius: '7px', + height: '30px', + minWidth: '45px', + }} + onClick={(event) => { + event.stopPropagation(); // Prevent event bubbling + setAnchorEl(event.currentTarget); + setSelectedReaction(reaction); }} > - +
+ {reaction} +
{' '} + {numberOfReactions > 1 && ( + + {numberOfReactions} + + )}
- )} + ); + })} +
- {!isShowingAsReply && ( - { - onReply(message); + {selectedReaction && ( + { + setAnchorEl(null); + setSelectedReaction(null); + }} + anchorOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + slotProps={{ + paper: { + style: { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }, + }} + > + + + {t('core:message.generic.people_reaction', { + reaction: selectedReaction, + postProcess: 'capitalizeFirstChar', + })} + + + - - - )} + {reactions[selectedReaction]?.map((reactionItem) => ( + + + + ))} + - {!isShowingAsReply && handleReaction && ( - { +
- - )} - - {htmlText && } - - {message?.decryptedData?.type === 'notification' ? ( - - ) : ( - - )} - {message?.images && messageHasImage(message) && ( - + )} - - {reactions && - Object.keys(reactions).map((reaction) => { - const numberOfReactions = reactions[reaction]?.length; - if (numberOfReactions === 0) return null; - return ( - { - event.stopPropagation(); // Prevent event bubbling - setAnchorEl(event.currentTarget); - setSelectedReaction(reaction); - }} - > -
- {reaction} -
{' '} - {numberOfReactions > 1 && ( - - {numberOfReactions} - - )} -
- ); - })} -
- - {selectedReaction && ( - { - setAnchorEl(null); - setSelectedReaction(null); + {message?.isNotEncrypted && isPrivate && ( + - - - {t('core:message.generic.people_reaction', { - reaction: selectedReaction, - postProcess: 'capitalizeFirstChar', - })} - - - - {reactions[selectedReaction]?.map((reactionItem) => ( - - - - ))} - - - - - + /> )} - - {message?.isNotEncrypted && isPrivate && ( - - )} - - {isUpdating ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.update_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.updating', { - postProcess: 'capitalizeFirstChar', - })} - - ) : isTemp ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.send_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.sending', { - postProcess: 'capitalizeFirstChar', - })} - - ) : ( - <> - {message?.isEdit && ( - - {t('core:message.generic.edited', { - postProcess: 'capitalizeFirstChar', - })} - - )} - + {isUpdating ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.update_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.updating', { + postProcess: 'capitalizeFirstChar', + })} + + ) : isTemp ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.send_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.sending', { + postProcess: 'capitalizeFirstChar', + })} + + ) : ( + <> + {message?.isEdit && ( - {formatTimestamp(message.timestamp)} + {t('core:message.generic.edited', { + postProcess: 'capitalizeFirstChar', + })} - - )} - + )} + + + {formatTimestamp(message.timestamp)} + + + )}
-
- - - ); - } -); + +
+
+ + ); +}; + +const MemoizedMessageItem = memo(MessageItemComponent); +MemoizedMessageItem.displayName = 'MessageItem'; // It ensures React DevTools shows MessageItem as the name (instead of "Anonymous" or "Memo") + +export const MessageItem = MemoizedMessageItem; export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme(); From d24e2ac3bc6a707f074c9b27bbeeeddef68fad08 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 06:55:32 +0200 Subject: [PATCH 09/20] Add type for Reaction --- src/components/Chat/ChatList.tsx | 11 ++++++++++- src/components/Chat/MessageItem.tsx | 11 ++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 305674c..3d8f84d 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -7,6 +7,15 @@ import { ChatOptions } from './ChatOptions'; import ErrorBoundary from '../../common/ErrorBoundary'; import { useTranslation } from 'react-i18next'; +type ReactionItem = { + sender: string; + senderName?: string; +}; + +export type ReactionsMap = { + [reactionType: string]: ReactionItem[]; +}; + export const ChatList = ({ initialMessages, myAddress, @@ -236,7 +245,7 @@ export const ChatList = ({ let message = messages[index] || null; // Safeguard against undefined let replyIndex = -1; let reply = null; - let reactions = null; + let reactions: ReactionsMap | null = null; let isUpdating = false; try { diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index c129408..4d412fd 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -53,6 +53,7 @@ import { messageHasImage, } from '../../utils/chat'; import { useTranslation } from 'react-i18next'; +import { ReactionsMap } from './ChatList'; const getBadgeImg = (level) => { switch (level?.toString()) { @@ -105,14 +106,14 @@ type MessageItemProps = { isShowingAsReply?: boolean; isTemp: boolean; isUpdating: boolean; - lastSignature: any; - message: any; - myAddress: any; + lastSignature: string; + message: string; + myAddress: string; onEdit: (messageId: string) => void; onReply: (messageId: string) => void; onSeen: () => void; - reactions: any; // could be null, or type it more strictly - reply: any; // same here + reactions: ReactionsMap | null; + reply: string | null; replyIndex: number; scrollToItem: (index: number) => void; }; From 656cbbde0e1b2d93fbd7948741201478f127d696 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:08:31 +0200 Subject: [PATCH 10/20] Add TextStyle and refactor --- src/components/Chat/ChatList.tsx | 10 +++++----- src/components/Chat/ChatOptions.tsx | 8 ++------ src/components/Chat/TipTap.tsx | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 3d8f84d..0651245 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -466,13 +466,13 @@ export const ChatList = ({ {enableMentions && (hasSecretKey || isPrivate === false) && ( )} diff --git a/src/components/Chat/ChatOptions.tsx b/src/components/Chat/ChatOptions.tsx index 2d2c352..ef1a4b8 100644 --- a/src/components/Chat/ChatOptions.tsx +++ b/src/components/Chat/ChatOptions.tsx @@ -25,7 +25,6 @@ import { AppsSearchRight, } from '../Apps/Apps-styles'; import IconClearInput from '../../assets/svgs/ClearInput.svg'; -import { CellMeasurerCache } from 'react-virtualized'; import { getBaseApiReact } from '../../App'; import { MessageDisplay } from './MessageDisplay'; import { useVirtualizer } from '@tanstack/react-virtual'; @@ -36,6 +35,7 @@ import { generateHTML } from '@tiptap/react'; import ErrorBoundary from '../../common/ErrorBoundary'; import { useTranslation } from 'react-i18next'; import { isHtmlString } from '../../utils/chat'; +import TextStyle from '@tiptap/extension-text-style'; const extractTextFromHTML = (htmlString = '') => { return convert(htmlString, { @@ -43,11 +43,6 @@ const extractTextFromHTML = (htmlString = '') => { })?.toLowerCase(); }; -const cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 50, -}); - export const ChatOptions = ({ messages: untransformedMessages, goToMessage, @@ -86,6 +81,7 @@ export const ChatOptions = ({ Underline, Highlight, Mention, + TextStyle, ]); return { ...item, diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 0baef0a..3ab63a1 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -361,8 +361,8 @@ const MenuBar = memo( ); const extensions = [ + TextStyle, Color.configure({ types: [TextStyle.name, ListItem.name] }), - TextStyle.configure({ types: [ListItem.name] }), StarterKit.configure({ bulletList: { keepMarks: true, From dded49145af691f899a027abbc05db71aa092391 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:30:44 +0200 Subject: [PATCH 11/20] Remove unused function textMatcher --- src/components/Chat/TipTap.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 3ab63a1..270dc47 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -34,16 +34,6 @@ import { fileToBase64 } from '../../utils/fileReading/index.js'; import { useTranslation } from 'react-i18next'; import i18n from 'i18next'; -function textMatcher(doc, from) { - const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); - const match = textBeforeCursor.match(/@[\w]*$/); // Match '@' followed by valid characters - if (!match) return null; - - const start = from - match[0].length; - const query = match[0]; - return { start, query }; -} - const MenuBar = memo( ({ setEditorRef, From 4c8c90a579d59a92c60e49008ba45cb1f296cf57 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:55:22 +0200 Subject: [PATCH 12/20] Define type --- src/components/Chat/TipTap.tsx | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 270dc47..4c69504 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -1,5 +1,5 @@ import { memo, useCallback, useEffect, useMemo, useRef } from 'react'; -import { EditorProvider, useCurrentEditor } from '@tiptap/react'; +import { Editor, EditorProvider, useCurrentEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { Color } from '@tiptap/extension-color'; import ListItem from '@tiptap/extension-list-item'; @@ -373,11 +373,26 @@ const extensions = [ const content = ``; -export default ({ +type TiptapProps = { + setEditorRef: (editorInstance: Editor | null) => void; + onEnter: () => void | Promise; + disableEnter?: boolean; + isChat?: boolean; + maxHeightOffset?: number; + overrideMobile?: boolean; + customEditorHeight?: number | null; + setIsFocusedParent: React.Dispatch>; + isFocusedParent: boolean; + membersWithNames: unknown[]; + enableMentions?: boolean; + insertImage: (image: any) => void; +}; + +const Tiptap = ({ setEditorRef, onEnter, - disableEnter, - isChat, + disableEnter = false, + isChat = false, maxHeightOffset, setIsFocusedParent, isFocusedParent, @@ -386,7 +401,7 @@ export default ({ membersWithNames, enableMentions, insertImage, -}) => { +}: TiptapProps) => { const theme = useTheme(); const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useAtom( isDisabledEditorEnterAtom @@ -613,3 +628,5 @@ export default ({ ); }; + +export default Tiptap; From f0805e1da5ad9ea31d452bce2358de5d206060f5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:55:42 +0200 Subject: [PATCH 13/20] Sort properties and add spaces in the code --- src/components/Wallets.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx index 75a36ea..aef22b3 100644 --- a/src/components/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -244,6 +244,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { )} )} + {wallets?.length > 0 && ( { postProcess: 'capitalizeFirstChar', })} + { if (!seedValue || !seedName || !password) return; onOk({ seedValue, seedName, password }); }} - autoFocus + variant="contained" > {t('core:action.add', { postProcess: 'capitalizeFirstChar', })} + Date: Fri, 6 Jun 2025 08:00:21 +0200 Subject: [PATCH 14/20] Add comment TODO --- src/background/background.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index fce3d83..3c688ff 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1281,7 +1281,6 @@ export async function addUserSettings({ keyValue }) { getData(`${address}-userSettings`) .then((storedData) => { storedData = storedData || {}; // Initialize if no data found - storedData[key] = value; // Update the key-value pair within stored data // Save updated structure back to localStorage @@ -3393,7 +3392,7 @@ const checkGroupList = async () => { .filter( (item) => item?.name !== 'extension-proxy' && - item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' + item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' // TODO put address in a specific file ) .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); @@ -3447,6 +3446,7 @@ export const checkNewMessages = async () => { const latestMessage = responseData.filter( (pub) => pub?.name !== myName )[0]; + if (!latestMessage) { return; // continue to the next group } From 203f14799fd53d7251de448abd3c10144b47db3b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 01:53:49 +0300 Subject: [PATCH 15/20] fix chat edits with image --- src/components/Chat/ChatGroup.tsx | 68 ++++++++++++++++------------ src/components/Chat/ChatList.tsx | 21 ++------- src/components/Chat/MessageItem.tsx | 4 +- src/components/Embeds/ImageEmbed.tsx | 17 ++++++- src/i18n/locales/de/core.json | 1 + src/i18n/locales/en/core.json | 1 + src/i18n/locales/es/core.json | 1 + src/i18n/locales/fr/core.json | 1 + src/i18n/locales/it/core.json | 1 + src/i18n/locales/ja/core.json | 1 + src/i18n/locales/ru/core.json | 1 + src/i18n/locales/zh/core.json | 1 + 12 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index be0f7c4..a0efe00 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -299,17 +299,15 @@ export const ChatGroup = ({ const formatted = combineUIAndExtensionMsgs .filter((rawItem) => !rawItem?.chatReference) .map((item) => { - const message = ( -

- {t('group:message.generic.group_key_created', { - postProcess: 'capitalizeFirstChar', - })} -

- ); const additionalFields = item?.data === 'NDAwMQ==' // TODO put magic string somewhere in a file ? { - text: message, + text: `

${t( + 'group:message.generic.group_key_created', + { + postProcess: 'capitalizeFirstChar', + } + )}

`, } : {}; return { @@ -450,17 +448,15 @@ export const ChatGroup = ({ const formatted = combineUIAndExtensionMsgs .filter((rawItem) => !rawItem?.chatReference) .map((item) => { - const message = ( -

- {t('group:message.generic.group_key_created', { - postProcess: 'capitalizeFirstChar', - })} -

- ); const additionalFields = item?.data === 'NDAwMQ==' ? { - text: message, + text: `

${t( + 'group:message.generic.group_key_created', + { + postProcess: 'capitalizeFirstChar', + } + )}

`, } : {}; const divide = @@ -818,13 +814,28 @@ export const ChatGroup = ({ ); pauseAllQueues(); if (editorRef.current) { - const htmlContent = editorRef.current.getHTML(); - - if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; + let htmlContent = editorRef.current.getHTML(); + const deleteImage = + onEditMessage && isDeleteImage && messageHasImage(onEditMessage); + const hasImage = + chatImagesToSave?.length > 0 || onEditMessage?.images?.length > 0; + if ( + (!htmlContent?.trim() || htmlContent?.trim() === '

') && + !hasImage && + !deleteImage + ) + return; + if (htmlContent?.trim() === '

') { + htmlContent = null; + } setIsSending(true); const message = - isPrivate === false ? editorRef.current.getJSON() : htmlContent; + isPrivate === false + ? !htmlContent + ? '

' + : editorRef.current.getJSON() + : htmlContent; const secretKeyObject = await getSecretKey(false, true); let repliedTo = replyMessage?.signature; @@ -849,8 +860,6 @@ export const ChatGroup = ({ } const imagesToPublish: ImageToPublish[] = []; - const deleteImage = - onEditMessage && isDeleteImage && messageHasImage(onEditMessage); if (deleteImage) { const fee = await getFee('ARBITRARY'); @@ -931,7 +940,6 @@ export const ChatGroup = ({ [isPrivate ? 'message' : 'messageText']: message, version: 3, }; - const message64: any = await objectToBase64(objectMessage); const encryptSingle = @@ -1042,11 +1050,15 @@ export const ChatGroup = ({ const onEdit = useCallback((message) => { setOnEditMessage(message); setReplyMessage(null); - editorRef.current - .chain() - .focus() - .setContent(message?.messageText || message?.text) - .run(); + try { + editorRef.current + .chain() + .focus() + .setContent(message?.messageText || message?.text || '

') + .run(); + } catch (error) { + console.error(error); + } }, []); const handleReaction = useCallback( diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 0651245..e7897c2 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -289,31 +289,18 @@ export const ChatList = ({ reactions = chatReferences[message.signature]?.reactions || null; - if ( - chatReferences[message.signature]?.edit?.message && - message?.text - ) { + if (chatReferences[message.signature]?.edit) { message.text = chatReferences[message.signature]?.edit?.message; - message.isEdit = true; - message.editTimestamp = - chatReferences[message.signature]?.edit?.timestamp; - } - if ( - chatReferences[message.signature]?.edit?.messageText && - message?.messageText - ) { message.messageText = chatReferences[message.signature]?.edit?.messageText; + message.images = + chatReferences[message.signature]?.edit?.images; + message.isEdit = true; message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp; } - if (chatReferences[message.signature]?.edit?.images) { - message.images = - chatReferences[message.signature]?.edit?.images; - message.isEdit = true; - } } // Check if message is updating diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 4d412fd..aeb2628 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -47,6 +47,7 @@ import level8Img from '../../assets/badges/level-8.png'; import level9Img from '../../assets/badges/level-9.png'; import level10Img from '../../assets/badges/level-10.png'; import { Embed } from '../Embeds/Embed'; +import CommentsDisabledIcon from '@mui/icons-material/CommentsDisabled'; import { buildImageEmbedLink, isHtmlString, @@ -660,6 +661,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => { ]); const replyMessageText = useMemo(() => { + if (!message?.messageText) return null; const isHtml = isHtmlString(message?.messageText); if (isHtml) return message?.messageText; return generateHTML(message?.messageText, [ @@ -715,7 +717,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
)} - {message?.messageText && ( + {message?.replyMessageText && ( )} diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index 13ec3e2..7402039 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -52,6 +52,8 @@ export const ImageCard = ({ backgroundColor: theme.palette.background.default, height: height, transition: 'height 0.6s ease-in-out', + display: 'flex', + flexDirection: 'column', }} > - - + + @@ -211,6 +223,7 @@ export function ImageViewer({ src = null, alt = '' }) { display: 'flex', justifyContent: 'center', maxWidth: '100%', // Prevent horizontal overflow + height: '100%', }} onClick={handleOpenFullscreen} > diff --git a/src/i18n/locales/de/core.json b/src/i18n/locales/de/core.json index 420894d..cc3a4e6 100644 --- a/src/i18n/locales/de/core.json +++ b/src/i18n/locales/de/core.json @@ -245,6 +245,7 @@ "no_data_image": "Keine Daten für das Bild", "no_description": "Keine Beschreibung", "no_messages": "Keine Nachrichten", + "no_message": "keine nachricht", "no_minting_details": "müngungsdetails auf dem Gateway können nicht angezeigt werden", "no_notifications": "Keine neuen Benachrichtigungen", "no_payments": "Keine Zahlungen", diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index 49a97e8..a5ca275 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -249,6 +249,7 @@ "no_data_image": "no data for image", "no_description": "no description", "no_messages": "no messages", + "no_message": "no message", "no_minting_details": "cannot view minting details on the gateway", "no_notifications": "no new notifications", "no_payments": "no payments", diff --git a/src/i18n/locales/es/core.json b/src/i18n/locales/es/core.json index f37b46f..39e7e7c 100644 --- a/src/i18n/locales/es/core.json +++ b/src/i18n/locales/es/core.json @@ -246,6 +246,7 @@ "no_data_image": "no hay datos para la imagen", "no_description": "sin descripción", "no_messages": "sin mensajes", + "no_message": "sin mensaje", "no_minting_details": "no se puede ver los detalles de acuñado en la puerta de enlace", "no_notifications": "no hay nuevas notificaciones", "no_payments": "sin pagos", diff --git a/src/i18n/locales/fr/core.json b/src/i18n/locales/fr/core.json index a6d0fa1..76c028b 100644 --- a/src/i18n/locales/fr/core.json +++ b/src/i18n/locales/fr/core.json @@ -247,6 +247,7 @@ "no_data_image": "aucune donnée pour l'image", "no_description": "aucune description", "no_messages": "pas de messages", + "no_message": "aucun message", "no_minting_details": "impossible d'afficher les détails de la passerelle sur la passerelle", "no_notifications": "pas de nouvelles notifications", "no_payments": "aucun paiement", diff --git a/src/i18n/locales/it/core.json b/src/i18n/locales/it/core.json index 9bff9a5..49794a4 100644 --- a/src/i18n/locales/it/core.json +++ b/src/i18n/locales/it/core.json @@ -249,6 +249,7 @@ "no_data_image": "nessun dato per l'immagine", "no_description": "nessuna descrizione", "no_messages": "nessun messaggio", + "no_message": "nessun messaggio", "no_minting_details": "impossibile visualizzare i dettagli di minire sul gateway", "no_notifications": "nessuna nuova notifica", "no_payments": "nessun pagamento", diff --git a/src/i18n/locales/ja/core.json b/src/i18n/locales/ja/core.json index b2b2ae0..d39a89b 100644 --- a/src/i18n/locales/ja/core.json +++ b/src/i18n/locales/ja/core.json @@ -246,6 +246,7 @@ "no_data_image": "画像のデータはありません", "no_description": "説明なし", "no_messages": "メッセージはありません", + "no_message": "メッセージなし", "no_minting_details": "ゲートウェイでミントの詳細を表示できません", "no_notifications": "新しい通知はありません", "no_payments": "支払いなし", diff --git a/src/i18n/locales/ru/core.json b/src/i18n/locales/ru/core.json index e90fb33..aed5ce5 100644 --- a/src/i18n/locales/ru/core.json +++ b/src/i18n/locales/ru/core.json @@ -247,6 +247,7 @@ "no_data_image": "Нет данных для изображения", "no_description": "Нет описания", "no_messages": "Нет сообщений", + "no_message": "нет сообщения", "no_minting_details": "Не могу просматривать детали маттинга на шлюзе", "no_notifications": "Нет новых уведомлений", "no_payments": "Нет платежей", diff --git a/src/i18n/locales/zh/core.json b/src/i18n/locales/zh/core.json index 5208c36..74f2e74 100644 --- a/src/i18n/locales/zh/core.json +++ b/src/i18n/locales/zh/core.json @@ -246,6 +246,7 @@ "no_data_image": "没有图像数据", "no_description": "没有描述", "no_messages": "没有消息", + "no_message": "没有消息", "no_minting_details": "无法在网关上查看薄荷细节", "no_notifications": "没有新的通知", "no_payments": "无付款", From 2915766a26973adf1bfc151ba84c227313cc46ea Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 16:11:33 +0300 Subject: [PATCH 16/20] fix --- src/components/Chat/MessageItem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index aeb2628..f1cfb40 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -717,9 +717,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => { )} - {message?.replyMessageText && ( - - )} + {replyMessageText && } {message?.decryptedData?.type === 'notification' ? ( From f6c6480fe8f6d001f4bcac67a4644d1c4a546c02 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 18:10:58 +0300 Subject: [PATCH 17/20] change scroll down style --- src/components/Chat/ChatList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index e7897c2..f5cb3f6 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -429,14 +429,14 @@ export const ChatList = ({