From 63c96f5ea0574997243bc9c55cc14fd2098c58ea Mon Sep 17 00:00:00 2001 From: PhilReact Date: Mon, 7 Apr 2025 21:20:29 +0300 Subject: [PATCH] enlarge back btn, increased qr timeout to 60secs --- src/App.tsx | 56 ++++++++++++- src/background.ts | 42 ++++++++++ .../Apps/useQortalMessageListener.tsx | 8 +- src/messaging/messagesToBackground.tsx | 2 +- src/qortalRequests.ts | 21 ++++- src/qortalRequests/get.ts | 83 +++++++++++++++++-- src/transactions/UpdateGroupTransaction.ts | 62 ++++++++++++++ src/transactions/transactions.ts | 2 + 8 files changed, 262 insertions(+), 14 deletions(-) create mode 100644 src/transactions/UpdateGroupTransaction.ts diff --git a/src/App.tsx b/src/App.tsx index 82f495b..2a3d1b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import { DialogContent, DialogContentText, DialogTitle, + FormControlLabel, Input, InputLabel, Popover, @@ -48,6 +49,7 @@ import CloseIcon from "@mui/icons-material/Close"; import './utils/seedPhrase/RandomSentenceGenerator'; import EngineeringIcon from '@mui/icons-material/Engineering'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; +import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; import { createAccount, generateRandomSentence, @@ -427,6 +429,7 @@ function App() { }); const [useLocalNode, setUseLocalNode] = useState(false); + const [confirmRequestRead, setConfirmRequestRead] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [showSeed, setShowSeed] = useState(false) const [creationStep, setCreationStep] = useState(1) @@ -798,6 +801,7 @@ function App() { qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false; } + setConfirmRequestRead(false) await showQortalRequestExtension(message?.payload); if (qortalRequestCheckbox1Ref.current) { @@ -2043,11 +2047,13 @@ function App() { justifyContent: "flex-start", paddingLeft: "22px", boxSizing: "border-box", + maxWidth: '700px' }} > { setRawWallet(null); @@ -2574,11 +2582,13 @@ function App() { justifyContent: "flex-start", paddingLeft: "22px", boxSizing: "border-box", + maxWidth: '700px' }} > { setRawWallet(null); @@ -2679,11 +2689,13 @@ function App() { justifyContent: "flex-start", paddingLeft: "22px", boxSizing: "border-box", + maxWidth: '700px' }} > { if(creationStep === 2){ @@ -3205,7 +3219,7 @@ function App() { > { @@ -3438,6 +3452,36 @@ function App() { )} +{messageQortalRequestExtension?.confirmCheckbox && ( + setConfirmRequestRead(e.target.checked)} + checked={confirmRequestRead} + edge="start" + tabIndex={-1} + disableRipple + sx={{ + "&.Mui-checked": { + color: "white", + }, + "& .MuiSvgIcon-root": { + color: "white", + }, + }} + /> + } + label={ + + + I have read this request + + + + } + /> + )} + { + if(messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead) return + onOkQortalRequestExtension("accepted") }} - onClick={() => onOkQortalRequestExtension("accepted")} > accept diff --git a/src/background.ts b/src/background.ts index eab57c7..20dddd1 100644 --- a/src/background.ts +++ b/src/background.ts @@ -2309,6 +2309,48 @@ export async function createGroup({ throw new Error(res?.message || "Transaction was not able to be processed"); return res; } +export async function updateGroup({ + groupId, + newOwner, + newIsOpen, + newDescription, + newApprovalThreshold, + newMinimumBlockDelay, + newMaximumBlockDelay +}) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + if (!address) throw new Error("Cannot find user"); + const lastReference = await getLastRef(); + const feeres = await getFee("UPDATE_GROUP"); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + const tx = await createTransaction(23, keyPair, { + fee: feeres.fee, + _groupId: groupId, + newOwner, + newIsOpen, + newDescription, + newApprovalThreshold, + newMinimumBlockDelay, + newMaximumBlockDelay, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error(res?.message || "Transaction was not able to be processed"); + return res; +} export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) { const address = await getNameOrAddress(qortalAddress); if (!address) throw new Error("Cannot find user"); diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index e017b16..98af42d 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -255,7 +255,8 @@ export const listOfAllQortalRequests = [ 'GET_NODE_INFO', 'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', - 'SHOW_PDF_READER' + 'SHOW_PDF_READER', + 'UPDATE_GROUP' ] export const UIQortalRequests = [ @@ -311,7 +312,8 @@ export const UIQortalRequests = [ 'GET_NODE_INFO', 'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', - 'SHOW_PDF_READER' + 'SHOW_PDF_READER', + 'UPDATE_GROUP' ]; @@ -575,7 +577,7 @@ isDOMContentLoaded: false result: null, error: { error: response?.error, - message: typeof response?.error === 'string' ? response?.error : 'An error has occurred' + message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred' }, }); } else { diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/messagesToBackground.tsx index 0ba8f71..4eebc5e 100644 --- a/src/messaging/messagesToBackground.tsx +++ b/src/messaging/messagesToBackground.tsx @@ -24,7 +24,7 @@ window.addEventListener("message", (event) => { } }); -export const sendMessageBackground = (action, data = {}, timeout = 180000, isExtension, appInfo, skipAuth) => { +export const sendMessageBackground = (action, data = {}, timeout = 240000, isExtension, appInfo, skipAuth) => { return new Promise((resolve, reject) => { const requestId = generateRequestId(); // Unique ID for each request callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 1df3364..83c2186 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,6 +1,6 @@ import { gateways, getApiKeyFromStorage } from "./background"; import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener"; -import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus } from "./qortalRequests/get"; +import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest } from "./qortalRequests/get"; import { getData, storeData } from "./utils/chromeStorage"; import { executeEvent } from "./utils/events"; @@ -1195,6 +1195,25 @@ export const isRunningGateway = async ()=> { } break; } + case "UPDATE_GROUP" : { + try { + const res = await updateGroupRequest(request.payload, isFromExtension) + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + payload: res, + type: "backgroundMessageResponse", + }, event.origin); + } catch (error) { + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + error: error?.message, + type: "backgroundMessageResponse", + }, event.origin); + } + break; + } case "GET_ARRR_SYNC_STATUS": { try { diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 9dae778..2b2607c 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -30,6 +30,7 @@ import { removeAdmin, cancelInvitationToGroup, createGroup, + updateGroup, } from "../background"; import { getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption"; import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener"; @@ -122,7 +123,7 @@ export async function retryTransaction(fn, args, throwError, retries = MAX_RETRI if(throwError){ throw new Error(error?.message || "Unable to process transaction") } else { - return null + throw new Error(error?.message || "Unable to process transaction") } } await new Promise(res => setTimeout(res, 10000)); @@ -391,7 +392,7 @@ async function getUserPermission(payload, isFromExtension) { responseResolvers.get(requestId)(false); // Resolve with `false` if no response responseResolvers.delete(requestId); } - }, 30000); // 30-second timeout + }, 60000); // 30-second timeout }); } @@ -1349,6 +1350,7 @@ export const publishMultipleQDNResources = async ( failedPublishesIdentifiers.push({ reason: errorMsg, identifier: resource.identifier, + service: resource.service, }); continue; } @@ -1357,6 +1359,7 @@ export const publishMultipleQDNResources = async ( failedPublishesIdentifiers.push({ reason: errorMsg, identifier: resource.identifier, + service: resource.service, }); continue; } @@ -1386,6 +1389,7 @@ export const publishMultipleQDNResources = async ( failedPublishesIdentifiers.push({ reason: errorMsg, identifier: resource.identifier, + service: resource.service, }); continue; } @@ -1413,6 +1417,7 @@ export const publishMultipleQDNResources = async ( failedPublishesIdentifiers.push({ reason: errorMsg, identifier: resource.identifier, + service: resource.service, }); continue; } @@ -1439,7 +1444,7 @@ export const publishMultipleQDNResources = async ( apiVersion: 2, withFee: true, }, - ], false); + ], true); await new Promise((res) => { setTimeout(() => { res(); @@ -1450,17 +1455,21 @@ export const publishMultipleQDNResources = async ( failedPublishesIdentifiers.push({ reason: errorMsg, identifier: resource.identifier, + service: resource.service, }); } } catch (error) { failedPublishesIdentifiers.push({ reason: error?.message || "Unknown error", identifier: resource.identifier, + service: resource.service, }); } } if (failedPublishesIdentifiers.length > 0) { - const obj = {}; + const obj = { + message: "Some resources have failed to publish.", + }; obj["error"] = { unsuccessfulPublishes: failedPublishesIdentifiers, }; @@ -3078,6 +3087,7 @@ export const sendCoin = async (data, isFromExtension) => { text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, fee: fee, + confirmCheckbox: true }, isFromExtension ); @@ -4522,15 +4532,15 @@ export const cancelGroupInviteRequest = async (data, isFromExtension) => { export const createGroupRequest = async (data, isFromExtension) => { - const requiredFields = ["groupId", "qortalAddress"]; + const requiredFields = ["groupId", "qortalAddress", "groupName", "type", "approvalThreshold", "minBlock", "maxBlock"]; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (!data[field]) { + if (data[field] !== undefined && data[field] !== null) { missingFields.push(field); } }); const groupName = data.groupName - const description = data?.description + const description = data?.description || "" const type = +data.type const approvalThreshold = +data?.approvalThreshold const minBlock = +data?.minBlock @@ -4563,6 +4573,65 @@ export const createGroupRequest = async (data, isFromExtension) => { } }; +export const updateGroupRequest = async (data, isFromExtension) => { + const requiredFields = ["groupId", "newOwner", "type", "approvalThreshold", "minBlock", "maxBlock"]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + const groupId = +data.groupId + const newOwner = data.newOwner + const description = data?.description || "" + const type = +data.type + const approvalThreshold = +data?.approvalThreshold + const minBlock = +data?.minBlock + const maxBlock = +data.maxBlock + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) throw new Error("Failed to fetch group"); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = (error && error.message) || "Group not found"; + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(newOwner) + + + const fee = await getFee("CREATE_GROUP"); + const resPermission = await getUserPermission( + { + text1: `Do you give this application permission to update this group?`, + text2: `New owner: ${displayInvitee || newOwner}`, + highlightedText: `Group: ${groupInfo.groupName}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await updateGroup({ + groupId, + newOwner, + newIsOpen: type, + newDescription: description, + newApprovalThreshold: approvalThreshold, + newMinimumBlockDelay: minBlock, + newMaximumBlockDelay: maxBlock + }) + return response + + } else { + throw new Error("User declined request"); + } +}; + export const decryptAESGCMRequest = async (data, isFromExtension) => { const requiredFields = ["encryptedData", "iv", "senderPublicKey"]; requiredFields.forEach((field) => { diff --git a/src/transactions/UpdateGroupTransaction.ts b/src/transactions/UpdateGroupTransaction.ts new file mode 100644 index 0000000..9d9856b --- /dev/null +++ b/src/transactions/UpdateGroupTransaction.ts @@ -0,0 +1,62 @@ +// @ts-nocheck + + +import { QORT_DECIMALS } from "../constants/constants"; +import TransactionBase from "./TransactionBase"; + +export default class UpdateGroupTransaction extends TransactionBase { + constructor() { + super() + this.type = 23 + } + + + + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + set newOwner(newOwner) { + this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner) + } + set newIsOpen(newIsOpen) { + + this._rGroupType = new Uint8Array(1) + this._rGroupType[0] = newIsOpen + } + set newDescription(newDescription) { + this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase()) + this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length) + } + set newApprovalThreshold(newApprovalThreshold) { + this._rGroupApprovalThreshold = new Uint8Array(1) + this._rGroupApprovalThreshold[0] = newApprovalThreshold; + } + set newMinimumBlockDelay(newMinimumBlockDelay) { + this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay) + } + set newMaximumBlockDelay(newMaximumBlockDelay) { + + this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay) + } + + set _groupId(_groupId){ + this._groupBytes = this.constructor.utils.int32ToBytes(_groupId) + } + get params() { + const params = super.params + params.push( + this._groupBytes, + this._newOwner, + this._rGroupDescLength, + this._rGroupDescBytes, + this._rGroupType, + this._rGroupApprovalThreshold, + this._rGroupMinimumBlockDelayBytes, + this._rGroupMaximumBlockDelayBytes, + this._feeBytes + ) + return params + } +} \ No newline at end of file diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts index 62c5e6a..1f0accb 100644 --- a/src/transactions/transactions.ts +++ b/src/transactions/transactions.ts @@ -20,6 +20,7 @@ import DeployAtTransaction from './DeployAtTransaction.js' import RewardShareTransaction from './RewardShareTransaction.js' import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js' import UpdateNameTransaction from './UpdateNameTransaction.js' +import UpdateGroupTransaction from './UpdateGroupTransaction.js' export const transactionTypes = { @@ -32,6 +33,7 @@ export const transactionTypes = { 18: ChatTransaction, 181: GroupChatTransaction, 22: CreateGroupTransaction, + 23: UpdateGroupTransaction, 24: AddGroupAdminTransaction, 25: RemoveGroupAdminTransaction, 26: GroupBanTransaction,