dynamic remote node feature

This commit is contained in:
PhilReact 2024-10-24 03:55:10 +03:00
parent 4f343178bc
commit aeeac2fdaa
4 changed files with 458 additions and 116 deletions

View File

@ -78,7 +78,9 @@ import { Label } from "./components/Group/AddGroup";
import { CustomizedSnackbars } from "./components/Snackbar/Snackbar";
import SettingsIcon from "@mui/icons-material/Settings";
import {
cleanUrl,
getFee,
getProtocol,
groupApi,
groupApiLocal,
groupApiSocket,
@ -147,7 +149,7 @@ const defaultValues: MyContextInterface = {
message: "",
},
};
export let isMobile = false;
export let isMobile = true;
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
@ -226,7 +228,7 @@ export const getBaseApiReact = (customApi?: string) => {
}
if (globalApiKey) {
return groupApiLocal;
return globalApiKey?.url;
} else {
return groupApi;
}
@ -252,7 +254,7 @@ export const getBaseApiReactSocket = (customApi?: string) => {
}
if (globalApiKey) {
return groupApiSocketLocal;
return `${getProtocol(globalApiKey?.url) === 'http' ? 'ws://': 'wss://'}${cleanUrl(globalApiKey?.url)}`
} else {
return groupApiSocket;
}
@ -329,7 +331,6 @@ function App() {
const [infoSnack, setInfoSnack] = useState(null);
const [openSnack, setOpenSnack] = useState(false);
const [hasLocalNode, setHasLocalNode] = useState(false);
const [openAdvancedSettings, setOpenAdvancedSettings] = useState(false);
const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false);
const [apiKey, setApiKey] = useState("");
const [isOpenSendQort, setIsOpenSendQort] = useState(false);
@ -406,7 +407,7 @@ function App() {
console.log('response', response)
handleSetGlobalApikey(response)
setApiKey(response);
setOpenAdvancedSettings(true);
}
});
}, []);
@ -420,18 +421,7 @@ function App() {
isFocusedRef.current = isFocused;
}, [isFocused]);
// Handler for file selection
const handleFileChangeApiKey = (event) => {
const file = event.target.files[0]; // Get the selected file
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const text = e.target.result; // Get the file content
setApiKey(text); // Store the file content in the state
};
reader.readAsText(file); // Read the file as text
}
};
// const checkIfUserHasLocalNode = useCallback(async () => {
// try {
@ -1068,8 +1058,6 @@ function App() {
setWalletToBeDownloadedError("");
setSendqortState(null);
setHasLocalNode(false);
setOpenAdvancedSettings(false);
setConfirmUseOfLocal(false);
setTxList([]);
setMemberGroups([]);
resetAllRecoil()
@ -1563,7 +1551,7 @@ function App() {
>
{extState === "not-authenticated" && (
<NotAuthenticated getRootProps={getRootProps} getInputProps={getInputProps} setExtstate={setExtstate} setOpenAdvancedSettings={setOpenAdvancedSettings} openAdvancedSettings={openAdvancedSettings} handleFileChangeApiKey={handleFileChangeApiKey} apiKey={apiKey} globalApiKey={globalApiKey} setApiKey={setApiKey} handleSetGlobalApikey={handleSetGlobalApikey}/>
<NotAuthenticated getRootProps={getRootProps} getInputProps={getInputProps} setExtstate={setExtstate} apiKey={apiKey} globalApiKey={globalApiKey} setApiKey={setApiKey} handleSetGlobalApikey={handleSetGlobalApikey}/>
)}
{/* {extState !== "not-authenticated" && (
<button onClick={logoutFunc}>logout</button>

View File

@ -1,11 +1,16 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Spacer } from "../common/Spacer";
import { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles";
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel,
Input,
Switch,
Tooltip,
Typography,
@ -14,14 +19,15 @@ import Logo1 from "../assets/svgs/Logo1.svg";
import Logo1Dark from "../assets/svgs/Logo1Dark.svg";
import Info from "../assets/svgs/Info.svg";
import { CustomizedSnackbars } from "../components/Snackbar/Snackbar";
import { set } from "lodash";
import { cleanUrl, isUsingLocal } from "../background";
export const NotAuthenticated = ({
getRootProps,
getInputProps,
setExtstate,
setOpenAdvancedSettings,
openAdvancedSettings,
handleFileChangeApiKey,
apiKey,
setApiKey,
globalApiKey,
@ -33,6 +39,36 @@ export const NotAuthenticated = ({
const [useLocalNode, setUseLocalNode] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [show, setShow] = React.useState(false);
const [mode, setMode] = React.useState("list");
const [customNodes, setCustomNodes] = React.useState(null);
const [currentNode, setCurrentNode] = React.useState({
url: "http://127.0.0.1:12391",
});
const [importedApiKey, setImportedApiKey] = React.useState(null);
//add and edit states
const [url, setUrl] = React.useState("http://");
const [customApikey, setCustomApiKey] = React.useState("");
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
React.useState(null);
const importedApiKeyRef = useRef(null)
const currentNodeRef = useRef(null)
const isLocal = cleanUrl(currentNode?.url) === "127.0.0.1:12391";
const handleFileChangeApiKey = (event) => {
const file = event.target.files[0]; // Get the selected file
console.log('file', file)
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const text = e.target.result; // Get the file content
console.log('text', text)
setImportedApiKey(text); // Store the file content in the state
};
reader.readAsText(file); // Read the file as text
}
};
const checkIfUserHasLocalNode = useCallback(async () => {
try {
@ -44,7 +80,7 @@ export const NotAuthenticated = ({
},
});
const data = await response.json();
if (data?.syncPercent) {
if (data?.height) {
setHasLocalNode(true);
}
} catch (error) {}
@ -54,14 +90,55 @@ export const NotAuthenticated = ({
checkIfUserHasLocalNode();
}, []);
useEffect(() => {
chrome?.runtime?.sendMessage(
{ action: "getCustomNodesFromStorage" },
(response) => {
if (response) {
console.log("response", response);
setCustomNodes(response || []);
}
}
);
}, []);
useEffect(()=> {
importedApiKeyRef.current = importedApiKey
}, [importedApiKey])
useEffect(()=> {
currentNodeRef.current = currentNode
}, [currentNode])
console.log('currentNode', currentNode)
const validateApiKey = useCallback(async (key) => {
try {
const url = `http://127.0.0.1:12391/admin/apikey/test`;
console.log('currentNodeRef.current', currentNodeRef.current, key)
if(!currentNodeRef.current) return
const isLocalKey = cleanUrl(key?.url) === "127.0.0.1:12391";
const isCurrentNodeLocal = cleanUrl(currentNodeRef.current?.url) === "127.0.0.1:12391";
if(isLocalKey && !isCurrentNodeLocal) {
setIsValidApiKey(false);
setUseLocalNode(false);
return
}
let payload = {};
if (currentNodeRef.current?.url === "http://127.0.0.1:12391") {
payload = {
apikey: importedApiKeyRef.current || key?.apikey,
url: currentNode?.url,
};
} else if(currentNodeRef.current) {
payload = currentNodeRef.current;
}
console.log('payload', payload)
const url = `${payload?.url}/admin/apikey/test`;
const response = await fetch(url, {
method: "GET",
headers: {
accept: "text/plain",
"X-API-KEY": key, // Include the API key here
"X-API-KEY": payload?.apikey, // Include the API key here
},
});
@ -69,7 +146,6 @@ export const NotAuthenticated = ({
const data = await response.text();
console.log("data", data);
if (data === "true") {
const payload = key;
chrome?.runtime?.sendMessage(
{ action: "setApiKey", payload },
(response) => {
@ -107,6 +183,44 @@ export const NotAuthenticated = ({
}
}, [apiKey]);
const addCustomNode = () => {
setMode("add-node");
};
const saveCustomNodes = (myNodes) => {
let nodes = [...(myNodes || [])];
console.log("customNodeToSaveIndex", customNodeToSaveIndex);
if (customNodeToSaveIndex !== null) {
nodes.splice(customNodeToSaveIndex, 1, {
url,
apikey: customApikey,
});
} else if (url && customApikey) {
nodes.push({
url,
apikey: customApikey,
});
}
setCustomNodes(nodes);
setCustomNodeToSaveIndex(null);
if (!nodes) return;
chrome?.runtime?.sendMessage(
{ action: "setCustomNodes", nodes },
(response) => {
console.log("setCustomNodes", response);
if (response) {
setMode("list");
setUrl("http://");
setCustomApiKey("");
// add alert
}
}
);
};
console.log("render customNodes", customNodes, mode);
return (
<>
<Spacer height="48px" />
@ -172,7 +286,16 @@ export const NotAuthenticated = ({
}}
/>
</Box>
<Spacer height="15px" />
<Typography
sx={{
fontSize: "12px",
visibility: !useLocalNode && 'hidden'
}}
>
{"Using node: "} {apiKey?.url}
</Typography>
<>
<Spacer height="15px" />
<Box
@ -196,97 +319,71 @@ export const NotAuthenticated = ({
<FormControlLabel
control={
<Switch
sx={{
'& .MuiSwitch-switchBase.Mui-checked': {
color: '#5EB049',
},
}}
sx={{
"& .MuiSwitch-switchBase.Mui-checked": {
color: "#5EB049",
},
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track":
{
backgroundColor: "white", // Change track color when checked
},
}}
checked={useLocalNode}
onChange={(event) => {
if (event.target.checked) {
validateApiKey(apiKey);
validateApiKey(currentNode);
} else {
setUseLocalNode(false);
const payload = null;
chrome?.runtime?.sendMessage(
{ action: "setApiKey", payload },
(response) => {
console.log("setApiKey", response);
if (response) {
globalApiKey = payload;
setApiKey(payload);
handleSetGlobalApikey(payload);
if (!globalApiKey) {
setUseLocalNode(false);
setOpenAdvancedSettings(false);
setApiKey("");
setCurrentNode({
url: "http://127.0.0.1:12391",
})
setUseLocalNode(false)
chrome?.runtime?.sendMessage(
{ action: "setApiKey", payload:null },
(response) => {
console.log("setApiKey", response);
if (response) {
setApiKey(payload);
handleSetGlobalApikey(payload);
}
}
}
);
);
}
}}
disabled={false}
defaultChecked
/>
}
label="Use Local Node"
label={`Use ${isLocal ? 'Local' : 'Custom'} Node`}
/>
</Box>
{currentNode?.url === "http://127.0.0.1:12391" && (
<>
<Button variant="contained" component="label">
{apiKey ? "Change " : "Import "} apiKey.txt
<input
type="file"
accept=".txt"
hidden
onChange={handleFileChangeApiKey} // File input handler
/>
</Button>
<Spacer height="5px" />
<>
<Button variant="contained" component="label">
{apiKey ? "Change " : "Import "} apiKey.txt
<input
type="file"
accept=".txt"
hidden
onChange={handleFileChangeApiKey} // File input handler
/>
</Button>
<Spacer height="5px" />
<Spacer height="5px" />
{apiKey && (
<>
<Button
onClick={() => {
const payload = null;
chrome?.runtime?.sendMessage(
{ action: "setApiKey", payload },
(response) => {
console.log("setApiKey", response);
if (response) {
globalApiKey = payload;
setApiKey(payload);
if (!globalApiKey) {
setUseLocalNode(false);
setOpenAdvancedSettings(false);
setApiKey("");
}
}
}
);
}}
variant="contained"
sx={{
color: "white",
}}
>
Clear Apikey
</Button>
<Typography
sx={{
fontSize: "12px",
}}
>
{"Apikey : "} {apiKey}
</Typography>
</>
)}
</>
</>
)}
<Button
onClick={() => {
setShow(true);
}}
variant="contained"
component="label"
>
Choose custom node
</Button>
</>
</Box>
</>
@ -296,6 +393,223 @@ export const NotAuthenticated = ({
info={infoSnack}
setInfo={setInfoSnack}
/>
{show && (
<Dialog
open={show}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
fullWidth
>
<DialogTitle id="alert-dialog-title">{"Custom nodes"}</DialogTitle>
<DialogContent>
<Box
sx={{
width: "100% !important",
overflow: "auto",
height: "60vh",
display: "flex",
flexDirection: "column",
}}
>
{mode === "list" && (
<Box
sx={{
gap: "20px",
display: "flex",
flexDirection: "column",
}}
>
<Box
sx={{
display: "flex",
gap: "10px",
flexDirection: "column",
}}
>
<Typography
sx={{
color: "white",
fontSize: "14px",
}}
>
http://127.0.0.1:12391
</Typography>
<Box
sx={{
display: "flex",
gap: "10px",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Button
disabled={currentNode?.url === "http://127.0.0.1:12391"}
size="small"
onClick={() => {
setCurrentNode({
url: "http://127.0.0.1:12391",
});
setMode("list");
setShow(false);
}}
variant="contained"
>
Choose
</Button>
</Box>
</Box>
{customNodes?.map((node, index) => {
return (
<Box
sx={{
display: "flex",
gap: "10px",
flexDirection: "column",
}}
>
<Typography
sx={{
color: "white",
fontSize: "14px",
}}
>
{node?.url}
</Typography>
<Box
sx={{
display: "flex",
gap: "10px",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Button
disabled={currentNode?.url === node?.url}
size="small"
onClick={() => {
setCurrentNode({
url: node?.url,
apikey: node?.apikey,
});
setMode("list");
setShow(false);
setIsValidApiKey(false);
setUseLocalNode(false);
}}
variant="contained"
>
Choose
</Button>
<Button
size="small"
onClick={() => {
setCustomApiKey(node?.apikey);
setUrl(node?.url);
setMode("add-node");
setCustomNodeToSaveIndex(index);
}}
variant="contained"
>
Edit
</Button>
<Button
size="small"
onClick={() => {
const nodesToSave = [
...(customNodes || []),
].filter((item) => item?.url !== node?.url);
console.log(
"nodesToSave",
nodesToSave,
customNodes,
node
);
saveCustomNodes(nodesToSave);
}}
variant="contained"
>
Remove
</Button>
</Box>
</Box>
);
})}
</Box>
)}
{mode === "add-node" && (
<Box
sx={{
display: "flex",
gap: "10px",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Input
placeholder="Url"
value={url}
onChange={(e) => {
setUrl(e.target.value);
}}
/>
<Input
placeholder="Api key"
value={customApikey}
onChange={(e) => {
setCustomApiKey(e.target.value);
}}
/>
</Box>
)}
</Box>
</DialogContent>
<DialogActions>
{mode === "list" && (
<>
<Button
variant="contained"
onClick={() => {
setShow(false);
}}
autoFocus
>
Close
</Button>
</>
)}
{mode === "list" && (
<Button variant="contained" onClick={addCustomNode}>
Add
</Button>
)}
{mode === "add-node" && (
<>
<Button
variant="contained"
onClick={() => {
setMode("list");
setCustomNodeToSaveIndex(null);
}}
>
Return to list
</Button>
<Button
variant="contained"
disabled={!customApikey || !url}
onClick={() => saveCustomNodes(customNodes)}
autoFocus
>
Save
</Button>
</>
)}
</DialogActions>
</Dialog>
)}
</>
);
};

View File

@ -31,6 +31,19 @@ import { Sha256 } from "asmcrypto.js";
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
export function cleanUrl(url) {
return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
}
export function getProtocol(url) {
if (url?.startsWith('https://')) {
return 'https';
} else if (url?.startsWith('http://')) {
return 'http';
} else {
return 'unknown'; // If neither protocol is present
}
}
let lastGroupNotification;
export const groupApi = "https://ext-node.qortal.link";
export const groupApiSocket = "wss://ext-node.qortal.link";
@ -40,6 +53,7 @@ const timeDifferenceForNotificationChatsBackground = 600000;
const requestQueueAnnouncements = new RequestQueueWithPromise(1);
let isMobile = false;
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
@ -106,6 +120,17 @@ const getApiKeyFromStorage = async () => {
});
};
const getCustomNodesFromStorage = async () => {
return new Promise((resolve, reject) => {
chrome.storage.local.get("customNodes", (result) => {
if (chrome.runtime.lastError) {
return reject(chrome.runtime.lastError);
}
resolve(result.customNodes || null); // Return null if apiKey isn't found
});
});
};
// const getArbitraryEndpoint = ()=> {
// const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
// if (apiKey) {
@ -130,7 +155,7 @@ export const getBaseApi = async (customApi?: string) => {
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey) {
return groupApiLocal;
return apiKey?.url;
} else {
return groupApi;
}
@ -145,15 +170,7 @@ export const isUsingLocal = async () => {
}
};
export const createEndpointSocket = async (endpoint) => {
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey) {
return `${groupApiSocketLocal}${endpoint}`;
} else {
return `${groupApiSocket}${endpoint}`;
}
};
export const createEndpoint = async (endpoint, customApi?: string) => {
if (customApi) {
@ -165,7 +182,7 @@ export const createEndpoint = async (endpoint, customApi?: string) => {
if (apiKey) {
// Check if the endpoint already contains a query string
const separator = endpoint.includes("?") ? "&" : "?";
return `${groupApiLocal}${endpoint}${separator}apiKey=${apiKey}`;
return `${apiKey?.url}${endpoint}${separator}apiKey=${apiKey?.apikey}`;
} else {
return `${groupApi}${endpoint}`;
}
@ -1816,7 +1833,7 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) {
let responseVar
const txn = new TradeBotRespondMultipleRequest().createTransaction(message)
const apiKey = await getApiKeyFromStorage();
const responseFetch = await fetch(`http://127.0.0.1:12391/crosschain/tradebot/respondmultiple?apiKey=${apiKey}`, {
const responseFetch = await fetch(`${apiKey?.url}/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -3291,6 +3308,16 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
return true;
break;
}
case "setCustomNodes": {
const { nodes } = request;
// Save the customNodes in chrome.storage.local for persistence
chrome.storage.local.set({ customNodes: nodes }, () => {
sendResponse(true);
});
return true;
break;
}
case "getApiKey": {
getApiKeyFromStorage()
.then((res) => {
@ -3303,6 +3330,19 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
return true;
break;
}
case "getCustomNodesFromStorage": {
getCustomNodesFromStorage()
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
return true;
break;
}
case "notifyAdminRegenerateSecretKey": {
const { groupName, adminAddress } = request.payload;
notifyAdminRegenerateSecretKey({ groupName, adminAddress })

View File

@ -28,7 +28,7 @@ export const useAppFullScreen = (setFullScreen) => {
}, []);
const toggleFullScreen = useCallback(() => {
if(!isMobile) return
if(!isMobile || isMobile) return
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
exitFullScreen();
setFullScreen(false)