diff --git a/src/App.tsx b/src/App.tsx index 8072bc9..fe1f497 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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" && ( - + )} {/* {extState !== "not-authenticated" && ( diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index c60a97f..4ccdb65 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -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 ( <> @@ -172,7 +286,16 @@ export const NotAuthenticated = ({ }} /> - + + + + {"Using node: "} {apiKey?.url} + <> { 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`} /> + {currentNode?.url === "http://127.0.0.1:12391" && ( + <> + + - <> - - - - - {apiKey && ( - <> - - - {"Apikey : "} {apiKey} - - - )} - + + + + )} + @@ -296,6 +393,223 @@ export const NotAuthenticated = ({ info={infoSnack} setInfo={setInfoSnack} /> + {show && ( + + {"Custom nodes"} + + + {mode === "list" && ( + + + + http://127.0.0.1:12391 + + + + + + + {customNodes?.map((node, index) => { + return ( + + + {node?.url} + + + + + + + + ); + })} + + )} + {mode === "add-node" && ( + + { + setUrl(e.target.value); + }} + /> + { + setCustomApiKey(e.target.value); + }} + /> + + )} + + + + {mode === "list" && ( + <> + + + )} + {mode === "list" && ( + + )} + + {mode === "add-node" && ( + <> + + + + + )} + + + )} ); }; diff --git a/src/background.ts b/src/background.ts index b4780bd..2f5f5c1 100644 --- a/src/background.ts +++ b/src/background.ts @@ -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 }) diff --git a/src/useAppFullscreen.tsx b/src/useAppFullscreen.tsx index 00a700c..3dbe4a1 100644 --- a/src/useAppFullscreen.tsx +++ b/src/useAppFullscreen.tsx @@ -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)