From ce530293ab5339deabf3bda17d5d16274870e9c8 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 26 Feb 2025 16:03:20 +0200 Subject: [PATCH] blocking --- src/App.tsx | 16 +- src/background-cases.ts | 80 +++++++++ src/background.ts | 15 +- src/components/Chat/ChatGroup.tsx | 33 +++- src/components/Chat/MessageItem.tsx | 10 +- src/components/Group/BlockedUsersModal.tsx | 190 ++++++++++++++++++++ src/components/Group/Group.tsx | 38 +++- src/components/Group/WebsocketActive.tsx | 2 +- src/components/Group/useBlockUsers.tsx | 192 +++++++++++++++++++++ src/components/WrapperUserAction.tsx | 63 ++++++- 10 files changed, 617 insertions(+), 22 deletions(-) create mode 100644 src/components/Group/BlockedUsersModal.tsx create mode 100644 src/components/Group/useBlockUsers.tsx diff --git a/src/App.tsx b/src/App.tsx index c6dbd42..3453234 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -143,6 +143,7 @@ import { Minting } from "./components/Minting/Minting"; import { isRunningGateway } from "./qortalRequests"; import { QMailStatus } from "./components/QMailStatus"; import { GlobalActions } from "./components/GlobalActions/GlobalActions"; +import { useBlockedAddresses } from "./components/Group/useBlockUsers"; type extStates = | "not-authenticated" @@ -402,6 +403,9 @@ function App() { const [isOpenSendQort, setIsOpenSendQort] = useState(false); const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false); const [rootHeight, setRootHeight] = useState("100%"); + const {isUserBlocked, + addToBlockList, + removeBlockFromList, getAllBlockedUsers} = useBlockedAddresses() const [currentNode, setCurrentNode] = useState({ url: "http://127.0.0.1:12391", }); @@ -1630,7 +1634,11 @@ function App() { infoSnackCustom: infoSnack, setInfoSnackCustom: setInfoSnack, downloadResource, - getIndividualUserInfo + getIndividualUserInfo, + isUserBlocked, + addToBlockList, + removeBlockFromList, + getAllBlockedUsers }} > @@ -1751,7 +1759,11 @@ function App() { infoSnackCustom: infoSnack, setInfoSnackCustom: setInfoSnack, downloadResource, - getIndividualUserInfo + getIndividualUserInfo, + isUserBlocked, + addToBlockList, + removeBlockFromList, + getAllBlockedUsers }} > { }, }); const data = await response.json(); + const copyGroups = [...(data?.groups || [])] + const findIndex = copyGroups?.findIndex(item => item?.groupId === 0) + if(findIndex !== -1){ + copyGroups[findIndex] = { + ...(copyGroups[findIndex] || {}), + groupId: "0" + } + } + const filteredGroups = copyGroups - const filteredGroups = - data.groups?.filter((item) => item?.groupId !== 0) || []; const sortedGroups = filteredGroups.sort( (a, b) => (b.timestamp || 0) - (a.timestamp || 0) ); diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index c41265d..e054bd9 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react' +import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react' import { CreateCommonSecret } from './CreateCommonSecret' import { reusableGet } from '../../qdn/publish/pubish' import { uint8ArrayToObject } from '../../backgroundFunctions/encryption' @@ -10,11 +10,11 @@ import Tiptap from './TipTap' import { CustomButton } from '../../App-styles' import CircularProgress from '@mui/material/CircularProgress'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar' -import { getBaseApiReact, getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App' +import { getBaseApiReact, getBaseApiReactSocket, isMobile, MyContext, pauseAllQueues, resumeAllQueues } from '../../App' import { CustomizedSnackbars } from '../Snackbar/Snackbar' import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes' import { useMessageQueue } from '../../MessageQueueContext' -import { executeEvent } from '../../utils/events' +import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events' import { Box, ButtonBase, Divider, Typography } from '@mui/material' import ShortUniqueId from "short-unique-id"; import { ReplyPreview } from './MessageItem' @@ -28,6 +28,7 @@ import { throttle } from 'lodash' const uid = new ShortUniqueId({ length: 5 }); export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent, hideView, isPrivate}) => { + const {isUserBlocked} = useContext(MyContext) const [messages, setMessages] = useState([]) const [chatReferences, setChatReferences] = useState({}) const [isSending, setIsSending] = useState(false) @@ -158,10 +159,28 @@ const [messageSize, setMessageSize] = useState(0) }) } + const updateChatMessagesWithBlocksFunc = (e) => { + if(e.detail){ + setMessages((prev)=> prev?.filter((item)=> { + return !isUserBlocked(item?.sender, item?.senderName) + })) + } + }; + + useEffect(() => { + subscribeToEvent("updateChatMessagesWithBlocks", updateChatMessagesWithBlocksFunc); + + return () => { + unsubscribeFromEvent("updateChatMessagesWithBlocks", updateChatMessagesWithBlocksFunc); + }; + }, []); + const middletierFunc = async (data: any, groupId: string) => { try { if (hasInitialized.current) { - decryptMessages(data, true); + const dataRemovedBlock = data?.filter((item)=> !isUserBlocked(item?.sender, item?.senderName)) + + decryptMessages(dataRemovedBlock, true); return; } hasInitialized.current = true; @@ -173,7 +192,11 @@ const [messageSize, setMessageSize] = useState(0) }, }); const responseData = await response.json(); - decryptMessages(responseData, false); + const dataRemovedBlock = responseData?.filter((item)=> { + return !isUserBlocked(item?.sender, item?.senderName) + }) + + decryptMessages(dataRemovedBlock, false); } catch (error) { console.error(error); } diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 8b43e77..5123ee2 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -132,13 +132,16 @@ const onSeenFunc = useCallback(()=> { return ( - - - {message?.divide && ( + <> + {message?.divide && (
Unread messages below
)} + + + +
{
+ ); }); diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx new file mode 100644 index 0000000..84fa3fa --- /dev/null +++ b/src/components/Group/BlockedUsersModal.tsx @@ -0,0 +1,190 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + TextField, + Typography, +} from "@mui/material"; +import React, { useContext, useEffect, useState } from "react"; +import { MyContext } from "../../App"; +import { Spacer } from "../../common/Spacer"; +import { executeEvent } from "../../utils/events"; + +export const BlockedUsersModal = ({ close }) => { + const [hasChanged, setHasChanged] = useState(false); + const [value, setValue] = useState(""); + + const { getAllBlockedUsers, removeBlockFromList, addToBlockList } = useContext(MyContext); + const [blockedUsers, setBlockedUsers] = useState({ + addresses: {}, + names: {}, + }); + const fetchBlockedUsers = () => { + setBlockedUsers(getAllBlockedUsers()); + }; + + useEffect(() => { + fetchBlockedUsers(); + }, []); + return ( + + Blocked Users + + + { + setValue(e.target.value); + }} + /> + + + + {Object.entries(blockedUsers?.addresses).length > 0 && ( + <> + + + Blocked Users for Chat ( addresses ) + + + + )} + + + {Object.entries(blockedUsers?.addresses || {})?.map( + ([key, value]) => { + return ( + + {key} + + + ); + } + )} + + {Object.entries(blockedUsers?.names).length > 0 && ( + <> + + + Blocked Users for QDN and Chat (names) + + + + )} + + + {Object.entries(blockedUsers?.names || {})?.map(([key, value]) => { + return ( + + {key} + + + ); + })} + + + + + + + ); +}; diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 73d80ba..48ef02c 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -77,9 +77,10 @@ import { AdminSpace } from "../Chat/AdminSpace"; import { useSetRecoilState } from "recoil"; import { addressInfoControllerAtom, selectedGroupIdAtom } from "../../atoms/global"; import { sortArrayByTimestampAndGroupName } from "../../utils/time"; - +import BlockIcon from '@mui/icons-material/Block'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; +import { BlockedUsersModal } from "./BlockedUsersModal"; export const getPublishesFromAdmins = async (admins: string[], groupId) => { @@ -419,6 +420,8 @@ export const Group = ({ const [groupAnnouncements, setGroupAnnouncements] = React.useState({}); const [defaultThread, setDefaultThread] = React.useState(null); const [isOpenDrawer, setIsOpenDrawer] = React.useState(false); + const [isOpenBlockedUserModal, setIsOpenBlockedUserModal] = React.useState(false); + const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false); const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(""); const [drawerMode, setDrawerMode] = React.useState("groups"); @@ -768,7 +771,10 @@ export const Group = ({ } if(isPrivate === false){ setTriedToFetchSecretKey(true); - getAdminsForPublic(selectedGroup) + if(selectedGroup?.groupId !== '0'){ + getAdminsForPublic(selectedGroup) + } + } }, [selectedGroup, isPrivate]); @@ -853,7 +859,7 @@ export const Group = ({ // Update the component state with the received 'sendqort' state setGroups(sortArrayByTimestampAndGroupName(message.payload)); getLatestRegularChat(message.payload); - setMemberGroups(message.payload); + setMemberGroups(message.payload?.filter((item)=> item?.groupId !== '0')); if (selectedGroupRef.current && groupSectionRef.current === "chat") { window.sendMessage("addTimestampEnterChat", { @@ -944,7 +950,7 @@ export const Group = ({ !initiatedGetMembers.current && selectedGroup?.groupId && secretKey && - admins.includes(myAddress) + admins.includes(myAddress) && selectedGroup?.groupId !== '0' ) { // getAdmins(selectedGroup?.groupId); getMembers(selectedGroup?.groupId); @@ -1998,9 +2004,11 @@ export const Group = ({ width: "100%", justifyContent: "center", padding: "10px", + gap: '10px' }} > {chatMode === "groups" && ( + <> { setOpenAddGroup(true); @@ -2013,6 +2021,22 @@ export const Group = ({ /> Group Mgmt + { + setIsOpenBlockedUserModal(true); + }} + sx={{ + minWidth: 'unset', + padding: '10px' + }} + > + + + )} {chatMode === "directs" && ( )} - + {isOpenBlockedUserModal && ( + { + setIsOpenBlockedUserModal(false) + }} /> + )} {selectedDirect && !newChat && ( <> diff --git a/src/components/Group/WebsocketActive.tsx b/src/components/Group/WebsocketActive.tsx index 63359a4..b9da4d2 100644 --- a/src/components/Group/WebsocketActive.tsx +++ b/src/components/Group/WebsocketActive.tsx @@ -91,7 +91,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { const sortedDirects = (data?.direct || []).filter(item => item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' ).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); - + window.sendMessage("handleActiveGroupDataFromSocket", { groups: sortedGroups, directs: sortedDirects, diff --git a/src/components/Group/useBlockUsers.tsx b/src/components/Group/useBlockUsers.tsx new file mode 100644 index 0000000..05cbe90 --- /dev/null +++ b/src/components/Group/useBlockUsers.tsx @@ -0,0 +1,192 @@ +import React, { useCallback, useEffect, useRef } from "react"; +import { getBaseApiReact } from "../../App"; +import { truncate } from "lodash"; + + + +export const useBlockedAddresses = () => { + const userBlockedRef = useRef({}) + const userNamesBlockedRef = useRef({}) + + const getAllBlockedUsers = useCallback(()=> { + + return { + names: userNamesBlockedRef.current, + addresses: userBlockedRef.current + } + }, []) + + const isUserBlocked = useCallback((address, name)=> { + try { + if(!address) return false + if(userBlockedRef.current[address] || userNamesBlockedRef.current[name]) return true + return false + + + } catch (error) { + //error + } + }, []) + + useEffect(()=> { + const fetchBlockedList = async ()=> { + try { + const response = await new Promise((res, rej) => { + window.sendMessage("listActions", { + + type: 'get', + listName: `blockedAddresses`, + + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + res(response); + } + }) + .catch((error) => { + console.error("Failed qortalRequest", error); + }); + }) + const blockedUsers = {} + response?.forEach((item)=> { + blockedUsers[item] = true + }) + userBlockedRef.current = blockedUsers + + const response2 = await new Promise((res, rej) => { + window.sendMessage("listActions", { + + type: 'get', + listName: `blockedNames`, + + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + res(response); + } + }) + .catch((error) => { + console.error("Failed qortalRequest", error); + }); + }) + const blockedUsers2 = {} + response2?.forEach((item)=> { + blockedUsers2[item] = true + }) + userNamesBlockedRef.current = blockedUsers2 + + + } catch (error) { + console.error(error) + } + } + fetchBlockedList() + }, []) + + const removeBlockFromList = useCallback(async (address, name)=> { + await new Promise((res, rej) => { + window.sendMessage("listActions", { + + type: 'remove', + items: name ? [name] : [address], + listName: name ? 'blockedNames' : 'blockedAddresses' + + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + if(!name){ + const copyObject = {...userBlockedRef.current} + delete copyObject[address] + userBlockedRef.current = copyObject + } else { + const copyObject = {...userNamesBlockedRef.current} + delete copyObject[name] + userNamesBlockedRef.current = copyObject + } + + res(response); + } + }) + .catch((error) => { + console.error("Failed qortalRequest", error); + }); + }) + if(name && userBlockedRef.current[address]){ + await new Promise((res, rej) => { + window.sendMessage("listActions", { + + type: 'remove', + items: !name ? [name] : [address], + listName: !name ? 'blockedNames' : 'blockedAddresses' + + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + const copyObject = {...userBlockedRef.current} + delete copyObject[address] + userBlockedRef.current = copyObject + res(response); + } + }) + .catch((error) => { + console.error("Failed qortalRequest", error); + }); + }) + } + + }, []) + + const addToBlockList = useCallback(async (address, name)=> { + await new Promise((res, rej) => { + window.sendMessage("listActions", { + + type: 'add', + items: name ? [name] : [address], + listName: name ? 'blockedNames' : 'blockedAddresses' + + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + if(name){ + + const copyObject = {...userNamesBlockedRef.current} + copyObject[name] = true + userNamesBlockedRef.current = copyObject + }else { + const copyObject = {...userBlockedRef.current} + copyObject[address] = true + userBlockedRef.current = copyObject + + } + + res(response); + } + }) + .catch((error) => { + console.error("Failed qortalRequest", error); + }); + }) + }, []) + + return { + isUserBlocked, + addToBlockList, + removeBlockFromList, + getAllBlockedUsers + }; +}; diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index f7af63e..8bcc03a 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -1,6 +1,7 @@ -import React, { useState } from 'react'; -import { Popover, Button, Box } from '@mui/material'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { Popover, Button, Box, CircularProgress } from '@mui/material'; import { executeEvent } from '../utils/events'; +import { MyContext } from '../App'; export const WrapperUserAction = ({ children, address, name, disabled }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -12,9 +13,9 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }; // Handle closing the Popover - const handleClose = () => { + const handleClose = useCallback(() => { setAnchorEl(null); - }; + }, []); // Determine if the popover is open const open = Boolean(anchorEl); @@ -120,9 +121,63 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { > Copy address +
)} ); }; + + +const BlockUser = ({address, name, handleClose})=> { + const [isAlreadyBlocked, setIsAlreadyBlocked] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const {isUserBlocked, + addToBlockList, + removeBlockFromList} = useContext(MyContext) + +useEffect(()=> { + if(!address) return + setIsAlreadyBlocked(isUserBlocked(address, name)) +}, [address, setIsAlreadyBlocked, isUserBlocked, name]) + + return ( + + ) +} \ No newline at end of file