mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
Merge pull request #113 from Qortal/feature/multi-instances
fix OS issues
This commit is contained in:
@@ -56,16 +56,14 @@ const checkForUpdates = async () => {
|
||||
// Initialize the app
|
||||
await myCapacitorApp.init();
|
||||
|
||||
// 🔧 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
|
||||
|
@@ -4,31 +4,6 @@ require('./rt/electron-rt');
|
||||
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', {
|
||||
@@ -50,18 +25,41 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
ipcRenderer.invoke('fs:selectAndZip', filePath),
|
||||
});
|
||||
|
||||
// 👇 New: Expose walletStorage to the frontend
|
||||
// Expose it
|
||||
contextBridge.exposeInMainWorld('walletStorage', {
|
||||
get: (key) => readData()[key],
|
||||
set: (key, value) => {
|
||||
const data = readData();
|
||||
data[key] = value;
|
||||
writeData(data);
|
||||
get: async (key) => {
|
||||
const raw = await ipcRenderer.invoke(
|
||||
'walletStorage:read',
|
||||
'wallet-storage.json'
|
||||
);
|
||||
const data = raw ? JSON.parse(raw) : {};
|
||||
return data[key];
|
||||
},
|
||||
delete: (key) => {
|
||||
const data = readData();
|
||||
set: async (key, value) => {
|
||||
const raw = await ipcRenderer.invoke(
|
||||
'walletStorage:read',
|
||||
'wallet-storage.json'
|
||||
);
|
||||
const data = raw ? JSON.parse(raw) : {};
|
||||
data[key] = value;
|
||||
await ipcRenderer.invoke(
|
||||
'walletStorage:write',
|
||||
'wallet-storage.json',
|
||||
JSON.stringify(data, null, 2)
|
||||
);
|
||||
},
|
||||
delete: async (key) => {
|
||||
const raw = await ipcRenderer.invoke(
|
||||
'walletStorage:read',
|
||||
'wallet-storage.json'
|
||||
);
|
||||
const data = raw ? JSON.parse(raw) : {};
|
||||
delete data[key];
|
||||
writeData(data);
|
||||
await ipcRenderer.invoke(
|
||||
'walletStorage:write',
|
||||
'wallet-storage.json',
|
||||
JSON.stringify(data, null, 2)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -6,7 +6,17 @@ import {
|
||||
} from '@capacitor-community/electron';
|
||||
import chokidar from 'chokidar';
|
||||
import type { MenuItemConstructorOptions } from 'electron';
|
||||
import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session, ipcMain, dialog } from 'electron';
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
MenuItem,
|
||||
nativeImage,
|
||||
Tray,
|
||||
session,
|
||||
ipcMain,
|
||||
dialog,
|
||||
} from 'electron';
|
||||
import electronIsDev from 'electron-is-dev';
|
||||
import electronServe from 'electron-serve';
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
@@ -14,24 +24,24 @@ const AdmZip = require('adm-zip');
|
||||
import { join } from 'path';
|
||||
import { myCapacitorApp } from '.';
|
||||
const fs = require('fs');
|
||||
const path = require('path')
|
||||
const path = require('path');
|
||||
|
||||
const defaultDomains = [
|
||||
'capacitor-electron://-',
|
||||
'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',
|
||||
'wss://appnode.qortal.org',
|
||||
"https://api.qortal.org",
|
||||
"https://api2.qortal.org",
|
||||
"https://apinode.qortalnodes.live",
|
||||
"https://apinode1.qortalnodes.live",
|
||||
"https://apinode2.qortalnodes.live",
|
||||
"https://apinode3.qortalnodes.live",
|
||||
"https://apinode4.qortalnodes.live",
|
||||
"https://www.qort.trade"
|
||||
'wss://ext-node.qortal.link',
|
||||
'https://appnode.qortal.org',
|
||||
'wss://appnode.qortal.org',
|
||||
'https://api.qortal.org',
|
||||
'https://api2.qortal.org',
|
||||
'https://apinode.qortalnodes.live',
|
||||
'https://apinode1.qortalnodes.live',
|
||||
'https://apinode2.qortalnodes.live',
|
||||
'https://apinode3.qortalnodes.live',
|
||||
'https://apinode4.qortalnodes.live',
|
||||
'https://www.qort.trade',
|
||||
];
|
||||
|
||||
// let allowedDomains: string[] = [...defaultDomains]
|
||||
@@ -44,7 +54,9 @@ const reloadWatcher = {
|
||||
ready: false,
|
||||
watcher: null,
|
||||
};
|
||||
export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void {
|
||||
export function setupReloadWatcher(
|
||||
electronCapacitorApp: ElectronCapacitorApp
|
||||
): void {
|
||||
reloadWatcher.watcher = chokidar
|
||||
.watch(join(app.getAppPath(), 'app'), {
|
||||
ignored: /[/\\]\./,
|
||||
@@ -93,7 +105,9 @@ export class ElectronCapacitorApp {
|
||||
) {
|
||||
this.CapacitorFileConfig = capacitorFileConfig;
|
||||
|
||||
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron';
|
||||
this.customScheme =
|
||||
this.CapacitorFileConfig.electron?.customUrlScheme ??
|
||||
'capacitor-electron';
|
||||
|
||||
if (trayMenuTemplate) {
|
||||
this.TrayMenuTemplate = trayMenuTemplate;
|
||||
@@ -126,7 +140,11 @@ export class ElectronCapacitorApp {
|
||||
|
||||
async init(): Promise<void> {
|
||||
const icon = nativeImage.createFromPath(
|
||||
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
|
||||
join(
|
||||
app.getAppPath(),
|
||||
'assets',
|
||||
process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png'
|
||||
)
|
||||
);
|
||||
this.mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1000,
|
||||
@@ -141,23 +159,29 @@ export class ElectronCapacitorApp {
|
||||
y: this.mainWindowState.y,
|
||||
width: this.mainWindowState.width,
|
||||
height: this.mainWindowState.height,
|
||||
backgroundColor: '#27282c',
|
||||
backgroundColor: '#27282c',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true,
|
||||
// Use preload to inject the electron varriant overrides for capacitor plugins.
|
||||
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
|
||||
preload: preloadPath },
|
||||
preload: preloadPath,
|
||||
},
|
||||
});
|
||||
this.mainWindowState.manage(this.MainWindow);
|
||||
|
||||
if (this.CapacitorFileConfig.backgroundColor) {
|
||||
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
|
||||
this.MainWindow.setBackgroundColor(
|
||||
this.CapacitorFileConfig.electron.backgroundColor
|
||||
);
|
||||
}
|
||||
|
||||
// If we close the main window with the splashscreen enabled we need to destory the ref.
|
||||
this.MainWindow.on('closed', () => {
|
||||
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) {
|
||||
if (
|
||||
this.SplashScreen?.getSplashWindow() &&
|
||||
!this.SplashScreen.getSplashWindow().isDestroyed()
|
||||
) {
|
||||
this.SplashScreen.getSplashWindow().close();
|
||||
}
|
||||
});
|
||||
@@ -186,11 +210,15 @@ export class ElectronCapacitorApp {
|
||||
}
|
||||
});
|
||||
this.TrayIcon.setToolTip(app.getName());
|
||||
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
|
||||
this.TrayIcon.setContextMenu(
|
||||
Menu.buildFromTemplate(this.TrayMenuTemplate)
|
||||
);
|
||||
}
|
||||
|
||||
// Setup the main manu bar at the top of our window.
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate));
|
||||
Menu.setApplicationMenu(
|
||||
Menu.buildFromTemplate(this.AppMenuBarMenuTemplate)
|
||||
);
|
||||
|
||||
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
|
||||
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
|
||||
@@ -198,7 +226,8 @@ export class ElectronCapacitorApp {
|
||||
imageFilePath: join(
|
||||
app.getAppPath(),
|
||||
'assets',
|
||||
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
|
||||
this.CapacitorFileConfig.electron?.splashScreenImageName ??
|
||||
'splash.png'
|
||||
),
|
||||
windowWidth: 400,
|
||||
windowHeight: 400,
|
||||
@@ -237,31 +266,36 @@ export class ElectronCapacitorApp {
|
||||
if (electronIsDev) {
|
||||
this.MainWindow.webContents.openDevTools();
|
||||
}
|
||||
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
|
||||
CapElectronEventEmitter.emit(
|
||||
'CAPELECTRON_DeeplinkListenerInitialized',
|
||||
''
|
||||
);
|
||||
}, 400);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export function setupContentSecurityPolicy(customScheme: string): void {
|
||||
session.defaultSession.webRequest.onHeadersReceived((details: any, callback) => {
|
||||
const allowedSources = ["'self'", customScheme, ...domainHolder.allowedDomains];
|
||||
const frameSources = [
|
||||
"'self'",
|
||||
'http://localhost:*',
|
||||
'https://localhost:*',
|
||||
'ws://localhost:*',
|
||||
'ws://127.0.0.1:*',
|
||||
'http://127.0.0.1:*',
|
||||
'https://127.0.0.1:*',
|
||||
...allowedSources,
|
||||
];
|
||||
session.defaultSession.webRequest.onHeadersReceived(
|
||||
(details: any, callback) => {
|
||||
const allowedSources = [
|
||||
"'self'",
|
||||
customScheme,
|
||||
...domainHolder.allowedDomains,
|
||||
];
|
||||
const frameSources = [
|
||||
"'self'",
|
||||
'http://localhost:*',
|
||||
'https://localhost:*',
|
||||
'ws://localhost:*',
|
||||
'ws://127.0.0.1:*',
|
||||
'http://127.0.0.1:*',
|
||||
'https://127.0.0.1:*',
|
||||
...allowedSources,
|
||||
];
|
||||
|
||||
// Create the Content Security Policy (CSP) string
|
||||
const csp = `
|
||||
// Create the Content Security Policy (CSP) string
|
||||
const csp = `
|
||||
default-src 'self' ${frameSources.join(' ')};
|
||||
frame-src ${frameSources.join(' ')};
|
||||
script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' ${frameSources.join(' ')};
|
||||
@@ -271,59 +305,57 @@ export function setupContentSecurityPolicy(customScheme: string): void {
|
||||
media-src 'self' blob: ${frameSources.join(' ')};
|
||||
style-src 'self' 'unsafe-inline';
|
||||
font-src 'self' data:;
|
||||
`.replace(/\s+/g, ' ').trim();
|
||||
|
||||
|
||||
// Get the request URL and origin
|
||||
const requestUrl = details.url;
|
||||
const requestOrigin = details.origin || details.referrer || 'capacitor-electron://-';
|
||||
`
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Parse the request URL to get its origin
|
||||
let requestUrlOrigin: string;
|
||||
try {
|
||||
const parsedUrl = new URL(requestUrl);
|
||||
requestUrlOrigin = parsedUrl.origin;
|
||||
} catch (e) {
|
||||
// Handle invalid URLs gracefully
|
||||
requestUrlOrigin = '';
|
||||
// Get the request URL and origin
|
||||
const requestUrl = details.url;
|
||||
const requestOrigin =
|
||||
details.origin || details.referrer || 'capacitor-electron://-';
|
||||
|
||||
// Parse the request URL to get its origin
|
||||
let requestUrlOrigin: string;
|
||||
try {
|
||||
const parsedUrl = new URL(requestUrl);
|
||||
requestUrlOrigin = parsedUrl.origin;
|
||||
} catch (e) {
|
||||
// Handle invalid URLs gracefully
|
||||
requestUrlOrigin = '';
|
||||
}
|
||||
|
||||
// Determine if the request is cross-origin
|
||||
const isCrossOrigin = requestOrigin !== requestUrlOrigin;
|
||||
|
||||
// Check if the response already includes Access-Control-Allow-Origin
|
||||
const hasAccessControlAllowOrigin = Object.keys(
|
||||
details.responseHeaders
|
||||
).some(
|
||||
(header) => header.toLowerCase() === 'access-control-allow-origin'
|
||||
);
|
||||
|
||||
// Prepare response headers
|
||||
const responseHeaders: Record<string, string | string[]> = {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [csp],
|
||||
};
|
||||
|
||||
if (isCrossOrigin && !hasAccessControlAllowOrigin) {
|
||||
// Handle CORS for cross-origin requests lacking CORS headers
|
||||
// Optionally, check if the requestOrigin is allowed
|
||||
responseHeaders['Access-Control-Allow-Origin'] = requestOrigin;
|
||||
responseHeaders['Access-Control-Allow-Methods'] =
|
||||
'GET, POST, OPTIONS, DELETE';
|
||||
responseHeaders['Access-Control-Allow-Headers'] =
|
||||
'Content-Type, Authorization, x-api-key';
|
||||
}
|
||||
|
||||
// Callback with modified headers
|
||||
callback({ responseHeaders });
|
||||
}
|
||||
|
||||
// Determine if the request is cross-origin
|
||||
const isCrossOrigin = requestOrigin !== requestUrlOrigin;
|
||||
|
||||
// Check if the response already includes Access-Control-Allow-Origin
|
||||
const hasAccessControlAllowOrigin = Object.keys(details.responseHeaders).some(
|
||||
(header) => header.toLowerCase() === 'access-control-allow-origin'
|
||||
);
|
||||
|
||||
// Prepare response headers
|
||||
const responseHeaders: Record<string, string | string[]> = {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [csp],
|
||||
};
|
||||
|
||||
if (isCrossOrigin && !hasAccessControlAllowOrigin) {
|
||||
// Handle CORS for cross-origin requests lacking CORS headers
|
||||
// Optionally, check if the requestOrigin is allowed
|
||||
responseHeaders['Access-Control-Allow-Origin'] = requestOrigin;
|
||||
responseHeaders['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, DELETE';
|
||||
responseHeaders['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, x-api-key';
|
||||
}
|
||||
|
||||
// Callback with modified headers
|
||||
callback({ responseHeaders });
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// IPC listener for updating allowed domains
|
||||
ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
|
||||
if (!Array.isArray(domains)) {
|
||||
@@ -331,20 +363,22 @@ 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[];
|
||||
.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])];
|
||||
const newAllowedDomains = [
|
||||
...new Set([...defaultDomains, ...validatedUserDomains]),
|
||||
];
|
||||
|
||||
// Sort both current allowed domains and new domains for comparison
|
||||
const sortedCurrentDomains = [...domainHolder.allowedDomains].sort();
|
||||
@@ -353,7 +387,9 @@ ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
|
||||
// Check if the lists are different
|
||||
const hasChanged =
|
||||
sortedCurrentDomains.length !== sortedNewDomains.length ||
|
||||
sortedCurrentDomains.some((domain, index) => domain !== sortedNewDomains[index]);
|
||||
sortedCurrentDomains.some(
|
||||
(domain, index) => domain !== sortedNewDomains[index]
|
||||
);
|
||||
|
||||
// If there's a change, update allowedDomains and reload the window
|
||||
if (hasChanged) {
|
||||
@@ -363,15 +399,14 @@ ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle('dialog:openFile', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'ZIP Files', extensions: ['zip'] } // Restrict to ZIP files
|
||||
{ name: 'ZIP Files', extensions: ['zip'] }, // Restrict to ZIP files
|
||||
],
|
||||
});
|
||||
return result.filePaths[0];
|
||||
@@ -389,9 +424,8 @@ ipcMain.handle('fs:readFile', async (_, filePath) => {
|
||||
|
||||
// Read the file as a Buffer
|
||||
const fileBuffer = fs.readFileSync(absolutePath);
|
||||
|
||||
return fileBuffer
|
||||
|
||||
return fileBuffer;
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error.message);
|
||||
return null; // Return null on error
|
||||
@@ -399,38 +433,47 @@ ipcMain.handle('fs:readFile', async (_, filePath) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('fs:selectAndZip', async (_, path) => {
|
||||
let directoryPath = path
|
||||
if(!directoryPath){
|
||||
let directoryPath = path;
|
||||
if (!directoryPath) {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
});
|
||||
if (canceled || filePaths.length === 0) {
|
||||
console.log('No directory selected');
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (canceled || filePaths.length === 0) {
|
||||
console.log('No directory selected');
|
||||
return null;
|
||||
}
|
||||
|
||||
directoryPath = filePaths[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try {
|
||||
directoryPath = filePaths[0];
|
||||
}
|
||||
|
||||
try {
|
||||
// Add the entire directory to the zip
|
||||
const zip = new AdmZip();
|
||||
|
||||
// Add the entire directory to the zip
|
||||
zip.addLocalFolder(directoryPath);
|
||||
// Add the entire directory to the zip
|
||||
zip.addLocalFolder(directoryPath);
|
||||
|
||||
// Generate the zip file as a buffer
|
||||
const zipBuffer = zip.toBuffer();
|
||||
// Generate the zip file as a buffer
|
||||
const zipBuffer = zip.toBuffer();
|
||||
|
||||
return {buffer: zipBuffer, directoryPath}
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
return { buffer: zipBuffer, directoryPath };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('walletStorage:read', async (_event, fileName: string) => {
|
||||
const filePath = path.join(app.getPath('userData'), fileName);
|
||||
const exists = fs.existsSync(filePath);
|
||||
if (!exists) return null;
|
||||
return fs.promises.readFile(filePath, 'utf-8');
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'walletStorage:write',
|
||||
async (_event, fileName: string, data: string) => {
|
||||
const filePath = path.join(app.getPath('userData'), fileName);
|
||||
await fs.promises.writeFile(filePath, data, 'utf-8');
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
@@ -1,36 +0,0 @@
|
||||
// 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);
|
||||
}
|
@@ -4,27 +4,29 @@ 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);
|
||||
});
|
||||
const fetchApiKey = async () => {
|
||||
try {
|
||||
if (window.walletStorage) {
|
||||
const res = await window.walletStorage.get('apiKey');
|
||||
if (res) {
|
||||
await window.sendMessage('setApiKey', res);
|
||||
setTimeout(() => setIsLoaded(true), 250);
|
||||
} else {
|
||||
setIsLoaded(true);
|
||||
}
|
||||
} else {
|
||||
setIsLoaded(true);
|
||||
}
|
||||
} else {
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error occurred when fetching apiKey info from file system',
|
||||
error
|
||||
);
|
||||
setIsLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoaded(true);
|
||||
console.error(
|
||||
'Error has occured when fetching apiKey info from file system'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApiKey();
|
||||
}, []);
|
||||
return !isLoaded ? null : children;
|
||||
};
|
||||
|
@@ -954,7 +954,7 @@ export async function setApiKeyCase(request, event) {
|
||||
const payload = request.payload;
|
||||
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('apiKey', payload);
|
||||
await window.walletStorage.set('apiKey', payload);
|
||||
}
|
||||
storeData('apiKey', payload);
|
||||
event.source.postMessage(
|
||||
@@ -982,7 +982,7 @@ export async function setCustomNodesCase(request, event) {
|
||||
try {
|
||||
const nodes = request.payload;
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('customNodes', nodes);
|
||||
await window.walletStorage.set('customNodes', nodes);
|
||||
} else {
|
||||
storeData('customNodes', nodes);
|
||||
}
|
||||
|
@@ -251,7 +251,7 @@ export const getApiKeyFromStorage = async (): Promise<string | null> => {
|
||||
|
||||
export const getCustomNodesFromStorage = async (): Promise<any | null> => {
|
||||
if (window?.walletStorage) {
|
||||
const res = window.walletStorage.get('customNodes');
|
||||
const res = await window.walletStorage.get('customNodes');
|
||||
|
||||
return res || null;
|
||||
} else {
|
||||
@@ -868,11 +868,11 @@ export async function getSaveWallet() {
|
||||
export async function getWallets() {
|
||||
let res;
|
||||
if (window?.walletStorage) {
|
||||
res = window.walletStorage.get('wallets');
|
||||
res = await window.walletStorage.get('wallets');
|
||||
if (!res) {
|
||||
const prevWallets = await getData<any>('wallets').catch(() => null);
|
||||
if (prevWallets) {
|
||||
window.walletStorage.set('wallets', prevWallets);
|
||||
await window.walletStorage.set('wallets', prevWallets);
|
||||
res = prevWallets;
|
||||
}
|
||||
}
|
||||
@@ -889,7 +889,7 @@ export async function getWallets() {
|
||||
|
||||
export async function storeWallets(wallets) {
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('wallets', wallets);
|
||||
await window.walletStorage.set('wallets', wallets);
|
||||
} else {
|
||||
storeData('wallets', wallets).catch((error) => {
|
||||
console.error(error);
|
||||
|
@@ -35,42 +35,49 @@ export const useHandleTutorials = () => {
|
||||
const { t } = useTranslation(['core', 'tutorial']);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
let storedData;
|
||||
if (window?.walletStorage) {
|
||||
storedData = window.walletStorage.get('shown-tutorials');
|
||||
} else {
|
||||
storedData = localStorage.getItem('shown-tutorials');
|
||||
}
|
||||
const fetchShownTutorials = async () => {
|
||||
try {
|
||||
let storedData: any;
|
||||
|
||||
if (storedData) {
|
||||
setShowTutorials(JSON.parse(storedData));
|
||||
} else {
|
||||
if (window?.walletStorage) {
|
||||
storedData = await window.walletStorage.get('shown-tutorials');
|
||||
} else {
|
||||
const local = localStorage.getItem('shown-tutorials');
|
||||
storedData = local ? JSON.parse(local) : {};
|
||||
}
|
||||
|
||||
setShowTutorials(storedData || {});
|
||||
} catch (error) {
|
||||
console.error('Failed to load tutorial state:', error);
|
||||
setShowTutorials({});
|
||||
}
|
||||
} catch (error) {
|
||||
//error
|
||||
}
|
||||
};
|
||||
|
||||
fetchShownTutorials();
|
||||
}, []);
|
||||
|
||||
const saveShowTutorial = useCallback((type) => {
|
||||
try {
|
||||
setShowTutorials((prev) => {
|
||||
const objectToSave = {
|
||||
...(prev || {}),
|
||||
const saveShowTutorial = useCallback(
|
||||
async (type) => {
|
||||
try {
|
||||
const updated = {
|
||||
...(shownTutorials || {}),
|
||||
[type]: true,
|
||||
};
|
||||
|
||||
setShowTutorials(updated);
|
||||
|
||||
if (window?.walletStorage) {
|
||||
window.walletStorage.set('shown-tutorials', objectToSave);
|
||||
await window.walletStorage.set('shown-tutorials', updated);
|
||||
} else {
|
||||
saveToLocalStorage('shown-tutorials', type, true);
|
||||
}
|
||||
return objectToSave;
|
||||
});
|
||||
} catch (error) {
|
||||
//error
|
||||
}
|
||||
}, []);
|
||||
} catch (error) {
|
||||
console.error('Failed to save tutorial state:', error);
|
||||
}
|
||||
},
|
||||
[shownTutorials]
|
||||
);
|
||||
|
||||
const showTutorial = useCallback(
|
||||
async (type, isForce) => {
|
||||
try {
|
||||
|
Reference in New Issue
Block a user