diff --git a/electron/src/index.ts b/electron/src/index.ts
index 759f662..338f553 100644
--- a/electron/src/index.ts
+++ b/electron/src/index.ts
@@ -23,7 +23,7 @@ const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig(
// Initialize our app. You can pass menu templates into the app here.
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
-const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
+export const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
// If deeplinking is enabled then we will set it up here.
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
diff --git a/electron/src/preload.ts b/electron/src/preload.ts
index 9911817..f3e42a9 100644
--- a/electron/src/preload.ts
+++ b/electron/src/preload.ts
@@ -5,11 +5,16 @@ console.log('User Preload!');
const { contextBridge, shell, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
- openExternal: (url) => shell.openExternal(url)
+ openExternal: (url) => shell.openExternal(url),
+ setAllowedDomains: (domains) => {
+ ipcRenderer.send('set-allowed-domains', domains);
+ },
});
contextBridge.exposeInMainWorld('electron', {
onUpdateAvailable: (callback) => ipcRenderer.on('update_available', callback),
onUpdateDownloaded: (callback) => ipcRenderer.on('update_downloaded', callback),
restartApp: () => ipcRenderer.send('restart_app')
-});
\ No newline at end of file
+});
+
+ipcRenderer.send('test-ipc');
\ No newline at end of file
diff --git a/electron/src/setup.ts b/electron/src/setup.ts
index 3f267b8..b757b96 100644
--- a/electron/src/setup.ts
+++ b/electron/src/setup.ts
@@ -6,13 +6,34 @@ import {
} from '@capacitor-community/electron';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
-import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron';
+import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session, ipcMain } from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
import { join } from 'path';
+import { myCapacitorApp } from '.';
+const defaultDomains = [
+ 'http://127.0.0.1:12391',
+ 'ws://127.0.0.1:12391',
+ 'https://ext-node.qortal.link',
+ 'wss://ext-node.qortal.link',
+ 'https://appnode.qortal.org',
+ "https://api.qortal.org",
+ "https://api2.qortal.org",
+ "https://appnode.qortal.org",
+ "https://apinode.qortalnodes.live",
+ "https://apinode1.qortalnodes.live",
+ "https://apinode2.qortalnodes.live",
+ "https://apinode3.qortalnodes.live",
+ "https://apinode4.qortalnodes.live"
+
+];
+// let allowedDomains: string[] = [...defaultDomains]
+const domainHolder = {
+ allowedDomains: [...defaultDomains],
+};
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
const reloadWatcher = {
debouncer: null,
@@ -220,15 +241,79 @@ export class ElectronCapacitorApp {
}
// Set a CSP up for our application based on the custom scheme
+// export function setupContentSecurityPolicy(customScheme: string): void {
+// session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
+// callback({
+// responseHeaders: {
+// ...details.responseHeaders,
+// 'Content-Security-Policy': [
+// "script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*",
+// ],
+// },
+// });
+// });
+// }
+
+
export function setupContentSecurityPolicy(customScheme: string): void {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
- callback({
- responseHeaders: {
- ...details.responseHeaders,
- 'Content-Security-Policy': [
- "script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*",
- ],
- },
+ const allowedSources = ["'self'", ...domainHolder.allowedDomains].join(' ');
+ const csp = `
+ script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' ${allowedSources};
+ object-src 'self';
+ connect-src ${allowedSources};
+ `.replace(/\s+/g, ' ').trim();
+
+ callback({
+ responseHeaders: {
+ ...details.responseHeaders,
+ 'Content-Security-Policy': [csp],
+ },
+ });
});
- });
+
}
+
+
+
+// IPC listener for updating allowed domains
+ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
+
+ // Validate and transform user-provided domains
+ const validatedUserDomains = domains
+ .flatMap((domain) => {
+ try {
+ const url = new URL(domain);
+ const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
+ const socketUrl = `${protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`;
+ return [url.origin, socketUrl];
+ } catch {
+ return [];
+ }
+ })
+ .filter(Boolean) as string[];
+
+ // Combine default and validated user domains
+ const newAllowedDomains = [...new Set([...defaultDomains, ...validatedUserDomains])];
+
+ // Sort both current allowed domains and new domains for comparison
+ const sortedCurrentDomains = [...domainHolder.allowedDomains].sort();
+ const sortedNewDomains = [...newAllowedDomains].sort();
+
+ // Check if the lists are different
+ const hasChanged =
+ sortedCurrentDomains.length !== sortedNewDomains.length ||
+ sortedCurrentDomains.some((domain, index) => domain !== sortedNewDomains[index]);
+
+ // If there's a change, update allowedDomains and reload the window
+ if (hasChanged) {
+ domainHolder.allowedDomains = newAllowedDomains;
+
+ const mainWindow = myCapacitorApp.getMainWindow();
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.reload();
+ }
+ }
+});
+
+
diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx
index e92357d..cd5f8d8 100644
--- a/src/ExtStates/NotAuthenticated.tsx
+++ b/src/ExtStates/NotAuthenticated.tsx
@@ -23,15 +23,14 @@ import { set } from "lodash";
import { cleanUrl, isUsingLocal } from "../background";
const manifestData = {
- version: '0.2.0'
-}
+ version: "0.2.0",
+};
export const NotAuthenticated = ({
getRootProps,
getInputProps,
setExtstate,
-
apiKey,
setApiKey,
globalApiKey,
@@ -54,9 +53,9 @@ export const NotAuthenticated = ({
const [customApikey, setCustomApiKey] = React.useState("");
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
React.useState(null);
- const importedApiKeyRef = useRef(null)
- const currentNodeRef = useRef(null)
- const hasLocalNodeRef = useRef(null)
+ const importedApiKeyRef = useRef(null);
+ const currentNodeRef = useRef(null);
+ const hasLocalNodeRef = 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
@@ -71,7 +70,6 @@ export const NotAuthenticated = ({
}
};
-
const checkIfUserHasLocalNode = useCallback(async () => {
try {
const url = `http://127.0.0.1:12391/admin/status`;
@@ -93,43 +91,48 @@ export const NotAuthenticated = ({
}, []);
useEffect(() => {
- window.sendMessage("getCustomNodesFromStorage")
- .then((response) => {
- if (response) {
- setCustomNodes(response || []);
- }
- })
- .catch((error) => {
- console.error("Failed to get custom nodes from storage:", error.message || "An error occurred");
- });
-
+ window
+ .sendMessage("getCustomNodesFromStorage")
+ .then((response) => {
+ if (response) {
+ setCustomNodes(response || []);
+ window.electronAPI.setAllowedDomains(response?.map((node)=> node.url))
+
+ }
+ })
+ .catch((error) => {
+ console.error(
+ "Failed to get custom nodes from storage:",
+ error.message || "An error occurred"
+ );
+ });
}, []);
- useEffect(()=> {
- importedApiKeyRef.current = importedApiKey
- }, [importedApiKey])
- useEffect(()=> {
- currentNodeRef.current = currentNode
- }, [currentNode])
+ useEffect(() => {
+ importedApiKeyRef.current = importedApiKey;
+ }, [importedApiKey]);
+ useEffect(() => {
+ currentNodeRef.current = currentNode;
+ }, [currentNode]);
- useEffect(()=> {
- hasLocalNodeRef.current = hasLocalNode
- }, [hasLocalNode])
+ useEffect(() => {
+ hasLocalNodeRef.current = hasLocalNode;
+ }, [hasLocalNode]);
const validateApiKey = useCallback(async (key, fromStartUp) => {
try {
- if(!currentNodeRef.current) return
- const isLocalKey = cleanUrl(key?.url) === "127.0.0.1:12391";
- if(isLocalKey && !hasLocalNodeRef.current && !fromStartUp){
- throw new Error('Please turn on your local node')
-
- }
- const isCurrentNodeLocal = cleanUrl(currentNodeRef.current?.url) === "127.0.0.1:12391";
- if(isLocalKey && !isCurrentNodeLocal) {
- setIsValidApiKey(false);
- setUseLocalNode(false);
- return
- }
+ if (!currentNodeRef.current) return;
+ const isLocalKey = cleanUrl(key?.url) === "127.0.0.1:12391";
+ if (isLocalKey && !hasLocalNodeRef.current && !fromStartUp) {
+ throw new Error("Please turn on your local node");
+ }
+ 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") {
@@ -137,7 +140,7 @@ export const NotAuthenticated = ({
apikey: importedApiKeyRef.current || key?.apikey,
url: currentNodeRef.current?.url,
};
- } else if(currentNodeRef.current) {
+ } else if (currentNodeRef.current) {
payload = currentNodeRef.current;
}
const url = `${payload?.url}/admin/apikey/test`;
@@ -152,21 +155,24 @@ export const NotAuthenticated = ({
// Assuming the response is in plain text and will be 'true' or 'false'
const data = await response.text();
if (data === "true") {
- window.sendMessage("setApiKey", payload)
- .then((response) => {
- if (response) {
- handleSetGlobalApikey(payload);
- setIsValidApiKey(true);
- setUseLocalNode(true);
- if (!fromStartUp) {
- setApiKey(payload);
+ window
+ .sendMessage("setApiKey", payload)
+ .then((response) => {
+ if (response) {
+ handleSetGlobalApikey(payload);
+ setIsValidApiKey(true);
+ setUseLocalNode(true);
+ if (!fromStartUp) {
+ setApiKey(payload);
+ }
}
- }
- })
- .catch((error) => {
- console.error("Failed to set API key:", error.message || "An error occurred");
- });
-
+ })
+ .catch((error) => {
+ console.error(
+ "Failed to set API key:",
+ error.message || "An error occurred"
+ );
+ });
} else {
setIsValidApiKey(false);
setUseLocalNode(false);
@@ -213,24 +219,28 @@ export const NotAuthenticated = ({
}
setCustomNodes(nodes);
+ window.electronAPI.setAllowedDomains(nodes?.map((node)=> node.url))
+
setCustomNodeToSaveIndex(null);
if (!nodes) return;
- window.sendMessage("setCustomNodes", nodes)
- .then((response) => {
- if (response) {
- setMode("list");
- setUrl("http://");
- setCustomApiKey("");
- // add alert if needed
- }
- })
- .catch((error) => {
- console.error("Failed to set custom nodes:", error.message || "An error occurred");
- });
-
+ window
+ .sendMessage("setCustomNodes", nodes)
+ .then((response) => {
+ if (response) {
+ setMode("list");
+ setUrl("http://");
+ setCustomApiKey("");
+ // add alert if needed
+ }
+ })
+ .catch((error) => {
+ console.error(
+ "Failed to set custom nodes:",
+ error.message || "An error occurred"
+ );
+ });
};
-
return (
<>
@@ -296,16 +306,16 @@ export const NotAuthenticated = ({
}}
/>
-
-
-
- {"Using node: "} {currentNode?.url}
-
+
+
+
+ {"Using node: "} {currentNode?.url}
+
<>
{
+ if (response) {
+ setApiKey(null);
+ handleSetGlobalApikey(null);
+ }
})
- setUseLocalNode(false)
- window.sendMessage("setApiKey", null)
- .then((response) => {
- if (response) {
- setApiKey(null);
- handleSetGlobalApikey(null);
- }
- })
- .catch((error) => {
- console.error("Failed to set API key:", error.message || "An error occurred");
- });
-
+ .catch((error) => {
+ console.error(
+ "Failed to set API key:",
+ error.message || "An error occurred"
+ );
+ });
}
-
}}
disabled={false}
defaultChecked
/>
}
- label={`Use ${isLocal ? 'Local' : 'Custom'} Node`}
+ label={`Use ${isLocal ? "Local" : "Custom"} Node`}
/>
{currentNode?.url === "http://127.0.0.1:12391" && (
@@ -379,31 +391,33 @@ export const NotAuthenticated = ({
onChange={handleFileChangeApiKey} // File input handler
/>
- {`api key : ${importedApiKey}`}
-
-
-
-
+ {`api key : ${importedApiKey}`}
>
)}
-
+
>
- Build version: {manifestData?.version}
+
+ Build version: {manifestData?.version}
+
>
-
{mode === "list" && (
{
- if (response) {
- setApiKey(null);
- handleSetGlobalApikey(null);
- }
- })
- .catch((error) => {
- console.error("Failed to set API key:", error.message || "An error occurred");
- });
-
+ window
+ .sendMessage("setApiKey", null)
+ .then((response) => {
+ if (response) {
+ setApiKey(null);
+ handleSetGlobalApikey(null);
+ }
+ })
+ .catch((error) => {
+ console.error(
+ "Failed to set API key:",
+ error.message || "An error occurred"
+ );
+ });
}}
variant="contained"
>
@@ -527,18 +543,21 @@ export const NotAuthenticated = ({
setMode("list");
setShow(false);
setIsValidApiKey(false);
- setUseLocalNode(false);
- window.sendMessage("setApiKey", null)
- .then((response) => {
- if (response) {
- setApiKey(null);
- handleSetGlobalApikey(null);
- }
- })
- .catch((error) => {
- console.error("Failed to set API key:", error.message || "An error occurred");
- });
-
+ setUseLocalNode(false);
+ window
+ .sendMessage("setApiKey", null)
+ .then((response) => {
+ if (response) {
+ setApiKey(null);
+ handleSetGlobalApikey(null);
+ }
+ })
+ .catch((error) => {
+ console.error(
+ "Failed to set API key:",
+ error.message || "An error occurred"
+ );
+ });
}}
variant="contained"
>
@@ -562,7 +581,6 @@ export const NotAuthenticated = ({
const nodesToSave = [
...(customNodes || []),
].filter((item) => item?.url !== node?.url);
-
saveCustomNodes(nodesToSave);
}}
@@ -601,9 +619,7 @@ export const NotAuthenticated = ({
/>
)}
-
-
{mode === "list" && (