diff --git a/docker/my-dojo/.env b/docker/my-dojo/.env index 8dd6548..f42db71 100644 --- a/docker/my-dojo/.env +++ b/docker/my-dojo/.env @@ -13,7 +13,7 @@ COMPOSE_CONVERT_WINDOWS_PATHS=1 DOJO_VERSION_TAG=1.9.0 DOJO_DB_VERSION_TAG=1.2.0 DOJO_BITCOIND_VERSION_TAG=1.8.0 -DOJO_NODEJS_VERSION_TAG=1.8.0 +DOJO_NODEJS_VERSION_TAG=1.9.0 DOJO_NGINX_VERSION_TAG=1.5.0 DOJO_TOR_VERSION_TAG=1.5.0 DOJO_EXPLORER_VERSION_TAG=1.3.0 diff --git a/keys/index-example.js b/keys/index-example.js index 7e6c98a..902b4ad 100644 --- a/keys/index-example.js +++ b/keys/index-example.js @@ -15,7 +15,7 @@ module.exports = { /* * Dojo version */ - dojoVersion: '1.8.0', + dojoVersion: '1.9.0', /* * Bitcoind */ @@ -232,7 +232,7 @@ module.exports = { * Testnet parameters */ testnet: { - dojoVersion: '1.8.0', + dojoVersion: '1.9.0', bitcoind: { rpc: { user: 'user', diff --git a/package-lock.json b/package-lock.json index da87e85..aa42406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "samourai-dojo", - "version": "1.8.0", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d2b4d14..e05353f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "samourai-dojo", - "version": "1.8.0", + "version": "1.9.0", "description": "Backend server for Samourai Wallet", "main": "accounts/index.js", "scripts": { diff --git a/static/admin/dmt/addresses-tools/addresses-tools.js b/static/admin/dmt/addresses-tools/addresses-tools.js index ccc302c..c8c4dbb 100644 --- a/static/admin/dmt/addresses-tools/addresses-tools.js +++ b/static/admin/dmt/addresses-tools/addresses-tools.js @@ -30,8 +30,7 @@ const screenAddressesToolsScript = { lib_api.getExplorerPairingInfo().then(explorerInfo => { this.explorerInfo = explorerInfo }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, @@ -68,14 +67,13 @@ const screenAddressesToolsScript = { this.showImportForm(false) } }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) throw e }) }, importAddress: function() { - lib_msg.displayMessage('Processing address import...'); + lib_msg.displayMessage('Processing address import. Please wait...'); const jsonData = {'active': this.currentAddress} return lib_api.getWallet(jsonData) .then(result => { @@ -83,13 +81,12 @@ const screenAddressesToolsScript = { lib_msg.displayInfo('Import complete') }) }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, rescanAddress: function() { - lib_msg.displayMessage('Processing address rescan...'); + lib_msg.displayMessage('Processing address rescan. Please wait...'); return lib_api.getAddressRescan(this.currentAddress) .then(result => { this.hideRescanForm() @@ -97,8 +94,7 @@ const screenAddressesToolsScript = { lib_msg.displayInfo('Rescan complete') }) }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, diff --git a/static/admin/dmt/blocks-rescan/blocks-rescan.html b/static/admin/dmt/blocks-rescan/blocks-rescan.html index b99a52f..80738b3 100644 --- a/static/admin/dmt/blocks-rescan/blocks-rescan.html +++ b/static/admin/dmt/blocks-rescan/blocks-rescan.html @@ -1,7 +1,7 @@

BLOCKS RESCAN

-
Force the Tracker to rescan a range of blocks.
+
Force the Tracker to rescan a small range of blocks.
diff --git a/static/admin/dmt/blocks-rescan/blocks-rescan.js b/static/admin/dmt/blocks-rescan/blocks-rescan.js index 0d47447..19b9bb8 100644 --- a/static/admin/dmt/blocks-rescan/blocks-rescan.js +++ b/static/admin/dmt/blocks-rescan/blocks-rescan.js @@ -32,8 +32,7 @@ const screenBlocksRescanScript = { const msg = `successfully rescanned blocks between height ${fromHeightRes} and height ${toHeightRes}` lib_msg.displayInfo(msg) }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }).then(() => { $('#rescan-from-height').val('') $('#rescan-to-height').val('') diff --git a/static/admin/dmt/index.html b/static/admin/dmt/index.html index 3140759..0237e01 100644 --- a/static/admin/dmt/index.html +++ b/static/admin/dmt/index.html @@ -15,6 +15,7 @@ + diff --git a/static/admin/dmt/pairing/pairing.js b/static/admin/dmt/pairing/pairing.js index 56d2ef6..4073473 100644 --- a/static/admin/dmt/pairing/pairing.js +++ b/static/admin/dmt/pairing/pairing.js @@ -27,8 +27,7 @@ const screenPairingScript = { lib_msg.cleanMessagesUi() return result }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) return result }) }, diff --git a/static/admin/dmt/pushtx/pushtx.js b/static/admin/dmt/pushtx/pushtx.js index 514d44d..6472e3b 100644 --- a/static/admin/dmt/pushtx/pushtx.js +++ b/static/admin/dmt/pushtx/pushtx.js @@ -15,7 +15,7 @@ const pushtxScript = { }, refreshPushTxStatus: function() { - lib_msg.displayMessage('Loading PushTx status info...'); + //lib_msg.displayMessage('Loading PushTx status info...'); lib_api.getPushtxStatus().then(pushTxStatus => { if (pushTxStatus) { const data = pushTxStatus['data'] @@ -23,19 +23,18 @@ const pushtxScript = { $('#pushed-uptime').text(uptime) $('#pushed-count').text(data['push']['count']) $('#pushed-amount').text(data['push']['amount']) - lib_msg.cleanMessagesUi() + //lib_msg.cleanMessagesUi() } }).catch(e => { $('#pushed-uptime').text('-') $('#pushed-count').text('-') $('#pushed-amount').text('-') - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, refreshScheduledTxsList: function() { - lib_msg.displayMessage('Loading PushTx orchestrator status info...'); + //lib_msg.displayMessage('Loading PushTx orchestrator status info...'); lib_api.getOrchestratorStatus().then(orchestrStatus => { if(orchestrStatus) { const data = orchestrStatus['data'] @@ -45,11 +44,10 @@ const pushtxScript = { this.processedSchedTxs.add(tx['schTxid']) } } - lib_msg.cleanMessagesUi() + //lib_msg.cleanMessagesUi() } }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, diff --git a/static/admin/dmt/status/status.js b/static/admin/dmt/status/status.js index e893b97..d9e5e78 100644 --- a/static/admin/dmt/status/status.js +++ b/static/admin/dmt/status/status.js @@ -13,27 +13,26 @@ const statusScript = { }, refreshApiStatus: function() { - lib_msg.displayMessage('Loading API status info...'); + //lib_msg.displayMessage('Loading API status info...'); return lib_api.getApiStatus().then(apiStatus => { if (apiStatus) { $('#tracker-status-ind').html('✓') $('#tracker-status-ind').css('color', '#76d776') $('#tracker-uptime').text(apiStatus['uptime']) $('#tracker-chaintip').text(apiStatus['blocks']) - lib_msg.cleanMessagesUi() + //lib_msg.cleanMessagesUi() } }).catch(e => { $('#tracker-status-ind').text('X') $('#tracker-status-ind').css('color', '#f77c7c') $('#tracker-uptime').text('-') $('#tracker-chaintip').text('-') - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, refreshPushTxStatus: function() { - lib_msg.displayMessage('Loading Tracker status info...'); + //lib_msg.displayMessage('Loading Tracker status info...'); lib_api.getPushtxStatus().then(pushTxStatus => { if (pushTxStatus) { const data = pushTxStatus['data'] @@ -47,7 +46,7 @@ const statusScript = { $('#node-network').text(network) $('#node-conn').text(data['bitcoind']['conn']) $('#node-relay-fee').text(data['bitcoind']['relayfee']) - lib_msg.cleanMessagesUi() + //lib_msg.cleanMessagesUi() } }).catch(e => { $('#node-status-ind').text('-') @@ -58,8 +57,7 @@ const statusScript = { $('#node-network').text('-') $('#node-conn').text('-') $('#node-relay-fee').text('-') - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, diff --git a/static/admin/dmt/txs-tools/txs-tools.js b/static/admin/dmt/txs-tools/txs-tools.js index b6e992e..6bf7f0e 100644 --- a/static/admin/dmt/txs-tools/txs-tools.js +++ b/static/admin/dmt/txs-tools/txs-tools.js @@ -24,8 +24,7 @@ const screenTxsToolsScript = { lib_api.getExplorerPairingInfo().then(explorerInfo => { this.explorerInfo = explorerInfo }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, diff --git a/static/admin/dmt/welcome/welcome.html b/static/admin/dmt/welcome/welcome.html index 4584cda..ac78347 100644 --- a/static/admin/dmt/welcome/welcome.html +++ b/static/admin/dmt/welcome/welcome.html @@ -26,7 +26,7 @@ Check if a transaction is found in a block or in the mempool of your full node. BLOCKS RESCAN - Rescan the transactions confirmed by the blocks in a given range. + Force the Tracker to rescan a small range of blocks.
For large rescans you should prefer XPUB or address rescans.
HELP diff --git a/static/admin/dmt/xpubs-tools/xpubs-tools.js b/static/admin/dmt/xpubs-tools/xpubs-tools.js index b8967d5..7f02d1c 100644 --- a/static/admin/dmt/xpubs-tools/xpubs-tools.js +++ b/static/admin/dmt/xpubs-tools/xpubs-tools.js @@ -3,6 +3,7 @@ const screenXpubsToolsScript = { explorerInfo: null, currentXpub: null, isReimport: false, + rescanStatusTimerId: null, initPage: function() { this.getExplorerInfo() @@ -32,8 +33,7 @@ const screenXpubsToolsScript = { lib_api.getExplorerPairingInfo().then(explorerInfo => { this.explorerInfo = explorerInfo }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) }) }, @@ -70,14 +70,13 @@ const screenXpubsToolsScript = { this.showImportForm(false) } }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + lib_errors.processError(e) throw e }) }, importXpub: function() { - lib_msg.displayMessage('Processing xpub import...'); + lib_msg.displayMessage('Processing xpub import. Please wait...'); const jsonData = { 'xpub': this.currentXpub, @@ -97,33 +96,73 @@ const screenXpubsToolsScript = { return lib_api.postXpub(jsonData) .then(result => { + // Successful import this._searchXpub(this.currentXpub).then(() => { lib_msg.displayInfo('Import complete') }) }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + // Check if import has timeout'd or if we have an error + if (e['status'] == 502 || e['status'] == 504) { + // Wait for import completion + this.checkRescanStatus(() => { + this._searchXpub(this.currentXpub).then(() => { + lib_msg.displayInfo('Import complete') + }) + }) + } else { + lib_errors.processError(e) + } }) }, rescanXpub: function() { - lib_msg.displayMessage('Processing xpub rescan...'); + lib_msg.displayMessage('Processing xpub rescan. Please wait...'); let startIdx = $('#rescan-start-idx').val() startIdx = (startIdx == null) ? 0 : parseInt(startIdx) let lookahead = $('#rescan-lookahead').val() lookahead = (lookahead == null) ? 100 : parseInt(lookahead) return lib_api.getXpubRescan(this.currentXpub, lookahead, startIdx) - .then(result => { + .then(() => { + // Successful rescan this.hideRescanForm() this._searchXpub(this.currentXpub).then(() => { lib_msg.displayInfo('Rescan complete') }) }).catch(e => { - lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e)) - console.log(e) + // Check if rescan has timeout'd or if we have an error + if (e['status'] == 502 || e['status'] == 504) { + // Wait for rescan completion + this.checkRescanStatus(() => { + this.hideRescanForm() + this._searchXpub(this.currentXpub).then(() => { + lib_msg.displayInfo('Rescan complete') + }) + }) + } else { + lib_errors.processError(e) + } }) }, + checkRescanStatus: function(callback) { + this.rescanStatusTimerId = setTimeout(() => { + lib_api.getXpubRescanStatus(this.currentXpub) + .then(result => { + if (result['data']['import_in_progress']) { + lib_msg.displayMessage('Rescan in progress. Please wait...'); + return this.checkRescanStatus(callback) + } else { + clearTimeout(this.rescanStatusTimerId) + return callback() + } + }).catch(e => { + lib_errors.processError(e) + lib_msg.displayMessage('Rescan in progress. Please wait...'); + return this.checkRescanStatus(callback) + }) + }, 10000) + }, + setXpubDetails: function(xpubInfo) { $('tr.tx-row').remove() $('tr.utxo-row').remove() diff --git a/static/admin/index.html b/static/admin/index.html index f66f05d..5073354 100644 --- a/static/admin/index.html +++ b/static/admin/index.html @@ -14,6 +14,7 @@ + diff --git a/static/admin/index.js b/static/admin/index.js index fd8980d..1349deb 100644 --- a/static/admin/index.js +++ b/static/admin/index.js @@ -33,8 +33,7 @@ function login() { } }, function (jqxhr) { - let msg = lib_msg.extractJqxhrErrorMsg(jqxhr) - lib_msg.displayErrors(msg) + lib_errors.processError(jqxhr) } ) } diff --git a/static/admin/lib/api-wrapper.js b/static/admin/lib/api-wrapper.js index 9a3b43b..47f492d 100644 --- a/static/admin/lib/api-wrapper.js +++ b/static/admin/lib/api-wrapper.js @@ -110,6 +110,14 @@ const lib_api = { ) }, + /** + * Gets the status of a xpub rescan + */ + getXpubRescanStatus: function(xpub) { + let uri = this.baseUri + '/xpub/' + xpub + '/import/status' + return this.sendGetUriEncoded(uri, {}) + }, + /** * Notifies the server of the new HD account for tracking. */ diff --git a/static/admin/lib/auth-utils.js b/static/admin/lib/auth-utils.js index d357971..656a3d7 100644 --- a/static/admin/lib/auth-utils.js +++ b/static/admin/lib/auth-utils.js @@ -9,6 +9,9 @@ const lib_auth = { /* SessionStorage Key used for refresh token */ SESSION_STORE_REFRESH_TOKEN: 'refresh_token', + /* SessionStorage Key used for the timestamp of the refresh token */ + SESSION_STORE_REFRESH_TOKEN_TS: 'refresh_token_ts', + /* JWT Scheme */ JWT_SCHEME: 'Bearer', @@ -43,6 +46,8 @@ const lib_auth = { * Stores refresh token in session storage */ setRefreshToken: function(token) { + const now = new Date(); + sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN_TS, now.getTime()) sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN, token) }, @@ -56,17 +61,23 @@ const lib_auth = { const now = new Date(); const atts = sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN_TS) - const timeElapsed = (now.getTime() - atts) / 1000 + let timeElapsed = (now.getTime() - atts) / 1000 // Refresh the access token if more than 5mn if (timeElapsed > 300) { - const dataJson = { - 'rt': this.getRefreshToken() + // Check if refresh token has expired or is about to expire + const rtts = sessionStorage.getItem(this.SESSION_STORE_REFRESH_TOKEN_TS) + if ((now.getTime() - rtts) / 1000 > 7200 - 60) { + // Force user to sign in again + this.logout() + return } let self = this - let deferred = lib_api.refreshToken(dataJson) + let deferred = lib_api.refreshToken({ + 'rt': this.getRefreshToken() + }) deferred.then( function (result) { diff --git a/static/admin/lib/errors-utils.js b/static/admin/lib/errors-utils.js new file mode 100644 index 0000000..e32c528 --- /dev/null +++ b/static/admin/lib/errors-utils.js @@ -0,0 +1,24 @@ +const lib_errors = { + + // Extract jqxhr error message + extractJqxhrErrorMsg: function(jqxhr) { + let hasErrorMsg = ('responseJSON' in jqxhr) && + (jqxhr['responseJSON'] != null) && + ('error' in jqxhr['responseJSON']) + + return hasErrorMsg ? jqxhr['responseJSON']['error'] : jqxhr.statusText + }, + + // Manage errors + processError: function(e) { + const errorMsg = this.extractJqxhrErrorMsg(e) + // Redirect to sign in page if authentication error + if (errorMsg == 'Invalid JSON Web Token' || errorMsg == 'Missing JSON Web Token') { + lib_auth.logout() + } else { + lib_msg.displayErrors(errorMsg) + console.log(e) + } + }, + +} diff --git a/static/admin/lib/messages.js b/static/admin/lib/messages.js index 8518491..b08adbb 100644 --- a/static/admin/lib/messages.js +++ b/static/admin/lib/messages.js @@ -1,14 +1,5 @@ const lib_msg = { - // Extracts jqxhr error message - extractJqxhrErrorMsg: function(jqxhr) { - let hasErrorMsg = ('responseJSON' in jqxhr) && - (jqxhr['responseJSON'] != null) && - ('error' in jqxhr['responseJSON']) - - return hasErrorMsg ? jqxhr['responseJSON']['error'] : jqxhr.statusText - }, - // UI functions addTextinID: function(text, id){ $(id).html(text.toUpperCase())