diff --git a/ionic.project b/ionic.project index 1114d8e80333f332780b7eb97d73c144889486e1..2aae2082ec11a1d2c7849726dc195e913886f1af 100644 --- a/ionic.project +++ b/ionic.project @@ -24,4 +24,4 @@ "version": "12.41.296.5" } ] -} +} \ No newline at end of file diff --git a/www/js/controllers/currency-controllers.js b/www/js/controllers/currency-controllers.js index c4cc0e83993c06cb563929b00eda8bbbca7ac550..70ec7390052fc58f9bc29aef3366acbed76573d6 100644 --- a/www/js/controllers/currency-controllers.js +++ b/www/js/controllers/currency-controllers.js @@ -100,7 +100,7 @@ function CurrencyLookupController($scope, $state, UIUtils, csCurrency) { }; } -function CurrencyViewController($scope, $q, BMA, UIUtils, csSettings, csCurrency) { +function CurrencyViewController($scope, $q, $timeout, BMA, UIUtils, csSettings, csCurrency, csNetwork) { $scope.formData = { useRelative: csSettings.data.useRelative, currency: '', @@ -120,49 +120,59 @@ function CurrencyViewController($scope, $q, BMA, UIUtils, csSettings, csCurrency stepMax: 0, xpercent: 0, durationFromLastUD: 0, + blockUid: null }; $scope.node = null; $scope.loading = true; $scope.screen = UIUtils.screen; $scope.$on('$ionicView.enter', function(e, state) { - if (state.stateParams && state.stateParams.name) { // Load by name - csCurrency.searchByName(state.stateParams.name) - .then(function(currency){ - $scope.load(currency); - }); - } - else { - csCurrency.all() - .then(function (currencies) { - if (currencies && currencies.length > 0) { - $scope.load(currencies[0]); + if ($scope.loading) { // run only once (first enter) + if (state.stateParams && state.stateParams.name) { // Load by name + csCurrency.searchByName(state.stateParams.name) + .then(function(currency){ + $scope.init(currency); + }); + } + else { + csCurrency.default() + .then(function (currency) { + $scope.init(currency); + }) + .catch(UIUtils.onError('ERROR.GET_CURRENCY_FAILED')); + } + + csNetwork.api.data.on.mainBlockChanged($scope, function(data) { + if ($scope.loading) return; + if ($scope.formData.blockUid !== data.mainBuid) { + console.debug("[currency] Updating parameters UI (new main block detected)"); + $timeout($scope.load, 1000 /*waiting propagation to requested node*/); } - }) - .catch(UIUtils.onError('ERROR.GET_CURRENCY_FAILED')); + }); } }); - $scope.load = function(currency) { + $scope.init = function(currency) { $scope.formData.currency = currency.name; $scope.node = !BMA.node.same(currency.peer.host, currency.peer.port) ? BMA.instance(currency.peer.host, currency.peer.port) : BMA; + UIUtils.loading.show(); - // Load currency parameters - $scope.loadParameter(); + // Load data + $scope.load() - // Show help tip - $scope.showHelpTip(); + // Show help tip + .then($scope.showHelpTip); }; - $scope.loadParameter = function() { + $scope.load = function() { if (!$scope.node) { return; } + // Load data from node - var data = {}; - var M, lastUDTime; + var data = {}, M, lastUDTime, now = new Date().getTime(); return $q.all([ // Get the currency parameters @@ -186,6 +196,7 @@ function CurrencyViewController($scope, $q, BMA, UIUtils, csSettings, csCurrency data.N = block.membersCount; data.medianTime = block.medianTime; data.difficulty = block.powMin; + data.blockUid = [block.number, block.hash].join('-'); }) .catch(function(err){ // Special case for currency init (root block not exists): use fixed values @@ -231,13 +242,15 @@ function CurrencyViewController($scope, $q, BMA, UIUtils, csSettings, csCurrency data.MoverN = (Mprev ? Mprev : M/*need at currency start only*/) / data.Nprev; data.UD = data.currentUD; data.durationFromLastUD = lastUDTime ? data.medianTime - lastUDTime : 0; + data.useRelative = $scope.formData.useRelative; // Apply to formData angular.copy(data, $scope.formData); + console.debug("[currency] Parameters loaded in " + (new Date().getTime() - now) + 'ms' ); $scope.loading = false; $scope.$broadcast('$$rebind::' + 'rebind'); // force bind of currency name - UIUtils.loading.hide(); + return UIUtils.loading.hide(); }) .catch(function(err) { $scope.loading = false; diff --git a/www/js/controllers/network-controllers.js b/www/js/controllers/network-controllers.js index 4066d69c8d1d4a41d41e3db9dfaa91a7efdd17ef..46fbfffe667fcad938b734c830799cbbaf269df7 100644 --- a/www/js/controllers/network-controllers.js +++ b/www/js/controllers/network-controllers.js @@ -76,7 +76,7 @@ function NetworkLookupController($scope, $timeout, $state, $ionicPopover, BMA, U if (!refreshing) { refreshing = true; $timeout(function() { // Timeout avoid to quick updates - console.debug("Updating UI Peers"); + console.debug("[peers] Updating UI"); $scope.search.results = data.peers; $scope.search.memberPeersCount = data.memberPeersCount; $scope.search.loading = csNetwork.isBusy(); diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js index 5ac3ed14530a06b6bb2e57b5ec41a7a12e30c8ae..2c3583082158a9120acdc2141a1654d72a6dde0c 100644 --- a/www/js/services/bma-services.js +++ b/www/js/services/bma-services.js @@ -346,4 +346,696 @@ angular.module('cesium.bma.services', ['ngResource', 'cesium.http.services', 'ce return service; }) + + .factory('BMA', function($q, csSettings, csHttp, csCache, $rootScope, $timeout) { + 'ngInject'; + + function factory(host, port, cacheEnable) { + + function exact(regexpContent) { + return new RegExp("^" + regexpContent + "$"); + } + + var + regex = { + USER_ID: "[A-Za-z0-9_-]+", + CURRENCY: "[A-Za-z0-9_-]+", + PUBKEY: "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}", + COMMENT: "[ a-zA-Z0-9-_:/;*\\[\\]()?!^\\+=@&~#{}|\\\\<>%.]*", + // duniter://[uid]:[pubkey]@[host]:[port] + URI_WITH_AT: "duniter://(?:([A-Za-z0-9_-]+):)?([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44})@([a-zA-Z0-9-.]+.[ a-zA-Z0-9-_:/;*?!^\\+=@&~#|<>%.]+)", + URI_WITH_PATH: "duniter://([a-zA-Z0-9-.]+.[a-zA-Z0-9-_:.]+)/([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44})(?:/([A-Za-z0-9_-]+))?" + }, + errorCodes = { + REVOCATION_ALREADY_REGISTERED: 1002, + HTTP_LIMITATION: 1006, + IDENTITY_SANDBOX_FULL: 1007, + NO_MATCHING_IDENTITY: 2001, + UID_ALREADY_USED: 2003, + NO_MATCHING_MEMBER: 2004, + NO_IDTY_MATCHING_PUB_OR_UID: 2021, + MEMBERSHIP_ALREADY_SEND: 2007, + NO_CURRENT_BLOCK: 2010, + BLOCK_NOT_FOUND: 2011, + TX_ALREADY_PROCESSED: 2030 + }, + constants = { + PROTOCOL_VERSION: 10, + ROOT_BLOCK_HASH: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', + LIMIT_REQUEST_COUNT: 5, // simultaneous async request to a Duniter node + LIMIT_REQUEST_DELAY: 1000 // time (in second) to wait between to call of a rest request + }; + + var exports = { + errorCodes: errorCodes, + constants: constants, + regex: { + USER_ID: exact(regex.USER_ID), + COMMENT: exact(regex.COMMENT), + PUBKEY: exact(regex.PUBKEY), + CURRENCY: exact(regex.CURRENCY), + URI: exact(regex.URI) + }, + node: { + server: csHttp.getServer(host, port), + host: host, + port: port, + summary: csHttp.getWithCache(host, port, '/node/summary', csHttp.cache.LONG), + same: function(host2, port2) { + return host2 == host && ((!port && !port2) || (port == port2)); + } + }, + network: { + peering: { + peers: csHttp.get(host, port, '/network/peering/peers') + }, + peers: csHttp.get(host, port, '/network/peers') + }, + wot: { + lookup: csHttp.get(host, port, '/wot/lookup/:search'), + certifiedBy: csHttp.get(host, port, '/wot/certified-by/:pubkey'), + certifiersOf: csHttp.get(host, port, '/wot/certifiers-of/:pubkey'), + member: { + all: cacheEnable ? csHttp.getWithCache(host, port, '/wot/members') : csHttp.get(host, port, '/wot/members'), + pending: csHttp.get(host, port, '/wot/pending') + }, + requirements: csHttp.get(host, port, '/wot/requirements/:pubkey'), + add: csHttp.post(host, port, '/wot/add'), + certify: csHttp.post(host, port, '/wot/certify'), + revoke: csHttp.post(host, port, '/wot/revoke') + }, + blockchain: { + parameters: csHttp.getWithCache(host, port, '/blockchain/parameters', csHttp.cache.LONG), + block: cacheEnable ? csHttp.getWithCache(host, port, '/blockchain/block/:block', csHttp.cache.SHORT) : csHttp.get(host, port, '/blockchain/block/:block'), + current: csHttp.get(host, port, '/blockchain/current'), + membership: csHttp.post(host, port, '/blockchain/membership'), + stats: { + ud: cacheEnable ? csHttp.getWithCache(host, port, '/blockchain/with/ud', csHttp.cache.SHORT) : csHttp.get(host, port, '/blockchain/with/ud'), + tx: csHttp.get(host, port, '/blockchain/with/tx'), + newcomers: csHttp.get(host, port, '/blockchain/with/newcomers'), + hardship: csHttp.get(host, port, '/blockchain/hardship/:pubkey') + } + }, + tx: { + sources: csHttp.get(host, port, '/tx/sources/:pubkey'), + process: csHttp.post(host, port, '/tx/process'), + history: { + all: csHttp.get(host, port, '/tx/history/:pubkey'), + times: cacheEnable ? csHttp.getWithCache(host, port, '/tx/history/:pubkey/times/:from/:to') : csHttp.get(host, port, '/tx/history/:pubkey/times/:from/:to'), + timesNoCache: csHttp.get(host, port, '/tx/history/:pubkey/times/:from/:to'), + blocks: cacheEnable ? csHttp.getWithCache(host, port, '/tx/history/:pubkey/blocks/:from/:to') : csHttp.get(host, port, '/tx/history/:pubkey/blocks/:from/:to'), + pending: csHttp.get(host, port, '/tx/history/:pubkey/pending') + } + }, + ud: { + history: csHttp.get(host, port, '/ud/history/:pubkey') + }, + uri: {}, + raw: { + + } + }; + + exports.copy = function(otherNode) { + if (!!this.instance) { // if main service impl + var instance = this.instance; // keep factory + csCache.clearAll(); // clean all caches + angular.copy(otherNode, this); + this.instance = instance; + } + else { + angular.copy(otherNode, this); + } + }; + + exports.wot.member.uids = function() { + return exports.wot.member.all() + .then(function(res){ + return res.results.reduce(function(res, member){ + res[member.pubkey] = member.uid; + return res; + }, {}); + }); + }; + + exports.wot.member.get = function(pubkey) { + return exports.wot.member.uids() + .then(function(memberUidsByPubkey){ + var uid = memberUidsByPubkey[pubkey]; + return { + pubkey: pubkey, + uid: (uid ? uid : null) + }; + }); + }; + + /** + * Return all expected blocks + * @param blockNumbers a rray of block number + */ + exports.blockchain.blocks = function(blockNumbers){ + return exports.raw.blocksRecursive(blockNumbers, 0, exports.constants.LIMIT_REQUEST_COUNT); + }; + + exports.raw.blocksRecursive = function(blockNumbers, offset, size) { + return $q(function(resolve, reject) { + var result = []; + var jobs = []; + _.each(blockNumbers.slice(offset, offset+size), function(blockNumber) { + jobs.push( + exports.blockchain.block({block: blockNumber}) + .then(function(block){ + if (!block) return; + result.push(block); + }) + ); + }); + + $q.all(jobs) + .then(function() { + if (offset < blockNumbers.length - 1) { + $timeout(function() { + exports.raw.blocksRecursive(blockNumbers, offset+size, size) + .then(function(blocks) { + if (!blocks || !blocks.length) { + resolve(result); + return; + } + resolve(result.concat(blocks)); + }) + .catch(function(err) { + reject(err); + }); + }, exports.constants.LIMIT_REQUEST_DELAY); + } + else { + resolve(result); + } + }) + .catch(function(err){ + if (err && err.ucode === errorCodes.HTTP_LIMITATION) { + resolve(result); + } + else { + reject(err); + } + }); + }); + }; + + exports.blockchain.lastUd = function() { + return exports.blockchain.stats.ud() + .then(function(res) { + if (!res.result.blocks || !res.result.blocks.length) { + return null; + } + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return exports.blockchain.block({block: lastBlockWithUD}) + .then(function(block){ + return (block.unitbase > 0) ? block.dividend * Math.pow(10, block.unitbase) : block.dividend; + }); + }); + }; + + exports.uri.parse = function(uri) { + return $q(function(resolve, reject) { + // If pubkey: not need to parse + if (exact(regex.PUBKEY).test(uri)) { + resolve({ + pubkey: uri + }); + } + else if(uri.startsWith('duniter://')) { + var parser = csHttp.uri.parse(uri), + pubkey, + uid, + currency = parser.host.indexOf('.') === -1 ? parser.host : null, + host = parser.host.indexOf('.') !== -1 ? parser.host : null; + if (parser.username) { + if (parser.password) { + uid = parser.username; + pubkey = parser.password; + } + else { + pubkey = parser.username; + } + } + if (parser.pathname) { + var paths = parser.pathname.split('/'); + var pathCount = !paths ? 0 : paths.length; + var index = 0; + if (!currency && pathCount > index) { + currency = paths[index++]; + } + if (!pubkey && pathCount > index) { + pubkey = paths[index++]; + } + if (!uid && pathCount > index) { + uid = paths[index++]; + } + if (pathCount > index) { + reject( {message: 'Bad Duniter URI format. Invalid path (incomplete or redundant): '+ parser.pathname}); return; + } + } + + if (!currency){ + if (host) { + csHttp.get(host + '/blockchain/parameters')() + .then(function(parameters){ + resolve({ + uid: uid, + pubkey: pubkey, + host: host, + currency: parameters.currency + }); + }) + .catch(function(err) { + console.log(err); + reject({message: 'Could not get node parameter. Currency could not be retrieve'}); + }); + } + else { + reject({message: 'Bad Duniter URI format. Missing currency name (or node address).'}); return; + } + } + else { + if (!host) { + resolve({ + uid: uid, + pubkey: pubkey, + currency: currency + }); + } + + // Check if currency are the same (between node and uri) + return csHttp.get(host + '/blockchain/parameters')() + .then(function(parameters){ + if (parameters.currency !== currency) { + reject( {message: "Node's currency ["+parameters.currency+"] does not matched URI's currency ["+currency+"]."}); return; + } + resolve({ + uid: uid, + pubkey: pubkey, + host: host, + currency: currency + }); + }); + } + } + else { + throw {message: 'Bad URI format: ' + uri}; + } + }) + + // Check values against regex + .then(function(result) { + if (result.pubkey && !(exact(regex.PUBKEY).test(result.pubkey))) { + reject({message: "Invalid pubkey format [" + result.pubkey + "]"}); return; + } + if (result.uid && !(exact(regex.USER_ID).test(result.uid))) { + reject({message: "Invalid uid format [" + result.uid + "]"}); return; + } + if (result.currency && !(exact(regex.CURRENCY).test(result.currency))) { + reject({message: "Invalid currency format ["+result.currency+"]"}); return; + } + return result; + }); + }; + + exports.websocket = { + block: function() { + return csHttp.ws('ws://' + exports.node.server + '/ws/block'); + }, + peer: function() { + return csHttp.ws('ws://' + exports.node.server + '/ws/peer'); + }, + close : csHttp.closeAllWs + }; + + return exports; + } + + var service = factory(csSettings.data.node.host, csSettings.data.node.port, true /*cache*/); + service.instance = factory; + + // Listen settings changes + csSettings.api.data.on.changed($rootScope, function(settings) { + + var nodeServer = csHttp.getServer(settings.node.host, settings.node.port); + if (nodeServer != service.node.server) { + var newService = factory(settings.node.host, settings.node.port, true /*cache*/); + service.copy(newService); // reload service + } + + }); + + return service; + }) + + .factory('BMA', function($q, csSettings, csHttp, csCache, $rootScope, $timeout) { + 'ngInject'; + + function factory(host, port, cacheEnable) { + + function exact(regexpContent) { + return new RegExp("^" + regexpContent + "$"); + } + + var + regex = { + USER_ID: "[A-Za-z0-9_-]+", + CURRENCY: "[A-Za-z0-9_-]+", + PUBKEY: "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}", + COMMENT: "[ a-zA-Z0-9-_:/;*\\[\\]()?!^\\+=@&~#{}|\\\\<>%.]*", + // duniter://[uid]:[pubkey]@[host]:[port] + URI_WITH_AT: "duniter://(?:([A-Za-z0-9_-]+):)?([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44})@([a-zA-Z0-9-.]+.[ a-zA-Z0-9-_:/;*?!^\\+=@&~#|<>%.]+)", + URI_WITH_PATH: "duniter://([a-zA-Z0-9-.]+.[a-zA-Z0-9-_:.]+)/([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44})(?:/([A-Za-z0-9_-]+))?" + }, + errorCodes = { + REVOCATION_ALREADY_REGISTERED: 1002, + HTTP_LIMITATION: 1006, + IDENTITY_SANDBOX_FULL: 1007, + NO_MATCHING_IDENTITY: 2001, + UID_ALREADY_USED: 2003, + NO_MATCHING_MEMBER: 2004, + NO_IDTY_MATCHING_PUB_OR_UID: 2021, + MEMBERSHIP_ALREADY_SEND: 2007, + NO_CURRENT_BLOCK: 2010, + BLOCK_NOT_FOUND: 2011, + TX_ALREADY_PROCESSED: 2030 + }, + constants = { + PROTOCOL_VERSION: 10, + ROOT_BLOCK_HASH: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', + LIMIT_REQUEST_COUNT: 5, // simultaneous async request to a Duniter node + LIMIT_REQUEST_DELAY: 1000 // time (in second) to wait between to call of a rest request + }; + + var exports = { + errorCodes: errorCodes, + constants: constants, + regex: { + USER_ID: exact(regex.USER_ID), + COMMENT: exact(regex.COMMENT), + PUBKEY: exact(regex.PUBKEY), + CURRENCY: exact(regex.CURRENCY), + URI: exact(regex.URI) + }, + node: { + server: csHttp.getServer(host, port), + host: host, + port: port, + summary: csHttp.getWithCache(host, port, '/node/summary', csHttp.cache.LONG), + same: function(host2, port2) { + return host2 == host && ((!port && !port2) || (port == port2)); + } + }, + network: { + peering: { + peers: csHttp.get(host, port, '/network/peering/peers') + }, + peers: csHttp.get(host, port, '/network/peers') + }, + wot: { + lookup: csHttp.get(host, port, '/wot/lookup/:search'), + certifiedBy: csHttp.get(host, port, '/wot/certified-by/:pubkey'), + certifiersOf: csHttp.get(host, port, '/wot/certifiers-of/:pubkey'), + member: { + all: cacheEnable ? csHttp.getWithCache(host, port, '/wot/members') : csHttp.get(host, port, '/wot/members'), + pending: csHttp.get(host, port, '/wot/pending') + }, + requirements: csHttp.get(host, port, '/wot/requirements/:pubkey'), + add: csHttp.post(host, port, '/wot/add'), + certify: csHttp.post(host, port, '/wot/certify'), + revoke: csHttp.post(host, port, '/wot/revoke') + }, + blockchain: { + parameters: csHttp.getWithCache(host, port, '/blockchain/parameters', csHttp.cache.LONG), + block: cacheEnable ? csHttp.getWithCache(host, port, '/blockchain/block/:block', csHttp.cache.SHORT) : csHttp.get(host, port, '/blockchain/block/:block'), + current: csHttp.get(host, port, '/blockchain/current'), + membership: csHttp.post(host, port, '/blockchain/membership'), + stats: { + ud: cacheEnable ? csHttp.getWithCache(host, port, '/blockchain/with/ud', csHttp.cache.SHORT) : csHttp.get(host, port, '/blockchain/with/ud'), + tx: csHttp.get(host, port, '/blockchain/with/tx'), + newcomers: csHttp.get(host, port, '/blockchain/with/newcomers'), + hardship: csHttp.get(host, port, '/blockchain/hardship/:pubkey') + } + }, + tx: { + sources: csHttp.get(host, port, '/tx/sources/:pubkey'), + process: csHttp.post(host, port, '/tx/process'), + history: { + all: csHttp.get(host, port, '/tx/history/:pubkey'), + times: cacheEnable ? csHttp.getWithCache(host, port, '/tx/history/:pubkey/times/:from/:to') : csHttp.get(host, port, '/tx/history/:pubkey/times/:from/:to'), + timesNoCache: csHttp.get(host, port, '/tx/history/:pubkey/times/:from/:to'), + blocks: cacheEnable ? csHttp.getWithCache(host, port, '/tx/history/:pubkey/blocks/:from/:to') : csHttp.get(host, port, '/tx/history/:pubkey/blocks/:from/:to'), + pending: csHttp.get(host, port, '/tx/history/:pubkey/pending') + } + }, + ud: { + history: csHttp.get(host, port, '/ud/history/:pubkey') + }, + uri: {}, + raw: { + + } + }; + + exports.copy = function(otherNode) { + if (!!this.instance) { // if main service impl + var instance = this.instance; // keep factory + csCache.clearAll(); // clean all caches + angular.copy(otherNode, this); + this.instance = instance; + } + else { + angular.copy(otherNode, this); + } + }; + + exports.wot.member.uids = function() { + return exports.wot.member.all() + .then(function(res){ + return res.results.reduce(function(res, member){ + res[member.pubkey] = member.uid; + return res; + }, {}); + }); + }; + + exports.wot.member.get = function(pubkey) { + return exports.wot.member.uids() + .then(function(memberUidsByPubkey){ + var uid = memberUidsByPubkey[pubkey]; + return { + pubkey: pubkey, + uid: (uid ? uid : null) + }; + }); + }; + + /** + * Return all expected blocks + * @param blockNumbers a rray of block number + */ + exports.blockchain.blocks = function(blockNumbers){ + return exports.raw.blocksRecursive(blockNumbers, 0, exports.constants.LIMIT_REQUEST_COUNT); + }; + + exports.raw.blocksRecursive = function(blockNumbers, offset, size) { + return $q(function(resolve, reject) { + var result = []; + var jobs = []; + _.each(blockNumbers.slice(offset, offset+size), function(blockNumber) { + jobs.push( + exports.blockchain.block({block: blockNumber}) + .then(function(block){ + if (!block) return; + result.push(block); + }) + ); + }); + + $q.all(jobs) + .then(function() { + if (offset < blockNumbers.length - 1) { + $timeout(function() { + exports.raw.blocksRecursive(blockNumbers, offset+size, size) + .then(function(blocks) { + if (!blocks || !blocks.length) { + resolve(result); + return; + } + resolve(result.concat(blocks)); + }) + .catch(function(err) { + reject(err); + }); + }, exports.constants.LIMIT_REQUEST_DELAY); + } + else { + resolve(result); + } + }) + .catch(function(err){ + if (err && err.ucode === errorCodes.HTTP_LIMITATION) { + resolve(result); + } + else { + reject(err); + } + }); + }); + }; + + exports.blockchain.lastUd = function() { + return exports.blockchain.stats.ud() + .then(function(res) { + if (!res.result.blocks || !res.result.blocks.length) { + return null; + } + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return exports.blockchain.block({block: lastBlockWithUD}) + .then(function(block){ + return (block.unitbase > 0) ? block.dividend * Math.pow(10, block.unitbase) : block.dividend; + }); + }); + }; + + exports.uri.parse = function(uri) { + return $q(function(resolve, reject) { + // If pubkey: not need to parse + if (exact(regex.PUBKEY).test(uri)) { + resolve({ + pubkey: uri + }); + } + else if(uri.startsWith('duniter://')) { + var parser = csHttp.uri.parse(uri), + pubkey, + uid, + currency = parser.host.indexOf('.') === -1 ? parser.host : null, + host = parser.host.indexOf('.') !== -1 ? parser.host : null; + if (parser.username) { + if (parser.password) { + uid = parser.username; + pubkey = parser.password; + } + else { + pubkey = parser.username; + } + } + if (parser.pathname) { + var paths = parser.pathname.split('/'); + var pathCount = !paths ? 0 : paths.length; + var index = 0; + if (!currency && pathCount > index) { + currency = paths[index++]; + } + if (!pubkey && pathCount > index) { + pubkey = paths[index++]; + } + if (!uid && pathCount > index) { + uid = paths[index++]; + } + if (pathCount > index) { + reject( {message: 'Bad Duniter URI format. Invalid path (incomplete or redundant): '+ parser.pathname}); return; + } + } + + if (!currency){ + if (host) { + csHttp.get(host + '/blockchain/parameters')() + .then(function(parameters){ + resolve({ + uid: uid, + pubkey: pubkey, + host: host, + currency: parameters.currency + }); + }) + .catch(function(err) { + console.log(err); + reject({message: 'Could not get node parameter. Currency could not be retrieve'}); + }); + } + else { + reject({message: 'Bad Duniter URI format. Missing currency name (or node address).'}); return; + } + } + else { + if (!host) { + resolve({ + uid: uid, + pubkey: pubkey, + currency: currency + }); + } + + // Check if currency are the same (between node and uri) + return csHttp.get(host + '/blockchain/parameters')() + .then(function(parameters){ + if (parameters.currency !== currency) { + reject( {message: "Node's currency ["+parameters.currency+"] does not matched URI's currency ["+currency+"]."}); return; + } + resolve({ + uid: uid, + pubkey: pubkey, + host: host, + currency: currency + }); + }); + } + } + else { + throw {message: 'Bad URI format: ' + uri}; + } + }) + + // Check values against regex + .then(function(result) { + if (result.pubkey && !(exact(regex.PUBKEY).test(result.pubkey))) { + reject({message: "Invalid pubkey format [" + result.pubkey + "]"}); return; + } + if (result.uid && !(exact(regex.USER_ID).test(result.uid))) { + reject({message: "Invalid uid format [" + result.uid + "]"}); return; + } + if (result.currency && !(exact(regex.CURRENCY).test(result.currency))) { + reject({message: "Invalid currency format ["+result.currency+"]"}); return; + } + return result; + }); + }; + + exports.websocket = { + block: function() { + return csHttp.ws('ws://' + exports.node.server + '/ws/block'); + }, + peer: function() { + return csHttp.ws('ws://' + exports.node.server + '/ws/peer'); + }, + close : csHttp.closeAllWs + }; + + return exports; + } + + var service = factory(csSettings.data.node.host, csSettings.data.node.port, true /*cache*/); + service.instance = factory; + + // Listen settings changes + csSettings.api.data.on.changed($rootScope, function(settings) { + + var nodeServer = csHttp.getServer(settings.node.host, settings.node.port); + if (nodeServer != service.node.server) { + var newService = factory(settings.node.host, settings.node.port, true /*cache*/); + service.copy(newService); // reload service + } + + }); + + return service; + }) + + ; diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js index 474e8099326df311e7c28e89a047f2603817adc5..7cd07a39313f2f6e2b54c330ed8cf83c2059bb0e 100644 --- a/www/js/services/network-services.js +++ b/www/js/services/network-services.js @@ -1,7 +1,7 @@ -angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.services']) +angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.services', 'cesium.http.services']) -.factory('csNetwork', function($rootScope, $q, $interval, $timeout, BMA, Api, csSettings, UIUtils) { +.factory('csNetwork', function($rootScope, $q, $interval, $timeout, BMA, csHttp, Api, csSettings, UIUtils) { 'ngInject'; factory = function(id) { @@ -64,6 +64,20 @@ angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.se return data.knownBlocks; }, + newLightBMA = function(peer) { + return { + node: { + summary: csHttp.getWithCache(peer.getHost(), peer.getPort(), '/node/summary', csHttp.cache.LONG) + }, + blockchain: { + current: csHttp.get(peer.getHost(), peer.getPort(), '/blockchain/current'), + stats: { + hardship: csHttp.get(peer.getHost(), peer.getPort(), '/blockchain/hardship/:pubkey') + } + } + }; + }, + loadPeers = function() { data.knownPeers = {}; data.peers = []; @@ -157,7 +171,7 @@ angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.se // Apply filter if (!applyPeerFilter(peer)) return $q.when(); - var node = new BMA.instance(peer.getHost(), peer.getPort(), false); + var node = newLightBMA(peer); // Get current block return node.blockchain.current() @@ -257,8 +271,9 @@ angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.se score += (-1 * (peer.uid ? peer.uid.charCodeAt(0) : 999)); // alphabetical order return -score; }); - if (updateMainBuid) { + if (updateMainBuid && mainBlock.buid) { data.mainBuid = mainBlock.buid; + console.log(data.mainBuid); api.data.raise.mainBlockChanged(data); // raise event } api.data.raise.changed(data); // raise event diff --git a/www/templates/currency/items_parameters.html b/www/templates/currency/items_parameters.html index f4eb74f8e27c7a16c3b3f5a693672cab676ed1ca..665a0d95ba8a58f59aa9fa5b010e505e410c0681 100644 --- a/www/templates/currency/items_parameters.html +++ b/www/templates/currency/items_parameters.html @@ -44,7 +44,7 @@ <div class="item item-toggle dark"> <span translate>COMMON.BTN_RELATIVE_UNIT</span> - <label class="toggle toggle-royal" id="helptip-currency-change-unit" ng-if="!loading"> + <label class="toggle toggle-royal" id="helptip-currency-change-unit"> <input type="checkbox" ng-model="formData.useRelative"> <div class="track"> <div class="handle"></div>