diff --git a/jsconfig.json b/jsconfig.json index 9e26dfeeb6e641a33dae4961196235bdb965b21b..9cb1d84b57165b066626eeca2e1428d98a8a2e26 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "compileOnSave": false +} diff --git a/www/js/config.js b/www/js/config.js index 0027636ef2017dcd29ec16061c4429d103dbd6b8..47f6eb71dda8eec252d41f26426775af37cd8865 100644 --- a/www/js/config.js +++ b/www/js/config.js @@ -15,7 +15,7 @@ angular.module("cesium.config", []) "fallbackLanguage": "en", "rememberMe": true, "showUDHistory": true, - "timeout": 40000, + "timeout": 3000, "timeWarningExpireMembership": 5184000, "timeWarningExpire": 7776000, "keepAuthIdle": 600, @@ -124,4 +124,4 @@ angular.module("cesium.config", []) "newIssueUrl": "https://git.duniter.org/clients/cesium-grp/cesium/issues/new" }) -; \ No newline at end of file +; diff --git a/www/js/entities/peer.js b/www/js/entities/peer.js index 91e7b6d131d4cf95804d9e4de425e49b5c8e672a..6a2e1fbbaadb869646c6e4fa03b46d74d06b0486 100644 --- a/www/js/entities/peer.js +++ b/www/js/entities/peer.js @@ -120,7 +120,7 @@ Peer.prototype.getIPv6 = function() { Peer.prototype.getPort = function() { var bma = this.bma || this.getBMA(); - return bma.port ? bma.port : null; + return bma.port ? parseInt(bma.port) : null; }; Peer.prototype.getHost = function(getHost) { diff --git a/www/js/platform.js b/www/js/platform.js index ae21c0b969a49e2ce58b9f4779765ab82352662c..39813ca2b962fe85a67aee1d6404245942ba8199 100644 --- a/www/js/platform.js +++ b/www/js/platform.js @@ -102,15 +102,14 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services'] .factory('csPlatform', function (ionicReady, $rootScope, $q, $state, $translate, $timeout, $ionicHistory, $window, UIUtils, Modals, BMA, Device, - csHttp, csConfig, csCache, csSettings, csCurrency, csWallet) { + csHttp, csConfig, csCache, csSettings, csNetwork, csCurrency, csWallet) { 'ngInject'; var - fallbackNodeIndex = 0, - defaultSettingsNode, + checkBmaNodeAliveCounter = 0, started = false, startPromise, - listeners, + listeners = [], removeChangeStateListener; // Fix csConfig values @@ -146,58 +145,120 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services'] function checkBmaNodeAlive(alive) { if (alive) return true; - // Remember the default node - defaultSettingsNode = defaultSettingsNode || csSettings.data.node; + checkBmaNodeAliveCounter++; + if (checkBmaNodeAliveCounter > 3) throw 'ERROR.CHECK_NETWORK_CONNECTION'; // Avoid infinite loop - var fallbackNode = csSettings.data.fallbackNodes && fallbackNodeIndex < csSettings.data.fallbackNodes.length && csSettings.data.fallbackNodes[fallbackNodeIndex++]; - if (!fallbackNode) { - throw 'ERROR.CHECK_NETWORK_CONNECTION'; - } - var newServer = fallbackNode.host + ((!fallbackNode.port && fallbackNode.port != 80 && fallbackNode.port != 443) ? (':' + fallbackNode.port) : ''); + return BMA.filterAliveNodes(csSettings.data.fallbackNodes, Math.min(csConfig.timeout, 3000)/*3s max*/) + .then(function (fallbackNodes) { + if (!fallbackNodes.length) { + throw 'ERROR.CHECK_NETWORK_CONNECTION'; + } + var randomIndex = Math.floor(Math.random() * fallbackNodes.length); + var fallbackNode = fallbackNodes[randomIndex]; + return fallbackNode; + }) + .then(function (fallbackNode) { - // Skip is same as actual node - if (BMA.node.same(fallbackNode)) { - console.debug('[platform] Skipping fallback node [{0}]: same as actual node'.format(newServer)); - return checkBmaNodeAlive(); // loop (= go to next node) - } + // Not expert mode: continue with the fallback node + if (!csSettings.data.expertMode) { + console.info("[platform] Switching to fallback node: {}".format(fallbackNode.server)); + return fallbackNode; + } - // Try to get summary - return csHttp.get(fallbackNode.host, fallbackNode.port, '/node/summary', fallbackNode.port == 443 || BMA.node.forceUseSsl)() - .catch(function (err) { - console.error('[platform] Could not reach fallback node [{0}]: skipping'.format(newServer)); - // silent, but return no result (will loop to the next fallback node) - }) - .then(function (res) { - if (!res) return checkBmaNodeAlive(); // Loop + // If expert mode: ask user to confirm, before switching to fallback node + var confirmMsgParams = {old: BMA.server, new: fallbackNode.server}; // Force to show port/ssl, if this is the only difference - var messageParam = {old: BMA.server, new: newServer}; - if (messageParam.old === messageParam.new) { + if (confirmMsgParams.old === confirmMsgParams.new) { if (BMA.port != fallbackNode.port) { - messageParam.new += ':' + fallbackNode.port; + confirmMsgParams.new += ':' + fallbackNode.port; } else if (BMA.useSsl == false && (fallbackNode.useSsl || fallbackNode.port == 443)) { - messageParam.new += ' (SSL)'; + confirmMsgParams.new += ' (SSL)'; } } - return $translate('CONFIRM.USE_FALLBACK_NODE', messageParam) - .then(function (msg) { - return UIUtils.alert.confirm(msg); - }) + return $translate('CONFIRM.USE_FALLBACK_NODE', confirmMsgParams) + .then(UIUtils.alert.confirm) .then(function (confirm) { - if (!confirm) return; + if (!confirm) return; // Stop + return fallbackNode; + }); + }) + .then(function (fallbackNode) { + if (!fallbackNode) return; // Skip + + // Only change BMA node in settings + csSettings.data.node = fallbackNode; + + // Add a marker, for UI + csSettings.data.node.temporary = true; + csHttp.cache.clear(); + + // loop + return BMA.copy(fallbackNode) + .then(checkBmaNodeAlive); + }); + } + + // Make sure the BMA node is synchronized (is on the main consensus block) + function checkBmaNodeSynchronized() { + var now = Date.now(); + console.info("[platform] Checking if node is synchronized..."); + + csNetwork.getSynchronizedBmaPeers(BMA, { + timeout: Math.min(csConfig.timeout, 3000 /*3s max*/) + }) + .then(function(peers) { + console.info("[platform] Network scanned in {0}ms, {1} peers (UP and synchronized) found".format(Date.now() - now, peers.length)); + + // TODO: store sync peers in storage ? + + // Try to find the current peer in the list of synchronized peers + var synchronized = _.some(peers, function(peer) { + return BMA.node.same({ + host: peer.getHost(), + port: peer.getPort(), + useSsl: peer.isSsl() + }); + }); + + // OK (BMA node is sync): continue + if (synchronized) { + console.info("[platform] Default peer [{0}] is well synchronized.".format(BMA.server)); + return true; + } + + var consensusBlockNumber = peers.length ? peers[0].currentNumber : undefined; + return csCurrency.blockchain.current() + .then(function(block) { + + // Only one block late: keep current node + if (Math.abs(block.number - consensusBlockNumber) <= 2) { + console.info("[platform] Keep BMA node [{0}], as current block #{1} is closed to consensus block #{2}".format(BMA.server, block.number, consensusBlockNumber)); + return true; + } + + // If Expert mode: ask user to select a node + if (csSettings.data.expertMode) { + return selectBmaNode(); + } + + var randomIndex = Math.floor(Math.random() * peers.length); + var randomPeer = peers[randomIndex]; + var node = { + host: randomPeer.getHost(), + port: randomPeer.getPort(), + useSsl: randomPeer.isSsl() + }; + console.info("[platform] Randomly selected peer {0}".format(randomPeer.server)); // Only change BMA node in settings - csSettings.data.node = fallbackNode; + csSettings.data.node = node; // Add a marker, for UI csSettings.data.node.temporary = true; - csHttp.cache.clear(); - - // loop - return BMA.copy(fallbackNode) - .then(checkBmaNodeAlive); + return BMA.copy(node); }); }); } @@ -263,10 +324,10 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services'] function addListeners() { - listeners = [ - // Listen if node changed + // Listen if node changed + listeners.push( BMA.api.node.on.restart($rootScope, restart, this) - ]; + ); } function removeListeners() { @@ -294,7 +355,6 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services'] // Avoid change state disableChangeState(); - // We use 'ionicReady()' instead of '$ionicPlatform.ready()', because this one is callable many times startPromise = ionicReady() @@ -308,9 +368,10 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services'] // Load BMA .then(function(){ + checkBmaNodeAliveCounter = 0; return BMA.ready() .then(checkBmaNodeAlive) - .then(selectBmaNode); + .then(checkBmaNodeSynchronized); }) // Load currency diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js index ffacc98f31e8b2d258929557bdfc0f5e12ebea9f..edf1cb67c1d4d5d46579407de5bff9a1d824ef8f 100644 --- a/www/js/services/bma-services.js +++ b/www/js/services/bma-services.js @@ -234,13 +234,13 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium. }; } - that.isAlive = function(node) { + that.isAlive = function(node, timeout) { node = node ||Â that; // WARN: // - Cannot use previous get() function, because // node can be !=that, or not be started yet // - Do NOT use cache here - return csHttp.get(node.host, node.port, '/node/summary', node.useSsl)() + return csHttp.get(node.host, node.port, '/node/summary', node.useSsl || that.forceUseSsl, timeout)() .then(function(json) { var software = json && json.duniter && json.duniter.software; var isCompatible = true; @@ -259,7 +259,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium. return isCompatible; }) .catch(function() { - return false; + return false; // Unreacheable }); }; @@ -382,6 +382,31 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium. }); }; + that.filterAliveNodes = function(fallbackNodes, timeout) { + var fallbackNodes = _.filter(fallbackNodes || [], function(node) { + node.server = node.server || node.host + ((!node.port && node.port != 80 && node.port != 443) ? (':' + node.port) : ''); + var same = that.node.same(node); + if (same) console.debug('[BMA] Skipping fallback node [{0}]: same as current BMA node'.format(node.server)); + return !same; + }); + + var aliveNodes = []; + return $q.all(_.map(fallbackNodes, function(node) { + return that.isAlive(node, timeout) + .then(function(alive) { + if (alive) { + aliveNodes.push(node); + } + else { + console.error('[BMA] Unreacheable (or not compatible) fallback node [{0}]: skipping'.format(node.server)); + } + }) + })) + .then(function() { + return aliveNodes; + }); + } + that.api.registerEvent('node', 'start'); that.api.registerEvent('node', 'stop'); that.api.registerEvent('node', 'restart'); diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js index e38aad0dfab5e25287d585dac660c6cc1dcd202f..64b14df498ae2c3206c87a7c0a9bd3ee9e6f2a4b 100644 --- a/www/js/services/network-services.js +++ b/www/js/services/network-services.js @@ -12,6 +12,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', }, isHttpsMode = $window.location.protocol === 'https:', api = new Api(this, "csNetwork"), + startPromise, data = { bma: null, @@ -169,7 +170,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', // The peer lookup end, we can make a clean final report sortPeers(true/*update main buid*/); - console.debug('[network] Finish: {0} peers found.'.format(data.peers.length)); + console.debug('[network] {0} peers found.'.format(data.peers.length)); } }, 1000); @@ -286,6 +287,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', }) .then(function(){ data.searchingPeersOnNetwork = false; + data.loading = false; + if (newPeers.length) { + flushNewPeersAndSort(newPeers, true/*update main buid*/); + } + return data.peers; }) .catch(function(err){ console.error(err); @@ -755,7 +761,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', start = function(bma, options) { options = options || {}; - return BMA.ready() + startPromise = BMA.ready() .then(function() { close(); @@ -775,17 +781,18 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', } }) .then(function() { - console.info('[network] Starting network from [{0}]'.format(bma.server)); + console.info('[network] Starting from [{0}]'.format(bma.server)); var now = Date.now(); addListeners(); return loadPeers() .then(function(peers){ - console.debug('[network] Started in '+(Date.now() - now)+'ms'); - return peers; + console.debug('[network] Started in {0}ms, {1} peers found'.format(Date.now() - now, peers.length)); + return data; }); }); + return startPromise; }, close = function() { @@ -797,36 +804,47 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', }, isStarted = function() { - return !data.bma; + return !!data.bma; }, - $q_started = function(callback) { - if (!isStarted()) { // start first - return start() + startIfNeed = function(bma, options) { + if (startPromise) return startPromise; + if (!isStarted()) { + // Start (then stop) if need + return start(bma, options) .then(function() { - return $q(callback); + close(); + return data; }); } else { - return $q(callback); + return $q.resolve(data); } }, - getMainBlockUid = function() { - return $q_started(function(resolve, reject){ - resolve (data.mainBuid); - }); + getMainBlockUid = function(bma, options) { + return startIfNeed(bma, options) + .then(function(data) { + return data.mainBlock && data.mainBlock.buid; + }); }, // Get peers on the main consensus blocks - getTrustedPeers = function() { - return $q_started(function(resolve, reject){ - resolve(data.peers.reduce(function(res, peer){ - return (peer.hasMainConsensusBlock && peer.uid) ? res.concat(peer) : res; - }, [])); - }); - } - ; + getSynchronizedBmaPeers = function(bma, options) { + options = options || {}; + options.filter = options.filter || {}; + options.filter.bma = angular.isDefined(options.filter.bma) ? options.filter.bma : true; + options.filter.ssl = isHttpsMode ? true : undefined; + options.filter.online = true; + options.filter.expertMode = false; + + return startIfNeed(bma, options) + .then(function(data){ + return _.filter(data.peers, function(peer) { + return peer.hasMainConsensusBlock && peer.isBma(); + }); + }); + }; // Register extension points api.registerEvent('data', 'changed'); @@ -840,7 +858,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', hasPeers: hasPeers, getPeers: getPeers, sort: sort, - getTrustedPeers: getTrustedPeers, + getSynchronizedBmaPeers: getSynchronizedBmaPeers, getKnownBlocks: getKnownBlocks, getMainBlockUid: getMainBlockUid, loadPeers: loadPeers,