save wallet data when having more than one instance

This commit is contained in:
2025-07-19 02:07:36 +03:00
parent d1bcf9ea35
commit 0b90e5466f
11 changed files with 201 additions and 36 deletions

3
.gitignore vendored
View File

@@ -27,4 +27,5 @@ release-builds/
.env
# reports from scripts
scripts/i18n_report*
scripts/i18n_report*
*wallet-storage.json

View File

@@ -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

View File

@@ -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
View 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
View 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;
};

View File

@@ -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(
{

View File

@@ -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() {

View File

@@ -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', {

View File

@@ -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={{

View File

@@ -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
}

View File

@@ -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>
</>