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 .env
# reports from scripts # 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 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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