diff --git a/main.js b/main.js index 3b05586..5ad411c 100644 --- a/main.js +++ b/main.js @@ -1,10 +1,8 @@ -const { app, dialog, Tray, Menu } = require('electron'); +const { app, Tray, Menu } = require('electron'); const path = require('path'); const fs = require('fs'); const url = require('url'); -const https = require('https'); const platform = require('os').platform(); -const crypto = require('crypto'); const Store = require('electron-store'); const settings = new Store({ name: 'Settings' }); const log = require('electron-log'); @@ -15,32 +13,12 @@ const IS_DEV = (process.argv[1] === 'dev' || process.argv[2] === 'dev'); const IS_DEBUG = IS_DEV || process.argv[1] === 'debug' || process.argv[2] === 'debug'; const LOG_LEVEL = IS_DEBUG ? 'debug' : 'warn'; const WALLET_CFGFILE = path.join(app.getPath('userData'), 'wconfig.txt'); - -const SVALINN_VERSION = app.getVersion() || '0.3.x'; -const SERVICE_FILENAME = (platform === 'win32' ? `${config.walletServiceBinaryFilename}.exe` : config.walletServiceBinaryFilename); -const SERVICE_OSDIR = (platform === 'win32' ? 'win' : (platform === 'darwin' ? 'osx' : 'lin')); -const DEFAULT_SERVICE_BIN = path.join(process.resourcesPath, 'bin', SERVICE_OSDIR, SERVICE_FILENAME); - -const DEFAULT_REMOTE_NODE = config.remoteNodeListFallback - .map((a) => ({ sort: Math.random(), value: a })) - .sort((a, b) => a.sort - b.sort) - .map((a) => a.value)[0]; +const SVALINN_VERSION = app.getVersion(); const DEFAULT_SETTINGS = { - service_bin: DEFAULT_SERVICE_BIN, - service_host: '127.0.0.1', - service_port: config.walletServiceRpcPort, - service_password: 'passwrd', - service_timeout: 10, - node_address: DEFAULT_REMOTE_NODE, - pubnodes_last_updated: 946697799000, - pubnodes_data: config.remoteNodeListFallback, - pubnodes_custom: ['127.0.0.1:11898'], - pubnodes_exclude_offline: false, tray_minimize: false, tray_close: false, darkmode: false, - service_config_format: config.walletServiceConfigFormat }; const DEFAULT_SIZE = { width: 840, height: 840 }; const WIN_TITLE = `${config.appName} ${SVALINN_VERSION} - ${config.appDescription}`; @@ -50,7 +28,6 @@ app.prompShown = false; app.needToExit = false; app.debug = IS_DEBUG; app.walletConfig = WALLET_CFGFILE; -app.publicNodesUpdated = false; app.setAppUserModelId(config.appId); log.transports.console.level = LOG_LEVEL; @@ -65,7 +42,8 @@ let trayIconHide = path.join(__dirname, 'src/assets/trayon.png'); let win; let tray; -function createWindow() { +function createWindow() +{ // Create the browser window. let darkmode = settings.get('darkmode', false); let bgColor = darkmode ? '#000000' : '#02853E'; @@ -152,7 +130,6 @@ function createWindow() { tray.setToolTip(config.appSlogan); tray.setContextMenu(contextMenu); - tray.on('click', () => { if(!win.isFocused() && win.isVisible()){ win.focus(); @@ -231,59 +208,13 @@ function createWindow() { }); } -function serviceBinCheck() { - /* - if (DEFAULT_SERVICE_BIN.startsWith('/tmp')) { - log.warn(`AppImage env, copying service bin file`); - let targetPath = path.join(app.getPath('userData'), SERVICE_FILENAME); - try { - fs.renameSync(targetPath, `${targetPath}.bak`, (err) => { - if (err) log.error(err); - }); - } catch (_e) { } - - try { - fs.copyFile(DEFAULT_SERVICE_BIN, targetPath, (err) => { - if (err) { - log.error(err); - return; - } - settings.set('service_bin', targetPath); - log.debug(`service binary copied to ${targetPath}`); - }); - } catch (_e) { } - } else { - // don't trust user's settings, recheck - let svcbin = settings.get('service_bin'); - try{ - if(!fs.existsSync(svcbin)){ - log.warn(`Service binary can't be found, falling back to default`); - settings.set('service_bin', DEFAULT_SERVICE_BIN); - }else{ - log.info('Service binary found'); - } - }catch(_e) { - log.warn('Failed to check for service binary path, falling back to default'); - settings.set('service_bin', DEFAULT_SERVICE_BIN); - } - } - */ -} - function initSettings() { Object.keys(DEFAULT_SETTINGS).forEach((k) => { if (!settings.has(k) || settings.get(k) === null) { settings.set(k, DEFAULT_SETTINGS[k]); } }); - settings.set('service_password', crypto.randomBytes(32).toString('hex')); settings.set('version', SVALINN_VERSION); - // serviceBinCheck(); - /* - fs.unlink(WALLET_CFGFILE, (err) => { - if (err) log.debug(err.code === 'ENOENT' ? 'No stalled wallet config' : err.message); - }); - */ } app.on('browser-window-created', function (e, window) { @@ -343,10 +274,4 @@ app.on('ready', () => { if (tx > 0 && ty > 0) { try { win.setPosition(parseInt(tx, 10), parseInt(ty, 10)); } catch (_e) { } } - - // remove old settings cruft if exist - setTimeout(() => { - try { settings.delete('pubnodes_checked'); } catch (e) { } - try { settings.delete('pubnodes_date'); } catch (e) { } - }, 2500); }); \ No newline at end of file diff --git a/package.json b/package.json index 0ce3880..1597dce 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "svalinn", "productName": "Svalinn", "description": "ICONation ICX Wallet", - "version": "0.1.5", + "version": "0.1.6", "homepage": "https://github.com/ICONation/svalinn", "repository": "https://github.com/ICONation/svalinn", "main": "main.js", @@ -37,7 +37,9 @@ "node-crypto-gcm": "^1.0.3", "qr-image": "^3.2.0", "request": "^2.88.0", - "request-promise-native": "^1.0.7" + "request-promise-native": "^1.0.7", + "shell": "^0.5.0", + "superagent": "^4.1.0" }, "build": { "appId": "org.iconation.svalinn", diff --git a/src/css/common.css b/src/css/common.css index b112057..f66bef0 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -1076,6 +1076,10 @@ h4.input-required:after{ text-shadow: 1px 1px 1px #000; } +.dont-color-link { + color : inherit; +} + .help-hl{ color: rgba(255,255,255,1.0); } diff --git a/src/html/about.html b/src/html/about.html index b5c81ae..d26f012 100644 --- a/src/html/about.html +++ b/src/html/about.html @@ -5,7 +5,7 @@
diff --git a/src/html/index.html b/src/html/index.html index 4a43537..1992c38 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -14,7 +14,6 @@ - @@ -60,7 +59,7 @@
- +
diff --git a/src/html/send_transaction.html b/src/html/send_transaction.html index 39abe1b..528f882 100644 --- a/src/html/send_transaction.html +++ b/src/html/send_transaction.html @@ -27,10 +27,10 @@

Select the transaction file

-
-

Transaction Hash:

- +
+

Transaction Hash:

+
diff --git a/src/html/settings.html b/src/html/settings.html deleted file mode 100644 index 328c4b9..0000000 --- a/src/html/settings.html +++ /dev/null @@ -1,63 +0,0 @@ - - diff --git a/src/js/ws_api.js b/src/js/ws_api.js deleted file mode 100644 index c85732f..0000000 --- a/src/js/ws_api.js +++ /dev/null @@ -1,269 +0,0 @@ -const request = require('request-promise-native'); -const config = require('./ws_config.js'); - -class SvalinnApi { - constructor(args) { - args = args || {}; - if (!(this instanceof SvalinnApi)) return new SvalinnApi(args); - this.service_host = args.service_host || '127.0.0.1'; - this.service_port = args.service_port || config.walletServiceRpcPort; - this.service_password = args.service_password || "WHATEVER1234567891"; - this.minimum_fee = (args.minimum_fee !== undefined) ? args.minimum_fee : (config.minimumFee * config.decimalDivisor); - this.anonimity = config.defaultMixin; - } - _sendRequest(method, params, timeout) { - return new Promise((resolve, reject) => { - if (method.length === 0) return reject(new Error('Invalid Method')); - params = params || {}; - timeout = timeout || 3000; - let data = { - jsonrpc: '2.0', - method: method, - params: params, - password: this.service_password - }; - let s_host = this.service_host; - let s_port = this.service_port; - request({ - uri: `http://${s_host}:${s_port}/json_rpc`, - method: 'POST', - headers: { - Connection: 'keep-alive', - }, - body: data, - json: true, - timeout: timeout - }).then((res) => { - if (!res) return resolve(true); - if (!res.error) { - if (res.result) return resolve(res.result); - return resolve(res); - } else { - return reject(res.error.message); - } - }).catch((err) => { - return reject(err); - }); - }); - } - // only get single addres only, no multi address support for this wallet, yet - getAddress() { - return new Promise((resolve, reject) => { - this._sendRequest('getAddresses').then((result) => { - return resolve(result.addresses[0]); - }).catch((err) => { - return reject(err); - }); - }); - } - getFeeInfo() { - return new Promise((resolve, reject) => { - this._sendRequest('getFeeInfo').then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - getBalance(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.address = params.address || ''; - let req_params = { - address: params.address - }; - this._sendRequest('getBalance', req_params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - getStatus() { - return new Promise((resolve, reject) => { - this._sendRequest('getStatus').then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - save() { - return new Promise((resolve, reject) => { - this._sendRequest('save', {}, 6000).then(() => { - return resolve(); - }).catch((err) => { - return reject(err); - }); - }); - } - getViewKey() { - return new Promise((resolve, reject) => { - this._sendRequest('getViewKey').then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - getSpendKeys(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.address = params.address || ''; - if (!params.address.length) - return reject(new Error('Missing address parameter')); - var req_params = { - address: params.address - }; - this._sendRequest('getSpendKeys', req_params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - getMnemonicSeed(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.address = params.address || ''; - if (params.address.length === 0) - return reject(new Error('Missing address parameter')); - var req_params = { - address: params.address - }; - this._sendRequest('getMnemonicSeed', req_params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - getBackupKeys(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.address = params.address || ''; - if (params.address.length === 0) return reject(new Error('Missing address parameter')); - var req_params = { - address: params.address - }; - var backupKeys = {}; - this.getViewKey().then((vkres) => { - backupKeys.viewSecretKey = vkres.viewSecretKey; - return backupKeys; - }).then(() => { - this.getSpendKeys(req_params).then((vsres) => { - backupKeys.spendSecretKey = vsres.spendSecretKey; - return backupKeys; - }).catch((err) => { - return reject(err); - }); - }).then(() => { - this.getMnemonicSeed(req_params).then((mres) => { - backupKeys.mnemonicSeed = mres.mnemonicSeed; - return resolve(backupKeys); - }).catch((_err) => { /* jshint ignore:line */ - backupKeys.mnemonicSeed = ""; - return resolve(backupKeys); - }); - }).catch((err) => { - return reject(err); - }); - }); - } - getTransactions(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.firstBlockIndex = params.firstBlockIndex || 1; - params.blockCount = params.blockCount || 100; - var req_params = { - firstBlockIndex: (params.firstBlockIndex >= 1) ? params.firstBlockIndex : 1, - blockCount: (params.blockCount >= 1) ? params.blockCount : 100 - }; - this._sendRequest('getTransactions', req_params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - // send single transaction - sendTransaction(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.amount = params.amount || false; - params.address = params.address || false; - //params.transfers = params.transfers || false; - params.paymentId = params.paymentId || false; - params.fee = params.fee || this.minimum_fee; - if (!params.address) return reject(new Error('Missing recipient address parameter')); - if (!params.amount) return reject(new Error('Missing transaction amount parameter')); - if (parseFloat(params.fee) < 0.1) return reject(new Error('Minimum fee is 0.1 ICX')); - //[{address: "ICXxxxx...", amount: 100}]; - var req_params = { - transfers: [{ address: params.address, amount: params.amount }], - fee: params.fee - }; - if (params.paymentId) req_params.paymentId = params.paymentId; - // give extra long timeout - this._sendRequest('sendTransaction', req_params, 10000).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - reset(params) { - return new Promise((resolve, reject) => { - params = params || {}; - params.scanHeight = params.scanHeight || 0; - let req_params = {}; - if (params.scanHeight && params.scanHeight > 1) { - req_params = { scanHeight: params.scanHeight }; - } - this._sendRequest('reset', req_params).then(() => { - return resolve(true); - }).catch((err) => { - return reject(err); - }); - }); - } - estimateFusion(params) { - return new Promise((resolve, reject) => { - params = params || {}; - if (!params.threshold) return reject(new Error('Missing threshold parameter')); - this._sendRequest('estimateFusion', params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - sendFusionTransaction(params) { - return new Promise((resolve, reject) => { - params = params || {}; - if (!params.threshold) return reject(new Error('Missing threshold parameter')); - if (!params.anonimity) params.anonimity = this.anonimity; - this._sendRequest('sendFusionTransaction', params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } - createIntegratedAddress(params) { - return new Promise((resolve, reject) => { - params = params || {}; - if (!params.address || !params.paymentId) { - return reject(new Error('Address and Payment Id parameters are required')); - } - - this._sendRequest('createIntegratedAddress', params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); - } -} - -module.exports = SvalinnApi; \ No newline at end of file diff --git a/src/js/ws_config.js b/src/js/ws_config.js index 4762110..427a695 100644 --- a/src/js/ws_config.js +++ b/src/js/ws_config.js @@ -7,44 +7,11 @@ config.appSlogan = 'Svalinn'; config.appId = 'org.iconation.svalinn'; config.appGitRepo = 'https://github.com/ICONation/svalinn'; -// default port number for your daemon -config.daemonDefaultRpcPort = 11898; - // wallet file created by this app will have this extension config.walletFileDefaultExt = 'icx'; // transaction file created by this app will have this extension config.transactionFileDefaultExt = 'tx'; -// change this to match your wallet service executable filename -config.walletServiceBinaryFilename = 'svalinn'; - -// version on the bundled service (turtle-service) -config.walletServiceBinaryVersion = "v0.12.0"; - -// config file format supported by wallet service, possible values: -// ini --> for turtle service (or its forks) version <= v0.8.3 -// json --> for turtle service (or its forks) version >= v0.8.4 -config.walletServiceConfigFormat = "json"; - -// default port number for your wallet service (e.g. turtle-service) -config.walletServiceRpcPort = 8070; - -// block explorer url, the [[TX_HASH]] will be substituted w/ actual transaction hash -config.blockExplorerUrl = 'https://explorer.turtlecoin.lol/transaction.html?hash=[[TX_HASH]]'; - -// default remote node to connect to, set this to a known reliable node for 'just works' user experience -config.remoteNodeDefaultHost = 'turtlenode.co'; - -// remote node list update url, set to null if you don't have one -config.remoteNodeListUpdateUrl = 'https://raw.githubusercontent.com/turtlecoin/turtlecoin-nodes-json/master/turtlecoin-nodes.json'; - -// fallback remote node list, in case fetching update failed, fill this with known to works remote nodes -config.remoteNodeListFallback = [ - 'turtlenode.co:11898', - 'nodes.hashvault.pro:11898', - 'turtle.mine.nu:11898', -]; - // your currency name config.assetName = 'ICX'; // your currency ticker @@ -58,10 +25,6 @@ config.addressLength = 42; config.minimumFee = 100000; // minimum amount for sending transaction config.mininumSend = 0.0; -// default mixin/anonimity for transaction -config.defaultMixin = 3; -// to convert from atomic unit -config.decimalDivisor = 1000000000000000000; // to represent human readable value config.decimalPlaces = 18; diff --git a/src/js/ws_manager.js b/src/js/ws_manager.js index 6fbefb4..bf5f243 100644 --- a/src/js/ws_manager.js +++ b/src/js/ws_manager.js @@ -1,86 +1,32 @@ -const path = require('path'); const fs = require('fs'); const os = require('os'); -const childProcess = require('child_process'); const log = require('electron-log'); const Store = require('electron-store'); const SvalinnSession = require('./ws_session'); -const SvalinnApi = require('./ws_api'); const uiupdater = require('./wsui_updater'); -const wsutil = require('./ws_utils'); -const config = require('./ws_config'); const IconService = require('icon-sdk-js'); const { remote } = require('electron'); const settings = new Store({ name: 'Settings' }); const sessConfig = { debug: remote.app.debug, walletConfig: remote.app.walletConfig }; const wsession = new SvalinnSession(sessConfig); -const SERVICE_LOG_DEBUG = wsession.get('debug'); -const SERVICE_LOG_LEVEL_DEFAULT = 0; -const SERVICE_LOG_LEVEL_DEBUG = 5; -const SERVICE_LOG_LEVEL = (SERVICE_LOG_DEBUG ? SERVICE_LOG_LEVEL_DEBUG : SERVICE_LOG_LEVEL_DEFAULT); - -const ERROR_WALLET_EXEC = `Failed to start ${config.walletServiceBinaryFilename}. Set the path to ${config.walletServiceBinaryFilename} properly in the settings tab.`; -const ERROR_WALLET_PASSWORD = 'Failed to load your wallet, please check your password'; -const ERROR_WALLET_IMPORT = 'Import failed, please check that you have entered all information correctly'; const ERROR_WALLET_CREATE = 'Wallet can not be created, please check your input and try again'; -const ERROR_TRANSACTION_CREATE = 'Transaction can not be created, please check your input and try again'; const ERROR_PASSWORD_FORMAT = 'Password must be at least 8 characters long and contain a combination of letters, numbers, and special characters. (?!:.,%+-/*<>{}()[]`"\'~_^\\|@#$&)'; -const ERROR_RPC_TIMEOUT = 'Unable to communicate with selected node, please try again in a few seconds or switch to another node address'; -const INFO_FUSION_DONE = 'Wallet optimization completed, your balance may appear incorrect for a while.'; -const INFO_FUSION_SKIPPED = 'Wallet already optimized. No further optimization is needed.'; -const ERROR_FUSION_FAILED = 'Unable to optimize your wallet, please try again in a few seconds'; -var SvalinnManager = function () { +var SvalinnManager = function () +{ if (!(this instanceof SvalinnManager)) { return new SvalinnManager(); } - let nodeAddress = settings.get('node_address').split(':'); - this.daemonHost = nodeAddress[0] || null; - this.daemonPort = nodeAddress[1] || null; - this.serviceProcess = null; - this.serviceBin = settings.get('service_bin'); - this.servicePassword = settings.get('service_password'); - this.serviceHost = settings.get('service_host'); - this.servicePort = settings.get('service_port'); - this.serviceTimeout = settings.get('service_timeout'); - this.serviceArgsDefault = ['--rpc-password', settings.get('service_password')]; - this.walletConfigDefault = { 'rpc-password': settings.get('service_password') }; - this.servicePid = null; - this.serviceLastPid = null; - this.serviceActiveArgs = []; - this.serviceApi = null; - this.syncWorker = null; - this.fusionTxHash = []; - this.iconNetworks = [ - {desc: "None", url: "unknown", nid: 0}, - {desc: "Mainnet", url: "https://ctz.solidwallet.io", nid: 1}, - {desc: "Testnet for Exchanges (Euljiro)", url: "https://test-ctz.solidwallet.io", nid: 2}, - {desc: "Testnet for DApps (Yeouido)", url: "https://bicon.net.solidwallet.io", nid: 3} + {desc: "None", url: "unknown", tracker: "", nid: 0}, + {desc: "Mainnet", url: "https://ctz.solidwallet.io", tracker: "https://tracker.icon.foundation", nid: 1}, + {desc: "Testnet for Exchanges (Euljiro)", url: "https://test-ctz.solidwallet.io", tracker: "https://trackerdev.icon.foundation", nid: 2}, + {desc: "Testnet for DApps (Yeouido)", url: "https://bicon.net.solidwallet.io", tracker: "https://bicon.tracker.solidwallet.io", nid: 3} ]; }; -SvalinnManager.prototype.init = function () { - this._getSettings(); - if (this.serviceApi !== null) return; - - let cfg = { - service_host: this.serviceHost, - service_port: this.servicePort, - service_password: this.servicePassword - }; - this.serviceApi = new SvalinnApi(cfg); -}; - -SvalinnManager.prototype._getSettings = function () { - let nodeAddress = settings.get('node_address').split(':'); - this.daemonHost = nodeAddress[0] || null; - this.daemonPort = nodeAddress[1] || null; - this.serviceBin = settings.get('service_bin'); -}; - SvalinnManager.prototype._reinitSession = function () { this._wipeConfig(); wsession.reset(); @@ -90,78 +36,13 @@ SvalinnManager.prototype._reinitSession = function () { }); }; -SvalinnManager.prototype._serviceBinExists = function () { - wsutil.isFileExist(this.serviceBin); -}; - -// check -SvalinnManager.prototype.serviceStatus = function () { - return (undefined !== this.serviceProcess && null !== this.serviceProcess); -}; - -SvalinnManager.prototype.isRunning = function () { - /* - this.init(); - let proc = path.basename(this.serviceBin); - let platform = process.platform; - let cmd = ''; - switch (platform) { - case 'win32': cmd = `tasklist`; break; - case 'darwin': cmd = `ps -ax | grep ${proc}`; break; - case 'linux': cmd = `ps -A`; break; - default: break; - } - if (cmd === '' || proc === '') return false; - - childProcess.exec(cmd, (err, stdout, stderr) => { - if (err) log.debug(err.message); - if (stderr) log.debug(stderr.toLocaleLowerCase()); - let found = stdout.toLowerCase().indexOf(proc.toLowerCase()) > -1; - log.debug(`Process found: ${found}`); - return found; - }); - */ -}; - -SvalinnManager.prototype._writeIniConfig = function (cfg) { - let configFile = wsession.get('walletConfig'); - if (!configFile) return ''; - - try { - fs.writeFileSync(configFile, cfg); - return configFile; - } catch (err) { - log.error(err); - return ''; - } -}; - -SvalinnManager.prototype._writeConfig = function (cfg) { - let configFile = wsession.get('walletConfig'); - if (!configFile) return ''; - - cfg = cfg || {}; - if (!cfg) return ''; - - let configData = ''; - Object.keys(cfg).map((k) => { configData += `${k}=${cfg[k]}${os.EOL}`; }); - try { - fs.writeFileSync(configFile, configData); - return configFile; - } catch (err) { - log.error(err); - return ''; - } -}; - SvalinnManager.prototype._wipeConfig = function () { try { fs.unlinkSync(wsession.get('walletConfig')); } catch (e) { } }; -SvalinnManager.prototype.startService = function (walletFile, onError, onSuccess, onDelay) { - +SvalinnManager.prototype.startService = function (walletFile, onError, onSuccess, onDelay) +{ const keystore = JSON.parse (fs.readFileSync (walletFile, 'utf8').replace(/^\uFEFF/, '')); - try { wsession.set ('loadedWalletAddress', keystore.address); wsession.set ('serviceReady', true); @@ -171,34 +52,6 @@ SvalinnManager.prototype.startService = function (walletFile, onError, onSuccess } }; -SvalinnManager.prototype._argsToIni = function (args) { - let configData = ""; - if ("object" !== typeof args || !args.length) return configData; - args.forEach((k, v) => { - let sep = ((v % 2) === 0) ? os.EOL : "="; - configData += `${sep}${k.toString().replace('--', '')}`; - }); - return configData.trim(); -}; - -SvalinnManager.prototype.stopService = function () { -}; - -SvalinnManager.prototype.terminateService = function (force) { -}; - -SvalinnManager.prototype.startSyncWorker = function () { -}; - -SvalinnManager.prototype.stopSyncWorker = function () { -}; - -SvalinnManager.prototype.getNodeFee = function () { -}; - -SvalinnManager.prototype.genIntegratedAddress = function (paymentId, address) { -}; - function check_password (password) { return /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[?!:\.,%+-/*<>{}\(\)\[\]`"'~_^\\|@#$&]).{8,}$/.test(password) } @@ -232,6 +85,7 @@ SvalinnManager.prototype.createTransaction = function (tx, transactionFile, wall // Open the wallet try { + // Wallets may have been saved in UTF-8 format for unknown reasons, thus we need to remove the BOM header const keystore = JSON.parse (fs.readFileSync (walletFile, 'utf8').replace(/^\uFEFF/, '')); const wallet = IconService.IconWallet.loadKeystore (keystore, walletPass); @@ -285,41 +139,8 @@ SvalinnManager.prototype.importFromKeys = function (walletFile, password, privat }); }; -SvalinnManager.prototype.importFromSeed = function (walletFile, password, mnemonicSeed, scanHeight) { - this.init(); - let wsm = this; - return new Promise((resolve, reject) => { - scanHeight = scanHeight || 0; - - let serviceArgs = wsm.serviceArgsDefault.concat([ - '-g', '-w', walletFile, '-p', password, - '--mnemonic-seed', mnemonicSeed, - '--log-level', 0, '--log-file', path.join(remote.app.getPath('temp'), 'ts.log') - ]); - - if (scanHeight >= 0) serviceArgs = serviceArgs.concat(['--scan-height', scanHeight]); - - childProcess.execFile( - wsm.serviceBin, serviceArgs, (error, stdout, stderr) => { - if (stdout) log.debug(stdout); - if (stderr) log.debug(stderr); - - if (error) { - log.debug(`Error importing seed: ${error.message}`); - return reject(new Error(ERROR_WALLET_IMPORT)); - } else { - if (!wsutil.isRegularFileAndWritable(walletFile)) { - return reject(new Error(ERROR_WALLET_IMPORT)); - } - return resolve(walletFile); - } - } - ); - }); -}; - -SvalinnManager.prototype.getSecretKeys = function (walletPass) { - +SvalinnManager.prototype.getSecretKeys = function (walletPass) +{ return new Promise((resolve, reject) => { try { const walletFile = settings.get ('recentWallet'); @@ -332,19 +153,8 @@ SvalinnManager.prototype.getSecretKeys = function (walletPass) { }); }; -SvalinnManager.prototype.sendTransaction = function (params) { - let wsm = this; - return new Promise((resolve, reject) => { - wsm.serviceApi.sendTransaction(params).then((result) => { - return resolve(result); - }).catch((err) => { - return reject(err); - }); - }); -}; - -SvalinnManager.prototype.sendSignedTransaction = function (tx) { - +SvalinnManager.prototype.sendSignedTransaction = function (tx) +{ let mgr = this; return new Promise((resolve, reject) => { @@ -361,218 +171,12 @@ SvalinnManager.prototype.sendSignedTransaction = function (tx) { }); }; -SvalinnManager.prototype.rescanWallet = function (scanHeight) { - let wsm = this; - - function resetSession() { - wsession.set('walletUnlockedBalance', 0); - wsession.set('walletLockedBalance', 0); - wsession.set('synchronized', false); - wsession.set('txList', []); - wsession.set('txLen', 0); - wsession.set('txLastHash', null); - wsession.set('txLastTimestamp', null); - wsession.set('txNew', []); - let fakeBlock = -300; - let resetdata = { - type: 'blockUpdated', - data: { - blockCount: fakeBlock, - displayBlockCount: fakeBlock, - knownBlockCount: fakeBlock, - displayKnownBlockCount: fakeBlock, - syncPercent: fakeBlock - } - }; - wsm.notifyUpdate(resetdata); - } - - return new Promise((resolve) => { - wsm.serviceApi.reset({ scanHeight: scanHeight }).then(() => { - resetSession(); - return resolve(true); - }).catch(() => { - resetSession(); - return resolve(false); - }); - }); -}; - -SvalinnManager.prototype._fusionGetMinThreshold = function (threshold, minThreshold, maxFusionReadyCount, counter) { - let wsm = this; - return new Promise((resolve, reject) => { - counter = counter || 0; - threshold = threshold || (parseInt(wsession.get('walletUnlockedBalance'), 10) * 100) + 1; - threshold = parseInt(threshold, 10); - minThreshold = minThreshold || threshold; - maxFusionReadyCount = maxFusionReadyCount || 0; - - let maxThreshCheckIter = 20; - - wsm.serviceApi.estimateFusion({ threshold: threshold }).then((res) => { - // nothing to optimize - if (counter === 0 && res.fusionReadyCount === 0) return resolve(0); - // stop at maxThreshCheckIter or when threshold too low - if (counter > maxThreshCheckIter || threshold < 10) return resolve(minThreshold); - // we got a possibly best minThreshold - if (res.fusionReadyCount < maxFusionReadyCount) { - return resolve(minThreshold); - } - // continue to find next best minThreshold - maxFusionReadyCount = res.fusionReadyCount; - minThreshold = threshold; - threshold /= 2; - counter += 1; - resolve(wsm._fusionGetMinThreshold(threshold, minThreshold, maxFusionReadyCount, counter).then((res) => { - return res; - })); - }).catch((err) => { - return reject(new Error(err)); - }); - }); -}; - -SvalinnManager.prototype._fusionSendTx = function (threshold, counter) { - let wsm = this; - const wtime = ms => new Promise(resolve => setTimeout(resolve, ms)); - - return new Promise((resolve, reject) => { - counter = counter || 0; - let maxIter = 256; - if (counter >= maxIter) return resolve(wsm.fusionTxHash); // stop at max iter - - wtime(2400).then(() => { - // keep sending fusion tx till it hit IOOR or reaching max iter - log.debug(`send fusion tx, iteration: ${counter}`); - wsm.serviceApi.sendFusionTransaction({ threshold: threshold }).then((resp) => { - wsm.fusionTxHash.push(resp.transactionHash); - counter += 1; - return resolve(wsm._fusionSendTx(threshold, counter).then((resp) => { - return resp; - })); - }).catch((err) => { - if (typeof err === 'string') { - if (!err.toLocaleLowerCase().includes('index is out of range')) { - log.debug(err); - return reject(new Error(err)); - } - } else if (typeof err === 'object') { - if (!err.message.toLowerCase().includes('index is out of range')) { - log.debug(err); - return reject(new Error(err)); - } - } - - counter += 1; - return resolve(wsm._fusionSendTx(threshold, counter).then((resp) => { - return resp; - })); - }); - - }); - }); -}; - -SvalinnManager.prototype.optimizeWallet = function () { - let wsm = this; - log.debug('running optimizeWallet'); - return new Promise((resolve, reject) => { - wsm.fusionTxHash = []; - wsm._fusionGetMinThreshold().then((res) => { - if (res <= 0) { - wsm.notifyUpdate({ - type: 'fusionTxCompleted', - data: INFO_FUSION_SKIPPED, - code: 0 - }); - log.debug('fusion skipped'); - log.debug(wsm.fusionTxHash); - return resolve(INFO_FUSION_SKIPPED); - } - - log.debug(`performing fusion tx, threshold: ${res}`); - - return resolve( - wsm._fusionSendTx(res).then(() => { - wsm.notifyUpdate({ - type: 'fusionTxCompleted', - data: INFO_FUSION_DONE, - code: 1 - }); - log.debug('fusion done'); - log.debug(wsm.fusionTxHash); - return INFO_FUSION_DONE; - }).catch((err) => { - let msg = err.message.toLowerCase(); - let outMsg = ERROR_FUSION_FAILED; - switch (msg) { - case 'index is out of range': - outMsg = wsm.fusionTxHash.length >= 1 ? INFO_FUSION_DONE : INFO_FUSION_SKIPPED; - break; - default: - break; - } - log.debug(`fusionTx outMsg: ${outMsg}`); - log.debug(wsm.fusionTxHash); - wsm.notifyUpdate({ - type: 'fusionTxCompleted', - data: outMsg, - code: outMsg === INFO_FUSION_SKIPPED ? 0 : 1 - }); - return outMsg; - }) - ); - }).catch((err) => { - // todo handle this differently! - log.debug('fusion error'); - return reject((err.message)); - }); - }); -}; - -SvalinnManager.prototype.networkStateUpdate = function (state) { - if (!this.syncWorker) return; - log.debug('ServiceProcess PID: ' + this.servicePid); - if (state === 0) { - // pause the syncworker, but leave service running - this.syncWorker.send({ - type: 'pause', - data: null - }); - } else { - this.init(); - // looks like turtle-service always stalled after disconnected, just kill & relaunch it - let pid = this.serviceProcess.pid || null; - this.terminateService(); - // remove config - this._wipeConfig(); - // wait a bit - setImmediate(() => { - if (pid) { - try { process.kill(pid, 'SIGKILL'); } catch (e) { } - // remove config - this._wipeConfig(); - } - setTimeout(() => { - log.debug(`respawning ${config.walletServiceBinaryFilename}`); - this.serviceProcess = childProcess.spawn(this.serviceBin, this.serviceActiveArgs); - // store new pid - this.servicePid = this.serviceProcess.pid; - this.syncWorker.send({ - type: 'resume', - data: null - }); - }, 15000); - }, 2500); - } -}; - SvalinnManager.prototype.notifyUpdate = function (msg) { uiupdater.updateUiState(msg); }; -SvalinnManager.prototype.getBalance = function (address, nid) { - +SvalinnManager.prototype.getBalance = function (address, nid) +{ let mgr = this; return new Promise ((resolve, reject) => { diff --git a/src/js/ws_session.js b/src/js/ws_session.js index 35ea9b7..5bbcf53 100644 --- a/src/js/ws_session.js +++ b/src/js/ws_session.js @@ -1,15 +1,4 @@ -//const path = require('path'); -//const remote = require('electron').remote; -const Store = require('electron-store'); -const settings = new Store({ name: 'Settings' }); -const config = require('./ws_config'); - -const WS_VERSION = settings.get('version', 'unknown'); -const DEFAULT_TITLE = `${config.appName} ${WS_VERSION} - ${config.appDescription}`; -const SESSION_KEY = 'wlshell'; - -//const IS_DEBUG = remote.getGlobal('wsession').debug; -//const WALLET_CFG = path.join(remote.app.getPath('userData'), 'wconfig.txt'); +const SESSION_KEY = 'svalinSessionKey'; var SvalinnSession = function (opts) { if (!(this instanceof SvalinnSession)) return new SvalinnSession(opts); @@ -19,43 +8,22 @@ var SvalinnSession = function (opts) { this.eventName = 'sessionUpdated'; this.sessDefault = { loadedWalletAddress: '', - walletHash: '', walletUnlockedBalance: 0, - walletLockedBalance: 0, walletConfig: opts.walletConfig || 'wconfig.txt', - synchronized: false, - syncStarted: false, serviceReady: false, - connectedNode: '', - txList: [], - txLen: 0, - txLastHash: null, - txLastTimestamp: null, - txNew: [], - nodeFee: 0, - nodeChoices: settings.get('pubnodes_data', []), - servicePath: settings.get('service_bin', 'svalinn'), - configUpdated: false, - uiStateChanged: false, - defaultTitle: DEFAULT_TITLE, debug: opts.debug || false, - fusionStarted: false, - fusionProgress: false, addressBookErr: false }; this.stickyVals = { - publicNodes: [], - addressBook: null // {id: null, name: null, path: null, data: {}} + addressBook: null }; - /* jshint ignore:start */ - this.keys = Object.keys({ ...this.sessDefault, ...this.stickyVals }); + this.keys = Object.keys({ ...this.sessDefault, ...this.stickyVals }); // initialize if (!sessionStorage.getItem(this.sessKey)) { sessionStorage.setItem(this.sessKey, JSON.stringify({ ...this.sessDefault, ...this.stickyVals })); } - /* jshint ignore:end */ }; SvalinnSession.prototype.get = function (key) { @@ -98,14 +66,13 @@ SvalinnSession.prototype.reset = function (key) { sessData[key] = this.sessDefault[key]; // set to default value return sessionStorage.setItem(this.sessKey, JSON.stringify(sessData[key])); } - //return sessionStorage.setItem(this.sessKey, JSON.stringify(this.sessDefault)); + let stickyData = {}; Object.keys(this.stickyVals).forEach((e) => { stickyData[e] = this.get(e); }); - /* jshint ignore: start */ + return sessionStorage.setItem(this.sessKey, JSON.stringify({ ...this.sessDefault, ...stickyData })); - /* jshint ignore: end */ }; SvalinnSession.prototype.destroy = function () { diff --git a/src/js/ws_syncworker.js b/src/js/ws_syncworker.js deleted file mode 100644 index 9fd560c..0000000 --- a/src/js/ws_syncworker.js +++ /dev/null @@ -1,307 +0,0 @@ -const log = require('electron-log'); -const SvalinnApi = require('./ws_api'); - -let DEBUG = false; -log.transports.file.maxSize = 5 * 1024 * 1024; -log.transports.console.level = 'debug'; -log.transports.file.level = 'debug'; - -const SYNC_INTERVAL = 4 * 1000; -const SYNC_FAILED_MAX = 24; - -var serviceConfig = null; // { service_host: '127.0.0.1', service_port: '8070', service_password: 'xxx'}; -var workerState = { - CONNECTED: true, - PAUSED: false, - TX_CHECK_INITIALIZED: false, - FAILED_COUNT: 0 -}; -var syncState = { - LAST_BLOCK_COUNT: 1, - LAST_KNOWN_BLOCK_COUNT: 1, - TX_LAST_INDEX: 1, - TX_LAST_COUNT: 0, - TX_CHECK_SKIPPED_COUNT: 0, - -}; -var wsapi = null; -var taskWorker = null; - -function logDebug(msg) { - if (!DEBUG) return; - log.debug(`[syncworker] ${msg}`); -} - -function initApi(cfg) { - if (wsapi instanceof SvalinnApi) return; - logDebug('Initializing SvalinnApi'); - serviceConfig = cfg; - wsapi = new SvalinnApi(serviceConfig); -} - -function checkBlockUpdate() { - if (!serviceConfig || wsapi === null) return; - logDebug('-> blockUpdater: fetching block update'); - wsapi.getStatus().then((blockStatus) => { - let lastConStatus = workerState.CONNECTED; - let conFailed = parseInt(blockStatus.knownBlockCount, 10) === 1; - if (conFailed) { - logDebug('-> blockUpdater: Got bad known block count, mark connection as broken'); - if (lastConStatus !== conFailed) { - let fakeStatus = { - blockCount: -200, - knownBlockCount: -200, - displayBlockCount: -200, - displayKnownBlockCount: -200, - syncPercent: -200 - }; - process.send({ - type: 'blockUpdated', - data: fakeStatus - }); - } - workerState.CONNECTED = false; - return; - } - - // we have good connection - workerState.CONNECTED = true; - workerState.FAILED_COUNT = 0; - let blockCount = parseInt(blockStatus.blockCount, 10); - let knownBlockCount = parseInt(blockStatus.knownBlockCount, 10); - - let blockCountUpdated = (blockCount > syncState.LAST_BLOCK_COUNT); - let knownBlockCountUpdated = (knownBlockCount > syncState.LAST_KNOWN_BLOCK_COUNT); - if (!blockCountUpdated && !knownBlockCountUpdated && syncState.TX_CHECK_SKIPPED_COUNT < 8) { - logDebug(`-> blockUpdater: no update, skip block notifier (${syncState.TX_CHECK_SKIPPED_COUNT})`); - syncState.TX_CHECK_SKIPPED_COUNT += 1; - return; - } - - syncState.TX_CHECK_SKIPPED_COUNT = 0; - logDebug('-> blockUpdater: block updated, notify block update'); - let txcheck = (syncState.LAST_KNOWN_BLOCK_COUNT < knownBlockCount || syncState.LAST_BLOCK_COUNT < knownBlockCount); - syncState.LAST_BLOCK_COUNT = blockCount; - syncState.LAST_KNOWN_BLOCK_COUNT = knownBlockCount; - - // add any extras here, so renderer not doing too much things - let dispKnownBlockCount = (knownBlockCount - 1); - let dispBlockCount = (blockCount > dispKnownBlockCount ? dispKnownBlockCount : blockCount); - let syncPercent = ((dispBlockCount / dispKnownBlockCount) * 100); - if (syncPercent <= 0 || syncPercent >= 99.995) { - syncPercent = 100; - } else { - syncPercent = syncPercent.toFixed(2); - } - - blockStatus.displayBlockCount = dispBlockCount; - blockStatus.displayKnownBlockCount = dispKnownBlockCount; - blockStatus.syncPercent = syncPercent; - process.send({ - type: 'blockUpdated', - data: blockStatus - }); - - // don't check if we can't get any block - if (syncState.LAST_BLOCK_COUNT <= 1) return; - - // don't check tx if block count not updated - if (!txcheck && workerState.TX_CHECK_INITIALIZED) { - logDebug('-> blockUpdater: Tx check skipped'); - return; - } - - checkTransactionsUpdate(); - - }).catch((err) => { - workerState.FAILED_COUNT++; - logDebug(`-> blockUpdater: FAILED, ${err.message} | failed count: ${workerState.FAILED_COUNT}`); - if (workerState.FAILED_COUNT > SYNC_FAILED_MAX) { - logDebug('-> blockUpdater: too many timeout, mark connection as broken'); - - let fakeStatus = { - blockCount: -200, - knownBlockCount: -200, - displayBlockCount: -200, - displayKnownBlockCount: -200, - syncPercent: -200 - }; - process.send({ - type: 'blockUpdated', - data: fakeStatus - }); - workerState.STATE_CONNECTED = false; - return; - } - return false; - }); -} - -function checkTransactionsUpdate() { - if (!serviceConfig || wsapi === null) return; - - wsapi.getBalance().then((balance) => { - process.send({ - type: 'balanceUpdated', - data: balance - }); - - if (syncState.LAST_BLOCK_COUNT > 1) { - logDebug('-> txUpdater: checking tx update'); - let currentBLockCount = syncState.LAST_BLOCK_COUNT - 1; - let startIndex = (!workerState.TX_CHECK_INITIALIZED ? 1 : syncState.TX_LAST_INDEX); - let searchCount = currentBLockCount; - let needCountMargin = false; - let blockMargin = 10; - if (workerState.TX_CHECK_INITIALIZED) { - searchCount = (currentBLockCount - syncState.TX_LAST_COUNT); - needCountMargin = true; - } - - let startIndexWithMargin = (startIndex === 1 ? 1 : (startIndex - blockMargin)); - let searchCountWithMargin = needCountMargin ? searchCount + blockMargin : searchCount; - let trx_args = { - firstBlockIndex: startIndexWithMargin, - blockCount: searchCountWithMargin - }; - logDebug(`-> txUpdater: args=${JSON.stringify(trx_args)}`); - wsapi.getTransactions(trx_args).then((trx) => { - process.send({ - type: 'transactionUpdated', - data: trx - }); - saveWallet(); - return true; - }).catch((err) => { - logDebug(`-> txUpdater: getTransactions FAILED, ${err.message}`); - return false; - }); - workerState.TX_CHECK_INITIALIZED = true; - syncState.TX_LAST_INDEX = currentBLockCount; - syncState.TX_LAST_COUNT = currentBLockCount; - } - }).catch((err) => { - logDebug(`-> txUpdater: getBalance FAILED, ${err.message}`); - return false; - }); -} - -function saveWallet() { - if (!serviceConfig) return; - wsapi.save().then(() => { - logDebug(`-> saveWallet: OK`); - return true; - }).catch(() => { - return false; - }); -} - -function syncWallet() { - taskWorker = setInterval(() => { - if (workerState.PAUSED) return; - logDebug(`Wallet sync tasks...`); - checkBlockUpdate(); - }, SYNC_INTERVAL); -} - -// {type: 'blah', msg: 'any'} -process.on('message', (msg) => { - let cmd = msg || ''; - cmd.type = msg.type || 'cfg'; - cmd.data = msg.data || null; - - switch (cmd.type) { - case 'cfg': - if (cmd.data) { - serviceConfig = cmd.data; - initApi(serviceConfig); - process.send({ - type: 'serviceStatus', - data: 'OK' - }); - } - if (cmd.debug) { - DEBUG = true; - logDebug('Config received.'); - logDebug('Running in debug mode.'); - } - break; - case 'start': - logDebug('Starting'); - try { clearInterval(taskWorker); } catch (err) { } - // initial block check; - checkBlockUpdate(); - - // initial check, only to get balance - checkTransactionsUpdate(); - - setTimeout(syncWallet, 5000); - break; - case 'pause': - if (workerState.PAUSED) return; - logDebug('Got suspend command'); - process.send({ - type: 'blockUpdated', - data: { - blockCount: -50, - knownBlockCount: -50, - displayBlockCount: -50, - displayKnownBlockCount: -50, - syncPercent: -50 - } - }); - workerState.PAUSED = true; - break; - case 'resume': - logDebug('Got resume command'); - syncState.TX_CHECK_SKIPPED_COUNT = 5; - wsapi = null; - initApi(serviceConfig); - setTimeout(() => { - wsapi.getBalance().then(() => { - logDebug(`Warming up: getBalance OK`); - }).catch((err) => { - logDebug(`Warming up: getBalance FAILED, ${err.message}`); - }); - workerState.PAUSED = false; - }, 15000); - - process.send({ - type: 'blockUpdated', - data: { - blockCount: -10, - knownBlockCount: -10, - displayBlockCount: -10, - displayKnownBlockCount: -10, - syncPercent: -10 - } - }); - break; - case 'stop': - logDebug('Got stop command, halting all tasks and exit...'); - syncState.TX_SKIPPED_COUNT = 0; - serviceConfig = wsapi = null; - if (taskWorker === undefined || taskWorker === null) { - try { - clearInterval(taskWorker); - process.exit(0); - } catch (e) { - logDebug(`FAILED, ${e.message}`); - process.exit(1); - } - } - break; - default: - break; - } -}); - -process.on('uncaughtException', function (err) { - logDebug(`worker uncaughtException: ${err.message}`); - process.exit(1); -}); - -process.on('disconnect', () => function () { - logDebug(`worker disconnected`); - process.exit(1); -}); \ No newline at end of file diff --git a/src/js/ws_utils.js b/src/js/ws_utils.js index 3f5da2b..5440403 100644 --- a/src/js/ws_utils.js +++ b/src/js/ws_utils.js @@ -7,11 +7,9 @@ const config = require('./ws_config'); const fnv = require('fnv-plus'); const GCM = require('node-crypto-gcm').GCM; -const ADDRESS_REGEX_STR = `^(hx|cx)(?=[aA-zZ0-9]*$)(?:.{${config.addressLength - config.addressPrefix.length}})$`; +const ADDRESS_REGEX_STR = `^(hx|cx)(?=[a-z0-9]*$)(?:.{${config.addressLength - config.addressPrefix.length}})$`; const ADDRESS_REGEX = new RegExp(ADDRESS_REGEX_STR); -const PAYMENT_ID_REGEX = new RegExp(/^([aA-zZ0-9]{64})$/); -const SECRET_KEY_REGEX = new RegExp(/^[aA-zZ0-9]{64}$/); -const MNEMONIC_SEED_REGEX = new RegExp(/^[aA-zZ]+(?!.* )[a-zA-Z0-9 ]*$/); +const SECRET_KEY_REGEX = new RegExp(/^[a-z0-9]{64}$/); /***** * DOM util *****/ exports.triggerEvent = (el, type) => { @@ -94,7 +92,7 @@ exports.showToast = (msg, duration, elId) => { }, duration + 100); } - blekok.innerText = msg; + blekok.innerHTML = msg; blekok.classList.remove('off'); window.TOASTT = setTimeout(function () { blekok.classList.add('off'); @@ -167,58 +165,14 @@ exports.genQrDataUrl = (inputStr) => { return nImg.toDataURL(); }; -let decimalAdjust = (type, value, exp) => { - if (typeof exp === 'undefined' || +exp === 0) { - return Math[type](value); - } - value = +value; - exp = +exp; - if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { - return NaN; - } - // Shift - value = value.toString().split('e'); - value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); - // Shift back - value = value.toString().split('e'); - return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); -}; - exports.validateAddress = (address) => { return ADDRESS_REGEX.test(address); }; -exports.validatePaymentId = (paymentId) => { - if (!paymentId) return true; // true allow empty - return PAYMENT_ID_REGEX.test(paymentId); -}; - exports.validateSecretKey = (key) => { return SECRET_KEY_REGEX.test(key); }; -exports.validateMnemonic = (seed) => { - if (!seed) return false; - - if (!MNEMONIC_SEED_REGEX.test(seed)) return false; - - if (seed.split(' ').length !== 25) return false; - - return true; -}; - -exports.amountForMortal = (amount) => { - if (!config.decimalDivisor) return amount; - let decimalPlaces = config.decimalPlaces || 2; - return (amount / config.decimalDivisor).toFixed(decimalPlaces); -}; - -exports.amountForImmortal = (amount) => { - if (!config.decimalDivisor) return amount; - let da = decimalAdjust("round", parseFloat(amount), -(config.decimalPlaces)); - return parseInt(da * config.decimalDivisor); -}; - exports.isFileExist = (filePath) => { if (!filePath) return false; return fs.existsSync(filePath); diff --git a/src/js/wsui_main.js b/src/js/wsui_main.js index 4b46cc0..4aba94c 100644 --- a/src/js/wsui_main.js +++ b/src/js/wsui_main.js @@ -1,9 +1,9 @@ /*jshint bitwise: false*/ /* global AbortController */ const os = require('os'); -const net = require('net'); const path = require('path'); const fs = require('fs'); +const superagent = require('superagent'); const { clipboard, remote, ipcRenderer, shell } = require('electron'); const Store = require('electron-store'); const Mousetrap = require('./extras/mousetrap.min.js'); @@ -12,7 +12,6 @@ const wsutil = require('./ws_utils'); const SvalinnSession = require('./ws_session'); const SvalinnManager = require('./ws_manager'); const config = require('./ws_config'); -const async = require('async'); const AgGrid = require('ag-grid-community'); const wsmanager = new SvalinnManager(); const sessConfig = { debug: remote.app.debug, walletConfig: remote.app.walletConfig }; @@ -116,11 +115,7 @@ let importSeedInputPassword; let importSeedInputMnemonic; let importSeedInputScanHeight; // transaction -let txButtonRefresh; -let txButtonSortAmount; let txButtonSortDate; -let txInputUpdated; -let txButtonExport; // misc let thtml; let dmswitch; @@ -145,14 +140,6 @@ function populateElementVars() // Main section link sectionButtons = document.querySelectorAll('[data-section]'); - // Settings input & elements - settingsInputServiceBin = document.getElementById('input-settings-path'); - settingsInputMinToTray = document.getElementById('checkbox-tray-minimize'); - settingsInputCloseToTray = document.getElementById('checkbox-tray-close'); - settingsInputExcludeOfflineNodes = document.getElementById('pubnodes-exclude-offline'); - settingsInputTimeout = document.getElementById('input-settings-timeout'); - settingsButtonSave = document.getElementById('button-settings-save'); - // Overview pages overviewWalletAddress = document.getElementById('wallet-address'); overviewWalletCloseButton = document.getElementById('button-overview-closewallet'); @@ -245,7 +232,7 @@ function setDarkMode(dark) dmswitch.firstChild.dataset.icon = 'sun'; } else { thtml.classList.remove('dark'); - dmswitch.setAttribute('title', 'Swith to dark mode'); + dmswitch.setAttribute('title', 'Switch to dark mode'); dmswitch.firstChild.classList.remove('fa-sun'); dmswitch.firstChild.classList.add('fa-moon'); settings.set('darkmode', false); @@ -269,6 +256,29 @@ function showKeyBindings() dialog.showModal(); } +function checkUpdate () +{ + superagent.get('https://api.github.com/repos/iconation/svalinn/releases/latest').end((err, res) => { + if (!err && res) { + let result = JSON.parse (res.text); + let tag = result.tag_name; + if (settings.get('version') != tag) + { + wsutil.showToast ('A new version of Svalinn is available (' + tag + ') ! ' + + 'Checkout https://github.com/iconation/svalinn', + 20 * 1000); + + dialog = document.getElementById('belekok'); + let goLatestRelease = dialog.querySelector ('#go-latest-release-github'); + + goLatestRelease.addEventListener('click', (event) => { + shell.openExternal ('https://github.com/iconation/Svalinn/releases/latest'); + }); + } + } + }); +} + function showAbout() { let dialog = document.getElementById('ab-dialog'); @@ -281,10 +291,10 @@ function showAbout()
`; dialog.innerHTML = info; dialog.showModal(); - } -function switchTab() { +function switchTab() +{ if (WALLET_OPEN_IN_PROGRESS) { wsutil.showToast('Opening wallet in progress, please wait...'); return; @@ -297,8 +307,6 @@ function switchTab() { if (!isServiceReady) { skippedSections = ['section-create-transaction', 'section-send-transaction']; if (nextSection === 'section-overview') nextSection = 'section-welcome'; - } else if (wsession.get('fusionProgress')) { - skippedSections = ['section-create-transaction']; } while (skippedSections.indexOf(nextSection) >= 0) { @@ -309,7 +317,8 @@ function switchTab() { } // section switcher -function changeSection(sectionId, targetRedir) { +function changeSection(sectionId, targetRedir) +{ wsutil.showToast(''); if (WALLET_OPEN_IN_PROGRESS) { @@ -320,23 +329,14 @@ function changeSection(sectionId, targetRedir) { targetRedir = targetRedir === true ? true : false; let targetSection = sectionId.trim(); - let isSynced = wsession.get('synchronized') || true; let isServiceReady = wsession.get('serviceReady') || false; let needServiceReady = ['section-create-transaction', 'section-overview']; let needServiceStopped = 'section-welcome'; - let needSynced = ['section-create-transaction']; let origTarget = targetSection; let finalTarget = targetSection; let toastMsg = ''; - - if (needSynced.includes(targetSection) && wsession.get('fusionProgress')) { - // fusion in progress, return early - wsutil.showToast('Wallet optimization in progress, please wait'); - return; - } - if (needServiceReady.includes(targetSection) && !isServiceReady) { // no access to wallet, send, tx when no wallet opened finalTarget = 'section-welcome'; @@ -344,10 +344,6 @@ function changeSection(sectionId, targetRedir) { if (!notoast.includes(origTarget)) { toastMsg = "Please create or open your wallet first !"; } - } else if (needSynced.includes(targetSection) && !isSynced) { - // need synced, return early - wsutil.showToast("Please wait until the syncing completes!"); - return; } else if (needServiceStopped.includes(targetSection) && isServiceReady) { finalTarget = 'section-overview'; } else { @@ -362,9 +358,6 @@ function changeSection(sectionId, targetRedir) { } // reset quick filters - if (finalTarget === 'section-send-transaction' && window.TXOPTSAPI) { - window.TXOPTSAPI.api.setQuickFilter(''); - } if (finalTarget === 'section-addressbook' && window.ABOPTSAPI) { window.ABOPTSAPI.api.setQuickFilter(''); } @@ -397,38 +390,6 @@ function changeSection(sectionId, targetRedir) { } } -function initNodeSelection(nodeAddr) -{ -} - -// initial settings value or updater -function initSettingVal(values) { - values = values || null; - if (values) { - // save new settings - settings.set('service_bin', values.service_bin); - settings.set('node_address', values.node_address); - settings.set('tray_minimize', values.tray_minimize); - settings.set('tray_close', values.tray_close); - settings.set('service_timeout', values.service_timeout); - settings.set('pubnodes_exclude_offline', values.pubnodes_exclude_offline); - } - settingsInputServiceBin.value = settings.get('service_bin'); - settingsInputMinToTray.checked = settings.get('tray_minimize'); - settingsInputCloseToTray.checked = settings.get('tray_close'); - settingsInputExcludeOfflineNodes.checked = settings.get('pubnodes_exclude_offline'); - - // if custom node, save it - let mynode = settings.get('node_address'); - let pnodes = settings.get('pubnodes_data'); - if (!settings.has('pubnodes_custom')) settings.set('pubnodes_custom', []); - let cnodes = settings.get('pubnodes_custom'); - if (pnodes.indexOf(mynode) === -1 && cnodes.indexOf(mynode) === -1) { - cnodes.push(mynode); - settings.set('pubnodes_custom', cnodes); - } -} - // generic form message reset function formMessageReset() { if (!genericFormMessage.length) return; @@ -453,74 +414,13 @@ function formMessageSet(target, status, txt) { } } -// utility: blank tx filler -function setTxFiller(show) { - /* - show = show || false; - let fillerRow = document.getElementById('txfiller'); - let txRow = document.getElementById('transaction-lists'); - - if (!show && fillerRow) { - fillerRow.classList.add('hidden'); - txRow.classList.remove('hidden'); - } else { - txRow.classList.add('hidden'); - fillerRow.classList.remove('hidden'); - } - */ -} - // display initial page, settings page on first run, else overview page function showInitialPage() { // other initiations here formMessageReset(); - initSettingVal(); // initial settings value changeSection('section-welcome'); - settings.set('firstRun', 0); let versionInfo = document.getElementById('svalinnVersion'); if (versionInfo) versionInfo.innerHTML = WS_VERSION; - let tsVersionInfo = document.getElementById('turtleServiceVersion'); - if (tsVersionInfo) tsVersionInfo.innerHTML = config.walletServiceBinaryVersion; -} - -// settings page handlers -function handleSettings() { - settingsButtonSave.addEventListener('click', function () { - formMessageReset(); - let serviceBinValue = settingsInputServiceBin.value ? settingsInputServiceBin.value.trim() : ''; - let timeoutValue = settingsInputTimeout.value ? parseInt(settingsInputTimeout.value,10): 30; - - if (!serviceBinValue.length) { - formMessageSet('settings', 'error', `Settings can't be saved, please enter correct values`); - return false; - } - - if (!wsutil.isRegularFileAndWritable(serviceBinValue)) { - formMessageSet('settings', 'error', `Unable to find ${config.walletServiceBinaryFilename}, please enter the correct path`); - return false; - } - - if(timeoutValue < 30 || timeoutValue > 120) { - formMessageSet('settings', 'error', `Timeout value must be between 0 and 120`); - return false; - } - - let vals = { - service_bin: serviceBinValue, - node_address: settings.get('node_address'), - service_timeout: timeoutValue, - tray_minimize: settingsInputMinToTray.checked, - tray_close: settingsInputCloseToTray.checked, - pubnodes_exclude_offline: settingsInputExcludeOfflineNodes.checked - }; - - initSettingVal(vals); - - formMessageReset(); - let goTo = wsession.get('loadedWalletAddress').length ? 'section-overview' : 'section-welcome'; - changeSection(goTo, true); - wsutil.showToast('Settings have been updated.', 8000); - }); } // address book completions @@ -558,7 +458,6 @@ function initAddressCompletion(data) { return `
${wname.replace(re, "$1")}
${waddr.replace(re, "$1")}
`; }, onSelect: function (e, term, item) { - // document.getElementById('input-send-payid').value = item.getAttribute('data-paymentid'); } }); } @@ -722,13 +621,6 @@ function handleAddressBook() { let dialog = document.getElementById('ab-dialog'); if (dialog.hasAttribute('open')) dialog.close(); - let sendTrtl = ''; - let myaddress = wsession.get('loadedWalletAddress'); - let isSynced = wsession.get('synchronized') || false; - if (myaddress && isSynced) { - sendTrtl = ``; - } - let tpl = `

Address Detail

@@ -746,7 +638,6 @@ function handleAddressBook() {
- ${sendTrtl}
@@ -758,20 +649,6 @@ function handleAddressBook() { dialog.showModal(); } - // disable payment id input for non standard adress - function setAbPaymentIdState(addr) { - } - - addressBookInputWallet.addEventListener('change', (event) => { - let val = event.target.value || ''; - setAbPaymentIdState(val); - }); - - addressBookInputWallet.addEventListener('keyup', (event) => { - let val = event.target.value || ''; - setAbPaymentIdState(val); - }); - // add new address book file addressBookButtonAdd.addEventListener('click', () => { let dialog = document.getElementById('ab-dialog'); @@ -947,7 +824,7 @@ function handleAddressBook() { } if (!wsutil.validateAddress(addressValue)) { - formMessageSet('addressbook', 'error', `Invalid ${config.assetName} address`); + formMessageSet('addressbook', 'error', `Invalid ${config.assetName} address : must be 'hx' or 'cx' + 40 lowercase characters 0123456789abcdef`); return; } @@ -958,7 +835,7 @@ function handleAddressBook() { let abook = wsession.get('addressBook'); let addressBookData = abook.data; if (addressBookData.hasOwnProperty(entryHash) && !isUpdate) { - formMessageSet('addressbook', 'error', "This combination of address and payment ID already exist, please enter new address or different payment id."); + formMessageSet('addressbook', 'error', "This address already exists, please enter a new address."); return; } @@ -1184,18 +1061,37 @@ function handleWalletOpen() { }); } - overviewNetworkId.addEventListener ('change', () => { + function startRefreshingWalletBalance () + { wsmanager.notifyUpdate ({ type: 'balanceUpdated', data: "..." }); + + overviewWalletAddress.value = wsession.get('loadedWalletAddress'); balanceUpdate (overviewWalletAddress.value); + + if (refreshWalletWorker) { + clearInterval (refreshWalletWorker); + } + + refreshWalletWorker = setInterval(() =>{ + balanceUpdate (overviewWalletAddress.value); + }, WALLET_REFRESH_INTERVAL); + } + + overviewNetworkId.addEventListener ('change', () => { + wsmanager.notifyUpdate ({ + type: 'balanceUpdated', + data: "..." + }); }); - walletOpenButtonOpen.addEventListener('click', () => { + walletOpenButtonOpen.addEventListener('click', () => + { formMessageReset(); - // actually open wallet + // Open the wallet if (!walletOpenInputPath.value) { formMessageSet('load', 'error', "Invalid wallet file path"); WALLET_OPEN_IN_PROGRESS = false; @@ -1213,12 +1109,7 @@ function handleWalletOpen() { function onSuccess() { walletOpenInputPath.value = settings.get('recentWallet'); - overviewWalletAddress.value = wsession.get('loadedWalletAddress'); - balanceUpdate (overviewWalletAddress.value); - - refreshWalletWorker = setInterval(() =>{ - balanceUpdate (overviewWalletAddress.value); - }, WALLET_REFRESH_INTERVAL); + startRefreshingWalletBalance(); WALLET_OPEN_IN_PROGRESS = false; changeSection('section-overview'); @@ -1246,8 +1137,6 @@ function handleWalletOpen() { WALLET_OPEN_IN_PROGRESS = true; settings.set('recentWallet', walletFile); settings.set('recentWalletDir', path.dirname(walletFile)); - // formMessageSet ('load', 'warning', "Accessing wallet...
"); - wsmanager.startService (walletFile, onError, onSuccess, onDelay); }); }); @@ -1279,10 +1168,6 @@ function handleWalletOpen() { }); } -function handleWalletRescan() { - -} - function handleWalletClose() { overviewWalletCloseButton.addEventListener('click', (event) => { event.preventDefault(); @@ -1295,8 +1180,6 @@ function handleWalletClose() { dialog = document.getElementById('main-dialog'); dialog.showModal(); // save + SIGTERMed wallet daemon - // reset tx - resetTransactions(); // clear form err msg formMessageReset(); changeSection('section-overview'); @@ -1492,7 +1375,7 @@ function handleCreateTransaction() let recipientAddress = createTransactionRecipientAddress.value ? createTransactionRecipientAddress.value.trim() : ''; if (!recipientAddress.length || !wsutil.validateAddress(recipientAddress)) { - formMessageSet('create-transaction', 'error', `Invalid ${config.assetName} address`); + formMessageSet('create-transaction', 'error', `Invalid ${config.assetName} address : must be 'hx' or 'cx' + 40 lowercase characters 0123456789abcdef`); return; } @@ -1708,7 +1591,13 @@ function handleCreateTransaction() md.close(); wsmanager.sendSignedTransaction (tx).then ((txHash) => { - sendTransactionTxHash.value = txHash; + let url = wsmanager.iconNetworks[nid].tracker + "/transaction/" + txHash; + sendTransactionTxHash.innerHTML = '' + txHash + ''; + let goTrackerTx = sendTransactionTxHash.querySelector ('#go-tracker-transaction'); + goTrackerTx.addEventListener('click', (event) => { + shell.openExternal (url); + }); + sendTransactionHiddenTxHash.style.display = "block"; formMessageSet('send-transaction', 'success', `Transaction sent successfully !`); }).catch((err) => { @@ -1720,287 +1609,9 @@ function handleCreateTransaction() }); } -function resetTransactions() { - setTxFiller(true); - if (window.TXGRID === null || window.TXOPTSAPI === null) { - return; - } - window.TXOPTSAPI.api.destroy(); - window.TXOPTSAPI = null; - window.TXGRID = null; - if (document.querySelector('.txlist-item')) { - let grid = document.getElementById('txGrid'); - wsutil.clearChild(grid); - } - -} - -window.TXOPTSAPI = null; -window.TXGRID = null; -function handleTransactions() { - function resetTxSortMark() { - let sortedEl = document.querySelectorAll('#transaction-lists .asc, #transaction-lists .desc'); - Array.from(sortedEl).forEach((el) => { - el.classList.remove('asc'); - el.classList.remove('desc'); - }); - } - - function sortDefault() { - resetTxSortMark(); - window.TXOPTSAPI.api.setSortModel(getSortModel('timestamp', 'desc')); - txButtonSortDate.dataset.dir = 'desc'; - txButtonSortDate.classList.add('desc'); - } - - function getSortModel(col, dir) { - return [{ colId: col, sort: dir }]; - } - - function renderList(txs) { - setTxFiller(); - if (window.TXGRID === null) { - let columnDefs = [ - { - headerName: 'Data', - field: 'timestamp', - autoHeight: true, - cellClass: 'txinfo', - cellRenderer: function (params) { - let out = ` -

${params.data.timeStr}

-

Tx. Hash: ${params.data.transactionHash}

-

Payment ID: ${params.data.paymentId}

- `; - return out; - }, - getQuickFilterText: function (params) { - let txdate = new Date(params.data.timeStr).toLocaleString( - 'en-US', - { weekday: 'long', year: 'numeric', month: 'long', timeZone: 'UTC' } - ); - let search_data = `${txdate} ${params.data.transactionHash}`; - if (params.data.paymentId !== '-') { - search_data += ` ${params.data.paymentId}`; - } - if (params.data.extra !== '-') { - search_data += ` ${params.data.extra}`; - } - search_data += ` ${params.data.amount}`; - return search_data; - } - }, - { - headerName: "Amount", - field: "rawAmount", - width: 200, - suppressSizeToFit: true, - cellClass: ['amount', 'tx-amount'], - cellRenderer: function (params) { - return `${params.data.amount}`; - }, - suppressFilter: true, - getQuickFilterText: function () { return null; } - } - ]; - let gridOptions = { - columnDefs: columnDefs, - rowData: txs, - pagination: true, - rowClass: 'txlist-item', - paginationPageSize: 20, - cacheQuickFilter: true, - enableSorting: true, - onRowClicked: showTransaction - - }; - let txGrid = document.getElementById('txGrid'); - window.TXGRID = new AgGrid.Grid(txGrid, gridOptions); - window.TXOPTSAPI = gridOptions; - - gridOptions.onGridReady = function () { - setTxFiller(); - txGrid.style.width = "100%"; - setTimeout(function () { - window.TXOPTSAPI.api.doLayout(); - window.TXOPTSAPI.api.sizeColumnsToFit(); - sortDefault(); - }, 10); - }; - - window.addEventListener('resize', () => { - if (window.TXOPTSAPI) { - window.TXOPTSAPI.api.sizeColumnsToFit(); - } - }); - - let txfilter = document.getElementById('tx-search'); - txfilter.addEventListener('input', function () { - if (window.TXOPTSAPI) { - window.TXOPTSAPI.api.setQuickFilter(this.value); - } - }); - } else { - window.TXOPTSAPI.api.updateRowData({ add: txs }); - window.TXOPTSAPI.api.resetQuickFilter(); - sortDefault(); - } - } - - function listTransactions() { - if (wsession.get('txLen') <= 0) { - //setTxFiller(true); - return; - } - - let txs = wsession.get('txNew'); - if (!txs.length) { - //if(window.TXGRID === null ) setTxFiller(true); - return; - } - setTxFiller(); - renderList(txs); - } - - function showTransaction(el) { - let tx = el.data; - let txdate = new Date(tx.timestamp * 1000).toUTCString(); - let txhashUrl = `View in block explorer`; - let dialogTpl = ` -
-

Transaction Detail

- - - - - - - - - - - - - - - - - - - - - - - -
Hash${tx.transactionHash}
Address${wsession.get('loadedWalletAddress')}
Payment Id${tx.paymentId}
Amount${tx.amount}
Fee${tx.fee}
Timestamp${tx.timestamp} (${txdate})
Block Index${tx.blockIndex}
Is Base?${tx.isBase}
Extra${tx.extra}
Unlock Time${tx.unlockTime}
-

${txhashUrl}

- -
- `; - - let dialog = document.getElementById('tx-dialog'); - wsutil.innerHTML(dialog, dialogTpl); - dialog = document.getElementById('tx-dialog'); - dialog.showModal(); - } - - // export - function exportAsCsv(mode) { - if (wsession.get('txLen') <= 0) return; - - formMessageReset(); - mode = mode || 'all'; - let recentDir = settings.get('recentWalletDir', remote.app.getPath('documents')); - let filename = remote.dialog.showSaveDialog({ - title: "Export transactions as scv...", - defaultPath: recentDir, - filters: [ - { name: 'CSV files', extensions: ['csv'] } - ] - }); - if (!filename) return; - - const createCsvWriter = require('csv-writer').createObjectCsvWriter; - const csvWriter = createCsvWriter({ - path: filename, - header: [ - { id: 'timeStr', title: 'Time' }, - { id: 'amount', title: 'Amount' }, - { id: 'paymentId', title: 'PaymentId' }, - { id: 'transactionHash', title: 'Transaction Hash' }, - { id: 'fee', title: 'Transaction Fee' }, - { id: 'extra', title: 'Extra Data' }, - { id: 'blockIndex', title: 'Block Height' } - ] - }); - let rawTxList = wsession.get('txList'); - let txlist = rawTxList.map((obj) => { - return { - timeStr: obj.timeStr, - amount: obj.amount, - paymentId: obj.paymentId, - transactionHash: obj.transactionHash, - fee: obj.fee, - extra: obj.extra, - blockIndex: obj.blockIndex, - txType: obj.txType - }; - }); - - let dialog = document.getElementById('ab-dialog'); - let outData = []; - let outType = ''; - switch (mode) { - case 'in': - outData = txlist.filter((obj) => obj.txType === "in"); - outType = "incoming"; - break; - case 'out': - outData = txlist.filter((obj) => { return obj.txType === "out"; }); - outType = "outgoing"; - break; - default: - outData = txlist; - outType = 'all'; - break; - } - - if (!outData.length) { - wsutil.showToast(`Transaction export failed, ${outType} transactions is not available!`); - if (dialog.hasAttribute('open')) dialog.close(); - return; - } - - csvWriter.writeRecords(outData).then(() => { - if (dialog.hasAttribute('open')) dialog.close(); - wsutil.showToast(`Transaction list exported to ${filename}`); - }).catch((err) => { - if (dialog.hasAttribute('open')) dialog.close(); - wsutil.showToast(`Transaction export failed, ${err.message}`); - }); - } - - wsutil.liveEvent('button.export-txtype', 'click', (event) => { - let txtype = event.target.dataset.txtype || 'all'; - return exportAsCsv(txtype); - }); -} - -function handleNetworkChange() { - window.addEventListener('online', () => { - let connectedNode = wsession.get('connectedNode'); - if (!connectedNode.length || connectedNode.startsWith('127.0.0.1')) return; - wsmanager.networkStateUpdate(1); - }); - window.addEventListener('offline', () => { - let connectedNode = wsession.get('connectedNode'); - if (!connectedNode.length || connectedNode.startsWith('127.0.0.1')) return; - wsmanager.networkStateUpdate(0); - }); -} - // event handlers -function initHandlers() { +function initHandlers() +{ initSectionTemplates(); setDarkMode(settings.get('darkmode', true)); @@ -2077,7 +1688,6 @@ function initHandlers() { }, 400); } - //handleNetworkChange(); // open wallet handleWalletOpen(); // create wallet @@ -2086,22 +1696,16 @@ function initHandlers() { handleWalletImportKeys(); // delay some handlers setTimeout(() => { - // settings handlers - handleSettings(); // addressbook handlers handleAddressBook(); // close wallet handleWalletClose(); - // rescan/reset wallet - handleWalletRescan(); // export keys/seed handleWalletExport(); - // send transfer + // Create Transaction handleCreateTransaction(); - // transactions - handleTransactions(); - // netstatus - handleNetworkChange(); + // Check update + checkUpdate(); //external link handler wsutil.liveEvent('a.external', 'click', (event) => { event.preventDefault(); @@ -2168,57 +1772,6 @@ function initHandlers() { } }); }); - //genpaymentid+integAddress - /* - overviewPaymentIdGen.addEventListener('click', () => { - genPaymentId(false); - }); - wsutil.liveEvent('#makePaymentId', 'click', () => { - let payId = genPaymentId(true); - let iaf = document.getElementById('genOutputIntegratedAddress'); - document.getElementById('genInputPaymentId').value = payId; - iaf.value = ''; - }); - overviewIntegratedAddressGen.addEventListener('click', showIntegratedAddressForm); - */ - - /* - wsutil.liveEvent('#doGenIntegratedAddr', 'click', () => { - formMessageReset(); - let genInputAddress = document.getElementById('genInputAddress'); - let genInputPaymentId = document.getElementById('genInputPaymentId'); - let outputField = document.getElementById('genOutputIntegratedAddress'); - let addr = genInputAddress.value ? genInputAddress.value.trim() : ''; - let pid = genInputPaymentId.value ? genInputPaymentId.value.trim() : ''; - outputField.value = ''; - outputField.removeAttribute('title'); - if (!addr.length || !pid.length) { - formMessageSet('gia', 'error', 'Address & Payment ID is required'); - return; - } - if (!wsutil.validateAddress(addr)) { - formMessageSet('gia', 'error', `Invalid ${config.assetName} address`); - return; - } - // only allow standard address - if (addr.length > 99) { - formMessageSet('gia', 'error', `Only standard ${config.assetName} address are supported`); - return; - } - if (!wsutil.validatePaymentId(pid)) { - formMessageSet('gia', 'error', 'Invalid Payment ID'); - return; - } - - wsmanager.genIntegratedAddress(pid, addr).then((res) => { - formMessageReset(); - outputField.value = res.integratedAddress; - outputField.setAttribute('title', 'click to copy'); - }).catch((err) => { - formMessageSet('gia', 'error', err.message); - }); - }); - */ // inputs click to copy handlers wsutil.liveEvent('textarea.ctcl, input.ctcl', 'click', (event) => { @@ -2342,100 +1895,10 @@ function initKeyBindings() { }); } -function fetchNodeInfo(force) { - force = force || false; - - function fetchWait(url, timeout) { - let controller = new AbortController(); - let signal = controller.signal; - timeout = timeout || 6800; - return Promise.race([ - fetch(url, { signal }), - new Promise((resolve) => - setTimeout(() => { - let fakeout = { "address": "", "amount": 0, "status": "KO" }; - window.FETCHNODESIG = controller; - return resolve(fakeout); - }, timeout) - ) - ]); - } - - window.ELECTRON_ENABLE_SECURITY_WARNINGS = false; - let aliveNodes = settings.get('pubnodes_tested', []); - if (aliveNodes.length && !force) { - initNodeSelection(settings.get('node_address')); - return aliveNodes; - } - - // todo: also check block height? - let nodes = settings.get('pubnodes_data'); - - let reqs = []; - //let hrstart = process.hrtime(); - nodes.forEach(h => { - let out = { - host: h, - label: h, - }; - - let url = `http://${h}/feeinfo`; - reqs.push(function (callback) { - return fetchWait(url) - .then((response) => { - if (response.hasOwnProperty('status')) { // fake/timeout response - try { window.FETCHNODESIG.abort(); } catch (e) { } - return response; - } else { - return response.json(); - } - }).then(json => { - if (!json || !json.hasOwnProperty("address") || !json.hasOwnProperty("amount")) { - return callback(null, null); - } - - let feeAmount = ""; - if (json.status === "KO") { - feeAmount = 'Fee: unknown/timeout'; - } else { - feeAmount = parseInt(json.amount, 10) > 0 ? `Fee: ${wsutil.amountForMortal(json.amount)} ${config.assetTicker}` : "FREE"; - } - out.label = `${h.split(':')[0]} | ${feeAmount}`; - return callback(null, out); - }).catch(() => { - callback(null, null); - }); - }); - }); - const parLimit = 12; - async.parallelLimit(reqs, parLimit, function (error, results) { - if (results) { - let res = results.filter(val => val); - if (res.length) { - settings.set('pubnodes_tested', res); - } - - //let hrend = process.hrtime(hrstart); - // console.info('Execution time (hr): %ds %dms', hrend[0], hrend[1] / 1000000); - // console.info(`parlimit: ${parLimit}`); - // console.info(`total nodes: ${nodes.length}`); - // console.info(`alive nodes: ${res.length}`); - initNodeSelection(); - } else { - initNodeSelection(); - } - }); -} - // spawn event handlers document.addEventListener('DOMContentLoaded', () => { initHandlers(); showInitialPage(); - if (navigator.onLine) { - fetchNodeInfo(); - } else { - setTimeout(() => initNodeSelection, 500); - } }, false); ipcRenderer.on('cleanup', () => { @@ -2453,7 +1916,6 @@ ipcRenderer.on('cleanup', () => { let htmlStr = `
${htmlText}
`; dialog.innerHTML = htmlStr; dialog.showModal(); - wsmanager.stopSyncWorker(); try { fs.unlinkSync(wsession.get('walletConfig')); } catch (e) { } win.close(); }); diff --git a/src/js/wsui_updater.js b/src/js/wsui_updater.js index af1bf82..9de98a7 100644 --- a/src/js/wsui_updater.js +++ b/src/js/wsui_updater.js @@ -1,216 +1,11 @@ -const { webFrame, remote } = require('electron'); +const { remote } = require('electron'); const Store = require('electron-store'); const wsutil = require('./ws_utils'); const SvalinnSession = require('./ws_session'); -const config = require('./ws_config'); - -const brwin = remote.getCurrentWindow(); const settings = new Store({ name: 'Settings' }); const sessConfig = { debug: remote.app.debug, walletConfig: remote.app.walletConfig }; const wsession = new SvalinnSession(sessConfig); -/* sync progress ui */ -const syncDiv = document.getElementById('navbar-div-sync'); -const syncInfoBar = document.getElementById('navbar-text-sync'); -const connInfoDiv = document.getElementById('conn-info'); -const syncStatus = { - NET_ONLINE: -10, - NET_OFFLINE: -50, - IDLE: -100, - NODE_ERROR: -200, - RESET: -300 -}; - -const WFCLEAR_INTERVAL = 5; -let WFCLEAR_TICK = 0; -let FUSION_CHECK = 0; - -function setWinTitle(title) { - const defaultTitle = wsession.get('defaultTitle'); - brwin.setTitle((title ? `${defaultTitle} ${title}` : defaultTitle)); -} - -function triggerTxRefresh() { - const txUpdateInputFlag = document.getElementById('transaction-updated'); - txUpdateInputFlag.value = 1; - txUpdateInputFlag.dispatchEvent(new Event('change')); -} - -function updateSyncProgress(data) { - const iconSync = document.getElementById('navbar-icon-sync'); - let blockCount = data.displayBlockCount; - let knownBlockCount = data.displayKnownBlockCount; - let blockSyncPercent = data.syncPercent; - let statusText = ''; - - switch (knownBlockCount) { - case syncStatus.NET_ONLINE: - // sync status text - statusText = 'RESUMING WALLET SYNC...'; - syncInfoBar.innerHTML = statusText; - // sync info bar class - syncDiv.className = 'syncing'; - // sync status icon - iconSync.setAttribute('data-icon', 'sync'); - iconSync.classList.add('fa-spin'); - // connection status - connInfoDiv.innerHTML = 'Connection restored, resuming sync process...'; - connInfoDiv.classList.remove('empty'); - connInfoDiv.classList.remove('conn-warning'); - - // sync sess flags - wsession.set('syncStarted', false); - wsession.set('synchronized', false); - brwin.setProgressBar(-1); - break; - case syncStatus.NET_OFFLINE: - // sync status text - statusText = 'PAUSED, NETWORK DISCONNECTED'; - syncInfoBar.innerHTML = statusText; - // sync info bar class - syncDiv.className = ''; - // sync status icon - iconSync.setAttribute('data-icon', 'ban'); - iconSync.classList.remove('fa-spin'); - // connection status - connInfoDiv.innerHTML = 'Synchronization paused, please check your network connection!'; - connInfoDiv.classList.remove('empty'); - connInfoDiv.classList.add('conn-warning'); - - // sync sess flags - wsession.set('syncStarted', false); - wsession.set('synchronized', false); - brwin.setProgressBar(-1); - // reset balance - let resetBalance = { - availableBalance: 0, - lockedAmount: 0 - }; - updateBalance(resetBalance); - break; - case syncStatus.IDLE: - // sync sess flags - wsession.set('syncStarted', false); - wsession.set('synchronized', false); - brwin.setProgressBar(-1); - // reset wintitle - setWinTitle(); - // no node connected - wsession.set('connectedNode', ''); - let resetBalance = { - availableBalance: 0, - lockedAmount: 0 - }; - updateBalance(resetBalance); - break; - case syncStatus.RESET: - if (!connInfoDiv.innerHTML.startsWith('Connected')) { - let connStatusText = `Connected to: ${wsession.get('connectedNode')}`; - let connNodeFee = wsession.get('nodeFee'); - if (connNodeFee > 0) { - connStatusText += ` | Node fee: ${connNodeFee.toFixed(config.decimalPlaces)} ${config.assetTicker}`; - } - connInfoDiv.innerHTML = connStatusText; - connInfoDiv.classList.remove('conn-warning'); - connInfoDiv.classList.remove('empty'); - } - wsession.set('syncStarted', true); - statusText = 'PREPARING RESCAN...'; - // info bar class - syncDiv.className = 'syncing'; - syncInfoBar.textContent = statusText; - // status icon - iconSync.setAttribute('data-icon', 'sync'); - iconSync.classList.add('fa-spin'); - // sync status sess flag - wsession.set('synchronized', false); - brwin.setProgressBar(-1); - break; - case syncStatus.NODE_ERROR: - // status info bar class - syncDiv.className = 'failed'; - // sync status text - statusText = 'NODE ERROR'; - syncInfoBar.textContent = statusText; - //sync status icon - iconSync.setAttribute('data-icon', 'times'); - iconSync.classList.remove('fa-spin'); - // connection status - connInfoDiv.innerHTML = 'Connection failed, try switching to another Node, close and reopen your wallet'; - connInfoDiv.classList.remove('empty'); - connInfoDiv.classList.add('conn-warning'); - wsession.set('connectedNode', ''); - brwin.setProgressBar(-1); - break; - default: - if (!connInfoDiv.innerHTML.startsWith('Connected')) { - let connStatusText = `Connected to: ${wsession.get('connectedNode')}`; - let connNodeFee = wsession.get('nodeFee'); - if (connNodeFee > 0) { - connStatusText += ` | Node fee: ${connNodeFee.toFixed(config.decimalPlaces)} ${config.assetTicker}`; - } - connInfoDiv.innerHTML = connStatusText; - connInfoDiv.classList.remove('conn-warning'); - connInfoDiv.classList.remove('empty'); - } - // sync sess flags - wsession.set('syncStarted', true); - statusText = `${blockCount}/${knownBlockCount}`; - if (blockCount + 1 >= knownBlockCount && knownBlockCount !== 0) { - // info bar class - syncDiv.classList = 'synced'; - // status text - statusText = `SYNCED ${statusText}`; - syncInfoBar.textContent = statusText; - // status icon - iconSync.setAttribute('data-icon', 'check'); - iconSync.classList.remove('fa-spin'); - // sync status sess flag - wsession.set('synchronized', true); - brwin.setProgressBar(-1); - } else { - // info bar class - syncDiv.className = 'syncing'; - // status text - statusText = `SYNCING ${statusText}`; - if (blockSyncPercent < 100) statusText += ` (${blockSyncPercent}%)`; - syncInfoBar.textContent = statusText; - // status icon - iconSync.setAttribute('data-icon', 'sync'); - iconSync.classList.add('fa-spin'); - // sync status sess flag - wsession.set('synchronized', false); - let taskbarProgress = +(parseFloat(blockSyncPercent) / 100).toFixed(2); - brwin.setProgressBar(taskbarProgress); - } - break; - } - - if (WFCLEAR_TICK === WFCLEAR_INTERVAL) { - webFrame.clearCache(); - WFCLEAR_TICK = 0; - } - WFCLEAR_TICK++; - - // handle failed fusion - if (true === wsession.get('fusionProgress')) { - let lockedBalance = wsession.get('walletLockedBalance'); - if (lockedBalance <= 0 && FUSION_CHECK === 3) { - fusionCompleted(); - } - FUSION_CHECK++; - } -} - -function fusionCompleted() { - const fusionProgressBar = document.getElementById('fusionProgress'); - fusionProgressBar.classList.add('hidden'); - FUSION_CHECK = 0; - wsession.set('fusionStarted', false); - wsession.set('fusionProgress', false); - wsutil.showToast('Optimization completed. You may need to repeat the process until your wallet is fully optimized.', 5000); -} - function updateBalance (availableBalance) { const balanceAvailableField = document.querySelector('#balance-available > span'); @@ -218,135 +13,6 @@ function updateBalance (availableBalance) wsession.set ('walletUnlockedBalance', availableBalance); } -function updateTransactions(result) { - let txlistExisting = wsession.get('txList'); - const blockItems = result.items; - - if (!txlistExisting.length && !blockItems.length) { - document.getElementById('transaction-export').classList.add('hidden'); - } else { - document.getElementById('transaction-export').classList.remove('hidden'); - } - - if (!blockItems.length) return; - - let txListNew = []; - - Array.from(blockItems).forEach((block) => { - block.transactions.map((tx) => { - if (tx.amount !== 0 && !wsutil.objInArray(txlistExisting, tx, 'transactionHash')) { - tx.amount = wsutil.amountForMortal(tx.amount); - tx.timeStr = new Date(tx.timestamp * 1000).toUTCString(); - tx.fee = wsutil.amountForMortal(tx.fee); - tx.paymentId = tx.paymentId.length ? tx.paymentId : '-'; - tx.txType = (tx.amount > 0 ? 'in' : 'out'); - tx.rawAmount = parseInt(tx.amount, 10); - tx.rawFee = tx.fee; - tx.rawPaymentId = tx.paymentId; - tx.rawHash = tx.transactionHash; - txListNew.unshift(tx); - } - }); - }); - - if (!txListNew.length) return; - let latestTx = txListNew[0]; - let newLastHash = latestTx.transactionHash; - let newLastTimestamp = latestTx.timestamp; - let newTxAmount = latestTx.amount; - - // store it - wsession.set('txLastHash', newLastHash); - wsession.set('txLastTimestamp', newLastTimestamp); - let txList = txListNew.concat(txlistExisting); - wsession.set('txList', txList); - wsession.set('txLen', txList.length); - wsession.set('txNew', txListNew); - - let currentDate = new Date(); - currentDate = `${currentDate.getUTCFullYear()}-${currentDate.getUTCMonth() + 1}-${currentDate.getUTCDate()}`; - let lastTxDate = new Date(newLastTimestamp * 1000); - lastTxDate = `${lastTxDate.getUTCFullYear()}-${lastTxDate.getUTCMonth() + 1}-${lastTxDate.getUTCDate()}`; - - // amount to check - triggerTxRefresh(); - - let rememberedLastHash = settings.get('last_notification', ''); - let notify = true; - if (lastTxDate !== currentDate || (newTxAmount < 0) || rememberedLastHash === newLastHash) { - notify = false; - } - - if (notify) { - settings.set('last_notification', newLastHash); - let notiOptions = { - 'body': `Amount: ${(newTxAmount)} ${config.assetTicker}\nHash: ${newLastHash.substring(24, -0)}...`, - 'icon': '../assets/svalinn_icon.png' - }; - let itNotification = new Notification('Incoming Transfer', notiOptions); - itNotification.onclick = (event) => { - event.preventDefault(); - let txNotifyFiled = document.getElementById('transaction-notify'); - txNotifyFiled.value = 1; - txNotifyFiled.dispatchEvent(new Event('change')); - if (!brwin.isVisible()) brwin.show(); - if (brwin.isMinimized()) brwin.restore(); - if (!brwin.isFocused()) brwin.focus(); - }; - } -} - -function showFeeWarning(fee) { - fee = fee || 0; // fee vale already for mortal - let nodeFee = parseFloat(fee); - if (nodeFee <= 0) return; - - let dialog = document.getElementById('main-dialog'); - if (dialog.hasAttribute('open')) return; - dialog.classList.add('dialog-warning'); - let htmlStr = ` -
Fee Info
-

You are connected to a public node (${settings.get('node_address')}) that charges a fee to send transactions.

-

The fee for sending transactions is: ${fee.toFixed(config.decimalPlaces)} ${config.assetTicker} .
- If you don't want to pay the node fee, please close your wallet, reopen and choose different public node (or run your own node). -

-

- `; - dialog.innerHTML = htmlStr; - dialog.showModal(); - dialog.addEventListener('close', function () { - dialog.classList.remove('dialog-warning'); - wsutil.clearChild(dialog); - }); -} - -function updateQr(address) -{ - if (!address) { - triggerTxRefresh(); - try { clearInterval(window.backupReminderTimer); } catch (_e) { } - return; - } - - let walletHash = wsutil.fnvhash(address); - wsession.set('walletHash', walletHash); - - let oldImg = document.getElementById('qr-gen-img'); - if (oldImg) oldImg.remove(); - - let qr_base64 = wsutil.genQrDataUrl(address); - if (qr_base64.length) { - let qrBox = document.getElementById('div-w-qr'); - let qrImg = document.createElement("img"); - qrImg.setAttribute('id', 'qr-gen-img'); - qrImg.setAttribute('src', qr_base64); - qrBox.prepend(qrImg); - document.getElementById('scan-qr-help').classList.remove('hidden'); - } else { - document.getElementById('scan-qr-help').classList.add('hidden'); - } -} - function resetFormState() { const allFormInputs = document.querySelectorAll('.section input,.section textarea'); if (!allFormInputs) return; @@ -371,14 +37,11 @@ function resetFormState() { } } } - - const settingsBackBtn = document.getElementById('button-settings-back'); - if (wsession.get('serviceReady')) { - // connInfoDiv.classList.remove('empty'); - settingsBackBtn.dataset.section = 'section-welcome'; - } else { - // connInfoDiv.classList.add('empty'); - settingsBackBtn.dataset.section = 'section-overview'; + + const hidemeElements = document.querySelectorAll('div.hideme'); + for (var i = 0; i < hidemeElements.length; i++) { + let el = hidemeElements[i]; + el.style.display = "none"; } } @@ -386,41 +49,14 @@ function resetFormState() { function updateUiState(msg) { // do something with msg switch (msg.type) { - case 'blockUpdated': - updateSyncProgress(msg.data); - break; case 'balanceUpdated': updateBalance(msg.data); break; - case 'transactionUpdated': - updateTransactions(msg.data); - break; - case 'nodeFeeUpdated': - showFeeWarning(msg.data); - break; - case 'addressUpdated': - updateQr(msg.data); - break; case 'sectionChanged': if (msg.data) resetFormState(msg.data); - break; - case 'fusionTxCompleted': - const fusionProgressBar = document.getElementById('fusionProgress'); - if (msg.code === 0) { // skipped - wsession.set('fusionProgress', false); - fusionProgressBar.classList.add('hidden'); - wsutil.showToast(msg.data, 5000); - } else { - // set progress flag - wsession.set('fusionProgress', true); - // show progress bar - fusionProgressBar.classList.remove('hidden'); - // do nothing, just wait - } - break; default: - // console.log('invalid command', msg); + console.log('invalid command', msg); break; } }