From 95df97ba19832bab858ab0e3fabbaad8fb5434ce Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 13 Nov 2024 05:05:40 +0200 Subject: [PATCH] added admin action qortalrequest --- src/App.tsx | 2 +- src/atoms/global.ts | 8 ++ src/components/Apps/AppViewer.tsx | 2 +- src/components/Apps/AppsLibrary.tsx | 4 +- src/components/Apps/AppsLibraryDesktop.tsx | 4 +- .../Apps/useQortalMessageListener.tsx | 10 +- src/qortalRequests.ts | 18 ++- src/qortalRequests/get.ts | 114 ++++++++++++++++-- 8 files changed, 139 insertions(+), 23 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index aebad47..803b1cb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -629,7 +629,7 @@ function App() { if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow) { try { if(message?.payload?.checkbox1){ - qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1 + qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false } await showQortalRequestExtension(message?.payload); diff --git a/src/atoms/global.ts b/src/atoms/global.ts index e9fcaed..3688c0b 100644 --- a/src/atoms/global.ts +++ b/src/atoms/global.ts @@ -28,6 +28,14 @@ export const sortablePinnedAppsAtom = atom({ { name: 'Q-Trade', service: 'APP' + }, + { + name: 'Q-Support', + service: 'APP' + }, + { + name: 'NodeInfo', + service: 'APP' } ], }); diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx index 8b0211b..8f76d1e 100644 --- a/src/components/Apps/AppViewer.tsx +++ b/src/components/Apps/AppViewer.tsx @@ -15,7 +15,7 @@ export const AppViewer = React.forwardRef(({ app , hide}, iframeRef) => { const { rootHeight } = useContext(MyContext); // const iframeRef = useRef(null); const { document, window: frameWindow } = useFrame(); - const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId) + const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, app?.name, app?.service) const [url, setUrl] = useState('') useEffect(()=> { diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx index 868b297..2dab8a7 100644 --- a/src/components/Apps/AppsLibrary.tsx +++ b/src/components/Apps/AppsLibrary.tsx @@ -42,7 +42,9 @@ const officialAppList = [ "qombo", "q-fund", "q-shop", - "q-trade" + "q-trade", + "q-support", + "NodeInfo" ]; const ScrollerStyled = styled('div')({ diff --git a/src/components/Apps/AppsLibraryDesktop.tsx b/src/components/Apps/AppsLibraryDesktop.tsx index f7fe9c0..88f6a0d 100644 --- a/src/components/Apps/AppsLibraryDesktop.tsx +++ b/src/components/Apps/AppsLibraryDesktop.tsx @@ -57,7 +57,9 @@ const officialAppList = [ "qombo", "q-fund", "q-shop", - "q-trade" + "q-trade", + "q-support", + "NodeInfo" ]; const ScrollerStyled = styled("div")({ diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index a3be2df..af715d6 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -140,7 +140,7 @@ const UIQortalRequests = [ 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', - 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY' + 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION' ]; @@ -317,7 +317,7 @@ const UIQortalRequests = [ return obj; // Updated object with references to stored files } -export const useQortalMessageListener = (frameWindow, iframeRef, tabId) => { +export const useQortalMessageListener = (frameWindow, iframeRef, tabId, appName, appService) => { const [path, setPath] = useState('') const [history, setHistory] = useState({ customQDNHistoryPaths: [], @@ -387,7 +387,9 @@ isDOMContentLoaded: false // Check if action is included in the predefined list of UI requests if (UIQortalRequests.includes(event.data.action)) { sendMessageToRuntime( - { action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true }, + { action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true, appInfo: { + name: appName, service: appService + } }, event.ports[0] ); } else if ( @@ -465,7 +467,7 @@ isDOMContentLoaded: false }; - }, []); // Empty dependency array to run once when the component mounts + }, [appName, appService]); // Empty dependency array to run once when the component mounts chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) { if(message.action === "SHOW_SAVE_FILE_PICKER"){ diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 16c1dd0..14c5f91 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,5 +1,5 @@ import { getApiKeyFromStorage } from "./background"; -import { addForeignServer, addListItems, cancelSellOrder, createBuyOrder, createPoll, createSellOrder, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; +import { addForeignServer, addListItems, adminAction, cancelSellOrder, createBuyOrder, createPoll, createSellOrder, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; @@ -74,9 +74,10 @@ function getLocalStorage(key) { chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { if (request) { const isFromExtension = request?.isExtension + const appInfo = request?.appInfo; switch (request.action) { case "GET_USER_ACCOUNT": { - getUserAccount() + getUserAccount({isFromExtension, appInfo}) .then((res) => { sendResponse(res); }) @@ -270,7 +271,7 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { case "GET_WALLET_BALANCE": { const data = request.payload; - getWalletBalance(data, false, isFromExtension) + getWalletBalance(data, false, isFromExtension, appInfo) .then((res) => { sendResponse(res); }) @@ -477,6 +478,17 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { }); break; } + + case "ADMIN_ACTION": { + adminAction(data, isFromExtension).then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error?.message }); + }); + + break; + } } } return true; diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index a774920..c8c0835 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -364,8 +364,30 @@ async function getUserPermission(payload: any, isFromExtension?: boolean) { }); } -export const getUserAccount = async () => { +export const getUserAccount = async ({isFromExtension, appInfo}) => { try { + const value = (await getPermission(`qAPPAutoAuth-${appInfo?.name}`)) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission + if(!skip){ + resPermission = await getUserPermission({ + text1: "Do you give this application permission to authenticate?", + checkbox1: { + value: false, + label: "Always authenticate automatically", + }, + }, isFromExtension); + } + + const { accepted = false, checkbox1 = false } = resPermission || {}; + if(resPermission){ + setPermission(`qAPPAutoAuth-${appInfo?.name}`, checkbox1); + } + if (accepted || skip) { + const wallet = await getSaveWallet(); const address = wallet.address0; const publicKey = wallet.publicKey; @@ -373,7 +395,12 @@ export const getUserAccount = async () => { address, publicKey, }; + } else { + throw new Error("User declined request"); + + } } catch (error) { + console.log('per error', error) throw new Error("Unable to fetch user account"); } }; @@ -447,8 +474,10 @@ export const decryptData = async (data) => { }; export const getListItems = async (data, isFromExtension) => { - const localNodeAvailable = await isUsingLocal() - if(!localNodeAvailable) throw new Error('Please use your local node.') + const isGateway = await isRunningGateway() + if(isGateway){ + throw new Error('This action cannot be done through a gateway') + } const requiredFields = ["list_name"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -499,8 +528,10 @@ export const getListItems = async (data, isFromExtension) => { }; export const addListItems = async (data, isFromExtension) => { - const localNodeAvailable = await isUsingLocal() - if(!localNodeAvailable) throw new Error('Please use your local node.') + const isGateway = await isRunningGateway() + if(isGateway){ + throw new Error('This action cannot be done through a gateway') + } const requiredFields = ["list_name", "items"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -552,8 +583,10 @@ export const addListItems = async (data, isFromExtension) => { }; export const deleteListItems = async (data, isFromExtension) => { - const localNodeAvailable = await isUsingLocal() - if(!localNodeAvailable) throw new Error('Please use your local node.') + const isGateway = await isRunningGateway() + if(isGateway){ + throw new Error('This action cannot be done through a gateway') + } const requiredFields = ["list_name", "item"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -1431,7 +1464,7 @@ export const getUserWallet = async (data, isFromExtension) => { } }; -export const getWalletBalance = async (data, bypassPermission?: boolean, isFromExtension) => { +export const getWalletBalance = async (data, bypassPermission?: boolean, isFromExtension, appInfo) => { const requiredFields = ["coin"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -1445,7 +1478,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE throw new Error(errorMsg); } - const value = (await getPermission(`qAPPAutoWalletBalance-${data.coin}`)) || false; + const value = (await getPermission(`qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`)) || false; let skip = false; if (value) { skip = true; @@ -1465,7 +1498,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE const { accepted = false, checkbox1 = false } = resPermission || {}; if(resPermission){ - setPermission(`qAPPAutoWalletBalance-${data.coin}`, checkbox1); + setPermission(`qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`, checkbox1); } if (accepted || bypassPermission || skip) { let coin = data.coin; @@ -2093,8 +2126,9 @@ export const sendCoin = async (data, isFromExtension) => { const address = wallet.address0; const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); - const localNodeAvailable = await isUsingLocal() - if(checkCoin !== 'QORT' && !localNodeAvailable) throw new Error('Cannot send a non-QORT coin through the gateway. Please use your local node.') + const isGateway = await isRunningGateway() + + if(checkCoin !== 'QORT' && isGateway) throw new Error('Cannot send a non-QORT coin through the gateway. Please use your local node.') if (checkCoin === "QORT") { // Params: data.coin, data.destinationAddress, data.amount, data.fee // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction @@ -2789,4 +2823,60 @@ export const cancelSellOrder = async (data, isFromExtension) => { } catch (error) { throw new Error(error?.message || "Failed to submit sell order."); } +}; + +export const adminAction = async (data, isFromExtension) => { + const requiredFields = [ + "type", + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(", "); + const errorMsg = `Missing fields: ${missingFieldsString}`; + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway() + if(isGateway){ + throw new Error('This action cannot be done through a gateway') + } + + let apiEndpoint = ''; + switch (data.type.toLowerCase()) { + case 'stop': + apiEndpoint = await createEndpoint('/admin/stop'); + break; + case 'restart': + apiEndpoint = await createEndpoint('/admin/restart'); + break; + case 'bootstrap': + apiEndpoint = await createEndpoint('/admin/bootstrap'); + break; + default: + throw new Error(`Unknown admin action type: ${data.type}`); + } + + const resPermission = await getUserPermission({ + text1: `Do you give this application permission to perform a node ${data.type}?`, + }, isFromExtension); + const { accepted } = resPermission; + if (accepted) { + const response = await fetch(apiEndpoint); + if (!response.ok) throw new Error("Failed to perform request"); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error("User declined request"); + } + }; \ No newline at end of file