mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
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
|
||||
|
||||
# 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 unhandled from 'electron-unhandled';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
import path from 'path'
|
||||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
||||
|
||||
// Graceful handling of unhandled errors.
|
||||
@@ -48,22 +48,32 @@ const checkForUpdates = async () => {
|
||||
|
||||
// Run Application
|
||||
(async () => {
|
||||
// Wait for electron app to be ready.
|
||||
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());
|
||||
// Initialize our app, build windows, and load content.
|
||||
|
||||
// Initialize the app
|
||||
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();
|
||||
|
||||
// Set up periodic update checks
|
||||
|
||||
setInterval(checkForUpdates, 24 * 60 * 60 * 1000); // 24 hours
|
||||
|
||||
setInterval(checkForUpdates, 24 * 60 * 60 * 1000);
|
||||
})();
|
||||
|
||||
|
||||
// Handle when all of our windows are close (platforms have their own expectations).
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
|
@@ -1,9 +1,36 @@
|
||||
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', {
|
||||
openExternal: (url) => shell.openExternal(url),
|
||||
setAllowedDomains: (domains) => {
|
||||
@@ -11,14 +38,31 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
},
|
||||
});
|
||||
|
||||
// Expose other utility functions
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
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'),
|
||||
selectFile: async () => ipcRenderer.invoke('dialog:openFile'),
|
||||
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) {
|
||||
try {
|
||||
const payload = request.payload;
|
||||
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('apiKey', payload);
|
||||
}
|
||||
storeData('apiKey', payload);
|
||||
event.source.postMessage(
|
||||
{
|
||||
@@ -977,7 +981,11 @@ export async function setApiKeyCase(request, event) {
|
||||
export async function setCustomNodesCase(request, event) {
|
||||
try {
|
||||
const nodes = request.payload;
|
||||
storeData('customNodes', nodes);
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('customNodes', nodes);
|
||||
} else {
|
||||
storeData('customNodes', nodes);
|
||||
}
|
||||
|
||||
event.source.postMessage(
|
||||
{
|
||||
|
@@ -250,7 +250,13 @@ export const getApiKeyFromStorage = async (): Promise<string | 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 () => {
|
||||
@@ -860,7 +866,20 @@ export async function getSaveWallet() {
|
||||
}
|
||||
|
||||
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) {
|
||||
return res;
|
||||
} else {
|
||||
@@ -869,9 +888,13 @@ export async function getWallets() {
|
||||
}
|
||||
|
||||
export async function storeWallets(wallets) {
|
||||
storeData('wallets', wallets).catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('wallets', wallets);
|
||||
} else {
|
||||
storeData('wallets', wallets).catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserInfo() {
|
||||
|
@@ -722,7 +722,7 @@ export const NotAuthenticated = ({
|
||||
component="label"
|
||||
>
|
||||
{apiKey
|
||||
? t('auth:node.use_local', {
|
||||
? t('auth:apikey.change', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
: t('auth:apikey.import', {
|
||||
|
@@ -88,17 +88,17 @@ export const QortPrice = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getPrice();
|
||||
// getPrice();
|
||||
getSupplyInCirculation();
|
||||
getLastBlock();
|
||||
const interval = setInterval(() => {
|
||||
getPrice();
|
||||
// getPrice();
|
||||
getSupplyInCirculation();
|
||||
getLastBlock();
|
||||
}, 900000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [getPrice]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -110,7 +110,7 @@ export const QortPrice = () => {
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
{/* <Tooltip
|
||||
title={
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
Based on the latest 20 trades
|
||||
@@ -163,7 +163,7 @@ export const QortPrice = () => {
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
|
@@ -36,7 +36,12 @@ export const useHandleTutorials = () => {
|
||||
|
||||
useEffect(() => {
|
||||
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) {
|
||||
setShowTutorials(JSON.parse(storedData));
|
||||
@@ -51,12 +56,17 @@ export const useHandleTutorials = () => {
|
||||
const saveShowTutorial = useCallback((type) => {
|
||||
try {
|
||||
setShowTutorials((prev) => {
|
||||
return {
|
||||
const objectToSave = {
|
||||
...(prev || {}),
|
||||
[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) {
|
||||
//error
|
||||
}
|
||||
|
@@ -6,13 +6,16 @@ import { MessageQueueProvider } from './messaging/MessageQueueContext.tsx';
|
||||
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import './i18n/i18n.js';
|
||||
import { OnLaunchWrapper } from './OnLaunchWrapper.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<>
|
||||
<ThemeProvider>
|
||||
<CssBaseline />
|
||||
<MessageQueueProvider>
|
||||
<App />
|
||||
<OnLaunchWrapper>
|
||||
<App />
|
||||
</OnLaunchWrapper>
|
||||
</MessageQueueProvider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
|
Reference in New Issue
Block a user