diff --git a/src/assets/Icons/AdminsIcon.tsx b/src/assets/Icons/AdminsIcon.tsx
new file mode 100644
index 0000000..d2c89c6
--- /dev/null
+++ b/src/assets/Icons/AdminsIcon.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+export const AdminsIcon= ({ color = 'white', height = 48, width = 50 }) => {
+ return (
+
+
+
+
+
+
+ );
+ };
+
\ No newline at end of file
diff --git a/src/background-cases.ts b/src/background-cases.ts
index 7f336fd..5f48c92 100644
--- a/src/background-cases.ts
+++ b/src/background-cases.ts
@@ -54,7 +54,7 @@ import {
updateThreadActivity,
walletVersion,
} from "./background";
-import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, publishGroupEncryptedResource, publishOnQDN } from "./backgroundFunctions/encryption";
+import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, encryptAndPublishSymmetricKeyGroupChatForAdmins, publishGroupEncryptedResource, publishOnQDN } from "./backgroundFunctions/encryption";
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes";
import { encryptSingle } from "./qdn/encryption/group-encryption";
import { _createPoll, _voteOnPoll } from "./qortalRequests/get";
@@ -1250,6 +1250,41 @@ export async function encryptAndPublishSymmetricKeyGroupChatCase(
}
}
+export async function encryptAndPublishSymmetricKeyGroupChatForAdminsCase(
+ request,
+ event
+) {
+ try {
+ const { groupId, previousData, admins } = request.payload;
+ const { data, numberOfMembers } =
+ await encryptAndPublishSymmetricKeyGroupChatForAdmins({
+ groupId,
+ previousData,
+ admins
+ });
+
+ event.source.postMessage(
+ {
+ requestId: request.requestId,
+ action: "encryptAndPublishSymmetricKeyGroupChatForAdmins",
+ payload: data,
+ type: "backgroundMessageResponse",
+ },
+ event.origin
+ );
+ } catch (error) {
+ event.source.postMessage(
+ {
+ requestId: request.requestId,
+ action: "encryptAndPublishSymmetricKeyGroupChat",
+ error: error?.message,
+ type: "backgroundMessageResponse",
+ },
+ event.origin
+ );
+ }
+}
+
export async function publishGroupEncryptedResourceCase(request, event) {
try {
const {encryptedData, identifier} = request.payload;
diff --git a/src/background.ts b/src/background.ts
index 9b266eb..6efc048 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -98,6 +98,7 @@ import {
versionCase,
createPollCase,
voteOnPollCase,
+ encryptAndPublishSymmetricKeyGroupChatForAdminsCase,
} from "./background-cases";
import { getData, removeKeysAndLogout, storeData } from "./utils/chromeStorage";
import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
@@ -2822,6 +2823,9 @@ function setupMessageListener() {
case "encryptAndPublishSymmetricKeyGroupChat":
encryptAndPublishSymmetricKeyGroupChatCase(request, event);
break;
+ case "encryptAndPublishSymmetricKeyGroupChatForAdmins":
+ encryptAndPublishSymmetricKeyGroupChatForAdminsCase(request, event);
+ break;
case "publishGroupEncryptedResource":
publishGroupEncryptedResourceCase(request, event);
break;
diff --git a/src/backgroundFunctions/encryption.ts b/src/backgroundFunctions/encryption.ts
index 3511597..8260d9d 100644
--- a/src/backgroundFunctions/encryption.ts
+++ b/src/backgroundFunctions/encryption.ts
@@ -85,6 +85,77 @@ const getPublicKeys = async (groupNumber: number) => {
return members
}
+ export const getPublicKeysByAddress = async (admins) => {
+ const validApi = await getBaseApi()
+
+
+ let members: any = [];
+ if (Array.isArray(admins)) {
+ for (const address of admins) {
+ if (address) {
+ const resAddress = await fetch(`${validApi}/addresses/${address}`);
+ const resData = await resAddress.json();
+ const publicKey = resData.publicKey;
+ members.push(publicKey)
+ }
+ }
+ }
+
+ return members
+ }
+
+
+ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({groupId, previousData, admins}: {
+ groupId: number,
+ previousData: Object,
+ }) => {
+ try {
+
+ let highestKey = 0
+ if(previousData){
+ highestKey = Math.max(...Object.keys((previousData || {})).filter(item=> !isNaN(+item)).map(Number));
+
+ }
+
+ const resKeyPair = await getKeyPair()
+ const parsedData = resKeyPair
+ const privateKey = parsedData.privateKey
+ const userPublicKey = parsedData.publicKey
+ const groupmemberPublicKeys = await getPublicKeysByAddress(admins.map((admin)=> admin.address))
+
+
+ const symmetricKey = createSymmetricKeyAndNonce()
+ const nextNumber = highestKey + 1
+ const objectToSave = {
+ ...previousData,
+ [nextNumber]: symmetricKey
+ }
+
+ const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave)
+
+ const encryptedData = encryptDataGroup({
+ data64: symmetricKeyAndNonceBase64,
+ publicKeys: groupmemberPublicKeys,
+ privateKey,
+ userPublicKey
+ })
+ if(encryptedData){
+ const registeredName = await getNameInfo()
+ const data = await publishData({
+ registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
+ })
+ return {
+ data,
+ numberOfMembers: groupmemberPublicKeys.length
+ }
+
+ } else {
+ throw new Error('Cannot encrypt content')
+ }
+ } catch (error: any) {
+ throw new Error(error.message);
+ }
+ }
export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousData}: {
diff --git a/src/components/Chat/AdminSpace.tsx b/src/components/Chat/AdminSpace.tsx
new file mode 100644
index 0000000..f340e45
--- /dev/null
+++ b/src/components/Chat/AdminSpace.tsx
@@ -0,0 +1,66 @@
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { GroupMail } from "../Group/Forum/GroupMail";
+import { MyContext, isMobile } from "../../App";
+import { getRootHeight } from "../../utils/mobile/mobileUtils";
+import { Box, Typography } from "@mui/material";
+import { AdminSpaceInner } from "./AdminSpaceInner";
+
+
+
+
+
+
+export const AdminSpace = ({
+ selectedGroup,
+ adminsWithNames,
+ userInfo,
+ secretKey,
+ getSecretKey,
+ isAdmin,
+ myAddress,
+ hide,
+ defaultThread,
+ setDefaultThread
+}) => {
+ const { rootHeight } = useContext(MyContext);
+ const [isMoved, setIsMoved] = useState(false);
+ useEffect(() => {
+ if (hide) {
+ setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving
+ } else {
+ setIsMoved(false); // Reset the position immediately when showing
+ }
+ }, [hide]);
+
+ return (
+
+ {!isAdmin &&
Sorry, this space is only for Admins.}
+ {isAdmin &&
}
+
+
+ );
+};
diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx
new file mode 100644
index 0000000..1307ad1
--- /dev/null
+++ b/src/components/Chat/AdminSpaceInner.tsx
@@ -0,0 +1,150 @@
+import React, { useCallback, useContext, useEffect, useState } from 'react'
+import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../../App';
+import { Box, Button, Typography } from '@mui/material';
+import { decryptResource, validateSecretKey } from '../Group/Group';
+import { getFee } from '../../background';
+import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
+import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
+import { formatTimestampForum } from '../../utils/time';
+import { Spacer } from '../../common/Spacer';
+
+
+export const getPublishesFromAdminsAdminSpace = async (admins: string[], groupId) => {
+ const queryString = admins.map((name) => `name=${name}`).join("&");
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=admins-symmetric-qchat-group-${
+ groupId
+ }&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error("network error");
+ }
+ const adminData = await response.json();
+
+ const filterId = adminData.filter(
+ (data: any) =>
+ data.identifier === `admins-symmetric-qchat-group-${groupId}`
+ );
+ if (filterId?.length === 0) {
+ return false;
+ }
+ const sortedData = filterId.sort((a: any, b: any) => {
+ // Get the most recent date for both a and b
+ const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
+ const dateB = b.updated ? new Date(b.updated) : new Date(b.created);
+
+ // Sort by most recent
+ return dateB.getTime() - dateA.getTime();
+ });
+
+ return sortedData[0];
+ };
+
+export const AdminSpaceInner = ({selectedGroup, adminsWithNames}) => {
+ const [adminGroupSecretKey, setAdminGroupSecretKey] = useState(null)
+ const [isFetchingAdminGroupSecretKey, setIsFetchingAdminGroupSecretKey] = useState(true)
+ const [adminGroupSecretKeyPublishDetails, setAdminGroupSecretKeyPublishDetails] = useState(null)
+
+ const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false)
+ const { show, setTxList, setInfoSnackCustom,
+ setOpenSnackGlobal } = useContext(MyContext);
+
+
+ const getAdminGroupSecretKey = useCallback(async ()=> {
+ try {
+ if(!selectedGroup) return
+ const getLatestPublish = await getPublishesFromAdminsAdminSpace(adminsWithNames.map((admin)=> admin?.name), selectedGroup)
+ if(getLatestPublish === false) return
+ let data;
+
+ const res = await fetch(
+ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${getLatestPublish.name}/${
+ getLatestPublish.identifier
+ }?encoding=base64`
+ );
+ data = await res.text();
+
+ const decryptedKey: any = await decryptResource(data);
+ const dataint8Array = base64ToUint8Array(decryptedKey.data);
+ const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
+ if (!validateSecretKey(decryptedKeyToObject))
+ throw new Error("SecretKey is not valid");
+ setAdminGroupSecretKey(decryptedKeyToObject)
+ setAdminGroupSecretKeyPublishDetails(getLatestPublish)
+ } catch (error) {
+
+ } finally {
+ setIsFetchingAdminGroupSecretKey(false)
+ }
+ }, [adminsWithNames, selectedGroup])
+
+ const createCommonSecretForAdmins = async ()=> {
+ try {
+ const fee = await getFee('ARBITRARY')
+ await show({
+ message: "Would you like to perform an ARBITRARY transaction?" ,
+ publishFee: fee.fee + ' QORT'
+ })
+ setIsLoadingPublishKey(true)
+
+
+ window.sendMessage("encryptAndPublishSymmetricKeyGroupChatForAdmins", {
+ groupId: selectedGroup,
+ previousData: null,
+ admins: adminsWithNames
+ })
+ .then((response) => {
+
+ if (!response?.error) {
+ setInfoSnackCustom({
+ type: "success",
+ message: "Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.",
+ });
+ setOpenSnackGlobal(true);
+ return
+ }
+ setInfoSnackCustom({
+ type: "error",
+ message: response?.error || "unable to re-encrypt secret key",
+ });
+ setOpenSnackGlobal(true);
+ })
+ .catch((error) => {
+ setInfoSnackCustom({
+ type: "error",
+ message: error?.message || "unable to re-encrypt secret key",
+ });
+ setOpenSnackGlobal(true);
+ });
+
+ } catch (error) {
+
+ }
+ }
+ useEffect(() => {
+ getAdminGroupSecretKey()
+ }, [getAdminGroupSecretKey]);
+ return (
+
+
+
+ {isFetchingAdminGroupSecretKey && Fetching Admins secret keys}
+ {!isFetchingAdminGroupSecretKey && !adminGroupSecretKey && No secret key published yet}
+ {adminGroupSecretKeyPublishDetails && (
+ Last encryption date: {formatTimestampForum(adminGroupSecretKeyPublishDetails?.updated || adminGroupSecretKeyPublishDetails?.created)}
+ )}
+
+
+
+ )
+}
diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx
index dd33b75..7dadd28 100644
--- a/src/components/Chat/ChatGroup.tsx
+++ b/src/components/Chat/ChatGroup.tsx
@@ -27,6 +27,7 @@ import { isFocusedParentGroupAtom } from '../../atoms/global'
import { useRecoilState } from 'recoil'
import AppViewerContainer from '../Apps/AppViewerContainer'
import CloseIcon from "@mui/icons-material/Close";
+import { throttle } from 'lodash'
const uid = new ShortUniqueId({ length: 5 });
@@ -53,6 +54,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference
const editorRef = useRef(null);
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
+ const handleUpdateRef = useRef(null);
const lastReadTimestamp = useRef(null)
@@ -476,23 +478,24 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
hasInitializedWebsocket.current = true
}, [secretKey])
+
useEffect(() => {
if (!editorRef?.current) return;
- const handleUpdate = () => {
- const htmlContent = editorRef?.current.getHTML();
- const stringified = JSON.stringify(htmlContent);
- const size = new Blob([stringified]).size;
+
+ handleUpdateRef.current = throttle(() => {
+ const htmlContent = editorRef.current.getHTML();
+ const size = new TextEncoder().encode(htmlContent).length;
setMessageSize(size + 100);
- };
+ }, 1200);
- // Add a listener for the editorRef?.current's content updates
- editorRef?.current.on('update', handleUpdate);
+ const currentEditor = editorRef.current;
+
+ currentEditor.on("update", handleUpdateRef.current);
- // Cleanup the listener on unmount
return () => {
- editorRef?.current.off('update', handleUpdate);
+ currentEditor.off("update", handleUpdateRef.current);
};
- }, [editorRef?.current]);
+ }, [editorRef, setMessageSize]);
useEffect(()=> {
@@ -579,6 +582,9 @@ const clearEditorContent = () => {
};
+
+
+
const sendMessage = async ()=> {
try {
if(isSending) return
diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx
index 491e1f7..5469404 100644
--- a/src/components/ContextMenu.tsx
+++ b/src/components/ContextMenu.tsx
@@ -1,42 +1,39 @@
-import React, { useState, useRef, useMemo, useEffect } from 'react';
+import React, { useState, useRef, useMemo } from 'react';
import { ListItemIcon, Menu, MenuItem, Typography, styled } from '@mui/material';
import MailOutlineIcon from '@mui/icons-material/MailOutline';
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
import { executeEvent } from '../utils/events';
const CustomStyledMenu = styled(Menu)(({ theme }) => ({
- '& .MuiPaper-root': {
- backgroundColor: '#f9f9f9',
- borderRadius: '12px',
- padding: theme.spacing(1),
- boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
+ '& .MuiPaper-root': {
+ backgroundColor: '#f9f9f9',
+ borderRadius: '12px',
+ padding: theme.spacing(1),
+ boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
+ },
+ '& .MuiMenuItem-root': {
+ fontSize: '14px',
+ color: '#444',
+ transition: '0.3s background-color',
+ '&:hover': {
+ backgroundColor: '#f0f0f0',
},
- '& .MuiMenuItem-root': {
- fontSize: '14px', // Smaller font size for the menu item text
- color: '#444',
- transition: '0.3s background-color',
- '&:hover': {
- backgroundColor: '#f0f0f0', // Explicit hover state
- },
-
- },
- }));
+ },
+}));
export const ContextMenu = ({ children, groupId, getUserSettings, mutedGroups }) => {
const [menuPosition, setMenuPosition] = useState(null);
const longPressTimeout = useRef(null);
const preventClick = useRef(false); // Flag to prevent click after long-press or right-click
+ const touchStartPosition = useRef({ x: 0, y: 0 });
+ const touchMoved = useRef(false);
- const isMuted = useMemo(()=> {
- return mutedGroups.includes(groupId)
- }, [mutedGroups, groupId])
+ const isMuted = useMemo(() => mutedGroups.includes(groupId), [mutedGroups, groupId]);
// Handle right-click (context menu) for desktop
const handleContextMenu = (event) => {
event.preventDefault();
- event.stopPropagation(); // Prevent parent click
-
- // Set flag to prevent any click event after right-click
+ event.stopPropagation();
preventClick.current = true;
setMenuPosition({
@@ -47,76 +44,81 @@ export const ContextMenu = ({ children, groupId, getUserSettings, mutedGroups })
// Handle long-press for mobile
const handleTouchStart = (event) => {
+ touchMoved.current = false; // Reset moved state
+ touchStartPosition.current = {
+ x: event.touches[0].clientX,
+ y: event.touches[0].clientY,
+ };
+
longPressTimeout.current = setTimeout(() => {
- preventClick.current = true; // Prevent the next click after long-press
- event.stopPropagation(); // Prevent parent click
- setMenuPosition({
- mouseX: event.touches[0].clientX,
- mouseY: event.touches[0].clientY,
- });
+ if (!touchMoved.current) {
+ preventClick.current = true;
+ event.stopPropagation();
+ setMenuPosition({
+ mouseX: event.touches[0].clientX,
+ mouseY: event.touches[0].clientY,
+ });
+ }
}, 500); // Long press duration
};
+ const handleTouchMove = (event) => {
+ const currentPosition = {
+ x: event.touches[0].clientX,
+ y: event.touches[0].clientY,
+ };
+
+ const distanceMoved = Math.sqrt(
+ Math.pow(currentPosition.x - touchStartPosition.current.x, 2) +
+ Math.pow(currentPosition.y - touchStartPosition.current.y, 2)
+ );
+
+ if (distanceMoved > 10) {
+ touchMoved.current = true; // Mark as moved
+ clearTimeout(longPressTimeout.current); // Cancel the long press
+ }
+ };
+
const handleTouchEnd = (event) => {
clearTimeout(longPressTimeout.current);
if (preventClick.current) {
event.preventDefault();
- event.stopPropagation(); // Prevent synthetic click after long-press
- preventClick.current = false; // Reset the flag
+ event.stopPropagation();
+ preventClick.current = false;
}
};
-
-
- const handleSetGroupMute = ()=> {
- try {
- let value = [...mutedGroups]
- if(isMuted){
- value = value.filter((group)=> group !== groupId)
- } else {
- value.push(groupId)
- }
- window.sendMessage("addUserSettings", {
- keyValue: {
- key: 'mutedGroups',
- value,
- },
- })
- .then((response) => {
- if (response?.error) {
- console.error("Error adding user settings:", response.error);
- } else {
- console.log("User settings added successfully");
- }
- })
- .catch((error) => {
- console.error("Failed to add user settings:", error.message || "An error occurred");
- });
-
- setTimeout(() => {
- getUserSettings()
- }, 400);
-
- } catch (error) {
-
- }
- }
-
-
-
const handleClose = (e) => {
e.preventDefault();
- e.stopPropagation();
+ e.stopPropagation();
setMenuPosition(null);
};
+ const handleSetGroupMute = () => {
+ const value = isMuted
+ ? mutedGroups.filter((group) => group !== groupId)
+ : [...mutedGroups, groupId];
+
+ window
+ .sendMessage("addUserSettings", {
+ keyValue: { key: 'mutedGroups', value },
+ })
+ .then((response) => {
+ if (response?.error) console.error("Error adding user settings:", response.error);
+ else console.log("User settings added successfully");
+ })
+ .catch((error) => console.error("Failed to add user settings:", error.message));
+
+ setTimeout(() => getUserSettings(), 400);
+ };
+
return (
{children}
@@ -131,16 +133,14 @@ export const ContextMenu = ({ children, groupId, getUserSettings, mutedGroups })
? { top: menuPosition.mouseY, left: menuPosition.mouseX }
: undefined
}
- onClick={(e)=> {
- e.stopPropagation();
- }}
+ onClick={(e) => e.stopPropagation()}
>
-
);
};
-
-
diff --git a/src/components/Group/Forum/Mail-styles.ts b/src/components/Group/Forum/Mail-styles.ts
index 5308bf0..534304d 100644
--- a/src/components/Group/Forum/Mail-styles.ts
+++ b/src/components/Group/Forum/Mail-styles.ts
@@ -729,7 +729,7 @@ font-size: 23px;
font-style: normal;
font-weight: 700;
line-height: normal;
-white-space: nowrap;
+white-space: wrap;
text-overflow: ellipsis;
overflow: hidden;
`
diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx
index cebb71d..7b8b26b 100644
--- a/src/components/Group/Group.tsx
+++ b/src/components/Group/Group.tsx
@@ -93,6 +93,7 @@ import { AppsNavBar } from "../Apps/AppsNavBar";
import { AppsDesktop } from "../Apps/AppsDesktop";
import { formatEmailDate } from "./QMailMessages";
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
+import { AdminSpace } from "../Chat/AdminSpace";
// let touchStartY = 0;
// let disablePullToRefresh = false;
@@ -2308,7 +2309,7 @@ export const Group = ({
handleNewEncryptionNotification={
setNewEncryptionNotification
}
- hide={groupSection !== "chat" || !secretKey}
+ hide={groupSection !== "chat" || !secretKey || selectedDirect || newChat}
handleSecretKeyCreationInProgress={
handleSecretKeyCreationInProgress
@@ -2422,6 +2423,7 @@ export const Group = ({
defaultThread={defaultThread}
setDefaultThread={setDefaultThread}
/>
+
>
)}
diff --git a/src/components/Group/GroupMenu.tsx b/src/components/Group/GroupMenu.tsx
index a02dbff..23cd90d 100644
--- a/src/components/Group/GroupMenu.tsx
+++ b/src/components/Group/GroupMenu.tsx
@@ -15,6 +15,7 @@ import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2";
import { ChatIcon } from "../../assets/Icons/ChatIcon";
import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
import { MembersIcon } from "../../assets/Icons/MembersIcon";
+import { AdminsIcon } from "../../assets/Icons/AdminsIcon";
export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers, goToAnnouncements, goToChat, hasUnreadChat, hasUnreadAnnouncements }) => {
const [anchorEl, setAnchorEl] = useState(null);
@@ -80,6 +81,9 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers,
)}
{groupSection === "forum" &&(
<> {" Threads"}>
+ )}
+ {groupSection === "adminSpace" &&(
+ <> {" Admins"}>
)}
@@ -196,6 +200,25 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers,
},
}} primary="Members" />
+ {
+ setGroupSection("adminSpace");
+ handleClose();
+ }}
+ >
+
+
+
+
+
+
);
diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts
index bc969fa..f2acbd9 100644
--- a/src/qortalRequests/get.ts
+++ b/src/qortalRequests/get.ts
@@ -44,6 +44,7 @@ import signTradeBotTransaction from "../transactions/signTradeBotTransaction";
import { executeEvent } from "../utils/events";
import { extractComponents } from "../components/Chat/MessageDisplay";
import { decryptResource, getGroupAdmins, getPublishesFromAdmins, validateSecretKey } from "../components/Group/Group";
+import { getPublishesFromAdminsAdminSpace } from "../components/Chat/AdminSpaceInner";
const btcFeePerByte = 0.00000100
const ltcFeePerByte = 0.00000030
@@ -382,10 +383,11 @@ export const encryptData = async (data, sender) => {
}
};
+
export const encryptQortalGroupData = async (data, sender) => {
let data64 = data.data64;
let groupId = data?.groupId
-
+ let isAdmins = data?.isAdmins
if(!groupId){
throw new Error('Please provide a groupId')
}
@@ -395,7 +397,10 @@ export const encryptQortalGroupData = async (data, sender) => {
if (!data64) {
throw new Error("Please include data to encrypt");
}
+
+
let secretKeyObject
+ if(!isAdmins){
if(groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && (Date.now() - groupSecretkeys[groupId]?.timestamp) < 1200000){
secretKeyObject = groupSecretkeys[groupId].secretKeyObject
}
@@ -428,7 +433,44 @@ url
timestamp: Date.now()
}
}
+} else {
+ if(groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && (Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp) < 1200000){
+ secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject
+ }
+
+ if(!secretKeyObject){
+ const { names } =
+ await getGroupAdmins(groupId)
+
+ const publish =
+ await getPublishesFromAdminsAdminSpace(names, groupId);
+ if(publish === false) throw new Error('No group key found.')
+ const url = await createEndpoint(`/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
+ publish.identifier
+ }?encoding=base64`);
+
+ const res = await fetch(
+url
+ );
+ const resData = await res.text();
+ const decryptedKey: any = await decryptResource(resData);
+
+ const dataint8Array = base64ToUint8Array(decryptedKey.data);
+ const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
+
+ if (!validateSecretKey(decryptedKeyToObject))
+ throw new Error("SecretKey is not valid");
+ secretKeyObject = decryptedKeyToObject
+ groupSecretkeys[`admins-${groupId}`] = {
+ secretKeyObject,
+ timestamp: Date.now()
+ }
+ }
+
+
+
+}
const resGroupEncryptedResource = encryptSingle({
data64, secretKeyObject: secretKeyObject,
@@ -444,17 +486,17 @@ url
export const decryptQortalGroupData = async (data, sender) => {
let data64 = data.data64;
let groupId = data?.groupId
+ let isAdmins = data?.isAdmins
if(!groupId){
throw new Error('Please provide a groupId')
}
- if (data.fileId) {
- data64 = await getFileFromContentScript(data.fileId);
- }
+
if (!data64) {
throw new Error("Please include data to encrypt");
}
let secretKeyObject
+ if(!isAdmins){
if(groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && (Date.now() - groupSecretkeys[groupId]?.timestamp) < 1200000){
secretKeyObject = groupSecretkeys[groupId].secretKeyObject
}
@@ -485,6 +527,40 @@ url
timestamp: Date.now()
}
}
+} else {
+ if(groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && (Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp) < 1200000){
+ secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject
+ }
+ if(!secretKeyObject){
+ const { names } =
+ await getGroupAdmins(groupId)
+
+ const publish =
+ await getPublishesFromAdminsAdminSpace(names, groupId);
+ if(publish === false) throw new Error('No group key found.')
+ const url = await createEndpoint(`/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
+ publish.identifier
+ }?encoding=base64`);
+
+ const res = await fetch(
+url
+ );
+ const resData = await res.text();
+ const decryptedKey: any = await decryptResource(resData);
+
+ const dataint8Array = base64ToUint8Array(decryptedKey.data);
+ const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
+ if (!validateSecretKey(decryptedKeyToObject))
+ throw new Error("SecretKey is not valid");
+ secretKeyObject = decryptedKeyToObject
+ groupSecretkeys[`admins-${groupId}`] = {
+ secretKeyObject,
+ timestamp: Date.now()
+ }
+ }
+
+
+}
const resGroupDecryptResource = decryptSingle({
data64, secretKeyObject: secretKeyObject, skipDecodeBase64: true