diff --git a/static/admin/dmt/xpubs-tools/xpubs-tools.js b/static/admin/dmt/xpubs-tools/xpubs-tools.js
index b8967d5..2ada7e5 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()
@@ -10,8 +11,11 @@ const screenXpubsToolsScript = {
$('#btn-xpub-search-go').click(() => {this.searchXpub()})
$('#btn-xpub-details-reset').click(() => {this.showSearchForm()})
$('#btn-xpub-details-rescan').click(() => {this.showRescanForm()})
+ $('#btn-xpub-details-delete').click(() => {this.showDeletionForm()})
$('#btn-xpub-rescan-go').click(() => {this.rescanXpub()})
$('#btn-xpub-rescan-cancel').click(() => {this.hideRescanForm()})
+ $('#btn-xpub-delete-go').click(() => {this.deleteXpub()})
+ $('#btn-xpub-delete-cancel').click(() => {this.hideDeletionForm()})
$('#btn-xpub-import-go').click(() => {this.importXpub()})
$('#btn-xpub-details-retype').click(() => {this.showImportForm(true)})
$('#btn-xpub-import-cancel').click(() => {this.hideImportForm(this.isReimport)})
@@ -24,6 +28,7 @@ const screenXpubsToolsScript = {
preparePage: function() {
this.hideRescanForm()
+ this.hideDeletionForm()
this.showSearchForm()
$("#xpub").focus()
},
@@ -32,8 +37,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 +74,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,
@@ -95,35 +98,75 @@ const screenXpubsToolsScript = {
jsonData['segwit'] = 'bip84'
}
- return lib_api.postXpub(jsonData)
- .then(result => {
+ try {
+ lib_api.postXpub(jsonData)
+ // Wait for import completion and display progress
+ this.checkRescanStatus(() => {
this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Import complete')
})
- }).catch(e => {
- lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
- console.log(e)
})
+ } catch(e) {
+ 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 => {
+
+ try {
+ lib_api.getXpubRescan(this.currentXpub, lookahead, startIdx)
+ // Wait for rescan completion and display progress
+ this.checkRescanStatus(() => {
this.hideRescanForm()
this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Rescan complete')
})
+ })
+ } catch(e) {
+ lib_errors.processError(e)
+ }
+ },
+
+ deleteXpub: function() {
+ lib_msg.displayMessage('Deleting a xpub. Please wait...')
+ return lib_api.getXpubDelete(this.currentXpub)
+ .then(result => {
+ this.currentXpub = null
+ this.preparePage()
+ lib_msg.displayInfo('Xpub successfully deleted')
}).catch(e => {
- lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
- console.log(e)
+ lib_errors.processError(e)
})
},
+ checkRescanStatus: function(callback) {
+ this.rescanStatusTimerId = setTimeout(() => {
+ lib_api.getXpubRescanStatus(this.currentXpub)
+ .then(result => {
+ const data = result['data']
+ if (data['import_in_progress']) {
+ const lblOp = (data['status'] == 'rescan') ? 'Rescan' : 'Import'
+ const lblHits = (data['status'] == 'rescan') ? 'hits detected' : 'transactions imported'
+ const msg = `${lblOp} in progress (${data['hits']} ${lblHits})`
+ lib_msg.displayMessage(msg)
+ 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)
+ })
+ }, 1000)
+ },
+
setXpubDetails: function(xpubInfo) {
$('tr.tx-row').remove()
$('tr.utxo-row').remove()
@@ -254,6 +297,17 @@ const screenXpubsToolsScript = {
$('#xpubs-tool-actions').show()
},
+ showDeletionForm: function() {
+ $('#xpubs-tool-actions').hide()
+ $('#xpubs-deletion-actions').show()
+ lib_msg.cleanMessagesUi()
+ },
+
+ hideDeletionForm: function() {
+ $('#xpubs-deletion-actions').hide()
+ $('#xpubs-tool-actions').show()
+ },
+
}
screenScripts.set('#screen-xpubs-tools', screenXpubsToolsScript)
diff --git a/static/admin/icons/samourai-logo-loading.png b/static/admin/icons/samourai-logo-loading.png
deleted file mode 100644
index 8df79af..0000000
Binary files a/static/admin/icons/samourai-logo-loading.png and /dev/null differ
diff --git a/static/admin/icons/samourai-logo-trans@2x.png b/static/admin/icons/samourai-logo-trans@2x.png
deleted file mode 100644
index 50a599c..0000000
Binary files a/static/admin/icons/samourai-logo-trans@2x.png and /dev/null differ
diff --git a/static/admin/icons/samourai-logo.png b/static/admin/icons/samourai-logo.png
new file mode 100644
index 0000000..0a0c108
Binary files /dev/null and b/static/admin/icons/samourai-logo.png differ
diff --git a/static/admin/index.html b/static/admin/index.html
index f66f05d..adb5ff0 100644
--- a/static/admin/index.html
+++ b/static/admin/index.html
@@ -14,6 +14,7 @@
+
@@ -22,7 +23,7 @@
-
+
DOJO // MAINTENANCE TOOL beta
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..2fdae1d 100644
--- a/static/admin/lib/api-wrapper.js
+++ b/static/admin/lib/api-wrapper.js
@@ -110,6 +110,23 @@ const lib_api = {
)
},
+ /**
+ * Deletes a xpub
+ */
+ getXpubDelete: function(xpub) {
+ let prefix = conf['prefixes']['support']
+ let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/delete'
+ return this.sendGetUriEncoded(uri, {})
+ },
+
+ /**
+ * 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..106cd16
--- /dev/null
+++ b/static/admin/lib/errors-utils.js
@@ -0,0 +1,25 @@
+const lib_errors = {
+
+ // Extract jqxhr error message
+ extractJqxhrErrorMsg: function(jqxhr) {
+ let hasErrorMsg = ('responseJSON' in jqxhr) &&
+ (jqxhr['responseJSON'] != null) &&
+ ('error' in jqxhr['responseJSON']) &&
+ (typeof jqxhr['responseJSON']['error'] == 'string')
+
+ 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())
diff --git a/tracker/blockchain-processor.js b/tracker/blockchain-processor.js
index b5f7069..f0ee571 100644
--- a/tracker/blockchain-processor.js
+++ b/tracker/blockchain-processor.js
@@ -60,7 +60,7 @@ class BlockchainProcessor extends AbstractProcessor {
const daemonNbHeaders = info.headers
// Consider that we are in IBD mode if Dojo is far in the past (> 13,000 blocks)
- this.isIBD = (highest.blockHeight < 612000) || (highest.blockHeight < daemonNbHeaders - 13000)
+ this.isIBD = (highest.blockHeight < 655000) || (highest.blockHeight < daemonNbHeaders - 13000)
if (this.isIBD)
return this.catchupIBDMode()
@@ -169,7 +169,7 @@ class BlockchainProcessor extends AbstractProcessor {
try {
const hash = await this.client.getblockhash(height)
const header = await this.client.getblockheader(hash)
- return this.processBlock(header)
+ return this.processBlock(header)
} catch(e) {
Logger.error(e, 'Tracker : BlockchainProcessor.catchupNormalMode()')
process.exit()
@@ -206,25 +206,25 @@ class BlockchainProcessor extends AbstractProcessor {
/**
* Upon receipt of a new block hash, retrieve the block header from bitcoind via
* RPC. Continue pulling block headers back through the chain until the database
- * contains header.previousblockhash, adding the headers to a stack. If the
- * previousblockhash is not found on the first call, this is either a chain
+ * contains header.previousblockhash, adding the headers to a stack. If the
+ * previousblockhash is not found on the first call, this is either a chain
* re-org or the tracker missed blocks during a shutdown.
*
* Once the chain has bottomed out with a known block in the database, delete
* all known database transactions confirmed in blocks at heights greater than
- * the last known block height. These transactions are orphaned but may reappear
- * in the new chain. Notify relevant accounts of balance updates /
+ * the last known block height. These transactions are orphaned but may reappear
+ * in the new chain. Notify relevant accounts of balance updates /
* transaction confirmation counts.
*
* Delete block entries not on the main chain.
*
- * Forward-scan through the block headers, pulling the full raw block hex via
+ * Forward-scan through the block headers, pulling the full raw block hex via
* RPC. The raw block contains all transactions and is parsed by bitcoinjs-lib.
- * Add the block to the database. Run checkTransaction for each transaction in
+ * Add the block to the database. Run checkTransaction for each transaction in
* the block that is not in the database. Confirm all transactions in the block.
*
* After each block, query bitcoin against all database unconfirmed outputs
- * to see if they remain in the mempool or have been confirmed in blocks.
+ * to see if they remain in the mempool or have been confirmed in blocks.
* Malleated transactions entering the wallet will disappear from the mempool on
* block confirmation.
*
@@ -247,7 +247,7 @@ class BlockchainProcessor extends AbstractProcessor {
} catch(err) {
Logger.error(err, `Tracker : BlockchainProcessor.onBlockHash() : error in getblockheader(${blockHash})`)
}
-
+
if(headers == null)
return null
@@ -263,7 +263,7 @@ class BlockchainProcessor extends AbstractProcessor {
// Process the blocks
return await util.seriesCall(headers, header => {
- return this.processBlock(header)
+ return this.processBlock(header)
})
} catch(e) {
@@ -302,7 +302,7 @@ class BlockchainProcessor extends AbstractProcessor {
}
/**
- * Cancel confirmation of transactions
+ * Cancel confirmation of transactions
* and delete blocks after a given height
* @param {integer} height - height of last block maintained
* @returns {Promise}
@@ -348,7 +348,7 @@ class BlockchainProcessor extends AbstractProcessor {
Logger.info(`Tracker : Rescanning block ${height}`)
const hash = await this.client.getblockhash(height)
const header = await this.client.getblockheader(hash)
- return this.processBlock(header)
+ return this.processBlock(header)
} catch(e) {
Logger.error(e, 'Tracker : BlockchainProcessor.rescan()')
throw e
@@ -367,7 +367,7 @@ class BlockchainProcessor extends AbstractProcessor {
const hex = await this.client.getblock(header.hash, false)
const block = new Block(hex, header)
-
+
const txsForBroadcast = await block.checkBlock()
// Send notifications