Merge branch 'develop' into feature/i18n-minor-refactoring

This commit is contained in:
nico.benaz 2025-06-06 19:15:17 +02:00 committed by GitHub
commit 29ae25a27b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 698 additions and 657 deletions

View File

@ -440,7 +440,7 @@ const handleNotificationDirect = async (directs) => {
let isFocused; let isFocused;
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
let isDisableNotifications = const isDisableNotifications =
(await getUserSettings({ key: 'disable-push-notifications' })) || false; (await getUserSettings({ key: 'disable-push-notifications' })) || false;
const dataDirects = directs.filter((direct) => direct?.sender !== address); const dataDirects = directs.filter((direct) => direct?.sender !== address);
try { try {
@ -1281,7 +1281,6 @@ export async function addUserSettings({ keyValue }) {
getData<any>(`${address}-userSettings`) getData<any>(`${address}-userSettings`)
.then((storedData) => { .then((storedData) => {
storedData = storedData || {}; // Initialize if no data found storedData = storedData || {}; // Initialize if no data found
storedData[key] = value; // Update the key-value pair within stored data storedData[key] = value; // Update the key-value pair within stored data
// Save updated structure back to localStorage // Save updated structure back to localStorage
@ -1734,7 +1733,7 @@ export async function decryptSingleFunc({
secretKeyObject, secretKeyObject,
skipDecodeBase64, skipDecodeBase64,
}) { }) {
let holdMessages = []; const holdMessages = [];
for (const message of messages) { for (const message of messages) {
try { try {
@ -1744,9 +1743,11 @@ export async function decryptSingleFunc({
skipDecodeBase64, skipDecodeBase64,
}); });
const decryptToUnit8Array = base64ToUint8Array(res); if (res) {
const responseData = uint8ArrayToObject(decryptToUnit8Array); const decryptToUnit8Array = base64ToUint8Array(res);
holdMessages.push({ ...message, decryptedData: responseData }); const responseData = uint8ArrayToObject(decryptToUnit8Array);
holdMessages.push({ ...message, decryptedData: responseData });
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -1758,7 +1759,7 @@ export async function decryptSingleForPublishes({
secretKeyObject, secretKeyObject,
skipDecodeBase64, skipDecodeBase64,
}) { }) {
let holdMessages = []; const holdMessages = [];
for (const message of messages) { for (const message of messages) {
try { try {
@ -2888,6 +2889,7 @@ export async function getTimestampEnterChat() {
return {}; return {};
} }
} }
export async function getTimestampMention() { export async function getTimestampMention() {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
@ -2900,6 +2902,7 @@ export async function getTimestampMention() {
return {}; return {};
} }
} }
export async function getTimestampGroupAnnouncement() { export async function getTimestampGroupAnnouncement() {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
@ -2996,6 +2999,7 @@ async function getGroupData() {
return {}; return {};
} }
} }
export async function getGroupDataSingle(groupId) { export async function getGroupDataSingle(groupId) {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
@ -3266,6 +3270,7 @@ function setupMessageListener() {
break; break;
case 'updateThreadActivity': case 'updateThreadActivity':
updateThreadActivityCase(request, event); updateThreadActivityCase(request, event);
break;
case 'decryptGroupEncryption': case 'decryptGroupEncryption':
decryptGroupEncryptionCase(request, event); decryptGroupEncryptionCase(request, event);
break; break;
@ -3387,7 +3392,7 @@ const checkGroupList = async () => {
.filter( .filter(
(item) => (item) =>
item?.name !== 'extension-proxy' && 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)); .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
@ -3411,7 +3416,7 @@ export const checkNewMessages = async () => {
myName = userData.name; myName = userData.name;
} }
let newAnnouncements = []; const newAnnouncements = [];
const activeData = (await getStoredData('active-groups-directs')) || { const activeData = (await getStoredData('active-groups-directs')) || {
groups: [], groups: [],
directs: [], directs: [],
@ -3441,6 +3446,7 @@ export const checkNewMessages = async () => {
const latestMessage = responseData.filter( const latestMessage = responseData.filter(
(pub) => pub?.name !== myName (pub) => pub?.name !== myName
)[0]; )[0];
if (!latestMessage) { if (!latestMessage) {
return; // continue to the next group return; // continue to the next group
} }
@ -3463,7 +3469,8 @@ export const checkNewMessages = async () => {
} }
}) })
); );
let isDisableNotifications =
const isDisableNotifications =
(await getUserSettings({ key: 'disable-push-notifications' })) || false; (await getUserSettings({ key: 'disable-push-notifications' })) || false;
if ( if (
@ -3611,8 +3618,8 @@ export const checkThreads = async (bringBack) => {
if (userData?.name) { if (userData?.name) {
myName = userData.name; myName = userData.name;
} }
let newAnnouncements = []; const newAnnouncements = [];
let dataToBringBack = []; const dataToBringBack = [];
const threadActivity = await getThreadActivity(); const threadActivity = await getThreadActivity();
if (!threadActivity) return null; if (!threadActivity) return null;
@ -3627,7 +3634,6 @@ export const checkThreads = async (bringBack) => {
for (const thread of selectedThreads) { for (const thread of selectedThreads) {
try { try {
const identifier = `thmsg-${thread?.threadId}`; const identifier = `thmsg-${thread?.threadId}`;
const name = thread?.qortalName;
const endpoint = await getArbitraryEndpoint(); const endpoint = await getArbitraryEndpoint();
const url = await createEndpoint( const url = await createEndpoint(
`${endpoint}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true` `${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( const latestMessage = responseData.filter(
(pub) => pub?.name !== myName (pub) => pub?.name !== myName
)[0]; )[0];
// const latestMessage = responseData[0]
if (!latestMessage) { if (!latestMessage) {
continue; continue;
@ -3717,7 +3722,7 @@ export const checkThreads = async (bringBack) => {
'_type=thread-post' + '_type=thread-post' +
`_data=${JSON.stringify(newAnnouncements[0])}` `_data=${JSON.stringify(newAnnouncements[0])}`
); );
let isDisableNotifications = const isDisableNotifications =
(await getUserSettings({ key: 'disable-push-notifications' })) || false; (await getUserSettings({ key: 'disable-push-notifications' })) || false;
if (!isDisableNotifications) { if (!isDisableNotifications) {
// Check user settings to see if notifications are disabled // Check user settings to see if notifications are disabled

View File

@ -7,6 +7,15 @@ import { ChatOptions } from './ChatOptions';
import ErrorBoundary from '../../common/ErrorBoundary'; import ErrorBoundary from '../../common/ErrorBoundary';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type ReactionItem = {
sender: string;
senderName?: string;
};
export type ReactionsMap = {
[reactionType: string]: ReactionItem[];
};
export const ChatList = ({ export const ChatList = ({
initialMessages, initialMessages,
myAddress, myAddress,
@ -236,7 +245,7 @@ export const ChatList = ({
let message = messages[index] || null; // Safeguard against undefined let message = messages[index] || null; // Safeguard against undefined
let replyIndex = -1; let replyIndex = -1;
let reply = null; let reply = null;
let reactions = null; let reactions: ReactionsMap | null = null;
let isUpdating = false; let isUpdating = false;
try { try {
@ -444,13 +453,13 @@ export const ChatList = ({
{enableMentions && (hasSecretKey || isPrivate === false) && ( {enableMentions && (hasSecretKey || isPrivate === false) && (
<ChatOptions <ChatOptions
openQManager={openQManager}
messages={messages}
goToMessage={goToMessage} goToMessage={goToMessage}
members={members}
myName={myName}
selectedGroup={selectedGroup}
isPrivate={isPrivate} isPrivate={isPrivate}
members={members}
messages={messages}
myName={myName}
openQManager={openQManager}
selectedGroup={selectedGroup}
/> />
)} )}
</Box> </Box>

View File

@ -25,7 +25,6 @@ import {
AppsSearchRight, AppsSearchRight,
} from '../Apps/Apps-styles'; } from '../Apps/Apps-styles';
import IconClearInput from '../../assets/svgs/ClearInput.svg'; import IconClearInput from '../../assets/svgs/ClearInput.svg';
import { CellMeasurerCache } from 'react-virtualized';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { MessageDisplay } from './MessageDisplay'; import { MessageDisplay } from './MessageDisplay';
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
@ -36,6 +35,7 @@ import { generateHTML } from '@tiptap/react';
import ErrorBoundary from '../../common/ErrorBoundary'; import ErrorBoundary from '../../common/ErrorBoundary';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isHtmlString } from '../../utils/chat'; import { isHtmlString } from '../../utils/chat';
import TextStyle from '@tiptap/extension-text-style';
const extractTextFromHTML = (htmlString = '') => { const extractTextFromHTML = (htmlString = '') => {
return convert(htmlString, { return convert(htmlString, {
@ -43,11 +43,6 @@ const extractTextFromHTML = (htmlString = '') => {
})?.toLowerCase(); })?.toLowerCase();
}; };
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50,
});
export const ChatOptions = ({ export const ChatOptions = ({
messages: untransformedMessages, messages: untransformedMessages,
goToMessage, goToMessage,
@ -86,6 +81,7 @@ export const ChatOptions = ({
Underline, Underline,
Highlight, Highlight,
Mention, Mention,
TextStyle,
]); ]);
return { return {
...item, ...item,

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'; 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 StarterKit from '@tiptap/starter-kit';
import { Color } from '@tiptap/extension-color'; import { Color } from '@tiptap/extension-color';
import ListItem from '@tiptap/extension-list-item'; import ListItem from '@tiptap/extension-list-item';
@ -34,16 +34,6 @@ import { fileToBase64 } from '../../utils/fileReading/index.js';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import i18n from '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( const MenuBar = memo(
({ ({
setEditorRef, setEditorRef,
@ -361,8 +351,8 @@ const MenuBar = memo(
); );
const extensions = [ const extensions = [
TextStyle,
Color.configure({ types: [TextStyle.name, ListItem.name] }), Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({ StarterKit.configure({
bulletList: { bulletList: {
keepMarks: true, keepMarks: true,
@ -383,11 +373,26 @@ const extensions = [
const content = ``; const content = ``;
export default ({ type TiptapProps = {
setEditorRef: (editorInstance: Editor | null) => void;
onEnter: () => void | Promise<void>;
disableEnter?: boolean;
isChat?: boolean;
maxHeightOffset?: number;
overrideMobile?: boolean;
customEditorHeight?: number | null;
setIsFocusedParent: React.Dispatch<React.SetStateAction<boolean>>;
isFocusedParent: boolean;
membersWithNames: unknown[];
enableMentions?: boolean;
insertImage: (image: any) => void;
};
const Tiptap = ({
setEditorRef, setEditorRef,
onEnter, onEnter,
disableEnter, disableEnter = false,
isChat, isChat = false,
maxHeightOffset, maxHeightOffset,
setIsFocusedParent, setIsFocusedParent,
isFocusedParent, isFocusedParent,
@ -396,7 +401,7 @@ export default ({
membersWithNames, membersWithNames,
enableMentions, enableMentions,
insertImage, insertImage,
}) => { }: TiptapProps) => {
const theme = useTheme(); const theme = useTheme();
const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useAtom( const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useAtom(
isDisabledEditorEnterAtom isDisabledEditorEnterAtom
@ -623,3 +628,5 @@ export default ({
</Box> </Box>
); );
}; };
export default Tiptap;

View File

@ -59,7 +59,7 @@ export const Embed = ({ embedLink }) => {
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [external, setExternal] = useState(null); const [external, setExternal] = useState(null);
const [imageUrl, setImageUrl] = useState(''); const [imageUrl, setImageUrl] = useState(null);
const [parsedData, setParsedData] = useState(null); const [parsedData, setParsedData] = useState(null);
const setBlobs = useSetAtom(blobControllerAtom); const setBlobs = useSetAtom(blobControllerAtom);
const [selectedGroupId] = useAtom(selectedGroupIdAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom);

View File

@ -208,9 +208,8 @@ export const ImageCard = ({
); );
}; };
export function ImageViewer({ src, alt = '' }) { export function ImageViewer({ src = null, alt = '' }) {
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const handleOpenFullscreen = () => setIsFullscreen(true); const handleOpenFullscreen = () => setIsFullscreen(true);
const handleCloseFullscreen = () => setIsFullscreen(false); const handleCloseFullscreen = () => setIsFullscreen(false);
const theme = useTheme(); const theme = useTheme();

View File

@ -276,15 +276,13 @@ export async function getNameInfo(address: string) {
} }
export const getGroupAdmins = async (groupNumber: number) => { export const getGroupAdmins = async (groupNumber: number) => {
// const validApi = await findUsableApi();
const response = await fetch( const response = await fetch(
`${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true` `${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true`
); );
const groupData = await response.json(); const groupData = await response.json();
let members: any = []; const members: any = [];
let membersAddresses = []; const membersAddresses = [];
let both = []; const both = [];
const getMemNames = groupData?.members?.map(async (member) => { const getMemNames = groupData?.members?.map(async (member) => {
if (member?.member) { if (member?.member) {
@ -600,10 +598,11 @@ export const Group = ({
}, [myAddress]); }, [myAddress]);
const getGroupOwner = async (groupId) => { const getGroupOwner = async (groupId) => {
if (groupId == '0') return; // general group has id=0
try { try {
const url = `${getBaseApiReact()}/groups/${groupId}`; const url = `${getBaseApiReact()}/groups/${groupId}`;
const response = await fetch(url); const response = await fetch(url);
let data = await response.json(); const data = await response.json();
const name = await getNameInfo(data?.owner); const name = await getNameInfo(data?.owner);
if (name) { if (name) {
@ -742,7 +741,7 @@ export const Group = ({
data = await res.text(); data = await res.text();
} }
const decryptedKey: any = await decryptResource(data); const decryptedKey: any = await decryptResource(data, null);
const dataint8Array = base64ToUint8Array(decryptedKey.data); const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
@ -877,6 +876,7 @@ export const Group = ({
}; };
const getOwnerNameForGroup = async (owner: string, groupId: string) => { const getOwnerNameForGroup = async (owner: string, groupId: string) => {
if (groupId == '0') return; // general group has id=0
try { try {
if (!owner) return; if (!owner) return;
if (groupsOwnerNamesRef.current[groupId]) return; if (groupsOwnerNamesRef.current[groupId]) return;
@ -899,7 +899,7 @@ export const Group = ({
const url = `${getBaseApiReact()}/groups/member/${address}`; const url = `${getBaseApiReact()}/groups/member/${address}`;
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) throw new Error('Cannot get group properties'); 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) => { const transformToObject = data.reduce((result, item) => {
result[item.groupId] = item; result[item.groupId] = item;
return result; return result;

View File

@ -124,7 +124,7 @@ export const ListOfJoinRequests = ({
setIsLoadingAccept(false); setIsLoadingAccept(false);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success,group_join', { message: t('group:message.success.group_join', {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
}), }),
}); });

View File

@ -244,6 +244,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
)} )}
</Box> </Box>
)} )}
{wallets?.length > 0 && ( {wallets?.length > 0 && (
<List <List
sx={{ sx={{
@ -442,20 +443,22 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
})} })}
</Button> </Button>
<LoadingButton <LoadingButton
loading={isLoadingEncryptSeed} autoFocus
disabled={!seedValue || !seedName || !password} disabled={!seedValue || !seedName || !password}
variant="contained" loading={isLoadingEncryptSeed}
onClick={() => { onClick={() => {
if (!seedValue || !seedName || !password) return; if (!seedValue || !seedName || !password) return;
onOk({ seedValue, seedName, password }); onOk({ seedValue, seedName, password });
}} }}
autoFocus variant="contained"
> >
{t('core:action.add', { {t('core:action.add', {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
})} })}
</LoadingButton> </LoadingButton>
<Typography <Typography
sx={{ sx={{
fontSize: '14px', fontSize: '14px',

View File

@ -77,7 +77,7 @@ export const encryptDataGroup = ({
userPublicKey, userPublicKey,
customSymmetricKey, customSymmetricKey,
}: any) => { }: any) => {
let combinedPublicKeys = [...publicKeys, userPublicKey]; const combinedPublicKeys = [...publicKeys, userPublicKey];
const decodedPrivateKey = Base58.decode(privateKey); const decodedPrivateKey = Base58.decode(privateKey);
const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)]; const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)];
const Uint8ArrayData = base64ToUint8Array(data64); const Uint8ArrayData = base64ToUint8Array(data64);
@ -114,7 +114,7 @@ export const encryptDataGroup = ({
const keyNonce = new Uint8Array(24); const keyNonce = new Uint8Array(24);
crypto.getRandomValues(keyNonce); crypto.getRandomValues(keyNonce);
// Encrypt the symmetric key for each recipient. // Encrypt the symmetric key for each recipient.
let encryptedKeys = []; const encryptedKeys = [];
publicKeysDuplicateFree.forEach((recipientPublicKey) => { publicKeysDuplicateFree.forEach((recipientPublicKey) => {
const publicKeyUnit8Array = Base58.decode(recipientPublicKey); const publicKeyUnit8Array = Base58.decode(recipientPublicKey);
const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey); const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey);
@ -153,7 +153,7 @@ export const encryptDataGroup = ({
encryptedKeysSize += key.length; encryptedKeysSize += key.length;
}); });
combinedDataSize += encryptedKeysSize; combinedDataSize += encryptedKeysSize;
let combinedData = new Uint8Array(combinedDataSize); const combinedData = new Uint8Array(combinedDataSize);
combinedData.set(strUint8Array); combinedData.set(strUint8Array);
combinedData.set(nonce, strUint8Array.length); combinedData.set(nonce, strUint8Array.length);
combinedData.set(keyNonce, strUint8Array.length + nonce.length); combinedData.set(keyNonce, strUint8Array.length + nonce.length);
@ -244,9 +244,6 @@ export const encryptSingle = async ({
encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey); encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey);
encryptedDataBase64 = uint8ArrayToBase64(encryptedData); encryptedDataBase64 = uint8ArrayToBase64(encryptedData);
// Convert the nonce to base64
const nonceBase64 = uint8ArrayToBase64(nonce);
// Concatenate the highest key, type number, nonce, and encrypted data (new format) // Concatenate the highest key, type number, nonce, and encrypted data (new format)
const highestKeyStr = highestKey.toString().padStart(10, '0'); // Fixed length of 10 digits const highestKeyStr = highestKey.toString().padStart(10, '0'); // Fixed length of 10 digits
@ -281,7 +278,7 @@ export const encryptSingle = async ({
}; };
export const decodeBase64ForUIChatMessages = (messages) => { export const decodeBase64ForUIChatMessages = (messages) => {
let msgs = []; const msgs = [];
for (const msg of messages) { for (const msg of messages) {
try { try {
const decoded = atob(msg?.data); const decoded = atob(msg?.data);
@ -306,107 +303,114 @@ export const decryptSingle = async ({
// First, decode the base64-encoded input (if skipDecodeBase64 is not set) // First, decode the base64-encoded input (if skipDecodeBase64 is not set)
const decodedData = skipDecodeBase64 ? data64 : atob(data64); const decodedData = skipDecodeBase64 ? data64 : atob(data64);
// Then, decode it again for the specific format (if double encoding is used) if (secretKeyObject) {
const decodeForNumber = atob(decodedData); // 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) // Extract the key (assuming it's always the first 10 characters)
const keyStr = decodeForNumber.slice(0, 10); const keyStr = decodeForNumber.slice(0, 10);
// Convert the key string back to a number // Convert the key string back to a number
const highestKey = parseInt(keyStr, 10); const highestKey = parseInt(keyStr, 10);
// Check if we have a valid secret key for the extracted highestKey // Check if we have a valid secret key for the extracted highestKey
if (!secretKeyObject[highestKey]) { if (!secretKeyObject[highestKey]) {
throw new Error( throw new Error(
i18n.t('auth:message.error.find_secret_key', { i18n.t('auth:message.error.find_secret_key', {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
}) })
); );
}
const secretKeyEntry = secretKeyObject[highestKey];
let typeNumberStr, 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
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 {
// 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 secretKeyEntry = secretKeyObject[highestKey];
const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64);
const nonce = base64ToUint8Array(nonceBase64);
const messageKey = base64ToUint8Array(secretKeyEntry.messageKey);
if (!(Uint8ArrayData instanceof Uint8Array)) { let nonceBase64, encryptedDataBase64;
throw new Error(
i18n.t('auth:message.error.invalid_uint8', { // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits
postProcess: 'capitalizeFirstChar', 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 return;
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);
}; };
export const decryptGroupEncryptionWithSharingKey = async ({ export const decryptGroupEncryptionWithSharingKey = async ({
@ -424,14 +428,9 @@ export const decryptGroupEncryptionWithSharingKey = async ({
// Extract the shared keyNonce // Extract the shared keyNonce
const keyNonceStartPosition = nonceEndPosition; const keyNonceStartPosition = nonceEndPosition;
const keyNonceEndPosition = keyNonceStartPosition + 24; // Nonce is 24 bytes const keyNonceEndPosition = keyNonceStartPosition + 24; // Nonce is 24 bytes
const keyNonce = allCombined.slice(
keyNonceStartPosition,
keyNonceEndPosition
);
// Extract the sender's public key // Extract the sender's public key
const senderPublicKeyStartPosition = keyNonceEndPosition; const senderPublicKeyStartPosition = keyNonceEndPosition;
const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32; // Public keys are 32 bytes const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32; // Public keys are 32 bytes
// Calculate count first // Calculate count first
const countStartPosition = allCombined.length - 4; // 4 bytes before the end, since count is stored in Uint32 (4 bytes) const countStartPosition = allCombined.length - 4; // 4 bytes before the end, since count is stored in Uint32 (4 bytes)
const countArray = allCombined.slice( const countArray = allCombined.slice(
@ -662,7 +661,6 @@ export function decryptDeprecatedSingle(uint8Array, publicKey, privateKey) {
const str = 'qortalEncryptedData'; const str = 'qortalEncryptedData';
const strEncoder = new TextEncoder(); const strEncoder = new TextEncoder();
const strUint8Array = strEncoder.encode(str); const strUint8Array = strEncoder.encode(str);
const strData = combinedData.slice(0, strUint8Array.length);
const nonce = combinedData.slice( const nonce = combinedData.slice(
strUint8Array.length, strUint8Array.length,
strUint8Array.length + 24 strUint8Array.length + 24

View File

@ -552,7 +552,7 @@ export const getUserAccount = async ({
export const encryptData = async (data, sender) => { export const encryptData = async (data, sender) => {
let data64 = data.data64 || data.base64; let data64 = data.data64 || data.base64;
let publicKeys = data.publicKeys || []; const publicKeys = data.publicKeys || [];
if (data?.file || data?.blob) { if (data?.file || data?.blob) {
data64 = await fileToBase64(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) => { export const encryptQortalGroupData = async (data, sender) => {
let data64 = data?.data64 || data?.base64; let data64 = data?.data64 || data?.base64;
let groupId = data?.groupId; const groupId = data?.groupId;
let isAdmins = data?.isAdmins; const isAdmins = data?.isAdmins;
if (!groupId) { if (!groupId) {
throw new Error( throw new Error(
i18n.t('question:message.generic.provide_group_id', { i18n.t('question:message.generic.provide_group_id', {
@ -613,7 +613,7 @@ export const encryptQortalGroupData = async (data, sender) => {
groupSecretkeys[groupId] && groupSecretkeys[groupId] &&
groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId].secretKeyObject &&
groupSecretkeys[groupId]?.timestamp && groupSecretkeys[groupId]?.timestamp &&
Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number
) { ) {
secretKeyObject = groupSecretkeys[groupId].secretKeyObject; secretKeyObject = groupSecretkeys[groupId].secretKeyObject;
} }
@ -659,7 +659,7 @@ export const encryptQortalGroupData = async (data, sender) => {
groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`] &&
groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`].secretKeyObject &&
groupSecretkeys[`admins-${groupId}`]?.timestamp && 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; secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject;
} }
@ -717,9 +717,9 @@ export const encryptQortalGroupData = async (data, sender) => {
}; };
export const decryptQortalGroupData = async (data, sender) => { export const decryptQortalGroupData = async (data, sender) => {
let data64 = data?.data64 || data?.base64; const data64 = data?.data64 || data?.base64;
let groupId = data?.groupId; const groupId = data?.groupId;
let isAdmins = data?.isAdmins; const isAdmins = data?.isAdmins;
if (!groupId) { if (!groupId) {
throw new Error( throw new Error(
i18n.t('question:message.generic.provide_group_id', { i18n.t('question:message.generic.provide_group_id', {
@ -742,7 +742,7 @@ export const decryptQortalGroupData = async (data, sender) => {
groupSecretkeys[groupId] && groupSecretkeys[groupId] &&
groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId].secretKeyObject &&
groupSecretkeys[groupId]?.timestamp && groupSecretkeys[groupId]?.timestamp &&
Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number
) { ) {
secretKeyObject = groupSecretkeys[groupId].secretKeyObject; secretKeyObject = groupSecretkeys[groupId].secretKeyObject;
} }
@ -785,7 +785,7 @@ export const decryptQortalGroupData = async (data, sender) => {
groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`] &&
groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`].secretKeyObject &&
groupSecretkeys[`admins-${groupId}`]?.timestamp && 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; secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject;
} }
@ -843,7 +843,7 @@ export const decryptQortalGroupData = async (data, sender) => {
export const encryptDataWithSharingKey = async (data, sender) => { export const encryptDataWithSharingKey = async (data, sender) => {
let data64 = data?.data64 || data?.base64; let data64 = data?.data64 || data?.base64;
let publicKeys = data.publicKeys || []; const publicKeys = data.publicKeys || [];
if (data?.file || data?.blob) { if (data?.file || data?.blob) {
data64 = await fileToBase64(data?.file || data?.blob); data64 = await fileToBase64(data?.file || data?.blob);
} }
@ -899,6 +899,7 @@ export const decryptDataWithSharingKey = async (data, sender) => {
data64EncryptedData: encryptedData, data64EncryptedData: encryptedData,
key, key,
}); });
const base64ToObject = JSON.parse(atob(decryptedData)); const base64ToObject = JSON.parse(atob(decryptedData));
if (!base64ToObject.data) if (!base64ToObject.data)

View File

@ -152,7 +152,7 @@ function setupMessageListenerQortalRequest() {
appInfo, appInfo,
skipAuth, skipAuth,
}); });
event.source.postMessage( event.source!.postMessage(
{ {
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -162,7 +162,7 @@ function setupMessageListenerQortalRequest() {
event.origin event.origin
); );
} catch (error) { } catch (error) {
event.source.postMessage( event.source!.postMessage(
{ {
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -236,7 +236,7 @@ function setupMessageListenerQortalRequest() {
request.payload, request.payload,
event.source event.source
); );
event.source.postMessage( event.source!.postMessage(
{ {
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -246,7 +246,7 @@ function setupMessageListenerQortalRequest() {
event.origin event.origin
); );
} catch (error) { } catch (error) {
event.source.postMessage( event.source!.postMessage(
{ {
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,