forked from Qortal/Qortal-Hub
save wallet data when having more than one instance
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,4 +27,5 @@ release-builds/
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
# reports from scripts
|
# reports from scripts
|
||||||
scripts/i18n_report*
|
scripts/i18n_report*
|
||||||
|
*wallet-storage.json
|
||||||
@@ -5,7 +5,7 @@ import { app, MenuItem } from 'electron';
|
|||||||
import electronIsDev from 'electron-is-dev';
|
import electronIsDev from 'electron-is-dev';
|
||||||
import unhandled from 'electron-unhandled';
|
import unhandled from 'electron-unhandled';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import path from 'path'
|
||||||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
||||||
|
|
||||||
// Graceful handling of unhandled errors.
|
// Graceful handling of unhandled errors.
|
||||||
@@ -48,22 +48,32 @@ const checkForUpdates = async () => {
|
|||||||
|
|
||||||
// Run Application
|
// Run Application
|
||||||
(async () => {
|
(async () => {
|
||||||
// Wait for electron app to be ready.
|
|
||||||
await app.whenReady();
|
await app.whenReady();
|
||||||
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
|
|
||||||
|
// Set Content Security Policy
|
||||||
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
|
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
|
||||||
// Initialize our app, build windows, and load content.
|
|
||||||
|
// Initialize the app
|
||||||
await myCapacitorApp.init();
|
await myCapacitorApp.init();
|
||||||
// Check for updates if we are in a packaged app.
|
|
||||||
|
// 🔧 Inject additional arguments for preload here:
|
||||||
|
const userDataPath = app.getPath('userData');
|
||||||
|
|
||||||
|
const win = myCapacitorApp.getMainWindow();
|
||||||
|
if (win) {
|
||||||
|
win.webContents.session.setPreloads([path.join(__dirname, 'preload.js')]); // optional if not using capacitor-managed preload
|
||||||
|
win.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
||||||
|
|
||||||
|
// ⚠ Inject CLI arg manually so preload can read it
|
||||||
|
process.argv.push(`--userDataPath=${userDataPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start update checks
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
|
setInterval(checkForUpdates, 24 * 60 * 60 * 1000);
|
||||||
// Set up periodic update checks
|
|
||||||
|
|
||||||
setInterval(checkForUpdates, 24 * 60 * 60 * 1000); // 24 hours
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
// Handle when all of our windows are close (platforms have their own expectations).
|
// Handle when all of our windows are close (platforms have their own expectations).
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
|
|||||||
@@ -1,9 +1,36 @@
|
|||||||
require('./rt/electron-rt');
|
require('./rt/electron-rt');
|
||||||
//////////////////////////////
|
|
||||||
// User Defined Preload scripts below
|
|
||||||
console.log('User Preload!');
|
|
||||||
const { contextBridge, shell, ipcRenderer } = require('electron');
|
|
||||||
|
|
||||||
|
// ------------------- User Preload starts here -------------------
|
||||||
|
console.log('User Preload!');
|
||||||
|
|
||||||
|
const { contextBridge, shell, ipcRenderer } = require('electron');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Grab `--userDataPath=...` from process arguments (injected from main process)
|
||||||
|
const userDataArg = process.argv.find((arg) =>
|
||||||
|
arg.startsWith('--userDataPath=')
|
||||||
|
);
|
||||||
|
const userDataPath = userDataArg?.split('=')[1] || '.';
|
||||||
|
|
||||||
|
// Define path to the wallet storage JSON file
|
||||||
|
const filePath = path.join(userDataPath, 'wallet-storage.json');
|
||||||
|
|
||||||
|
// Manual JSON storage functions
|
||||||
|
function readData() {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeData(data) {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose Electron API
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
openExternal: (url) => shell.openExternal(url),
|
openExternal: (url) => shell.openExternal(url),
|
||||||
setAllowedDomains: (domains) => {
|
setAllowedDomains: (domains) => {
|
||||||
@@ -11,14 +38,31 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Expose other utility functions
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
onUpdateAvailable: (callback) => ipcRenderer.on('update_available', callback),
|
onUpdateAvailable: (callback) => ipcRenderer.on('update_available', callback),
|
||||||
onUpdateDownloaded: (callback) => ipcRenderer.on('update_downloaded', callback),
|
onUpdateDownloaded: (callback) =>
|
||||||
|
ipcRenderer.on('update_downloaded', callback),
|
||||||
restartApp: () => ipcRenderer.send('restart_app'),
|
restartApp: () => ipcRenderer.send('restart_app'),
|
||||||
selectFile: async () => ipcRenderer.invoke('dialog:openFile'),
|
selectFile: async () => ipcRenderer.invoke('dialog:openFile'),
|
||||||
readFile: async (filePath) => ipcRenderer.invoke('fs:readFile', filePath),
|
readFile: async (filePath) => ipcRenderer.invoke('fs:readFile', filePath),
|
||||||
selectAndZipDirectory: async (filePath) => ipcRenderer.invoke('fs:selectAndZip', filePath),
|
selectAndZipDirectory: async (filePath) =>
|
||||||
|
ipcRenderer.invoke('fs:selectAndZip', filePath),
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.send('test-ipc');
|
// 👇 New: Expose walletStorage to the frontend
|
||||||
|
contextBridge.exposeInMainWorld('walletStorage', {
|
||||||
|
get: (key) => readData()[key],
|
||||||
|
set: (key, value) => {
|
||||||
|
const data = readData();
|
||||||
|
data[key] = value;
|
||||||
|
writeData(data);
|
||||||
|
},
|
||||||
|
delete: (key) => {
|
||||||
|
const data = readData();
|
||||||
|
delete data[key];
|
||||||
|
writeData(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.send('test-ipc');
|
||||||
|
|||||||
36
electron/src/storage.ts
Normal file
36
electron/src/storage.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// wallet-storage.ts
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { app } from 'electron';
|
||||||
|
|
||||||
|
const filePath = path.join(app.getPath('userData'), 'wallet-storage.json');
|
||||||
|
|
||||||
|
function readData(): Record<string, any> {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeData(data: Record<string, any>) {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValue<T = any>(key: string): T | undefined {
|
||||||
|
const data = readData();
|
||||||
|
return data[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setValue(key: string, value: any): void {
|
||||||
|
const data = readData();
|
||||||
|
data[key] = value;
|
||||||
|
writeData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteValue(key: string): void {
|
||||||
|
const data = readData();
|
||||||
|
delete data[key];
|
||||||
|
writeData(data);
|
||||||
|
}
|
||||||
30
src/OnLaunchWrapper.tsx
Normal file
30
src/OnLaunchWrapper.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const OnLaunchWrapper = ({ children }) => {
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
if (window.walletStorage) {
|
||||||
|
const res = window.walletStorage.get('apiKey');
|
||||||
|
if (res) {
|
||||||
|
window.sendMessage('setApiKey', res).finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoaded(true);
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setIsLoaded(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoaded(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoaded(true);
|
||||||
|
console.error(
|
||||||
|
'Error has occured when fetching apiKey info from file system'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return !isLoaded ? null : children;
|
||||||
|
};
|
||||||
@@ -952,6 +952,10 @@ export async function addTimestampEnterChatCase(request, event) {
|
|||||||
export async function setApiKeyCase(request, event) {
|
export async function setApiKeyCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const payload = request.payload;
|
const payload = request.payload;
|
||||||
|
|
||||||
|
if (window?.walletStorage) {
|
||||||
|
window.walletStorage.set('apiKey', payload);
|
||||||
|
}
|
||||||
storeData('apiKey', payload);
|
storeData('apiKey', payload);
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
@@ -977,7 +981,11 @@ export async function setApiKeyCase(request, event) {
|
|||||||
export async function setCustomNodesCase(request, event) {
|
export async function setCustomNodesCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const nodes = request.payload;
|
const nodes = request.payload;
|
||||||
storeData('customNodes', nodes);
|
if (window?.walletStorage) {
|
||||||
|
window.walletStorage.set('customNodes', nodes);
|
||||||
|
} else {
|
||||||
|
storeData('customNodes', nodes);
|
||||||
|
}
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -250,7 +250,13 @@ export const getApiKeyFromStorage = async (): Promise<string | null> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCustomNodesFromStorage = async (): Promise<any | null> => {
|
export const getCustomNodesFromStorage = async (): Promise<any | null> => {
|
||||||
return getData<any>('customNodes').catch(() => null);
|
if (window?.walletStorage) {
|
||||||
|
const res = window.walletStorage.get('customNodes');
|
||||||
|
|
||||||
|
return res || null;
|
||||||
|
} else {
|
||||||
|
return getData<any>('customNodes').catch(() => null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArbitraryEndpoint = async () => {
|
const getArbitraryEndpoint = async () => {
|
||||||
@@ -860,7 +866,20 @@ export async function getSaveWallet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getWallets() {
|
export async function getWallets() {
|
||||||
const res = await getData<any>('wallets').catch(() => null);
|
let res;
|
||||||
|
if (window?.walletStorage) {
|
||||||
|
res = window.walletStorage.get('wallets');
|
||||||
|
if (!res) {
|
||||||
|
const prevWallets = await getData<any>('wallets').catch(() => null);
|
||||||
|
if (prevWallets) {
|
||||||
|
window.walletStorage.set('wallets', prevWallets);
|
||||||
|
res = prevWallets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = await getData<any>('wallets').catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
@@ -869,9 +888,13 @@ export async function getWallets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function storeWallets(wallets) {
|
export async function storeWallets(wallets) {
|
||||||
storeData('wallets', wallets).catch((error) => {
|
if (window?.walletStorage) {
|
||||||
console.error(error);
|
window.walletStorage.set('wallets', wallets);
|
||||||
});
|
} else {
|
||||||
|
storeData('wallets', wallets).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserInfo() {
|
export async function getUserInfo() {
|
||||||
|
|||||||
@@ -722,7 +722,7 @@ export const NotAuthenticated = ({
|
|||||||
component="label"
|
component="label"
|
||||||
>
|
>
|
||||||
{apiKey
|
{apiKey
|
||||||
? t('auth:node.use_local', {
|
? t('auth:apikey.change', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
: t('auth:apikey.import', {
|
: t('auth:apikey.import', {
|
||||||
|
|||||||
@@ -88,17 +88,17 @@ export const QortPrice = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPrice();
|
// getPrice();
|
||||||
getSupplyInCirculation();
|
getSupplyInCirculation();
|
||||||
getLastBlock();
|
getLastBlock();
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
getPrice();
|
// getPrice();
|
||||||
getSupplyInCirculation();
|
getSupplyInCirculation();
|
||||||
getLastBlock();
|
getLastBlock();
|
||||||
}, 900000);
|
}, 900000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [getPrice]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -110,7 +110,7 @@ export const QortPrice = () => {
|
|||||||
width: '322px',
|
width: '322px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip
|
{/* <Tooltip
|
||||||
title={
|
title={
|
||||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||||
Based on the latest 20 trades
|
Based on the latest 20 trades
|
||||||
@@ -163,7 +163,7 @@ export const QortPrice = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip> */}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ export const useHandleTutorials = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const storedData = localStorage.getItem('shown-tutorials');
|
let storedData;
|
||||||
|
if (window?.walletStorage) {
|
||||||
|
storedData = window.walletStorage.get('shown-tutorials');
|
||||||
|
} else {
|
||||||
|
storedData = localStorage.getItem('shown-tutorials');
|
||||||
|
}
|
||||||
|
|
||||||
if (storedData) {
|
if (storedData) {
|
||||||
setShowTutorials(JSON.parse(storedData));
|
setShowTutorials(JSON.parse(storedData));
|
||||||
@@ -51,12 +56,17 @@ export const useHandleTutorials = () => {
|
|||||||
const saveShowTutorial = useCallback((type) => {
|
const saveShowTutorial = useCallback((type) => {
|
||||||
try {
|
try {
|
||||||
setShowTutorials((prev) => {
|
setShowTutorials((prev) => {
|
||||||
return {
|
const objectToSave = {
|
||||||
...(prev || {}),
|
...(prev || {}),
|
||||||
[type]: true,
|
[type]: true,
|
||||||
};
|
};
|
||||||
|
if (window?.walletStorage) {
|
||||||
|
window.walletStorage.set('shown-tutorials', objectToSave);
|
||||||
|
} else {
|
||||||
|
saveToLocalStorage('shown-tutorials', type, true);
|
||||||
|
}
|
||||||
|
return objectToSave;
|
||||||
});
|
});
|
||||||
saveToLocalStorage('shown-tutorials', type, true);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//error
|
//error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ import { MessageQueueProvider } from './messaging/MessageQueueContext.tsx';
|
|||||||
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
import './i18n/i18n.js';
|
import './i18n/i18n.js';
|
||||||
|
import { OnLaunchWrapper } from './OnLaunchWrapper.tsx';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<>
|
<>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<MessageQueueProvider>
|
<MessageQueueProvider>
|
||||||
<App />
|
<OnLaunchWrapper>
|
||||||
|
<App />
|
||||||
|
</OnLaunchWrapper>
|
||||||
</MessageQueueProvider>
|
</MessageQueueProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user