UNPKG

@aladas-org/cryptocalc

Version:
1,244 lines (1,008 loc) 69.9 kB
// ==================================================================================== // =============================== electron_main.js =============================== // ==================================================================================== // https://www.electronjs.org/docs/latest/tutorial/quick-start "use strict"; // =============================== ElectronMain class =============================== // NB: "Singleton" class // * static GetInstance() // createBrowserWindow( url ) // * getMainWindow() // * getMenuTemplate() // * createWindow() // updateWindowTitle() // // * async doFileNew() // * doFileSave() // * async doFileOpen() // * async doFileRead( json_data ) // // * async selectFileOrDirectoryPathWithDialogBox() // showFolderInExplorer() // // * getNewFortuneCookie() // * toggleDebugPanel() // * getUserSelectedFile() // // readOptionsFile() // * async loadOptions() // * async setDefaultOptions() // * async updateOptions( options_data ) // * async saveOptions( options_data ) // * async resetOptions() // // * setCallbacks() // ------------------------------------------------------ const MAIN_WINDOW_WIDTH = 1040; // NB: 'width' is wider because of 'Cardano' const MAIN_WINDOW_HEIGHT = 651; const { app, Menu, BrowserWindow, ipcMain, shell, remote, dialog } = require('electron'); // https://stackoverflow.com/questions/35916158/how-to-prevent-multiple-instances-in-electron require('v8-compile-cache'); const os = require('os'); const { exec } = require('child_process'); // https://nodejs.org/api/os.html#os_os_platform // 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32' const WINDOWS = "win32"; const LINUX = "linux"; const FREE_BSD = "freebsd"; const OPEN_BSD = "openbsd"; const fs = require('fs'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const checkInternetConnected = require('check-internet-connected'); const PasswordGenerator = require('generate-password'); const { _CYAN_, _RED_, _PURPLE_, _YELLOW_, _END_ } = require('../util/color/color_console_codes.js'); const { pretty_func_header_log, pretty_log } = require('../util/log/log_utils.js'); const { Skribi } = require('../util/log/skribi.js'); const { APP_VERSION, PROGRAM, ELECTRON_LAUNCHER, EXE_LAUNCHER, OUTPUT_DIR_PATH, INVALID_OUTPUT_DIR_PATH, SELECT_DIRECTORY_PATH_MODE, SELECT_FILE_PATH_MODE, WITS_PATH, PATH, ARGS, BLOCKCHAIN, BIP32_PASSPHRASE, BIP32_PROTOCOL, ACCOUNT, ADDRESS_INDEX, TO_RAW_TEXT, TO_BASE64, TO_BASE58, TO_MNEMONICS, TO_BINARY, FROM_RAW_TEXT, FROM_BASE64, FROM_BASE58, FROM_MNEMONICS, FROM_BINARY } = require('../const_keywords.js'); const { CRYPTO_NET } = require('../crypto/const_wallet.js'); const { CMD_OPEN_WALLET, VIEW_TOGGLE_DEVTOOLS, TOOLS_OPTIONS, TOOLS_DATABASE_MANAGEMENT, TOOLS_BIP38_ENCRYPTER_DECRYTER, TOOLS_SECRET_PHRASE_TRANSLATOR, ToMain_RQ_QUIT_APP, ToMain_RQ_LOG_2_MAIN, ToMain_RQ_LOG_2_MAIN_SYNC, ToMain_RQ_GET_APP_PATH, ToMain_RQ_EXEC_CMD, ToMain_RQ_SET_WINDOW_TITLE, ToMain_RQ_TOGGLE_DEBUG_PANEL, ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG, ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE, ToMain_RQ_NEW_WALLET_INFO, ToMain_RQ_OPEN_WALLET_INFO, ToMain_RQ_SAVE_WALLET_INFO, ToMain_RQ_OPEN_URL, ToMain_RQ_SHOW_OUTPUT_FOLDER_IN_EXPLORER, ToMain_RQ_LOAD_IMG_FROM_FILE, ToMain_RQ_DRAW_RND_CRYPTO_LOGO, ToMain_RQ_MNEMONICS_TO_ENTROPY_INFO, ToMain_RQ_MNEMONICS_TO_ENTROPY_INFO_CUSTOM, ToMain_RQ_MNEMONICS_TO_HD_WALLET_INFO, ToMain_RQ_GET_SIMPLE_WALLET, ToMain_RQ_ENTROPY_TO_MNEMONICS, ToMain_RQ_ENTROPY_TO_CHECKSUM, ToMain_RQ_ENTROPY_SRC_TO_ENTROPY, ToMain_RQ_MNEMONICS_AS_4LETTER, ToMain_RQ_MNEMONICS_AS_TWO_PARTS, ToMain_RQ_GET_UUID, ToMain_RQ_GET_L10N_KEYPAIRS, ToMain_RQ_GET_L10N_MSG, ToMain_RQ_SET_MENU_ITEM_STATE, ToMain_RQ_CHECK_MNEMONICS, ToMain_RQ_WORD_INDEX_TO_MNEMONIC, ToMain_RQ_WORD_INDEXES_TO_MNEMONICS, ToMain_RQ_MNEMONIC_TO_WORD_INDEX, ToMain_RQ_MNEMONICS_TO_WORD_INDEXES, ToMain_RQ_GUESS_MNEMONICS_LANG, ToMain_RQ_SAVE_OPTIONS, ToMain_RQ_RESET_OPTIONS, ToMain_RQ_UPDATE_OPTIONS, ToMain_RQ_GET_FORTUNE_COOKIE, ToMain_RQ_GENERATE_ENTROPY, ToMain_RQ_GENERATE_PASSWORD, ToMain_RQ_FROM_HEX_CONVERSIONS, ToMain_RQ_TO_HEX_CONVERSIONS, ToMain_RQ_GET_HD_WALLET, ToMain_RQ_BIP38_ENCRYPT, ToMain_RQ_BIP38_DECRYPT, ToMain_RQ_GET_PASSWORD_STRENGTH, FromMain_DID_FINISH_LOAD, FromMain_EXEC_CMD, FromMain_FILE_NEW, FromMain_FILE_OPEN, FromMain_FILE_SAVE, FromMain_HELP_ABOUT, FromMain_TOOLS_OPTIONS_DIALOG, FromMain_TOOLS_DB_MANAGEMENT_DIALOG, FromMain_TOOLS_BIP38_ENCRYPT_DECRYPT_DIALOG, FromMain_TOOLS_SECRET_PHRASE_TRANSLATOR_DIALOG, FromMain_TOOLS_ENTROPY_CONVERTER_DIALOG, FromMain_UPDATE_OPTIONS, FromMain_SEND_IMG_URL, FromMain_SET_FORTUNE_COOKIE, FromMain_SET_VARIABLE, FromMain_INTERNET_CONNECTED } = require('../const_events.js'); const { ENTROPY_SOURCE_IMG_ID, ENTROPY_CONVERTER_DIALOG_ID } = require('../view/const_gui.js'); const { DEFAULT_OPTIONS } = require('../crypto/const_default_options.js'); const { getShortenedString } = require('../util/values/string_utils.js'); const { FileUtils } = require('../util/system/file_utils.js'); const { Bip39Utils } = require('../crypto/bip39_utils.js'); const { Bip38Utils } = require('../crypto/bip38_utils.js'); const { PasswordStrengthEvaluator } = require('../crypto/password_strength_evaluator.js'); const { isHexString, hexToB64, b64ToHex, hexToBinary, binaryToHex, getRandomInt, getRandomHexValue } = require('../crypto/hex_utils.js'); const { hexToB58, b58ToHex } = require('../crypto/base58_utils.js'); const { getFortuneCookie } = require('../util/fortune/fortune.js'); const { L10nUtils } = require('../L10n/L10n_utils.js'); const { Bip32Utils } = require('../crypto/HDWallet/bip32_utils.js'); const { HDWallet } = require('../crypto/HDWallet/hd_wallet.js'); const { SimpleWallet } = require('../crypto/SimpleWallet/simple_wallet.js'); const { MainModel } = require('../model/main_model.js'); const { SqLiteUtils } = require('./db/sqlite_utils.js'); const DEFAULT_APP_CONFIG = { "ToFile": true }; // DEFAULT_APP_CONFIG const gotTheLock = app.requestSingleInstanceLock(); const error_handler = (err) => { if (err) return Skribi.log("error: " + err); Skribi.log('saving file... '+ filename); }; // error_handler() class ElectronMain { static #Key = Symbol(); static #Singleton = new ElectronMain( this.#Key ); static #InstanceCount = 0; // Note: KO static get This() { static GetInstance() { if ( ElectronMain.#Singleton == undefined ) { this.#Singleton = new ElectronMain( this.#Key ); if ( this.#InstanceCount > 0 ) { throw new TypeError("'ElectronMain' constructor called more than once"); } this.#InstanceCount++; } return ElectronMain.#Singleton; } // ElectronMain.GetInstance() // ** Private constructor ** constructor( key ) { if ( key !== ElectronMain.#Key ) { throw new TypeError("'ElectronMain' constructor is private"); } this.cryptowallet_version = "x.x.x"; this.app_config = DEFAULT_APP_CONFIG; this.cmd_line = {}; this.cmd_line[PROGRAM] = ELECTRON_LAUNCHER; this.cmd_line[PATH] = "."; this.cmd_line[ARGS] = ""; this.DidFinishLoad_FiredCount = 0; this.Show_DebugPanel = false; this.MainWindow = undefined; this.Options = {}; this.SupportedBlockchains = {}; this.FirstImageAsEntropySource = true; this.output_path = ""; } // ** Private constructor ** createBrowserWindow( url ) { const win = new BrowserWindow( { height: 900, width: 1200 } ); win.loadURL( url ); } // createBrowserWindow getMainWindow() { return this.MainWindow; } // getMainWindow() isLaunchedFromExe() { if ( this.cmd_line[PROGRAM] == EXE_LAUNCHER ) return true; return false; } // isLaunchedFromExe() getCmdLineArgs() { let nb_args = process.argv.length; if ( nb_args > 0 ) { this.cmd_line[PROGRAM] = path.basename( process.argv[0] ); }; if ( nb_args > 1 ) { this.cmd_line[PATH] = process.argv[1]; }; if ( nb_args > 2 ) { this.cmd_line[ARGS] = process.argv[2]; }; let msg = "nb_args: " + nb_args + PROGRAM + ": " + this.cmd_line[PROGRAM] + PATH + ": " + this.cmd_line[PATH] + ARGS + ": " + this.cmd_line[ARGS]; return msg; } // getCmdLineArgs() // https://github.com/electron/electron/issues/19775 // https://stackoverflow.com/questions/44391448/electron-require-is-not-defined getMenuTemplate() { let ELECTRON_MAIN_MENU_TEMPLATE = [ { label: L10nUtils.GetLocalizedMsg("File"), submenu: [ { label: L10nUtils.GetLocalizedMsg("New"), async click() { await ElectronMain.GetInstance().doFileNew(); } }, { label: L10nUtils.GetLocalizedMsg("Open"), async click() { await ElectronMain.GetInstance().doFileOpen(); } }, { label: L10nUtils.GetLocalizedMsg("Save"), click() { ElectronMain.GetInstance().doFileSave(); }, id: "file_save_menu_item_id" }, { label: L10nUtils.GetLocalizedMsg("SaveAs"), click() { ElectronMain.GetInstance().doFileSaveAs(); }, id: "file_save_as_menu_item_id" }, { label: L10nUtils.GetLocalizedMsg("Quit"), click() { app.quit(); } } ] }, { label: L10nUtils.GetLocalizedMsg("View"), submenu: [ { label: L10nUtils.GetLocalizedMsg("ToggleDebug"), type: 'checkbox', click() { pretty_func_header_log( "[Electron]", VIEW_TOGGLE_DEVTOOLS ); ElectronMain.GetInstance().toggleDebugPanel(); } } ] }, { label: L10nUtils.GetLocalizedMsg("Tools"), submenu: [ { label: "Options...", click() { pretty_func_header_log( "[Electron]", TOOLS_OPTIONS ); ElectronMain.GetInstance().getMainWindow() .webContents.send ( 'fromMain', [ FromMain_TOOLS_OPTIONS_DIALOG, ElectronMain.GetInstance().Options ] ); } }, { label: L10nUtils.GetLocalizedMsg("DatabaseManagementTool"), click() { pretty_func_header_log( "[Electron]", TOOLS_DATABASE_MANAGEMENT ); ElectronMain.GetInstance().getMainWindow() .webContents.send ( 'fromMain', [ FromMain_TOOLS_DB_MANAGEMENT_DIALOG, SqLiteUtils.This.appFolderPath ] ); } }, { label: L10nUtils.GetLocalizedMsg("SecretPhraseTranslatorTool"), click() { pretty_func_header_log( "[Electron]", TOOLS_SECRET_PHRASE_TRANSLATOR ); ElectronMain.GetInstance().getMainWindow() .webContents.send ( 'fromMain', [ FromMain_TOOLS_SECRET_PHRASE_TRANSLATOR_DIALOG, ElectronMain.GetInstance().Options ] ); } }, { label: L10nUtils.GetLocalizedMsg("EntropyConverterTool"), click() { pretty_func_header_log( "[Electron]", ENTROPY_CONVERTER_DIALOG_ID ); ElectronMain.GetInstance().getMainWindow() .webContents.send ( 'fromMain', [ FromMain_TOOLS_ENTROPY_CONVERTER_DIALOG, ElectronMain.GetInstance().Options ] ); } }, { label: L10nUtils.GetLocalizedMsg("Bip38EncryptDecryptTool"), click() { pretty_func_header_log( "[Electron]", TOOLS_BIP38_ENCRYPTER_DECRYTER ); ElectronMain.GetInstance().getMainWindow() .webContents.send ( 'fromMain', [ FromMain_TOOLS_BIP38_ENCRYPT_DECRYPT_DIALOG, ElectronMain.GetInstance().Options ] ); } } ] }, { label: L10nUtils.GetLocalizedMsg("Help"), //"Help" submenu: [ { label: "Setup guide and User's Manual", click() { ElectronMain.GetInstance() .createBrowserWindow( app.getAppPath() + "/_doc/README.html"); } }, { label: "Cryptocalc Test protocols", click() { ElectronMain.GetInstance() .createBrowserWindow( app.getAppPath() + "/tests/_doc/index.html"); } }, { label: L10nUtils.GetLocalizedMsg("Resources"), submenu: [ { label: "Ian Coleman BIP39", click() { // https://stackoverflow.com/questions/53390798/opening-new-window-electron ElectronMain.GetInstance() .createBrowserWindow("https://iancoleman.io/bip39/"); } }, { label: "Guarda", click() { // https://stackoverflow.com/questions/53390798/opening-new-window-electron ElectronMain.GetInstance() .createBrowserWindow("https://guarda.com/"); } } ] }, { label: L10nUtils.GetLocalizedMsg("About"), //'About...', click() { ElectronMain.GetInstance().getMainWindow() .webContents.send('fromMain', [ FromMain_HELP_ABOUT ]); } } ] } ]; // menu_template return ELECTRON_MAIN_MENU_TEMPLATE; } // getMenuTemplate() //==================== createWindow() ==================== // https://stackoverflow.com/questions/44391448/electron-require-is-not-defined createWindow() { pretty_func_header_log( "ElectronMain.createWindow" ); // Hide 'Security Warning' process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; this.MainWindow = new BrowserWindow( { width: MAIN_WINDOW_WIDTH, height: MAIN_WINDOW_HEIGHT, icon: path.join(__dirname, "../../icons/Cryptocalc_Icon.png"), webPreferences: { contextIsolation: true, // NB: 'true' is default value but keep it there anyway preload: path.join(__dirname, "./preload.js") } } ); console.log( "ElectronMain.createWindow() this.MainWindow: " + typeof this.MainWindow ); MainModel.This.setMainWindow( this.MainWindow ); const menu_bar = Menu.buildFromTemplate( this.getMenuTemplate() ); Menu.setApplicationMenu( menu_bar ); // https://www.electronjs.org/docs/latest/api/web-contents#instance-events // https://stackoverflow.com/questions/42284627/electron-how-to-know-when-renderer-window-is-ready // Note: index.html loaded twice (first index.html redirect) // ==================== 'did-finish-load' event handler ==================== this.MainWindow.webContents.on( 'did-finish-load', async () => { Skribi.Initialize( this.app_config ); //Skribi.log(">> " + _CYAN_ + "[*Electron*] " + _YELLOW_ + " did-finish-load --" + _END_); this.app_config = this.readAppConfig(); // Skribi.log(">> " + _CYAN_ + "eMain.evtH('did-finish-load')> FiredCount: " + this.DidFinishLoad_FiredCount + _END_ ); this.getCmdLineArgs(); // Skribi.log(" ** this.cmd_line: " + JSON.stringify(this.cmd_line) ); Skribi.log(">> " + _CYAN_ + "eMain.evtH('" + _YELLOW_ + "'did-finish-load'" + _CYAN_ + ")> this.cmd_line[PROGRAM]: " + _END_ + this.cmd_line[PROGRAM]); //---------- Set 'Cryptowallet_version' in Renderer GUI ---------- this.cryptowallet_version = MainModel.This.getAppVersion(); //Skribi.log(">> " + _CYAN_ + "eMain.evtH('" + _YELLOW_ + "'did-finish-load'" // + _CYAN_ + ")> cryptowallet_version: " + _END_ + this.cryptowallet_version); this.updateWindowTitle(); //---------- Set 'this.cryptowallet_version' in Renderer GUI this.MainWindow.webContents.send ( "fromMain", [ FromMain_DID_FINISH_LOAD ] ); this.MainWindow.webContents.send ( "fromMain", [ FromMain_SET_VARIABLE, APP_VERSION, this.cryptowallet_version ] ); // https://stackoverflow.com/questions/31749625/make-a-link-from-electron-open-in-browser // Open urls in the user's browser // nB: Triggered by 'MainGUI.OnExploreWallet()' this.MainWindow.webContents.setWindowOpenHandler( (edata) => { shell.openExternal(edata.url); return { action: "deny" }; } ); this.setCallbacks(); this.Options = await this.loadOptions(); // =================== Open file by association =================== let wits_path = this.cmd_line[PATH]; if ( wits_path.endsWith(".wits") ) { Skribi.log(">> " + _CYAN_ + "eMain.evtH('" + _RED_ + "'did-finish-load'" + _CYAN_ + ")> SET_VARIABLE(" + WITS_PATH + ") in Renderer" + _END_ + wits_path); // await this.openWits( wits_path ); await this.MainWindow.webContents.send ( "fromMain", [ FromMain_SET_VARIABLE, WITS_PATH, wits_path ] ); } // =================== Open file by association // Note: will require 'Main' to 'Open Wits' if: // - 'wits_path' in Renderer is not empty // and // - not 'first_time' in Renderer await this.doFileNew(); } // 'did-finish-load' callback ); // ==================== 'did-finish-load' event handler // let index_html_path = app.getAppPath() + '/www/index.html'; let index_html_path = path.join(__dirname, '../../../www/index.html'); this.MainWindow.loadFile( index_html_path ); } // createWindow() async openWits( wits_path ) { if ( wits_path == undefined ) { throw new Error("**ERROR** in Cryptowallet: '.wits' path is undefined"); } Skribi.log(">> " + _CYAN_ + "eMain.evtH('" + _YELLOW_ + "'openWits'" + _CYAN_ + ")> *TEST* Trying to Open '.wits': " + _END_ + wits_path); if ( wits_path.endsWith(".wits") ) { const json_data_str = fs.readFileSync( wits_path, { encoding: 'utf8', flag: 'r' }); let wits_json_data = JSON.parse( json_data_str ); this.Options = await this.loadOptions(); await this.MainWindow.webContents.send ( "fromMain", [ FromMain_EXEC_CMD, CMD_OPEN_WALLET, wits_json_data ] ); } } // openWits() updateWindowTitle( coin, wallet_mode ) { //pretty_func_header_log( "ElectronMain.updateWindowTitle" ); let window_title = 'Cryptowallet ' + this.cryptowallet_version; if ( wallet_mode != undefined && wallet_mode != "" ) window_title += " - " + wallet_mode; if ( coin != undefined && coin != "" ) window_title += ": " + coin; this.MainWindow.setTitle( window_title ); } // updateWindowTitle() async doFileNew() { Skribi.log( ">> " + _CYAN_ + "ElectronMain.doFileNew" + _END_ ); //this.Options = this.readOptionsFile(); this.Options = await this.loadOptions(); await this.MainWindow.webContents .send( "fromMain", [ FromMain_FILE_NEW, this.Options ] ); } // doFileNew() doFileSave() { // pretty_func_header_log( "ElectronMain.doFileSave" ); Skribi.log( ">> " + _CYAN_ + "ElectronMain.doFileSave" + _END_ ); this.MainWindow.webContents.send( "fromMain", [ FromMain_FILE_SAVE ] ); } // doFileSave() doFileSaveAs() { // pretty_func_header_log( "ElectronMain.doFileSaveAs" ); Skribi.log( ">> " + _CYAN_ + "ElectronMain.doFileSaveAs" + _END_ ); this.MainWindow.webContents.send( "fromMain", [ FromMain_FILE_SAVE ] ); } // doFileSaveAs() async doFileOpen( in_file_path ) { // pretty_func_header_log( "ElectronMain.doFileOpen" ); Skribi.log( ">> " + _CYAN_ + "ElectronMain.doFileOpen" + _END_ ); if ( in_file_path == undefined ) { let input_path = app.getAppPath() + "\\_output"; in_file_path = await this.selectFileWithDialogBox( input_path, "Wallet Informations", "wits" ); pretty_log( "in_file_path", in_file_path ); if ( in_file_path == "" ) return; } const json_data_str = fs.readFileSync( in_file_path, { encoding: 'utf8', flag: 'r' }); //pretty_log( "json_data_str", json_data_str ); let json_data = JSON.parse( json_data_str ); await this.MainWindow.webContents .send( "fromMain", [ FromMain_FILE_OPEN, json_data ] ); } // doFileOpen() async selectFileWithDialogBox( input_path, label, extension ) { pretty_func_header_log( "ElectronMain.selectFileWithDialogBox" ); let in_file_path = ""; // Modal window let result = await dialog.showOpenDialog( this.MainWindow, { defaultPath: input_path, filters: [ { name: label, extensions: [extension] } ], properties: ['openFile'] }); //.then( result => { //pretty_log( "result", JSON.stringify(result)); if ( result.filePaths.length > 0 ) { //pretty_log( "filePaths", JSON.stringify(result.filePaths)); in_file_path = result.filePaths[0]; // pretty_log( "in_file_path", in_file_path); } //}).catch(err => { // Skribi.log(err) //}); // pretty_log( "in_file_path", in_file_path); return in_file_path; } // selectFileWithDialogBox() async selectFileOrDirectoryPathWithDialogBox( default_path, title, select_mode, path_type ) { pretty_func_header_log( "ElectronMain.selectFileOrDirectoryPathWithDialogBox" ); if ( select_mode == undefined ) select_mode = SELECT_DIRECTORY_PATH_MODE; if ( path_type == undefined ) path_type = ''; pretty_log( ">> -=-=-=-=-=-= selectFileOrDirectoryPathWithDialogBox path_type: '" + path_type + "'"); // pretty_log( ">> selectFileOrDirectoryPathWithDialogBox default_path: '" + default_path + "'"); let out_dir_path = ""; let result = undefined; let selected_file_path = undefined; if ( select_mode == SELECT_DIRECTORY_PATH_MODE ) { // ===== Modal window ===== // pretty_log( "====----====----====----====----==== dialog.showOpenDialog"); result = await dialog.showOpenDialog( this.MainWindow, { 'title': title, 'defaultPath': default_path, properties: ['openDirectory'], buttonLabel: 'Select' }); } else if ( select_mode == SELECT_FILE_PATH_MODE ) { result = await dialog.showSaveDialog( this.MainWindow, { 'title': title, 'defaultPath': default_path, filters: [ { name: 'Fichier SQLite', extensions: ['db'] } ], buttonLabel: 'Select', showsTagField: false }); if ( !result.canceled && result.filePath ) { console.log('Selected file path:', result.filePath); selected_file_path = result.filePath; // Handle the save operation } } pretty_log( ">> ---------------- selected_file_path: '" + selected_file_path + "'"); pretty_log( ">> ---------------- result.filePaths: '" + result.filePaths + "'"); pretty_log( ">> ---------------- result.filePath: '" + result.filePath + "'"); pretty_log( ">> ---------------- selected_file_path: '" + selected_file_path + "'"); if ( result.filePaths == undefined ) { console.log(">> ********* result.filePaths == undefined\n selected_file_path: " + selected_file_path); out_dir_path = selected_file_path; if ( ! fs.existsSync( out_dir_path ) ) { try { fs.writeFileSync(out_dir_path, ''); console.log('✓ Fichier créé avec succès'); } catch (err) { console.error('✗ Erreur:', err.message); } } } else { pretty_log( ">> ---------------- result.filePaths.length: '" + result.filePaths.length + "'"); if ( result.filePaths.length > 0 ) { pretty_log( "filePaths: " + JSON.stringify(result.filePaths)); out_dir_path = result.filePaths[0]; // pretty_log( ">> -------- out_dir_path: '" + out_dir_path + "'"); } } // console.log( ">> +++++++++++++++ path_type: '" + path_type + "' vs '" + OUTPUT_DIR_PATH + "'"); if ( path_type == OUTPUT_DIR_PATH ) { const validate_output_path = ( in_path ) => { // 2025_ 12_ 15_ 16h- 26m- 24s- 6_ BTC_ EN // 1 2 3 4 5 6 7 8 9 const pattern = /^(\d{4})_(\d{2})_(\d{2})_(\d{2})h-(\d{2})m-(\d{2})s-(\d+)_([A-Z]{3,4})_([A-Z]{2})$/; try { const pattern_match = in_path.match( pattern ); if (! pattern_match ) return false; const year = parseInt( pattern_match[1], 10 ); // console.log("> year: " + year ); const month = parseInt( pattern_match[2], 10 ); // console.log("> month: " + month ); const day = parseInt( pattern_match[3], 10 ); // console.log("> day: " + day ); const hour = parseInt( pattern_match[4], 10 ); // console.log("> hour: " + hour ); const minute = parseInt( pattern_match[5], 10 ); // console.log("> minute: " + minute ); const second = parseInt( pattern_match[6], 10 ); // console.log("> second: " + second ); const milli_second = parseInt( pattern_match[7], 10 ); // console.log("> milli_second: " + milli_second ); // Validate ranges if ( month < 1 || month > 12 ) return false; if ( day < 1 || day > 31 ) return false; if ( hour < 0 || hour > 23 ) return false; if ( minute < 0 || minute > 59 ) return false; if ( second < 0 || second > 59 ) return false; const coin = pattern_match[8]; // console.log("> coin: " + coin ); const lang = pattern_match[9]; // console.log("> lang: " + lang ); } catch ( err ) { console.log("** Parsing Error ** in ElectronMain.selectFileOrDirectoryPathWithDialogBox.validate_output_path" ); return false; } return true; } // validate_output_path() let at_least_one_wits_file = false; const sub_dirs = fs.readdirSync( out_dir_path, { withFileTypes: true } ); for ( let i=0; i < sub_dirs.length; i++ ) { let current_sub_dir = sub_dirs[i]; console.log('> -------- current_sub_dir.path[' + i + ']: ' + current_sub_dir.path); let subdir_name = current_sub_dir.name; console.log('> -------- subdir_name[' + i + ']: ' + subdir_name); let valid_as_ouput_dir_path = validate_output_path( subdir_name ); console.log('> -------- valid_as_ouput_dir_path[' + i + ']: ' + valid_as_ouput_dir_path); if ( valid_as_ouput_dir_path ) { let wits_file_path = current_sub_dir.path + '\\' + subdir_name + '\\wallet_info.wits'; console.log('> wits_file_path[' + i + ']: ' + wits_file_path); if ( fs.existsSync( wits_file_path ) ) { at_least_one_wits_file = true; return out_dir_path; } } } if ( ! at_least_one_wits_file ) return INVALID_OUTPUT_DIR_PATH; } // if ( path_type == OUTPUT_DIR_PATH ) return out_dir_path; } // selectFileOrDirectoryPathWithDialogBox() // https://stackoverflow.com/questions/43991267/electron-open-file-directory-in-specific-application showFolderInExplorer() { pretty_func_header_log( "ElectronMain.showFolderInExplorer" ); // Detect OS // https://www.freecodecamp.org/news/how-to-write-os-specific-code-in-electron-bf6379c62ff6/ let os_platform = os.platform(); // console.log("this.output_path: '" + this.output_path + "'"); let path_separator = '/'; let output_path = this.output_path; if (os_platform == WINDOWS) { path_separator = '\\'; } else if (os_platform == LINUX) { path_separator = '/'; } output_path = this.output_path.replaceAll('\\','\0').replaceAll('/','\0'); let output_path_items = output_path.split('\0'); // console.log("output_path_items: '" + JSON.stringify(output_path_items) + "'"); let popped = output_path_items.pop(); output_path = output_path_items.join(path_separator); // console.log("output_path: '" + output_path + "'"); const open_folder_LINUX = (path) => { exec(`xdg-open "${path}"`, (error) => { if (error) { console.error(`Error opening folder: ${error}`); } }); }; // open_folder_LINUX() // console.log("output_path: 1: '" + output_path + "'"); if (os_platform == WINDOWS) { // console.log("output_path: '" + output_path + "'"); shell.openPath( output_path ); } else if (os_platform == LINUX) { output_path = output_path.replaceAll('\\','/'); // console.log("output_path: '" + output_path + "'"); open_folder_LINUX( output_path ); } } // showFolderInExplorer() // File/Import/Random Fortune Cookie getNewFortuneCookie() { pretty_func_header_log( "ElectronMain.getNewFortuneCookie" ); let fortune_cookie = getFortuneCookie(); Skribi.log(" fortune_cookie: " + getShortenedString( fortune_cookie) ); this.MainWindow.webContents .send( "fromMain", [ FromMain_SET_FORTUNE_COOKIE, fortune_cookie ] ); } // getNewFortuneCookie() toggleDebugPanel() { pretty_func_header_log( "ElectronMain.toggleDebugPanel" ); this.Show_DebugPanel = ! this.Show_DebugPanel; if ( this.Show_DebugPanel ) this.MainWindow.webContents.openDevTools(); else this.MainWindow.webContents.closeDevTools(); } // toggleDebugPanel() readAppConfig() { pretty_func_header_log( "ElectronMain.readAppConfig" ); let app_config = DEFAULT_APP_CONFIG; let config_path = app.getAppPath() + '/www/config'; let app_config_path = config_path + '/app_config.json'; if ( fs.existsSync( app_config_path ) ) { const app_config_str = fs.readFileSync( app_config_path ); if ( app_config_str != "" && app_config_str != "[]" && app_config_str != "{}" ) { app_config = JSON.parse( app_config_str ); } } return app_config; } // readAppConfig() readOptionsFile() { pretty_func_header_log( "ElectronMain.readOptionsFile" ); let current_options = DEFAULT_OPTIONS; let config_path = app.getAppPath() + '/www/config'; //Skribi.log("config_path: " + config_path); let options_path = config_path + '/options.json'; if ( fs.existsSync( options_path ) ) { const options_str = fs.readFileSync( options_path ); //Skribi.log(" options_str: " + options_str); if ( options_str != "" && options_str != "[]" && options_str != "{}" ) { current_options = JSON.parse( options_str ); } } return current_options; } // readOptionsFile() async loadOptions() { pretty_func_header_log( "ElectronMain.loadOptions" ); this.Options = this.readOptionsFile(); //Skribi.log(" A this.Options: " + JSON.stringify( this.Options )); await this.MainWindow.webContents .send('fromMain', [ FromMain_UPDATE_OPTIONS, this.Options ]); return this.Options; } // async loadOptions() async setDefaultOptions() { pretty_func_header_log( "ElectronMain.setDefaultOptions" ); this.Options = DEFAULT_OPTIONS; await this.saveOptions( this.Options ); } // async setDefaultOptions() async updateOptions( options_data ) { pretty_func_header_log( "ElectronMain.updateOptions" ); this.Options = options_data; Skribi.log(" this.Options: " + JSON.stringify( this.Options )); } // async updateOptions() async saveOptions( options_data ) { pretty_func_header_log( "ElectronMain.saveOptions" ); this.Options = options_data; let config_path = app.getAppPath() + '/www/config'; let options_path = config_path + '/options.json'; fs.writeFileSync( options_path, JSON.stringify( this.Options ) ); Skribi.log(" B this.Options: " + JSON.stringify( this.Options )); await this.MainWindow.webContents .send('fromMain', [ FromMain_UPDATE_OPTIONS, options_data ]); } // async saveOptions() async resetOptions() { pretty_func_header_log( "ElectronMain.resetOptions" ); let config_path = app.getAppPath() + '/www/config'; let default_options_path = config_path + '/defaults/options.json'; let default_options_str = fs.readFileSync( default_options_path ).toString(); Skribi.log(" default_options_str: " + default_options_str); this.Options = JSON.parse( default_options_str ); await this.saveOptions( this.Options ); } // async resetOptions() setCallbacks() { Skribi.log(">> " + _CYAN_ + "ElectronMain.setCallbacks" + _END_); // ====================== ToMain_RQ_QUIT_APP ====================== //Skribi.log(">> register: " + ToMain_RQ_QUIT_APP); // called like this by Renderer: await window.ipcMain.QuitApp(data) ipcMain.handle( ToMain_RQ_QUIT_APP, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_QUIT_APP ); app.quit(); }); // "ToMain:Request/quit_app" event handler // ====================== ToMain_RQ_EXEC_CMD ====================== // called like this by Renderer: await window.ipcMain.ExecuteCommand(data) ipcMain.handle( ToMain_RQ_EXEC_CMD, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_EXEC_CMD ); const { cmd_name, cmd_args } = data; Skribi.log( "eMain.evtH('ExecCmd')> cmd_name: " + cmd_name ); Skribi.log( "eMain.evtH('ExecCmd')> cmd_args: " + cmd_args ); if ( cmd_name == CMD_OPEN_WALLET ) { await ElectronMain.GetInstance().openWits( cmd_args ); } }); // "ToMain:Request/ExecCmd" event handler // ====================== ToMain_RQ_LOG_2_MAIN_SYNC ====================== // called like this by Renderer: window.ipcMain.logToMain(data) ipcMain.on( ToMain_RQ_LOG_2_MAIN, (event, data) => { Skribi.log( data ); }); // "ToMain:Request/log2main" event handler // ====================== ToMain_RQ_LOG_2_MAIN_SYNC ====================== // called like this by Renderer: await window.ipcMain.logToMainSync(data) ipcMain.handle( ToMain_RQ_LOG_2_MAIN_SYNC, async (event, data) => { Skribi.log( data ); }); // "ToMain:Request/log2main_sync" event handler // ====================== ToMain_RQ_GET_APP_PATH ====================== // called like this by Renderer: await window.ipcMain.GetAppPath(data) ipcMain.handle( ToMain_RQ_GET_APP_PATH, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_GET_APP_PATH ); return app.getAppPath(); }); // "ToMain:Request/get_app_path" event handler // ====================== ToMain_RQ_SET_WINDOW_TITLE ====================== // called like this by Renderer: window.ipcMain.SetWindowTitle(data) ipcMain.on( ToMain_RQ_SET_WINDOW_TITLE, (event, data) => { // pretty_func_header_log( "[Electron]", ToMain_RQ_SET_WINDOW_TITLE ); const { coin, wallet_mode } = data; ElectronMain.GetInstance().updateWindowTitle( coin, wallet_mode ); }); // "ToMain:Request/set_window_title" event handler // ========================== ToMain_RQ_TOGGLE_DEBUG_PANEL ========================== // called like this by Renderer: window.ipcMain.ToggleDebugPanel(data) ipcMain.on( ToMain_RQ_TOGGLE_DEBUG_PANEL, (event, data) => { ElectronMain.GetInstance().toggleDebugPanel(); }); // "ToMain:Request/toggle_debug_panel" event handler // ==================== ToMain_RQ_SHOW_OUTPUT_FOLDER_IN_EXPLORER ==================== // called like this by Renderer: window.ipcMain.ShowOutputFolderInExplorer() ipcMain.on( ToMain_RQ_SHOW_OUTPUT_FOLDER_IN_EXPLORER, (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_SHOW_OUTPUT_FOLDER_IN_EXPLORER ); // let output_path = app.getAppPath() + "\\_output"; this.showFolderInExplorer(); }); // "ToMain:Request/show_output_folder_in_explorer" event handler // ====================== ToMain_RQ_OPEN_URL ====================== // called like this by Renderer: window.ipcMain.OpenURL(url) ipcMain.on( ToMain_RQ_OPEN_URL, (event, url) => { pretty_func_header_log( "[Electron]", ToMain_RQ_OPEN_URL ); Skribi.log("eMain.evtH('OpnURL')> " + url); // https://stackoverflow.com/questions/31749625/make-a-link-from-electron-open-in-browser this.MainWindow.location = url; }); // "ToMain:Request/open_URL" event handler // ====================== ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG ====================== Skribi.log(">> register ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG: '" + ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG + "'"); // called like this by Renderer: await window.ipcMain.selectFileOrDirectoryPathWithDialogBox( data ) ipcMain.handle( ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG, async ( event, data ) => { pretty_func_header_log( "[Electron]", ToMain_RQ_SELECT_FILE_OR_DIRECTORY_PATH_DIALOG ); Skribi.log( "eMain.evtH('selectFileOrDirPath')>" ); let { default_path, title, select_mode, path_type } = data; return await ElectronMain.GetInstance().selectFileOrDirectoryPathWithDialogBox( default_path, title, select_mode, path_type ); }); // "ToMain:Request/select_file_of_directory_path_dialog" event handler // ====================== ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE ====================== Skribi.log(">> register ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE: '" + ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE + "'"); // called like this by Renderer: await window.ipcMain.ImportOutputFilesInDatabase( data ) ipcMain.handle( ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE, async ( event, data ) => { pretty_func_header_log( "[Electron]", ToMain_RQ_IMPORT_OUTPUT_FILES_IN_DATABASE ); Skribi.log( "eMain.evtH(importOutpuFilesInDB')>" ); let { output_dirs_path, db_path } = data; await SqLiteUtils.This.importWallets( output_dirs_path, db_path ); }); // "ToMain:Request/import_output_files_in_database" event handler // ====================== ToMain_RQ_NEW_WALLET_INFO ====================== //Skribi.log(">> register: " + ToMain_RQ_NEW_WALLET_INFO); // called like this by Renderer: await window.ipcMain.NewWalletInfo( data ) ipcMain.handle( ToMain_RQ_NEW_WALLET_INFO, async ( event, data ) => { pretty_func_header_log( "[Electron]", ToMain_RQ_NEW_WALLET_INFO ); Skribi.log( "eMain.evtH('NewWinf')>" ); await ElectronMain.GetInstance().doFileNew(); }); // "ToMain:Request/new_wallet_info" event handler // ====================== ToMain_RQ_OPEN_WALLET_INFO ====================== // called like this by Renderer: await window.ipcMain.OpenWalletInfo( data ) ipcMain.handle( ToMain_RQ_OPEN_WALLET_INFO, async ( event, data ) => { pretty_func_header_log( "[Electron]", ToMain_RQ_OPEN_WALLET_INFO ); await ElectronMain.GetInstance().doFileOpen(); }); // "ToMain:Request/open_wallet_info" event handler // ====================== ToMain_RQ_SAVE_WALLET_INFO ====================== //Skribi.log(">> register: " + ToMain_RQ_SAVE_WALLET_INFO); // called like this by Renderer: window.ipcMain.SaveWalletInfo( data ) ipcMain.handle( ToMain_RQ_SAVE_WALLET_INFO, async ( event, crypto_info ) => { pretty_func_header_log( "[Electron]", ToMain_RQ_SAVE_WALLET_INFO ); Skribi.log( "eMain.evtH('SaveWinf')>" ); this.output_path = await MainModel.This.saveWalletInfo( crypto_info ); }); // "ToMain:Request/save_wallet_info" event handler // ====================== ToMain_RQ_RESET_OPTIONS ====================== // called like this by Renderer: await window.ipcMain.ResetOptions( data ) ipcMain.handle( ToMain_RQ_RESET_OPTIONS, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_RESET_OPTIONS ); Skribi.log( "eMain.evtH('RstOpt')>" ); ElectronMain.GetInstance().resetOptions(); }); // "ToMain:Request/reset_options" event handler // ====================== ToMain_RQ_UPDATE_OPTIONS ====================== // called like this by Renderer: await window.ipcMain.UpdateOptions( options_data ) ipcMain.handle( ToMain_RQ_UPDATE_OPTIONS, async (event, options_data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_UPDATE_OPTIONS ); if ( options_data == undefined ) { Skribi.log(">> " + _RED_ + ToMain_RQ_QUIT_APP + _END_); app.quit(); } // Skribi.log( "eMain.evtH('UpOpt')> options_data: " + JSON.stringify(options_data)); this.Options = options_data; Skribi.log( "eMain.evtH('UpOpt')> this.Options: " + JSON.stringify( this.Options )); await this.MainWindow.webContents .send('fromMain', [ FromMain_UPDATE_OPTIONS, this.Options ]); }); // "ToMain:Request/update_options" event handler // ====================== ToMain_RQ_SAVE_OPTIONS ====================== //Skribi.log(">> register: " + ToMain_RQ_SAVE_OPTIONS); // called like this by Renderer: await window.ipcMain.SaveOptions( options_data ) ipcMain.handle( ToMain_RQ_SAVE_OPTIONS, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_SAVE_OPTIONS ); let options_data = data; pretty_log( "eMain.evtH('SaveOpt')> options_data: " + JSON.stringify(options_data) ); ElectronMain.GetInstance().saveOptions( options_data ); pretty_log( "eMain.evtH('SaveOpt')> this.Options: " + JSON.stringify( this.Options ) ); await this.MainWindow.webContents .send( 'fromMain', [ FromMain_UPDATE_OPTIONS, options_data ] ); }); // "ToMain:Request/save_options" event handler const loadImageFromFile = ( image_file_path ) => { let img_data_asURL = fs.readFileSync( image_file_path, {encoding: 'base64'} ); let image_file_path_items = image_file_path.split('.'); let image_file_extension = "png"; let items_count = image_file_path_items.length; if (items_count > 1) { image_file_extension = image_file_path_items[items_count-1].toLowerCase(); if (image_file_extension == "jpg") { image_file_extension = "jpeg"; } else if (image_file_extension == "svg") { image_file_extension = "svg+xml"; } } //Skribi.log(" image_file_extension: " + image_file_extension); //Skribi.log(" img_data_asURL:\n" + img_data_asURL); this.MainWindow.webContents .send('fromMain', [ FromMain_SEND_IMG_URL, ENTROPY_SOURCE_IMG_ID, img_data_asURL, image_file_extension ]); return img_data_asURL; }; // loadImageFromFile // ====================== ToMain_RQ_LOAD_IMG_FROM_FILE ====================== // called like this by Renderer: await window.ipcMain.LoadImageFromFile(data) ipcMain.handle( ToMain_RQ_LOAD_IMG_FROM_FILE, async (event, image_file_path) => { pretty_func_header_log( "[Electron]", ToMain_RQ_LOAD_IMG_FROM_FILE ); let img_data_asURL = loadImageFromFile( image_file_path ); return img_data_asURL; }); // "ToMain:Request/load_image_from_file" event handler // ====================== ToMain_RQ_DRAW_RND_CRYPTO_LOGO ====================== //Skribi.log(">> register: " + ToMain_RQ_DRAW_RND_CRYPTO_LOGO); // called like this by Renderer: await window.ipcMain.DrawRandomCryptoLogo(data) ipcMain.handle( ToMain_RQ_DRAW_RND_CRYPTO_LOGO, (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_DRAW_RND_CRYPTO_LOGO ); let app_path = app.getAppPath(); //Skribi.log(" app_path: " + app_path); let crypto_logos_path = app_path + "/www/img/CryptoCurrency"; let crypto_logos = FileUtils.GetFilesInFolder( crypto_logos_path ); //Skribi.log(" crypto_logos.length: " + crypto_logos.length); let image_file_path = crypto_logos_path + "/"; let crypto_logo_filename = "Zilver_64px.svg"; if (! this.FirstImageAsEntropySource ) { let random_index = getRandomInt( crypto_logos.length ); crypto_logo_filename = crypto_logos[random_index]; } this.FirstImageAsEntropySource = false; image_file_path += crypto_logo_filename; let img_data_asURL = loadImageFromFile( image_file_path ); return img_data_asURL; }); // "ToMain:Request/drop_rnd_crypto_logo" event handler // ================== ToMain_RQ_GENERATE_ENTROPY ================== // called like this by Renderer: await window.ipcMain.GenerateEntropy( data ) // https://www.npmjs.com/package/generate-password ipcMain.handle( ToMain_RQ_GENERATE_ENTROPY, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_GENERATE_ENTROPY ); let entropy_size = data; let bytes_count = data / 8; let random_entropy_hex = getRandomHexValue( bytes_count ); return random_entropy_hex; }); // "ToMain:Request/GenerateEntropy event handler // ================== ToMain_RQ_GENERATE_PASSWORD ================== // called like this by Renderer: await window.ipcMain.GeneratePassword( data ) // https://www.npmjs.com/package/generate-password ipcMain.handle( ToMain_RQ_GENERATE_PASSWORD, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_GENERATE_PASSWORD ); // const {} = data; let new_password = PasswordGenerator.generate({ length: 24, numbers: true, symbols: true, uppercase: true, lowercase: true, excludeSimilarCharacters : true, strict: true }); return new_password; }); // "ToMain:Request/GeneratePassword event handler // ================== ToMain_RQ_FROM_HEX_CONVERSIONS ================== // called like this by Renderer: await window.ipcMain.ConvertFromHexToBases( data ) // https://www.npmjs.com/package/generate-password ipcMain.handle( ToMain_RQ_FROM_HEX_CONVERSIONS, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_FROM_HEX_CONVERSIONS ); let hex_str = data; const validateString = ( in_str ) => { // Cette regex accepte : // - Lettres majuscules/minuscules (incluant accentuées) // - Chiffres // - Caractères spéciaux courants const regex = /^[a-zA-Z0-9À-ÿ\s\p{P}\p{S}]+$/u; return regex.test( in_str ); }; // validateString() const hex_to_ascii = ( hex_value ) => { let hex_str = hex_value.toString(); // force conversion console.log(" hex_str: " + hex_str); let ascii_str = ''; for (let i = 0; i < hex_str.length; i += 2) { let hex_byte = hex_str.substr(i, 2); // console.log(" hex_byte(" + i + "): " + hex_byte); let char_code = parseInt( hex_byte, 16 ); // console.log(" char_code(" + i + "): " + char_code); // if ( char_code >= 32 ) { let new_character = String.fromCharCode(char_code); if ( validateString( new_character ) ) { ascii_str += new_character; // console.log(" ascii_str(" + i + "): " + ascii_str); } } return ascii_str; }; // hex_to_ascii() let raw_text_value = ''; let base64_value = ''; let base58_value = ''; // let mnemonics_value = ''; let binary_value = ''; raw_text_value = hex_to_ascii( hex_str ); base64_value = hexToB64( hex_str ); base58_value = hexToB58( hex_str ); binary_value = hexToBinary( hex_str ); // mnemonics_value = hexa_to_ascii( hex_str ); let conversions_to_bases = { [TO_RAW_TEXT]: raw_text_value, [TO_BASE64]: base64_value, [TO_BASE58]: base58_value, [TO_BINARY]: binary_value }; // console.log(" conversions_to_bases: " + JSON.stringify(conversions_to_bases)); return conversions_to_bases; }); // "ToMain:Request/from_hex_conversions event handler // ================== ToMain_RQ_TO_HEX_CONVERSIONS ================== // called like this by Renderer: await window.ipcMain.ConvertFromBasesToHex( data ) // https://www.npmjs.com/package/generate-password ipcMain.handle( ToMain_RQ_TO_HEX_CONVERSIONS, async (event, data) => { pretty_func_header_log( "[Electron]", ToMain_RQ_TO_HEX_CONVERSIONS ); let bases_data = data; const ascii_to_hex = ( in_str ) => { // Initialize an empty array to store the hexadecimal values let hex_str = []; // Iterate through each character in the input string for (let i = 0; i < in_str.length; i++) { // Convert the ASCII value of the current character to its hexadecimal re