added admin action qortal request

This commit is contained in:
PhilReact 2024-11-13 05:04:54 +02:00
parent f1eef8da71
commit 93a2071e77
11 changed files with 156 additions and 26 deletions

View File

@ -651,7 +651,7 @@ function App() {
if (message.action === "QORTAL_REQUEST_PERMISSION") { if (message.action === "QORTAL_REQUEST_PERMISSION") {
try { try {
if(message?.payload?.checkbox1){ if(message?.payload?.checkbox1){
qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1 qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false
} }
await showQortalRequestExtension(message?.payload); await showQortalRequestExtension(message?.payload);

View File

@ -28,6 +28,14 @@ export const sortablePinnedAppsAtom = atom({
{ {
name: 'Q-Trade', name: 'Q-Trade',
service: 'APP' service: 'APP'
},
{
name: 'Q-Support',
service: 'APP'
},
{
name: 'NodeInfo',
service: 'APP'
} }
], ],
}); });

View File

@ -15,7 +15,7 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef)
const { rootHeight } = useContext(MyContext); const { rootHeight } = useContext(MyContext);
// const iframeRef = useRef(null); // const iframeRef = useRef(null);
const { document, window: frameWindow } = useFrame(); const { document, window: frameWindow } = useFrame();
const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode) const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service)
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
useEffect(()=> { useEffect(()=> {

View File

@ -39,6 +39,9 @@ const officialAppList = [
"qombo", "qombo",
"q-fund", "q-fund",
"q-shop", "q-shop",
"q-trade",
"q-support",
"NodeInfo"
]; ];
const ScrollerStyled = styled('div')({ const ScrollerStyled = styled('div')({

View File

@ -47,7 +47,9 @@ const officialAppList = [
"qombo", "qombo",
"q-fund", "q-fund",
"q-shop", "q-shop",
"q-trade" "q-trade",
"q-support",
"NodeInfo"
]; ];
const ScrollerStyled = styled("div")({ const ScrollerStyled = styled("div")({

View File

@ -41,7 +41,9 @@ const officialAppList = [
"qombo", "qombo",
"q-fund", "q-fund",
"q-shop", "q-shop",
"q-trade" "q-trade",
"q-support",
"NodeInfo"
]; ];
const ScrollerStyled = styled('div')({ const ScrollerStyled = styled('div')({

View File

@ -56,7 +56,9 @@ const officialAppList = [
"qombo", "qombo",
"q-fund", "q-fund",
"q-shop", "q-shop",
"q-trade" "q-trade",
"q-support",
"NodeInfo"
]; ];
const ScrollerStyled = styled("div")({ const ScrollerStyled = styled("div")({

View File

@ -182,7 +182,7 @@ const UIQortalRequests = [
'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO',
'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE',
'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', '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' 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION'
]; ];
@ -382,7 +382,7 @@ const UIQortalRequests = [
return obj; // Updated object with references to stored files return obj; // Updated object with references to stored files
} }
export const useQortalMessageListener = (frameWindow, iframeRef, tabId, isDevMode) => { export const useQortalMessageListener = (frameWindow, iframeRef, tabId, isDevMode, appName, appService) => {
const [path, setPath] = useState('') const [path, setPath] = useState('')
const [history, setHistory] = useState({ const [history, setHistory] = useState({
customQDNHistoryPaths: [], customQDNHistoryPaths: [],
@ -436,7 +436,9 @@ isDOMContentLoaded: false
if (event?.data?.requestedHandler !== 'UI') return; if (event?.data?.requestedHandler !== 'UI') return;
const sendMessageToRuntime = (message, eventPort) => { const sendMessageToRuntime = (message, eventPort) => {
window.sendMessage(message.action, message.payload, 300000, message.isExtension) window.sendMessage(message.action, message.payload, 300000, message.isExtension, {
name: appName, service: appService
})
.then((response) => { .then((response) => {
if (response.error) { if (response.error) {
eventPort.postMessage({ eventPort.postMessage({
@ -553,7 +555,7 @@ isDOMContentLoaded: false
}; };
}, [isDevMode]); // Empty dependency array to run once when the component mounts }, [isDevMode, appName, appService]); // Empty dependency array to run once when the component mounts

View File

@ -24,13 +24,13 @@ window.addEventListener("message", (event) => {
} }
}); });
export const sendMessageBackground = (action, data = {}, timeout = 60000, isExtension) => { export const sendMessageBackground = (action, data = {}, timeout = 60000, isExtension, appInfo) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const requestId = generateRequestId(); // Unique ID for each request const requestId = generateRequestId(); // Unique ID for each request
callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks
const targetOrigin = window.location.origin const targetOrigin = window.location.origin
// Send the message with `backgroundMessage` type // Send the message with `backgroundMessage` type
window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension }, targetOrigin); window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension, appInfo }, targetOrigin);
// Set up a timeout to automatically reject if no response is received // Set up a timeout to automatically reject if no response is received
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {

View File

@ -1,5 +1,5 @@
import { gateways, getApiKeyFromStorage } from "./background"; import { gateways, 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";
import { getData, storeData } from "./utils/chromeStorage"; import { getData, storeData } from "./utils/chromeStorage";
@ -68,6 +68,7 @@ export const isRunningGateway = async ()=> {
// Ensure the message is from a trusted source // Ensure the message is from a trusted source
const isFromExtension = request?.isExtension; const isFromExtension = request?.isExtension;
const appInfo = request?.appInfo;
if (request?.type !== "backgroundMessage") return; // Only process messages of type 'backgroundMessage' if (request?.type !== "backgroundMessage") return; // Only process messages of type 'backgroundMessage'
@ -75,7 +76,7 @@ export const isRunningGateway = async ()=> {
switch (request.action) { switch (request.action) {
case "GET_USER_ACCOUNT": { case "GET_USER_ACCOUNT": {
try { try {
const res = await getUserAccount(); const res = await getUserAccount({isFromExtension, appInfo});
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -357,7 +358,7 @@ export const isRunningGateway = async ()=> {
case "GET_WALLET_BALANCE": { case "GET_WALLET_BALANCE": {
try { try {
const res = await getWalletBalance(request.payload, false, isFromExtension); const res = await getWalletBalance(request.payload, false, isFromExtension, appInfo);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -671,6 +672,25 @@ export const isRunningGateway = async ()=> {
} }
break; break;
} }
case "ADMIN_ACTION": {
try {
const res = await adminAction(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;
}
default: default:
break; break;
} }

View File

@ -287,8 +287,30 @@ async function getUserPermission(payload, isFromExtension) {
} }
export const getUserAccount = async () => { export const getUserAccount = async ({isFromExtension, appInfo}) => {
try { 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 wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
const publicKey = wallet.publicKey; const publicKey = wallet.publicKey;
@ -296,7 +318,12 @@ export const getUserAccount = async () => {
address, address,
publicKey, publicKey,
}; };
} else {
throw new Error("User declined request");
}
} catch (error) { } catch (error) {
console.log('per error', error)
throw new Error("Unable to fetch user account"); throw new Error("Unable to fetch user account");
} }
}; };
@ -369,8 +396,10 @@ export const decryptData = async (data) => {
}; };
export const getListItems = async (data, isFromExtension) => { export const getListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal() const isGateway = await isRunningGateway()
if(!localNodeAvailable) throw new Error('Please use your local node.') if(isGateway){
throw new Error('This action cannot be done through a gateway')
}
const requiredFields = ["list_name"]; const requiredFields = ["list_name"];
const missingFields: string[] = []; const missingFields: string[] = [];
requiredFields.forEach((field) => { requiredFields.forEach((field) => {
@ -421,8 +450,10 @@ export const getListItems = async (data, isFromExtension) => {
}; };
export const addListItems = async (data, isFromExtension) => { export const addListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal() const isGateway = await isRunningGateway()
if(!localNodeAvailable) throw new Error('Please use your local node.') if(isGateway){
throw new Error('This action cannot be done through a gateway')
}
const requiredFields = ["list_name", "items"]; const requiredFields = ["list_name", "items"];
const missingFields: string[] = []; const missingFields: string[] = [];
requiredFields.forEach((field) => { requiredFields.forEach((field) => {
@ -474,8 +505,10 @@ export const addListItems = async (data, isFromExtension) => {
}; };
export const deleteListItems = async (data, isFromExtension) => { export const deleteListItems = async (data, isFromExtension) => {
const localNodeAvailable = await isUsingLocal() const isGateway = await isRunningGateway()
if(!localNodeAvailable) throw new Error('Please use your local node.') if(isGateway){
throw new Error('This action cannot be done through a gateway')
}
const requiredFields = ["list_name", "item"]; const requiredFields = ["list_name", "item"];
const missingFields: string[] = []; const missingFields: string[] = [];
requiredFields.forEach((field) => { requiredFields.forEach((field) => {
@ -1359,7 +1392,7 @@ export const getUserWallet = async (data, isFromExtension) => {
} }
}; };
export const getWalletBalance = async (data, bypassPermission?: boolean, isFromExtension) => { export const getWalletBalance = async (data, bypassPermission?: boolean, isFromExtension?: boolean, appInfo?: any) => {
const requiredFields = ["coin"]; const requiredFields = ["coin"];
const missingFields: string[] = []; const missingFields: string[] = [];
requiredFields.forEach((field) => { requiredFields.forEach((field) => {
@ -1373,7 +1406,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE
throw new Error(errorMsg); 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; let skip = false;
if (value) { if (value) {
skip = true; skip = true;
@ -1392,7 +1425,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE
} }
const { accepted = false, checkbox1 = false } = resPermission || {}; const { accepted = false, checkbox1 = false } = resPermission || {};
if(resPermission){ if(resPermission){
setPermission(`qAPPAutoWalletBalance-${data.coin}`, checkbox1); setPermission(`qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`, checkbox1);
} }
if (accepted || bypassPermission || skip) { if (accepted || bypassPermission || skip) {
let coin = data.coin; let coin = data.coin;
@ -2020,8 +2053,9 @@ export const sendCoin = async (data, isFromExtension) => {
const address = wallet.address0; const address = wallet.address0;
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
const parsedData = resKeyPair; const parsedData = resKeyPair;
const localNodeAvailable = await isUsingLocal() const isGateway = await isRunningGateway()
if(checkCoin !== 'QORT' && !localNodeAvailable) throw new Error('Cannot send a non-QORT coin through the gateway. Please use your local node.')
if(checkCoin !== 'QORT' && isGateway) throw new Error('Cannot send a non-QORT coin through the gateway. Please use your local node.')
if (checkCoin === "QORT") { if (checkCoin === "QORT") {
// Params: data.coin, data.destinationAddress, data.amount, data.fee // 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 // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction
@ -2716,4 +2750,61 @@ export const cancelSellOrder = async (data, isFromExtension) => {
} catch (error) { } catch (error) {
throw new Error(error?.message || "Failed to submit sell order."); 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");
}
}; };