diff --git a/.gitignore b/.gitignore index c31fbcdc2c5c74f2ed9dbffeb26b27e630361cb8..dad43d5c42eb92672d82f08c4f81dd3c08b1647c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules/ platforms/ plugins/ -.idea/ \ No newline at end of file +.idea/ +cesium.iml +pom.xml \ No newline at end of file diff --git a/www/index.html b/www/index.html index 32d7cbdc6c7c675a71d109b3c149f97caec71c0b..eb0d15a85c8a49e7e65cfd2ec1d6fa6a6cc2a096 100644 --- a/www/index.html +++ b/www/index.html @@ -29,13 +29,27 @@ <!-- cordova script (this will be a 404 during development) --> <script src="cordova.js"></script> - <!-- your app's js --> - <script src="js/app.js"></script> + <!-- services --> + <script src="js/services/crypto-services.js"></script> + <script src="js/services/utils-services.js"></script> + <script src="js/services/wallet-services.js"></script> + <script src="js/services/bma-services.js"></script> + + <!-- entities --> <script src="js/entity/peer.js"></script> - <script src="js/controllers/peer_controller.js"></script> - <script src="js/home-controller.js"></script> - <script src="js/explore-controller.js"></script> + + <!-- controllers --> + <script src="js/controllers/home-controllers.js"></script> + <script src="js/controllers/wot-controllers.js"></script> + <script src="js/controllers/peer-controllers.js"></script> + <script src="js/controllers/currency-controllers.js"></script> + <script src="js/controllers/wallet-controllers.js"></script> + + <!-- App --> + <script src="js/app.js"></script> <script src="js/services.js"></script> + <script src="js/controllers.js"></script> + </head> <body ng-app="cesium"> <ion-nav-view></ion-nav-view> diff --git a/www/js/app.js b/www/js/app.js index 856f0649a19f118db2debccea43b4ba7d388ca74..bc9ab915a74ef33df27aa93732c1356a82325485 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -58,87 +58,5 @@ angular.module('cesium', ['ionic', 'cesium.controllers']) }); }) -.config(function($stateProvider, $urlRouterProvider) { - $stateProvider - .state('app', { - url: "/app", - abstract: true, - templateUrl: "templates/menu.html", - controller: 'HomeCtrl' - }) - - .state('app.home', { - url: "/home", - views: { - 'menuContent': { - templateUrl: "templates/home.html", - controller: 'HomeCtrl' - } - } - }) - - .state('app.explore_currency', { - url: "/home/explore", - views: { - 'menuContent': { - templateUrl: "templates/explore/explore_currency.html", - controller: 'CurrenciesCtrl' - } - } - }) - - .state('app.explore_tabs', { - url: "/currency", - views: { - 'menuContent': { - templateUrl: "templates/explore/explore_tabs.html", - controller: 'ExploreCtrl' - } - } - }) - - .state('app.view_peer', { - url: "/peer/:server", - views: { - 'menuContent': { - templateUrl: "templates/explore/view_peer.html", - controller: 'PeerCtrl' - } - } - }) - - .state('app.view_identity', { - url: "/wot/:pub", - views: { - 'menuContent': { - templateUrl: "templates/wot/view_identity.html", - controller: 'IdentityCtrl' - } - } - }) - - .state('app.view_wallet', { - url: "/wallet", - views: { - 'menuContent': { - templateUrl: "templates/account/view_wallet.html", - controller: 'WalletCtrl' - } - } - }) - - .state('app.view_transfer', { - url: "/transfer/:pubkey/:uid", - views: { - 'menuContent': { - templateUrl: "templates/account/view_transfer.html", - controller: 'TransferCtrl' - } - } - }); - - // if none of the above states are matched, use this as the fallback - $urlRouterProvider.otherwise('/app/home'); -}) ; diff --git a/www/js/controllers.js b/www/js/controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..0d217aaafe891f05ccaee098e1a8cec11a7da1bb --- /dev/null +++ b/www/js/controllers.js @@ -0,0 +1,16 @@ + +angular.module('cesium.controllers', [ + 'cesium.home.controllers', + 'cesium.wallet.controllers', + 'cesium.currency.controllers', + 'cesium.wot.controllers' + ]) + + .config(function($httpProvider) { + //Enable cross domain calls + $httpProvider.defaults.useXDomain = true; + + //Remove the header used to identify ajax call that would prevent CORS from working + delete $httpProvider.defaults.headers.common['X-Requested-With']; + }) +; diff --git a/www/js/controllers/currency-controllers.js b/www/js/controllers/currency-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..d8989dbaaf86b494701f1e6c22bb65e4c7aa979f --- /dev/null +++ b/www/js/controllers/currency-controllers.js @@ -0,0 +1,305 @@ + +angular.module('cesium.currency.controllers', ['cesium.services']) + +.config(function($stateProvider, $urlRouterProvider) { + $stateProvider + + .state('app.explore_currency', { + url: "/home/explore", + views: { + 'menuContent': { + templateUrl: "templates/explore/explore_currency.html", + controller: 'CurrenciesCtrl' + } + } + }) + + .state('app.explore_tabs', { + url: "/currency", + views: { + 'menuContent': { + templateUrl: "templates/explore/explore_tabs.html", + controller: 'ExploreCtrl' + } + } + }) + + .state('app.view_peer', { + url: "/peer/:server", + views: { + 'menuContent': { + templateUrl: "templates/explore/view_peer.html", + controller: 'PeerCtrl' + } + } + }) +}) + +.controller('CurrenciesCtrl', CurrenciesController) + +.controller('ExploreCtrl', ExploreController) + +.controller('PeerCtrl', PeerController) + +; + +function CurrenciesController($scope, $state) { + + $scope.selectedCurrency = ''; + $scope.knownCurrencies = ['meta_brouzouf']; + + // Called to navigate to the main app + $scope.selectCurrency = function(currency) { + $scope.selectedCurrency = currency; + $state.go('app.explore_tabs'); + }; +} + +function ExploreController($scope, $rootScope, $state, BMA, $q, UIUtils, $interval, $timeout) { + + var USE_RELATIVE_DEFAULT = true; + + CurrenciesController.call(this, $scope, $state); + WotLookupController.call(this, $scope, BMA, $state); + PeersController.call(this, $scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout); + + $scope.accountTypeMember = null; + $scope.accounts = []; + $scope.search = { text: '', results: {} }; + $scope.knownCurrencies = ['meta_brouzouf']; + $scope.formData = { useRelative: false }; + $scope.knownBlocks = []; + $scope.entered = false; + + $scope.$on('$ionicView.enter', function(e, $state) { + if (!$scope.entered) { + $scope.entered = true; + $scope.startListeningOnSocket(); + } + $timeout(function() { + if ((!$scope.search.peers || $scope.search.peers.length == 0) && $scope.search.lookingForPeers){ + $scope.updateExploreView(); + } + }, 2000); + }); + + $scope.startListeningOnSocket = function() { + + // Currency OK + BMA.websocket.block().on('block', function(block) { + var theFPR = fpr(block); + if ($scope.knownBlocks.indexOf(theFPR) === -1) { + $scope.knownBlocks.push(theFPR); + // We wait 2s when a new block is received, just to wait for network propagation + var wait = $scope.knownBlocks.length === 1 ? 0 : 2000; + $timeout(function() { + $scope.updateExploreView(); + }, wait); + } + }); + BMA.websocket.peer().on('peer', function(peer) { + console.log(peer); + }); + }; + + $scope.$watch('formData.useRelative', function() { + if ($scope.formData.useRelative) { + $scope.M = $scope.M / $scope.currentUD; + $scope.MoverN = $scope.MoverN / $scope.currentUD; + $scope.UD = $scope.UD / $scope.currentUD; + $scope.unit = 'universal_dividend'; + $scope.udUnit = $scope.baseUnit; + } else { + $scope.M = $scope.M * $scope.currentUD; + $scope.MoverN = $scope.MoverN * $scope.currentUD; + $scope.UD = $scope.UD * $scope.currentUD; + $scope.unit = $scope.baseUnit; + $scope.udUnit = ''; + } + }, true); + + $scope.doUpdate = function() { + $scope.updateExploreView(); + }; + + $scope.updateExploreView = function() { + + UIUtils.loading.show(); + $scope.formData.useRelative = false; + + $q.all([ + + // Get the currency parameters + BMA.currency.parameters() + .then(function(json){ + $scope.c = json.c; + $scope.baseUnit = json.currency; + $scope.unit = json.currency; + }), + + // Get the current block informations + BMA.blockchain.current() + .then(function(block){ + $scope.M = block.monetaryMass; + $scope.N = block.membersCount; + $scope.time = moment(block.medianTime*1000).format('YYYY-MM-DD HH:mm'); + $scope.difficulty = block.powMin; + }), + + // Get the UD informations + BMA.blockchain.stats.ud() + .then(function(res){ + if (res.result.blocks.length) { + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return BMA.blockchain.block({ block: lastBlockWithUD }) + .then(function(block){ + $scope.currentUD = block.dividend; + $scope.UD = block.dividend; + $scope.Nprev = block.membersCount; + }); + } + }) + ]) + + // Done + .then(function(){ + $scope.M = $scope.M - $scope.UD*$scope.Nprev; + $scope.MoverN = $scope.M / $scope.Nprev; + $scope.cactual = 100 * $scope.UD / $scope.MoverN; + $scope.formData.useRelative = USE_RELATIVE_DEFAULT; + UIUtils.loading.hide(); + }) + .catch(function(err) { + console.error('>>>>>>>' , err); + UIUtils.alert.error('Could not fetch informations from remote uCoin node.'); + UIUtils.loading.hide(); + }) + .then(function(){ + // Network + $scope.searchPeers(); + }); + }; +} + + +function PeersController($scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout) { + + var newPeers = [], interval, lookingForPeers; + $scope.search.lookingForPeers = false; + $scope.search.peers = []; + + $scope.overviewPeers = function() { + var currents = {}, block; + for (var i = 0, len = $scope.search.peers.length; i < len; i++) { + block = $scope.search.peers[i].current; + if (block) { + var bid = fpr(block); + currents[bid] = currents[bid] || 0; + currents[bid]++; + } + } + var fprs = _.keys(currents).map(function(key) { + return { fpr: key, qty: currents[key] }; + }); + var best = _.max(fprs, function(obj) { + return obj.qty; + }); + var p; + for (var j = 0, len2 = $scope.search.peers.length; j < len2; j++) { + p = $scope.search.peers[j]; + p.hasMainConsensusBlock = fpr(p.current) == best.fpr; + p.hasConsensusBlock = !p.hasMainConsensusBlock && currents[fpr(p.current)] > 1; + } + $scope.search.peers = _.uniq($scope.search.peers, false, function(peer) { + return peer.pubkey; + }); + $scope.search.peers = _.sortBy($scope.search.peers, function(p) { + var score = 1 + + 10000 * (p.online ? 1 : 0) + + 1000 * (p.hasMainConsensusBlock ? 1 : 0) + + + 100 * (p.uid ? 1 : 0); + return -score; + }); + }; + + $scope.searchPeers = function() { + + if (interval) { + $interval.cancel(interval); + } + + interval = $interval(function() { + if (newPeers.length) { + $scope.search.peers = $scope.search.peers.concat(newPeers.splice(0)); + $scope.overviewPeers(); + } else if (lookingForPeers && !$scope.search.lookingForPeers) { + // The peer lookup endend, we can make a clean final report + $timeout(function(){ + lookingForPeers = false; + $scope.overviewPeers(); + }, 1000); + } + }, 1000); + + var known = {}; + $rootScope.members = []; + $scope.search.peers = []; + $scope.search.lookingForPeers = true; + lookingForPeers = true; + return BMA.network.peering.peers({ leaves: true }) + .then(function(res){ + return BMA.wot.members() + .then(function(json){ + $rootScope.members = json.results; + return res; + }); + }) + .then(function(res){ + return $q.all(res.leaves.map(function(leaf) { + return BMA.network.peering.peers({ leaf: leaf }) + .then(function(subres){ + var peer = subres.leaf.value; + if (peer) { + peer = new Peer(peer); + // Test each peer only once + if (!known[peer.getURL()]) { + peer.dns = peer.getDns(); + peer.blockNumber = peer.block.replace(/-.+$/, ''); + var member = _.findWhere($rootScope.members, { pubkey: peer.pubkey }); + peer.uid = member && member.uid; + newPeers.push(peer); + var node = BMA.instance(peer.getURL()); + return node.blockchain.current() + .then(function(block){ + peer.current = block; + peer.online = true; + peer.server = peer.getURL(); + if ($scope.knownBlocks.indexOf(fpr(block)) === -1) { + $scope.knownBlocks.push(fpr(block)); + } + }) + .catch(function(err) { + }) + } + } + }) + })) + .then(function(){ + $scope.search.lookingForPeers = false; + }) + }) + .catch(function(err) { + //console.log(err); + //UIUtils.alert.error('Could get peers from remote uCoin node.'); + //$scope.search.lookingForPeers = false; + }); + }; + + $scope.viewPeer = function() { + + }; +} + +function fpr(block) { + return block && [block.number, block.hash].join('-'); +} diff --git a/www/js/controllers/home-controllers.js b/www/js/controllers/home-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..5a55420b159970654e113c708afe20daced71b58 --- /dev/null +++ b/www/js/controllers/home-controllers.js @@ -0,0 +1,263 @@ + +angular.module('cesium.home.controllers', ['cesium.services']) + + .config(function($httpProvider) { + //Enable cross domain calls + $httpProvider.defaults.useXDomain = true; + + //Remove the header used to identify ajax call that would prevent CORS from working + delete $httpProvider.defaults.headers.common['X-Requested-With']; + }) + + .controller('HomeCtrl', HomeController) + + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + + .state('app', { + url: "/app", + abstract: true, + templateUrl: "templates/menu.html", + controller: 'HomeCtrl' + }) + + .state('app.home', { + url: "/home", + views: { + 'menuContent': { + templateUrl: "templates/home.html", + controller: 'HomeCtrl' + } + } + }) + + // if none of the above states are matched, use this as the fallback + $urlRouterProvider.otherwise('/app/home'); + }) +; + +function LoginController($scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate) { + // Form data for the login modal + $scope.loginData = { + username: null, + password: null + }; + + // Login modal + $scope.loginModal = "undefined"; + + // Create the login modal that we will use later + $ionicModal.fromTemplateUrl('templates/login.html', { + scope: $scope, + focusFirstInput: true + }).then(function(modal) { + $scope.loginModal = modal; + $scope.loginModal.hide(); + }); + + // Open login modal + $scope.login = function(callback) { + if ($scope.loginModal != "undefined" && $scope.loginModal != null) { + $scope.loginModal.show(); + $scope.loginData.callback = callback; + } + else{ + $timeout($scope.login, 2000); + } + }; + + // Login and load wallet + $scope.loadWallet = function() { + return $q(function(resolve, reject){ + if (!Wallet.isLogin()) { + $scope.login(function() { + Wallet.loadData() + .then(function(walletData){ + resolve(walletData); + }) + .catch(function(err) { + console.error('>>>>>>>' , err); + UIUtils.alert.error('Your browser is not compatible with cryptographic features.'); + UIUtils.loading.hide(); + reject(err); + }); + }); + } + else if (!Wallet.data.loaded) { + Wallet.loadData() + .then(function(walletData){ + resolve(walletData); + }) + .catch(function(err) { + console.error('>>>>>>>' , err); + UIUtils.alert.error('Could not fetch wallet data from remote uCoin node.'); + UIUtils.loading.hide(); + reject(err); + }); + } + else { + resolve(Wallet.data); + } + }); + }; + + // Triggered in the login modal to close it + $scope.closeLogin = function() { + return $scope.loginModal.hide(); + }; + + // Login form submit + $scope.doLogin = function() { + $scope.closeLogin(); + UIUtils.loading.show(); + + // Call wallet login + Wallet.login($scope.loginData.username, $scope.loginData.password) + .catch(function(err) { + $scope.loginData = {}; // Reset login data + UIUtils.loading.hide(); + console.error('>>>>>>>' , err); + UIUtils.alert.error('Your browser is not compatible with cryptographic libraries.'); + }) + .then(function(){ + UIUtils.loading.hide(); + var callback = $scope.loginData.callback; + $scope.loginData = {}; // Reset login data + if (callback != "undefined" && callback != null) { + callback(); + } + // Default: redirect to wallet view + else { + $state.go('app.view_wallet'); + } + }); + }; + + $scope.loginDataChanged = function() { + $scope.loginData.computing=false; + $scope.loginData.pubkey=null; + }; + + $scope.showLoginPubkey = function() { + $scope.loginData.computing=true; + CryptoUtils.connect($scope.loginData.username, $scope.loginData.password).then( + function(keypair) { + $scope.loginData.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); + $scope.loginData.computing=false; + } + ) + .catch(function(err) { + $scope.loginData.computing=false; + UIUtils.loading.hide(); + console.error('>>>>>>>' , err); + UIUtils.alert.error('Your browser is not compatible with cryptographic libraries.'); + }); + }; + + // Logout + $scope.logout = function() { + UIUtils.loading.show(); + Wallet.logout().then( + function() { + UIUtils.loading.hide(); + $ionicSideMenuDelegate.toggleLeft(); + $state.go('app.home'); + } + ); + }; + + // Is connected + $scope.isLogged = function() { + return Wallet.isLogin(); + }; + + // Is not connected + $scope.isNotLogged = function() { + return !Wallet.isLogin(); + }; +} + +function HomeController($scope, $ionicSlideBoxDelegate, $ionicModal, $state, BMA, UIUtils, $q, $timeout, Wallet, CryptoUtils, $ionicSideMenuDelegate) { + + // With the new view caching in Ionic, Controllers are only called + // when they are recreated or on app start, instead of every page change. + // To listen for when this page is active (for example, to refresh data), + // listen for the $ionicView.enter event: + //$scope.$on('$ionicView.enter', function(e) { + //}); + + //CurrenciesController.call(this, $scope, $state); + //LookupController.call(this, $scope, BMA, $state); + LoginController.call(this, $scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate); + + $scope.accountTypeMember = null; + $scope.accounts = []; + $scope.search = { text: '', results: {} }; + $scope.knownCurrencies = ['meta_brouzouf']; + + // Called to navigate to the main app + $scope.cancel = function() { + $scope.modal.hide(); + $timeout(function(){ + $scope.selectedCurrency = ''; + $scope.accountTypeMember = null; + $scope.search.text = ''; + $scope.search.results = []; + }, 200); + }; + + $scope.$on('currencySelected', function() { + $ionicSlideBoxDelegate.slide(1); + }); + + $scope.selectAccountTypeMember = function(bool) { + $scope.accountTypeMember = bool; + $ionicSlideBoxDelegate.slide(2); + }; + + $scope.next = function() { + $ionicSlideBoxDelegate.next(); + }; + $scope.previous = function() { + $ionicSlideBoxDelegate.previous(); + }; + + // Called each time the slide changes + $scope.slideChanged = function(index) { + $scope.slideIndex = index; + $scope.nextStep = $scope.slideIndex == 2 ? 'Start using MyApp' : 'Next'; + }; + + $scope.addAccount = function() { + $scope.modal.show(); + $scope.slideChanged(0); + $ionicSlideBoxDelegate.slide(0); + $ionicSlideBoxDelegate.enableSlide(false); + // TODO: remove default + //$timeout(function() { + // $scope.selectedCurrency = $scope.knownCurrencies[0]; + // $scope.accountTypeMember = true; + // $scope.searchChanged(); + // $scope.search.text = 'cgeek'; + // $ionicSlideBoxDelegate.next(); + // $ionicSlideBoxDelegate.next(); + //}, 300); + }; + + // Create the account modal that we will use later + $ionicModal.fromTemplateUrl('templates/account/new_account.html', { + scope: $scope + }).then(function(modal) { + $scope.modal = modal; + $scope.modal.hide(); + // TODO: remove auto add account when done + //$timeout(function() { + // $scope.addAccount(); + //}, 400); + }); + + $scope.selectCurrency = function(currency) { + $scope.selectedCurrency = currency; + $scope.next(); + } +} diff --git a/www/js/controllers/peer_controller.js b/www/js/controllers/peer-controllers.js similarity index 100% rename from www/js/controllers/peer_controller.js rename to www/js/controllers/peer-controllers.js diff --git a/www/js/controllers/wallet-controllers.js b/www/js/controllers/wallet-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..b7e91e9b6871c4c46b9c2b221faec8f9c3133acb --- /dev/null +++ b/www/js/controllers/wallet-controllers.js @@ -0,0 +1,225 @@ + +angular.module('cesium.wallet.controllers', ['cesium.services', 'cesium.currency.controllers']) + + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + + .state('app.view_wallet', { + url: "/wallet", + views: { + 'menuContent': { + templateUrl: "templates/account/view_wallet.html", + controller: 'WalletCtrl' + } + } + }) + + .state('app.view_transfer_uid', { + url: "/transfer/:pubkey/:uid", + views: { + 'menuContent': { + templateUrl: "templates/account/view_transfer.html", + controller: 'TransferCtrl' + } + } + }) + + .state('app.view_transfer_pubkey', { + url: "/transfer/:pubkey", + views: { + 'menuContent': { + templateUrl: "templates/account/view_transfer.html", + controller: 'TransferCtrl' + } + } + }) + ; + }) + + .controller('WalletCtrl', WalletController) + + .controller('TransferCtrl', TransferController) +; + +function WalletController($scope, $state, $q, $ionicPopup, UIUtils, Wallet) { + + $scope.walletData = {}; + $scope.convertedBalance = 0; + $scope.hasCredit = false; + $scope.isMember = false; + + $scope.$on('$ionicView.enter', function(e, $state) { + $scope.loadWallet() + .then(function(wallet) { + $scope.updateWalletView(wallet); + }); + }); + + $scope.refreshConvertedBalance = function() { + if ($scope.walletData.useRelative) { + $scope.convertedBalance = $scope.walletData.balance / $scope.walletData.currentUD; + $scope.unit = 'universal_dividend'; + $scope.udUnit = $scope.walletData.currency; + } else { + $scope.convertedBalance = $scope.walletData.balance; + $scope.unit = $scope.walletData.currency; + $scope.udUnit = ''; + } + }; + $scope.$watch('walletData.useRelative', $scope.refreshConvertedBalance, true); + $scope.$watch('walletData.balance', $scope.refreshConvertedBalance, true); + + // Update view + $scope.updateWalletView = function(wallet) { + $scope.walletData = wallet; + $scope.hasCredit = ($scope.walletData.balance != "undefined" && $scope.walletData.balance > 0); + $scope.isMember = ($scope.walletData.requirements != "undefined" && $scope.walletData.requirements != null + && $scope.walletData.requirements.uid != "undefined" && $scope.walletData.requirements.uid != null); + }; + + // Has credit + $scope.hasCredit= function() { + return $scope.balance > 0; + }; + + // Transfer click + $scope.transfer= function() { + $state.go('app.view_transfer'); + }; + + // Self cert + $scope.self= function() { + + // Choose UID popup + $ionicPopup.show({ + template: '<input type="text" ng-model="walletData.uid">', + title: 'Enter a pseudo', + subTitle: 'A pseudo is need to let other member find you.', + scope: $scope, + buttons: [ + { text: 'Cancel' }, + { + text: '<b>Send</b>', + type: 'button-positive', + onTap: function(e) { + if (!$scope.walletData.uid) { + //don't allow the user to close unless he enters a uid + e.preventDefault(); + } else { + // TODO : check if not already used + return $scope.walletData.uid; + } + } + } + ] + }) + .then(function(uid) { + UIUtils.loading.show(); + Wallet.self(uid) + .then(function() { + UIUtils.loading.hide(); + }) + .catch(UIUtils.onError('Could not send self certification')); + }); + + }; +} + +function TransferController($scope, $ionicModal, $state, $ionicHistory, BMA, Wallet, UIUtils) { + + $scope.walletData = {}; + $scope.formData = { + destPub: null, + amount: null, + comments: null + }; + $scope.dest = null; + $scope.udAmount = null; + + WotLookupController.call(this, $scope, BMA, $state); + + $scope.$on('$ionicView.enter', function(e, $state) { + if ($state.stateParams != null + && $state.stateParams.pubkey != null + && $state.stateParams.pubkey != "undefined") { + $scope.destPub = $state.stateParams.pubkey; + if ($state.stateParams.uid != null + && $state.stateParams.uid != "undefined") { + $scope.dest = $state.stateParams.uid; + } + else { + $scope.dest = $scope.destPub; + } + } + + // Login and load wallet + $scope.loadWallet() + .then(function(walletData) { + $scope.walletData = walletData; + $scope.onUseRelativeChanged(); + }); + }); + + // When chaing use relative UD + $scope.onUseRelativeChanged = function() { + if ($scope.walletData.useRelative) { + $scope.udAmount = $scope.amount * $scope.walletData.currentUD; + $scope.unit = 'universal_dividend'; + $scope.udUnit = $scope.walletData.currency; + } else { + $scope.formData.amount = ($scope.formData.amount != "undefined" && $scope.formData.amount != null) + ? Math.floor(parseFloat($scope.formData.amount.replace(new RegExp('[,]'), '.'))) + : null; + $scope.udAmount = $scope.amount / $scope.walletData.currentUD; + $scope.unit = $scope.walletData.currency; + $scope.udUnit = ''; + } + }; + $scope.$watch('walletData.useRelative', $scope.onUseRelativeChanged, true); + + $ionicModal.fromTemplateUrl('templates/wot/modal_lookup.html', { + scope: $scope, + focusFirstInput: true + }).then(function(modal) { + $scope.lookupModal = modal; + $scope.lookupModal.hide(); + }); + + $scope.openSearch = function() { + $scope.lookupModal.show(); + } + + $scope.doTransfer = function() { + UIUtils.loading.show(); + + var amount = $scope.formData.amount; + if ($scope.walletData.useRelative + && amount != "undefined" + && amount != null) { + amount = $scope.walletData.currentUD + * amount.replace(new RegExp('[.,]'), '.'); + } + + Wallet.transfer($scope.formData.destPub, amount, $scope.formData.comments) + .then(function() { + UIUtils.loading.hide(); + $ionicHistory.goBack() + }) + .catch(UIUtils.onError('Could not send transaction')); + }; + + $scope.closeLookup = function() { + $scope.lookupModal.hide(); + } + + $scope.doSelectIdentity = function(pub, uid) { + if (uid != "undefined" && uid != null) { + $scope.dest = uid; + } + else { + $scope.dest = uid; + } + $scope.formData.destPub = pub; + $scope.lookupModal.hide(); + } +} diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..6b6c7058d3f1d6ae22d9a02c6b0e9588b2820ef5 --- /dev/null +++ b/www/js/controllers/wot-controllers.js @@ -0,0 +1,109 @@ +angular.module('cesium.wot.controllers', ['cesium.services']) + + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + + .state('app.view_identity', { + url: "/wot/:pub", + views: { + 'menuContent': { + templateUrl: "templates/wot/view_identity.html", + controller: 'IdentityCtrl' + } + } + }) + }) + + .controller('IdentityCtrl', IdentityController) + + .controller('WotLookupCtrl', WotLookupController) +; + +function WotLookupController($scope, BMA, $state) { + + $scope.searchChanged = function() { + $scope.search.text = $scope.search.text.toLowerCase(); + if ($scope.search.text.length > 1) { + $scope.search.looking = true; + return BMA.wot.lookup({ search: $scope.search.text }) + .then(function(res){ + $scope.search.looking = false; + $scope.search.results = res.results.reduce(function(idties, res) { + return idties.concat(res.uids.reduce(function(uids, idty) { + return uids.concat({ + uid: idty.uid, + pub: res.pubkey, + sigDate: idty.meta.timestamp + }) + }, [])); + }, []); + }) + .catch(function() { + $scope.search.looking = false; + $scope.search.results = []; + }); + } + else { + $scope.search.results = []; + } + }; + + $scope.doSelectIdentity = function(pub, uid) { + $state.go('app.view_identity', {pub: pub}); + }; +} + +function IdentityController($scope, $state, BMA, Wallet, UIUtils, $q) { + + $scope.identity = {}; + $scope.hasSelf = false; + + $scope.$on('$ionicView.enter', function(e, $state) { + $scope.loadIdentity($state.stateParams.pub); + }); + + $scope.loadIdentity = function(pub) { + UIUtils.loading.show(); + BMA.wot.lookup({ search: pub }) + .then(function(res){ + $scope.identity = res.results.reduce(function(idties, res) { + return idties.concat(res.uids.reduce(function(uids, idty) { + return uids.concat({ + uid: idty.uid, + pub: res.pubkey, + sigDate: idty.meta.timestamp, + sig: idty.self + }) + }, [])); + }, [])[0]; + $scope.hasSelf = ($scope.identity.uid && $scope.identity.sigDate && $scope.identity.sig); + UIUtils.loading.hide(); + }) + .catch(UIUtils.onError('Could not load identity')); + }; + + $scope.signIdentity = function(identity) { + $scope.loadWallet() + .then(function(walletData) { + UIUtils.loading.show(); + Wallet.sign($scope.identity.uid, + $scope.identity.pub, + $scope.identity.sigDate, + $scope.identity.sig) + .then(function() { + UIUtils.loading.hide(); + UIUtils.alertInfo('Identity successfully signed'); + }) + .catch(UIUtils.onError('Could not certify identity')); + }) + .catch(UIUtils.onError('Error while login')); + }; + + // Transfer click + $scope.transfer = function() { + $state.go('app.view_transfer_uid', { + pubkey: $scope.identity.pubkey, + uid: $scope.identity.uid, + }); + }; +} \ No newline at end of file diff --git a/www/js/explore-controller.js b/www/js/explore-controller.js deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/www/js/home-controller.js b/www/js/home-controller.js deleted file mode 100644 index b9f1ebea9f2bb48a316729001bca1c1ba192f70e..0000000000000000000000000000000000000000 --- a/www/js/home-controller.js +++ /dev/null @@ -1,805 +0,0 @@ - -angular.module('cesium.controllers', ['cesium.services']) - - .config(function($httpProvider) { - //Enable cross domain calls - $httpProvider.defaults.useXDomain = true; - - //Remove the header used to identify ajax call that would prevent CORS from working - delete $httpProvider.defaults.headers.common['X-Requested-With']; - }) - - .controller('HomeCtrl', HomeController) - - .controller('CurrenciesCtrl', CurrenciesController) - - .controller('ExploreCtrl', ExploreController) - - .controller('IdentityCtrl', IdentityController) - - .controller('PeerCtrl', PeerController) - - .controller('WalletCtrl', WalletController) - - .controller('TransferCtrl', TransferController) - -; - -function LoginController($scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate) { - // Form data for the login modal - $scope.loginData = { - username: null, - password: null - }; - - // Login modal - $scope.loginModal = "undefined"; - - // Create the login modal that we will use later - $ionicModal.fromTemplateUrl('templates/login.html', { - scope: $scope, - focusFirstInput: true - }).then(function(modal) { - $scope.loginModal = modal; - $scope.loginModal.hide(); - }); - - // Open login modal - $scope.login = function(callback) { - if ($scope.loginModal != "undefined" && $scope.loginModal != null) { - $scope.loginModal.show(); - $scope.loginData.callback = callback; - } - else{ - $timeout($scope.login, 2000); - } - }; - - // Login and load wallet - $scope.loadWallet = function() { - return $q(function(resolve, reject){ - if (!Wallet.isLogin()) { - $scope.login(function() { - Wallet.loadData() - .then(function(walletData){ - resolve(walletData); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Your braower is not compatible with cryptographic features.'); - UIUtils.loading.hide(); - reject(err); - }); - }); - } - else if (!Wallet.data.loaded) { - Wallet.loadData() - .then(function(walletData){ - resolve(walletData); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Could not fetch wallet informations from remote uCoin node.'); - UIUtils.loading.hide(); - reject(err); - }); - } - else { - resolve(Wallet.data); - } - }); - }; - - // Triggered in the login modal to close it - $scope.closeLogin = function() { - return $scope.loginModal.hide(); - }; - - // Login form submit - $scope.doLogin = function() { - $scope.closeLogin(); - UIUtils.loading.show(); - - // Call wallet login - $q.all([ - Wallet.login($scope.loginData.username, $scope.loginData.password) - .catch(function(err) { - $scope.loginData = {}; // Reset login data - UIUtils.loading.hide(); - console.error('>>>>>>>' , err); - UIUtils.alert.error('Your browser is not compatible with cryptographic libraries.'); - }) - .then(function(){ - UIUtils.loading.hide(); - var callback = $scope.loginData.callback; - $scope.loginData = {}; // Reset login data - if (callback != "undefined" && callback != null) { - callback(); - } - // Default: redirect to wallet view - else { - $state.go('app.view_wallet'); - } - }) - ]); - }; - - $scope.loginDataChanged = function() { - $scope.loginData.computing=false; - $scope.loginData.pubkey=null; - }; - - $scope.showLoginPubkey = function() { - $scope.loginData.computing=true; - CryptoUtils.connect($scope.loginData.username, $scope.loginData.password).then( - function(keypair) { - $scope.loginData.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); - $scope.loginData.computing=false; - } - ) - .catch(function(err) { - $scope.loginData.computing=false; - UIUtils.loading.hide(); - console.error('>>>>>>>' , err); - UIUtils.alert.error('Your browser is not compatible with cryptographic libraries.'); - }); - }; - - // Logout - $scope.logout = function() { - UIUtils.loading.show(); - Wallet.logout().then( - function() { - UIUtils.loading.hide(); - $ionicSideMenuDelegate.toggleLeft(); - $state.go('app.home'); - } - ); - }; - - // Is connected - $scope.isLogged = function() { - return Wallet.isLogin(); - }; - - // Is not connected - $scope.isNotLogged = function() { - return !Wallet.isLogin(); - }; -} - -function CurrenciesController($scope, $state) { - - $scope.selectedCurrency = ''; - $scope.knownCurrencies = ['meta_brouzouf']; - - // Called to navigate to the main app - $scope.selectCurrency = function(currency) { - $scope.selectedCurrency = currency; - $state.go('app.explore_tabs'); - }; -} - -function ExploreController($scope, $rootScope, $state, BMA, $q, UIUtils, $interval, $timeout) { - - var USE_RELATIVE_DEFAULT = true; - - CurrenciesController.call(this, $scope, $state); - LookupController.call(this, $scope, BMA, $state); - PeersController.call(this, $scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout); - - $scope.accountTypeMember = null; - $scope.accounts = []; - $scope.search = { text: '', results: {} }; - $scope.knownCurrencies = ['meta_brouzouf']; - $scope.formData = { useRelative: false }; - $scope.knownBlocks = []; - $scope.entered = false; - - $scope.$on('$ionicView.enter', function(e, $state) { - if (!$scope.entered) { - $scope.entered = true; - $scope.startListeningOnSocket(); - } - $timeout(function() { - if ((!$scope.search.peers || $scope.search.peers.length == 0) && $scope.search.lookingForPeers){ - $scope.updateExploreView(); - } - }, 2000); - }); - - $scope.startListeningOnSocket = function() { - - // Currency OK - BMA.websocket.block().on('block', function(block) { - var theFPR = fpr(block); - if ($scope.knownBlocks.indexOf(theFPR) === -1) { - $scope.knownBlocks.push(theFPR); - // We wait 2s when a new block is received, just to wait for network propagation - var wait = $scope.knownBlocks.length === 1 ? 0 : 2000; - $timeout(function() { - $scope.updateExploreView(); - }, wait); - } - }); - BMA.websocket.peer().on('peer', function(peer) { - console.log(peer); - }); - }; - - $scope.$watch('formData.useRelative', function() { - if ($scope.formData.useRelative) { - $scope.M = $scope.M / $scope.currentUD; - $scope.MoverN = $scope.MoverN / $scope.currentUD; - $scope.UD = $scope.UD / $scope.currentUD; - $scope.unit = 'universal_dividend'; - $scope.udUnit = $scope.baseUnit; - } else { - $scope.M = $scope.M * $scope.currentUD; - $scope.MoverN = $scope.MoverN * $scope.currentUD; - $scope.UD = $scope.UD * $scope.currentUD; - $scope.unit = $scope.baseUnit; - $scope.udUnit = ''; - } - }, true); - - $scope.doUpdate = function() { - $scope.updateExploreView(); - }; - - $scope.updateExploreView = function() { - - UIUtils.loading.show(); - $scope.formData.useRelative = false; - - $q.all([ - - // Get the currency parameters - BMA.currency.parameters() - .then(function(json){ - $scope.c = json.c; - $scope.baseUnit = json.currency; - $scope.unit = json.currency; - }), - - // Get the current block informations - BMA.blockchain.current() - .then(function(block){ - $scope.M = block.monetaryMass; - $scope.N = block.membersCount; - $scope.time = moment(block.medianTime*1000).format('YYYY-MM-DD HH:mm'); - $scope.difficulty = block.powMin; - }), - - // Get the UD informations - BMA.blockchain.stats.ud() - .then(function(res){ - if (res.result.blocks.length) { - var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; - return BMA.blockchain.block({ block: lastBlockWithUD }) - .then(function(block){ - $scope.currentUD = block.dividend; - $scope.UD = block.dividend; - $scope.Nprev = block.membersCount; - }); - } - }) - ]) - - // Done - .then(function(){ - $scope.M = $scope.M - $scope.UD*$scope.Nprev; - $scope.MoverN = $scope.M / $scope.Nprev; - $scope.cactual = 100 * $scope.UD / $scope.MoverN; - $scope.formData.useRelative = USE_RELATIVE_DEFAULT; - UIUtils.loading.hide(); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Could not fetch informations from remote uCoin node.'); - UIUtils.loading.hide(); - }) - .then(function(){ - // Network - $scope.searchPeers(); - }); - }; -} - -function LookupController($scope, BMA, $state) { - - $scope.searchChanged = function() { - $scope.search.text = $scope.search.text.toLowerCase(); - if ($scope.search.text.length > 1) { - $scope.search.looking = true; - return BMA.wot.lookup({ search: $scope.search.text }) - .then(function(res){ - $scope.search.looking = false; - $scope.search.results = res.results.reduce(function(idties, res) { - return idties.concat(res.uids.reduce(function(uids, idty) { - return uids.concat({ - uid: idty.uid, - pub: res.pubkey, - sigDate: idty.meta.timestamp - }) - }, [])); - }, []); - }) - .catch(function() { - $scope.search.looking = false; - $scope.search.results = []; - }); - } - else { - $scope.search.results = []; - } - }; - - $scope.doSelectIdentity = function(pub, uid) { - $state.go('app.view_identity', {pub: pub}); - }; - -} - -function IdentityController($scope, $state, BMA, Wallet, UIUtils, $q) { - - $scope.$on('$ionicView.enter', function(e, $state) { - $scope.showIdentity($state.stateParams.pub); - }); - - $scope.identity = {}; - - $scope.showIdentity = function(pub) { - - BMA.wot.lookup({ search: pub }) - .then(function(res){ - $scope.identity = res.results.reduce(function(idties, res) { - return idties.concat(res.uids.reduce(function(uids, idty) { - return uids.concat({ - uid: idty.uid, - pub: res.pubkey, - sigDate: idty.meta.timestamp, - sig: idty.self - }) - }, [])); - }, [])[0]; - }) - }; - - $scope.hasSelf = function() { - return ($scope.identity.uid - && $scope.identity.sigDate - && $scope.identity.sig); - } - - $scope.signIdentity = function(identity) { - $scope.loadWallet() - .then(function(walletData) { - UIUtils.loading.show(); - Wallet.sign($scope.identity.uid, - $scope.identity.pub, - $scope.identity.sigDate, - $scope.identity.sig) - .then(function() { - UIUtils.loading.hide(); - alert('Identity signed'); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Error while signing identity: ' + err); - UIUtils.loading.hide(); - }); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Error while signing identity: ' + err); - }); - }; - - // Transfer click - $scope.transfer = function() { - $state.go('app.view_transfer', { - pubkey: $scope.identity.pubkey, - uid: $scope.identity.uid, - }); - }; - - -} - -function PeersController($scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout) { - - var newPeers = [], interval, lookingForPeers; - $scope.search.lookingForPeers = false; - $scope.search.peers = []; - - $scope.overviewPeers = function() { - var currents = {}, block; - for (var i = 0, len = $scope.search.peers.length; i < len; i++) { - block = $scope.search.peers[i].current; - if (block) { - var bid = fpr(block); - currents[bid] = currents[bid] || 0; - currents[bid]++; - } - } - var fprs = _.keys(currents).map(function(key) { - return { fpr: key, qty: currents[key] }; - }); - var best = _.max(fprs, function(obj) { - return obj.qty; - }); - var p; - for (var j = 0, len2 = $scope.search.peers.length; j < len2; j++) { - p = $scope.search.peers[j]; - p.hasMainConsensusBlock = fpr(p.current) == best.fpr; - p.hasConsensusBlock = !p.hasMainConsensusBlock && currents[fpr(p.current)] > 1; - } - $scope.search.peers = _.uniq($scope.search.peers, false, function(peer) { - return peer.pubkey; - }); - $scope.search.peers = _.sortBy($scope.search.peers, function(p) { - var score = 1 - + 10000 * (p.online ? 1 : 0) - + 1000 * (p.hasMainConsensusBlock ? 1 : 0) + - + 100 * (p.uid ? 1 : 0); - return -score; - }); - }; - - $scope.searchPeers = function() { - - if (interval) { - $interval.cancel(interval); - } - - interval = $interval(function() { - if (newPeers.length) { - $scope.search.peers = $scope.search.peers.concat(newPeers.splice(0)); - $scope.overviewPeers(); - } else if (lookingForPeers && !$scope.search.lookingForPeers) { - // The peer lookup endend, we can make a clean final report - $timeout(function(){ - lookingForPeers = false; - $scope.overviewPeers(); - }, 1000); - } - }, 1000); - - var known = {}; - $rootScope.members = []; - $scope.search.peers = []; - $scope.search.lookingForPeers = true; - lookingForPeers = true; - return BMA.network.peering.peers({ leaves: true }) - .then(function(res){ - return BMA.wot.members() - .then(function(json){ - $rootScope.members = json.results; - return res; - }); - }) - .then(function(res){ - return $q.all(res.leaves.map(function(leaf) { - return BMA.network.peering.peers({ leaf: leaf }) - .then(function(subres){ - var peer = subres.leaf.value; - if (peer) { - peer = new Peer(peer); - // Test each peer only once - if (!known[peer.getURL()]) { - peer.dns = peer.getDns(); - peer.blockNumber = peer.block.replace(/-.+$/, ''); - var member = _.findWhere($rootScope.members, { pubkey: peer.pubkey }); - peer.uid = member && member.uid; - newPeers.push(peer); - var node = BMA.instance(peer.getURL()); - return node.blockchain.current() - .then(function(block){ - peer.current = block; - peer.online = true; - peer.server = peer.getURL(); - if ($scope.knownBlocks.indexOf(fpr(block)) === -1) { - $scope.knownBlocks.push(fpr(block)); - } - }) - .catch(function(err) { - }) - } - } - }) - })) - .then(function(){ - $scope.search.lookingForPeers = false; - }) - }) - .catch(function(err) { - //console.log(err); - //UIUtils.alert.error('Could get peers from remote uCoin node.'); - //$scope.search.lookingForPeers = false; - }); - }; - - $scope.viewPeer = function() { - - }; -} - -function fpr(block) { - return block && [block.number, block.hash].join('-'); -} - -function HomeController($scope, $ionicSlideBoxDelegate, $ionicModal, $state, BMA, UIUtils, $q, $timeout, Wallet, CryptoUtils, $ionicSideMenuDelegate) { - - // With the new view caching in Ionic, Controllers are only called - // when they are recreated or on app start, instead of every page change. - // To listen for when this page is active (for example, to refresh data), - // listen for the $ionicView.enter event: - //$scope.$on('$ionicView.enter', function(e) { - //}); - - CurrenciesController.call(this, $scope, $state); - LookupController.call(this, $scope, BMA, $state); - LoginController.call(this, $scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate); - - $scope.accountTypeMember = null; - $scope.accounts = []; - $scope.search = { text: '', results: {} }; - $scope.knownCurrencies = ['meta_brouzouf']; - - // Called to navigate to the main app - $scope.cancel = function() { - $scope.modal.hide(); - $timeout(function(){ - $scope.selectedCurrency = ''; - $scope.accountTypeMember = null; - $scope.search.text = ''; - $scope.search.results = []; - }, 200); - }; - - $scope.$on('currencySelected', function() { - $ionicSlideBoxDelegate.slide(1); - }); - - $scope.selectAccountTypeMember = function(bool) { - $scope.accountTypeMember = bool; - $ionicSlideBoxDelegate.slide(2); - }; - - $scope.next = function() { - $ionicSlideBoxDelegate.next(); - }; - $scope.previous = function() { - $ionicSlideBoxDelegate.previous(); - }; - - // Called each time the slide changes - $scope.slideChanged = function(index) { - $scope.slideIndex = index; - $scope.nextStep = $scope.slideIndex == 2 ? 'Start using MyApp' : 'Next'; - }; - - $scope.addAccount = function() { - $scope.modal.show(); - $scope.slideChanged(0); - $ionicSlideBoxDelegate.slide(0); - $ionicSlideBoxDelegate.enableSlide(false); - // TODO: remove default - //$timeout(function() { - // $scope.selectedCurrency = $scope.knownCurrencies[0]; - // $scope.accountTypeMember = true; - // $scope.searchChanged(); - // $scope.search.text = 'cgeek'; - // $ionicSlideBoxDelegate.next(); - // $ionicSlideBoxDelegate.next(); - //}, 300); - }; - - // Create the account modal that we will use later - $ionicModal.fromTemplateUrl('templates/account/new_account.html', { - scope: $scope - }).then(function(modal) { - $scope.modal = modal; - $scope.modal.hide(); - // TODO: remove auto add account when done - //$timeout(function() { - // $scope.addAccount(); - //}, 400); - }); - - $scope.selectCurrency = function(currency) { - $scope.selectedCurrency = currency; - $scope.next(); - } -} - - -function WalletController($scope, $state, $q, UIUtils, Wallet, $ionicPopup) { - - $scope.walletData = {}; - $scope.convertedBalance = 0; - $scope.hasCredit = false; - $scope.isMember = false; - - $scope.$on('$ionicView.enter', function(e, $state) { - $scope.loadWallet() - .then(function(wallet) { - $scope.updateWalletView(wallet); - }); - }); - - $scope.refreshConvertedBalance = function() { - if ($scope.walletData.useRelative) { - $scope.convertedBalance = $scope.walletData.balance / $scope.walletData.currentUD; - $scope.unit = 'universal_dividend'; - $scope.udUnit = $scope.walletData.currency; - } else { - $scope.convertedBalance = $scope.walletData.balance; - $scope.unit = $scope.walletData.currency; - $scope.udUnit = ''; - } - }; - $scope.$watch('walletData.useRelative', $scope.refreshConvertedBalance, true); - $scope.$watch('walletData.balance', $scope.refreshConvertedBalance, true); - - // Update view - $scope.updateWalletView = function(wallet) { - $scope.walletData = wallet; - $scope.hasCredit = $scope.walletData.balance != "undefined" && ($scope.walletData.balance > 0); - $scope.isMember = (wallet.requirements != null); - }; - - // Has credit - $scope.hasCredit= function() { - return $scope.balance > 0; - }; - - // Transfer click - $scope.transfer= function() { - $state.go('app.view_transfer'); - }; - - // Self cert - $scope.self= function() { - - // ASk uid - $ionicPopup.show({ - template: '<input type="text" ng-model="walletData.uid">', - title: 'Enter Pseudo', - subTitle: 'Pseudo is used by other member member to find you', - scope: $scope, - buttons: [ - { text: 'Cancel' }, - { - text: '<b>Send</b>', - type: 'button-positive', - onTap: function(e) { - if (!$scope.walletData.uid) { - //don't allow the user to close unless he enters wifi password - e.preventDefault(); - } else { - return $scope.walletData.uid; - } - } - } - ] - }) - .then(function(uid) { - UIUtils.loading.show(); - Wallet.self(uid) - .then(function() { - UIUtils.loading.hide(); - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Error while sending self certification: ' + err); - UIUtils.loading.hide(); - }); - }); - - - }; -} - - -function TransferController($scope, $ionicModal, Wallet, UIUtils, $state, $ionicHistory) { - - $scope.walletData = {}; - $scope.formData = { - destPub: null, - amount: null, - comments: null - }; - $scope.dest = null; - $scope.udAmount = null; - - $scope.$on('$ionicView.enter', function(e, $state) { - if ($state.stateParams != null - && $state.stateParams.pubkey != null - && $state.stateParams.pubkey != "undefined") { - $scope.destPub = $state.stateParams.pubkey; - if ($state.stateParams.uid != null - && $state.stateParams.uid != "undefined") { - $scope.dest = $state.stateParams.uid; - } - else { - $scope.dest = $scope.destPub; - } - } - - // Login and load wallet - $scope.loadWallet() - .then(function(walletData) { - $scope.walletData = walletData; - $scope.onUseRelativeChanged(); - }); - }); - - // When chaing use relative UD - $scope.onUseRelativeChanged = function() { - if ($scope.walletData.useRelative) { - $scope.udAmount = $scope.amount * $scope.walletData.currentUD; - $scope.unit = 'universal_dividend'; - $scope.udUnit = $scope.walletData.currency; - } else { - $scope.formData.amount = ($scope.formData.amount != "undefined" && $scope.formData.amount != null) - ? Math.floor(parseFloat($scope.formData.amount.replace(new RegExp('[,]'), '.'))) - : null; - $scope.udAmount = $scope.amount / $scope.walletData.currentUD; - $scope.unit = $scope.walletData.currency; - $scope.udUnit = ''; - } - }; - $scope.$watch('walletData.useRelative', $scope.onUseRelativeChanged, true); - - $ionicModal.fromTemplateUrl('templates/wot/modal_lookup.html', { - scope: $scope, - focusFirstInput: true - }).then(function(modal) { - $scope.lookupModal = modal; - $scope.lookupModal.hide(); - }); - - $scope.openSearch = function() { - $scope.lookupModal.show(); - } - - $scope.doTransfer = function() { - UIUtils.loading.show(); - - var amount = $scope.formData.amount; - if ($scope.walletData.useRelative - && amount != "undefined" - && amount != null) { - amount = $scope.walletData.currentUD - * amount.replace(new RegExp('[.,]'), '.'); - } - - Wallet.transfer($scope.formData.destPub, amount, $scope.formData.comments) - .then(function() { - UIUtils.loading.hide(); - $ionicHistory.goBack() - }) - .catch(function(err) { - console.error('>>>>>>>' , err); - UIUtils.alert.error('Could not send transaction: ' + err); - UIUtils.loading.hide(); - }); - }; - - $scope.closeLookup = function() { - $scope.lookupModal.hide(); - } - - $scope.doSelectIdentity = function(pub, uid) { - if (uid != "undefined" && uid != null) { - $scope.dest = uid; - } - else { - $scope.dest = uid; - } - $scope.formData.destPub = pub; - $scope.lookupModal.hide(); - } -} diff --git a/www/js/services.js b/www/js/services.js index 4ba0959585279ce5c6bbea4f654582404c4800ef..44821043a3a0394403c3405523ae03c7e11181ed 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -1,849 +1,6 @@ -//var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; - -angular.module('cesium.services', ['ngResource']) - -.factory('BMA', function($http, $q) { - - function BMA(server, wsServer) { - if (wsServer == "undefined" || wsServer == null) { - wsServer = server; - } - - function processError(reject, data) { - if (data != null && data.message != "undefined" && data.message != null) { - reject(data.ucode + ": " + data.message); - } - else { - reject('Unknown error from ucoin node'); - } - } - - function prepare(uri, params, config, callback) { - var pkeys = [], queryParams = {}, newUri = uri; - if (typeof params == 'object') { - pkeys = _.keys(params); - } - - pkeys.forEach(function(pkey){ - var prevURI = newUri; - newUri = newUri.replace(new RegExp(':' + pkey), params[pkey]); - if (prevURI == newUri) { - queryParams[pkey] = params[pkey]; - } - }); - config.params = queryParams; - callback(newUri, config); - } - - function getResource(uri) { - return function(params) { - return $q(function(resolve, reject) { - var config = { - timeout: 4000 - }; - - prepare(uri, params, config, function(uri, config) { - $http.get(uri, config) - .success(function(data, status, headers, config) { - resolve(data); - }) - .error(function(data, status, headers, config) { - processError(reject, data); - }); - }); - }); - } - } - - function postResource(uri) { - return function(data, params) { - return $q(function(resolve, reject) { - var config = { - timeout: 4000, - headers : {'Content-Type' : 'application/json'} - }; - - prepare(uri, params, config, function(uri, config) { - $http.post(uri, data, config) - .success(function(data, status, headers, config) { - resolve(data); - }) - .error(function(data, status, headers, config) { - processError(reject, data); - }); - }); - }); - } - } - - function ws(uri) { - var sock = new WebSocket(uri); - return { - on: function(type, callback) { - sock.onmessage = function(e) { - callback(JSON.parse(e.data)); - }; - } - }; - } - - return { - wot: { - lookup: getResource('http://' + server + '/wot/lookup/:search'), - members: getResource('http://' + server + '/wot/members'), - requirements: getResource('http://' + server + '/wot/requirements/:pubkey'), - add: postResource('http://' + server + '/wot/add') - }, - network: { - peering: { - peers: getResource('http://' + server + '/network/peering/peers') - }, - peers: getResource('http://' + server + '/network/peers') - }, - currency: { - parameters: getResource('http://' + server + '/blockchain/parameters') - }, - blockchain: { - current: getResource('http://' + server + '/blockchain/current'), - block: getResource('http://' + server + '/blockchain/block/:block'), - stats: { - ud: getResource('http://' + server + '/blockchain/with/ud'), - tx: getResource('http://' + server + '/blockchain/with/tx') - }, - membership: postResource('http://' + server + '/blockchain/membership'), - }, - - tx: { - sources: getResource('http://' + server + '/tx/sources/:pubkey'), - process: postResource('http://' + server + '/tx/process'), - history: { - all: getResource('http://' + server + '/tx/history/:pubkey'), - times: getResource('http://' + server + '/tx/history/:pubkey/times/:from/:to'), - blocks: getResource('http://' + server + '/tx/history/:pubkey/blocks/:from/:to') - } - }, - websocket: { - block: function() { - return ws('ws://' + wsServer + '/ws/block'); - }, - peer: function() { - return ws('ws://' + wsServer + '/ws/peer'); - } - } - } - } - //var service = BMA('metab.ucoin.fr', 'metab.ucoin.fr:9201'); - //var service = BMA('192.168.0.28:9201'); - var service = BMA('metab.ucoin.io'); - service.instance = BMA; - return service; -}) - -.factory('UIUtils', function($ionicLoading, $ionicPopup) { - function alertError(err, subtitle) { - var message = err.message || err; - return $ionicPopup.show({ - template: '<p>' + (message || 'Unknown error') + '</p>', - title: 'Application error', - subTitle: subtitle, - buttons: [ - { - text: '<b>OK</b>', - type: 'button-assertive' - } - ] - }); - } - - function hideLoading(){ - $ionicLoading.hide(); - } - - function showLoading() { - $ionicLoading.show({ - template: 'Loading...' - }); - } - - function onError(msg) { - return function(err) { - console.error('>>>>>>>' , err); - alertError(msg + ': ' + err); - hideLoading(); - } - } - - return { - alert: { - error: alertError - }, - loading: { - show: showLoading, - hide: hideLoading - }, - onError: onError - }; -}) - -.factory('CryptoUtils', function($q, $timeout) { - - var async_load_scrypt = function() { - if (typeof module !== 'undefined' && module.exports) { - // add node.js implementations - require('scrypt-em'); - return scrypt_module_factory(); - } - else if (scrypt_module_factory !== null){ - return scrypt_module_factory(); - } - else { - return $timeout(async_load_scrypt, 100); - } - }, - - async_load_nacl = function() { - if (typeof module !== 'undefined' && module.exports) { - // add node.js implementations - require('nacl_factory'); - return nacl_factory.instantiate(); - } - else if (nacl_factory !== null){ - return nacl_factory.instantiate(); - } - else { - return $timeout(async_load_nacl, 100); - } - }, - - async_load_base58 = function() { - if (typeof module !== 'undefined' && module.exports) { - // add node.js implementations - require('base58'); - return Base58; - } - else if (Base58 !== null){ - return Base58; - } - else { - return $timeout(async_load_base58, 100); - } - }, - - async_load_base64 = function() { - if (typeof module !== 'undefined' && module.exports) { - // add node.js implementations - require('base58'); - return Base64; - } - else if (Base64 !== null){ - return Base64; - } - else { - return setTimetout(async_load_base64, 100); - } - }; - - function CryptoUtils() { - var - // Const - crypto_sign_BYTES= 64, - SEED_LENGTH= 32, // Length of the key - SCRYPT_PARAMS= { - "N":4096, - "r":16, - "p":1 - } - - // load libraries - scrypt = async_load_scrypt(), - nacl = async_load_nacl(), - base58 = async_load_base58(), - base64 = async_load_base64(), - decode_utf8 = function(s) { - var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length); - for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); - return b; - }, - encode_base58 = function(a) { - return base58.encode(a); - }, - - /** - * Create a key pair, from salt+password, and return a wallet object - */ - connect = function(salt, password) { - return $q(function(resolve, reject) { - var seed = scrypt.crypto_scrypt( - nacl.encode_utf8(password), - nacl.encode_utf8(salt), - 4096, 16, 1, 32 // TODO: put in var SCRYPT_PARAMS - ); - var keypair = nacl.crypto_sign_keypair_from_seed(seed); - resolve(keypair); - }) - }, - - /** - * Verify a signature of a message, for a pubkey - */ - verify = function (message, signature, pubkey) { - return $q(function(resolve, reject) { - var msg = decode_utf8(message); - var sig = base64.decode(signature); - var pub = base58.decode(pubkey); - var m = new Uint8Array(crypto_sign_BYTES + msg.length); - var sm = new Uint8Array(crypto_sign_BYTES + msg.length); - var i; - for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; - for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; - - // Call to verification lib... - var verified = nacl.crypto_sign_open(sm, pub) !== null; - resolve(verified); - }); - }, - - /** - * Sign a message, from a wallet - */ - sign = function(message, keypair) { - return $q(function(resolve, reject) { - var m = decode_utf8(message); - var sk = keypair.signSk; - var signedMsg = nacl.crypto_sign(m, sk); - var sig = new Uint8Array(crypto_sign_BYTES); - for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; - var signature = base64.encode(sig); - resolve(signature); - }) - } - - ; - - // Service's exposed methods - return { - /* - TODO: uncomment if need to expose - nacl: nacl, - scrypt: scrypt, - base58: base58, - base64: base64,*/ - util: { - encode_utf8: nacl.encode_utf8, - decode_utf8: decode_utf8, - encode_base58: encode_base58 - }, - - - connect: connect, - sign: sign, - verify: verify - //,isCompatible: isCompatible - } - } - var service = CryptoUtils(); - service.instance = CryptoUtils; - return service; -}) - -.factory('$localstorage', ['$window', 'CryptoUtils', '$q', function($window, CryptoUtils, $q) { - return { - set: function(key, value) { - $window.localStorage[key] = value; - }, - get: function(key, defaultValue) { - return $window.localStorage[key] || defaultValue; - }, - setObject: function(key, value) { - $window.localStorage[key] = JSON.stringify(value); - }, - getObject: function(key) { - return JSON.parse($window.localStorage[key] || '{}'); - } - } -}]) - -.factory('Wallet', ['CryptoUtils', 'BMA', '$q', function(CryptoUtils, BMA, $q) { - Wallet = function(id) { - - var - - USE_RELATIVE_DEFAULT = true, - - createData = function() { - return { - uid: null, - pubkey: null, - keypair: { - signSk: null, - signPk: null - }, - balance: 0, - sources: null, - useRelative: USE_RELATIVE_DEFAULT, - currency: null, - currentUD: null, - history: {}, - loaded: false, - requirements: null - }; - }, - - data = createData(), - - reduceTx = function(txArray) { - var list = []; - txArray.forEach(function(tx) { - var issuerIndex = -1; - var issuer = tx.issuers.reduce(function(issuer, res, index) { - issuerIndex = (res == data.pubkey) ? index : issuerIndex; - return issuer + ((res != data.pubkey) ? ', ' + res : ''); - }, ', ').substring(2); - var amount = - tx.inputs.reduce(function(sum, input) { - var inputArray = input.split(':',5); - return sum - ((inputArray[0] == issuerIndex) ? parseInt(inputArray[4]) : 0); - }, 0); - amount += tx.outputs.reduce(function(sum, output) { - var outputArray = output.split(':',2); - return sum + ((outputArray[0] == data.pubkey) ? parseInt(outputArray[1]) : 0); - }, 0); - - list.push({ - time: ((tx.time != null && tx.time != "undefined") ? tx.time : 9999999), - amount: amount, - issuer: issuer, - comments: 'comments', - isUD: false, - hash: tx.hash, - block_number: tx.block_number - }); - }); - - return list; - }, - - login = function(salt, password) { - return $q(function(resolve, reject) { - CryptoUtils.connect(salt, password).then( - function(keypair) { - // Copy result to properties - data.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); - data.keypair = keypair; - resolve(); - } - ); - }); - }, - - logout = function(username, password) { - return $q(function(resolve, reject) { - data = createData(); - resolve(); - }); - }, - - isLogin = function() { - return data.pubkey != "undefined" - && data.pubkey != null; - }, - - isSourceEquals = function(arg1, arg2) { - return arg1.type == arg2.type - && arg1.fingerprint == arg2.fingerprint - && arg1.number == arg2.number - && arg1.amount == arg2.amount; - }, - - loadData = function() { - if (data.loaded) { - return refreshData(); - } - - return $q(function(resolve, reject){ - data.loaded = false; - - //console.log('calling loadData'); - $q.all([ - - // Get currency parameters - BMA.currency.parameters() - .then(function(json){ - data.currency = json.currency; - }), - - // Get the UD informations - BMA.blockchain.stats.ud() - .then(function(res){ - if (res.result.blocks.length) { - var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; - return BMA.blockchain.block({ block: lastBlockWithUD }) - .then(function(block){ - data.currentUD = block.dividend; - }); - } - }), - - // Get sources - BMA.tx.sources({pubkey: data.pubkey}) - .then(function(res){ - data.sources = res.sources; - - var balance = 0; - if (res.sources.length) { - for (var i=0; i<res.sources.length; i++) { - balance += res.sources[i].amount; - res.sources[i].consumed = false; - } - } - data.balance = balance; - }), - - // Get requirements - BMA.wot.requirements({pubkey: data.pubkey}) - .then(function(res){ - if (res.identities != "undefined" - && res.identities != null - && res.identities.length == 1) { - data.requirements = res.identities[0]; - data.uid = res.identities[0].uid; - } - }) - .catch(function(err) { - data.requirements = null; - }), - - // Get transactions - BMA.tx.history.all({pubkey: data.pubkey}) - .then(function(res){ - var list = reduceTx(res.history.sent); - list.push(reduceTx(res.history.received)); - list.push(reduceTx(res.history.sending)); - list.push(reduceTx(res.history.receiving)); - list.push(reduceTx(res.history.pending)); - - var history = []; - list.forEach(function(tx){ - history['T:'+ tx.block_number + tx.hash] = tx; - }); - var result = []; - _.keys(history).forEach(function(key) { - result.push(history[key]); - }) - data.history = result.sort(function(tx1, tx2) { - return tx2.time - tx1.time; - }); - }) - ]) - .then(function() { - data.loaded = true; - resolve(data); - }) - .catch(function(err) { - data.loaded = false; - reject(err); - }); - }); - }, - - refreshData = function() { - return $q(function(resolve, reject){ - console.log('calling refreshData'); - - $q.all([ - - // Get the UD informations - BMA.blockchain.stats.ud() - .then(function(res){ - if (res.result.blocks.length) { - var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; - return BMA.blockchain.block({ block: lastBlockWithUD }) - .then(function(block){ - data.currentUD = block.dividend; - }); - } - }), - - // Get requirements - BMA.wot.requirements({pubkey: data.pubkey}) - .then(function(res){ - if (res.identities != "undefined" - && res.identities != null - && res.identities.length == 1) { - data.requirements = res.identities[0]; - data.uid = res.identities[0].uid; - } - else { - data.requirements = null; - } - }) - .catch(function(err) { - data.requirements = null; - }), - - // Get sources - BMA.tx.sources({pubkey: data.pubkey}) - .then(function(res){ - - var balance = 0; - if (res.sources.length) { - for (var i=0; i<res.sources.length; i++) { - res.sources[i].consumed = false; - if (data.sources.length) { - for (var j=0; j<data.sources.length; j++) { - if (isSourceEquals(res.sources[i], data.sources[j]) - && data.sources[j].consumed){ - res.sources[i].consumed = true; - break; - } - } - } - if (!res.sources[i].consumed){ - balance += res.sources[i].amount; - } - } - data.sources = res.sources; - } - data.balance = balance; - }) - ]) - .then(function() { - resolve(data); - }) - .catch(function(err) { - reject(err); - }); - }); - }, - - /** - * Send a new transaction - */ - transfer = function(destPub, amount, comments) { - return $q(function(resolve, reject) { - - if (!isLogin()){ - reject('Wallet required to be login first.'); return; - } - if (amount == null) { - reject('amount must not be null'); return; - } - amount = Math.round(amount); - if (amount <= 0) { - reject('amount must be greater than zero'); return; - } - if (amount > data.balance) { - reject('Not enought credit'); return; - } - - var tx = "Version: 1\n" - + "Type: Transaction\n" - + "Currency: " + data.currency + "\n" - + "Issuers:\n" - + data.pubkey + "\n" - + "Inputs:\n"; - var sourceAmount = 0; - var inputs = []; - for (var i = 0; i<data.sources.length; i++) { - var input = data.sources[i]; - if (input.consumed == "undefined" || !input.consumed){ - // INDEX:SOURCE:NUMBER:FINGERPRINT:AMOUNT - tx += "0:"+input.type+":"+ input.number+":" - + input.fingerprint+":" - + input.amount+"\n"; - sourceAmount += input.amount; - inputs.push(input); - if (sourceAmount >= amount) { - break; - } - } - } - - if (sourceAmount < amount) { - reject('Not enought sources (max amount: ' - +(data.useRelative ? (sourceAmount / data.currentUD)+' UD' : sourceAmount) - +'). Please wait next block computation.'); - return; - } - - tx += "Outputs:\n" - // ISSUERS:AMOUNT - + destPub +":" + amount + "\n"; - if (sourceAmount > amount) { - tx += data.pubkey+":"+(sourceAmount-amount)+"\n"; - } - - tx += "Comment: "+ (comments!=null?comments:"") + "\n"; - - - - CryptoUtils.sign(tx, data.keypair) - .then(function(signature) { - var signedTx = tx + signature + "\n"; - BMA.tx.process({transaction: signedTx}) - .then(function(result) { - data.balance -= amount; - for(var i=0;i<inputs.length;i++)inputs[i].consumed=true; - resolve(result); - }) - .catch(function(err){ - reject(err); - }); - }) - .catch(function(err){ - reject(err); - }); - }); - } - - /** - * Send self certification - */ - self = function(uid) { - return $q(function(resolve, reject) { - - BMA.blockchain.current() - .then(function(block) { - // Create the self part to sign - var self = 'UID:' + uid + '\n' - + 'META:TS:' + (block.time+1) + '\n'; - - CryptoUtils.sign(self, data.keypair) - .then(function(signature) { - var signedSelf = self + signature + '\n'; - // Send self - BMA.wot.add({pubkey: data.pubkey, self: signedSelf, other: ''}) - .then(function(result) { - // Check requirements - BMA.wot.requirements({pubkey: data.pubkey}) - .then(function(res){ - if (res.identities != "undefined" - && res.identities != null - && res.identities.length == 1) { - data.requirements = res.identities[0]; - data.uid = uid; - resolve(); - } - else{ - reject(); - } - }) - .catch(function(err) { - reject(); - }) - }) - .catch(function(err){ - reject(err); - }); - }) - .catch(function(err){ - reject(err); - }); - }) - .catch(function(err) { - reject(err); - }); - }); - }, - - /** - * Send identity certification - */ - sign = function(uid, pubkey, timestamp, signature) { - return $q(function(resolve, reject) { - - BMA.blockchain.current() - .then(function(block) { - // Create the self part to sign - var self = 'UID:' + uid + '\n' - + 'META:TS:' + timestamp + '\n' - + signature /*+"\n"*/; - - var cert = self + '\n' - + 'META:TS:' + block.number + '-' + block.hash + '\n'; - - CryptoUtils.sign(cert, data.keypair) - .then(function(signature) { - var inlineCert = data.pubkey - + ':' + pubkey - + ':' + block.number - + ':' + signature + '\n'; - BMA.wot.add({pubkey: pubkey, self: self, other: inlineCert}) - .then(function(result) { - resolve(result); - }) - .catch(function(err){ - reject(err); - }); - }) - .catch(function(err){ - reject(err); - }); - }) - .catch(function(err) { - reject(err); - }); - }); - }, - - /** - * Serialize to JSON string - */ - toJson = function() { - return $q(function(resolve, reject) { - var json = JSON.stringify(data); - resolve(json); - }) - }, - - /** - * De-serialize from JSON string - */ - fromJson = function(json) { - return $q(function(resolve, reject) { - var obj = JSON.parse(json || '{}'); - if (obj.keypair != "undefined" - && obj.keypair != null) { - var keypair = obj.keypair; - - // Convert to Uint8Array type - var signPk = new Uint8Array(32); - for (var i = 0; i < 32; i++) signPk[i] = keypair.signPk[i]; - keypair.signPk = signPk; - - var signSk = new Uint8Array(64); - for (var i = 0; i < 64; i++) signSk[i] = keypair.signSk[i]; - keypair.signSk = signSk; - - data.pubkey = obj.pubkey; - data.keypair = keypair; - - resolve(); - } - else { - reject('Not a valid Wallet.data object'); - } - }) - }; - - return { - id: id, - data: data, - - login: login, - logout: logout, - isLogin: isLogin, - toJson: toJson, - fromJson: fromJson, - loadData: loadData, - refreshData: refreshData, - transfer: transfer, - self: self, - sign: sign - } - } - var service = Wallet('default'); - service.instance = service; - return service; -}]) +angular.module('cesium.services', [ + 'cesium.bma.services', + 'cesium.crypto.services', + 'cesium.utils.services', + 'cesium.wallet.services']) ; diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js new file mode 100644 index 0000000000000000000000000000000000000000..75d5fdb2f07e39169833626af1844b51d182da62 --- /dev/null +++ b/www/js/services/bma-services.js @@ -0,0 +1,141 @@ +//var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; + +angular.module('cesium.bma.services', ['ngResource']) + +.factory('BMA', function($http, $q) { + + function BMA(server, wsServer) { + if (wsServer == "undefined" || wsServer == null) { + wsServer = server; + } + + function processError(reject, data) { + if (data != null && data.message != "undefined" && data.message != null) { + reject(data.ucode + ": " + data.message); + } + else { + reject('Unknown error from ucoin node'); + } + } + + function prepare(uri, params, config, callback) { + var pkeys = [], queryParams = {}, newUri = uri; + if (typeof params == 'object') { + pkeys = _.keys(params); + } + + pkeys.forEach(function(pkey){ + var prevURI = newUri; + newUri = newUri.replace(new RegExp(':' + pkey), params[pkey]); + if (prevURI == newUri) { + queryParams[pkey] = params[pkey]; + } + }); + config.params = queryParams; + callback(newUri, config); + } + + function getResource(uri) { + return function(params) { + return $q(function(resolve, reject) { + var config = { + timeout: 4000 + }; + + prepare(uri, params, config, function(uri, config) { + $http.get(uri, config) + .success(function(data, status, headers, config) { + resolve(data); + }) + .error(function(data, status, headers, config) { + processError(reject, data); + }); + }); + }); + } + } + + function postResource(uri) { + return function(data, params) { + return $q(function(resolve, reject) { + var config = { + timeout: 4000, + headers : {'Content-Type' : 'application/json'} + }; + + prepare(uri, params, config, function(uri, config) { + $http.post(uri, data, config) + .success(function(data, status, headers, config) { + resolve(data); + }) + .error(function(data, status, headers, config) { + processError(reject, data); + }); + }); + }); + } + } + + function ws(uri) { + var sock = new WebSocket(uri); + return { + on: function(type, callback) { + sock.onmessage = function(e) { + callback(JSON.parse(e.data)); + }; + } + }; + } + + return { + wot: { + lookup: getResource('http://' + server + '/wot/lookup/:search'), + members: getResource('http://' + server + '/wot/members'), + requirements: getResource('http://' + server + '/wot/requirements/:pubkey'), + add: postResource('http://' + server + '/wot/add') + }, + network: { + peering: { + peers: getResource('http://' + server + '/network/peering/peers') + }, + peers: getResource('http://' + server + '/network/peers') + }, + currency: { + parameters: getResource('http://' + server + '/blockchain/parameters') + }, + blockchain: { + current: getResource('http://' + server + '/blockchain/current'), + block: getResource('http://' + server + '/blockchain/block/:block'), + membership: postResource('http://' + server + '/blockchain/membership'), + stats: { + ud: getResource('http://' + server + '/blockchain/with/ud'), + tx: getResource('http://' + server + '/blockchain/with/tx') + } + }, + + tx: { + sources: getResource('http://' + server + '/tx/sources/:pubkey'), + process: postResource('http://' + server + '/tx/process'), + history: { + all: getResource('http://' + server + '/tx/history/:pubkey'), + times: getResource('http://' + server + '/tx/history/:pubkey/times/:from/:to'), + blocks: getResource('http://' + server + '/tx/history/:pubkey/blocks/:from/:to') + } + }, + websocket: { + block: function() { + return ws('ws://' + wsServer + '/ws/block'); + }, + peer: function() { + return ws('ws://' + wsServer + '/ws/peer'); + } + } + } + } + //var service = BMA('metab.ucoin.fr', 'metab.ucoin.fr:9201'); + //var service = BMA('192.168.0.28:9201'); + var service = BMA('metab.ucoin.io'); + service.instance = BMA; + return service; +}) +; diff --git a/www/js/services/crypto-services.js b/www/js/services/crypto-services.js new file mode 100644 index 0000000000000000000000000000000000000000..77d5b07da3776626cd1c06e4feafdac3e4a3ab95 --- /dev/null +++ b/www/js/services/crypto-services.js @@ -0,0 +1,165 @@ +//var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; + +angular.module('cesium.crypto.services', ['ngResource']) + +.factory('CryptoUtils', function($q, $timeout) { + + var async_load_scrypt = function() { + if (typeof module !== 'undefined' && module.exports) { + // add node.js implementations + require('scrypt-em'); + return scrypt_module_factory(); + } + else if (scrypt_module_factory !== null){ + return scrypt_module_factory(); + } + else { + return $timeout(async_load_scrypt, 100); + } + }, + + async_load_nacl = function() { + if (typeof module !== 'undefined' && module.exports) { + // add node.js implementations + require('nacl_factory'); + return nacl_factory.instantiate(); + } + else if (nacl_factory !== null){ + return nacl_factory.instantiate(); + } + else { + return $timeout(async_load_nacl, 100); + } + }, + + async_load_base58 = function() { + if (typeof module !== 'undefined' && module.exports) { + // add node.js implementations + require('base58'); + return Base58; + } + else if (Base58 !== null){ + return Base58; + } + else { + return $timeout(async_load_base58, 100); + } + }, + + async_load_base64 = function() { + if (typeof module !== 'undefined' && module.exports) { + // add node.js implementations + require('base58'); + return Base64; + } + else if (Base64 !== null){ + return Base64; + } + else { + return setTimetout(async_load_base64, 100); + } + }; + + function CryptoUtils() { + var + // Const + crypto_sign_BYTES= 64, + SEED_LENGTH= 32, // Length of the key + SCRYPT_PARAMS= { + "N":4096, + "r":16, + "p":1 + } + + // load libraries + scrypt = async_load_scrypt(), + nacl = async_load_nacl(), + base58 = async_load_base58(), + base64 = async_load_base64(), + decode_utf8 = function(s) { + var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length); + for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); + return b; + }, + encode_base58 = function(a) { + return base58.encode(a); + }, + + /** + * Create a key pair, from salt+password, and return a wallet object + */ + connect = function(salt, password) { + return $q(function(resolve, reject) { + var seed = scrypt.crypto_scrypt( + nacl.encode_utf8(password), + nacl.encode_utf8(salt), + 4096, 16, 1, 32 // TODO: put in var SCRYPT_PARAMS + ); + var keypair = nacl.crypto_sign_keypair_from_seed(seed); + resolve(keypair); + }) + }, + + /** + * Verify a signature of a message, for a pubkey + */ + verify = function (message, signature, pubkey) { + return $q(function(resolve, reject) { + var msg = decode_utf8(message); + var sig = base64.decode(signature); + var pub = base58.decode(pubkey); + var m = new Uint8Array(crypto_sign_BYTES + msg.length); + var sm = new Uint8Array(crypto_sign_BYTES + msg.length); + var i; + for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; + for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; + + // Call to verification lib... + var verified = nacl.crypto_sign_open(sm, pub) !== null; + resolve(verified); + }); + }, + + /** + * Sign a message, from a wallet + */ + sign = function(message, keypair) { + return $q(function(resolve, reject) { + var m = decode_utf8(message); + var sk = keypair.signSk; + var signedMsg = nacl.crypto_sign(m, sk); + var sig = new Uint8Array(crypto_sign_BYTES); + for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; + var signature = base64.encode(sig); + resolve(signature); + }) + } + + ; + + // Service's exposed methods + return { + /* + TODO: uncomment if need to expose + nacl: nacl, + scrypt: scrypt, + base58: base58, + base64: base64,*/ + util: { + encode_utf8: nacl.encode_utf8, + decode_utf8: decode_utf8, + encode_base58: encode_base58 + }, + + + connect: connect, + sign: sign, + verify: verify + //,isCompatible: isCompatible + } + } + var service = CryptoUtils(); + service.instance = CryptoUtils; + return service; +}) +; diff --git a/www/js/services/utils-services.js b/www/js/services/utils-services.js new file mode 100644 index 0000000000000000000000000000000000000000..41ce70c4d37e8916198795cca61217d9ad44928a --- /dev/null +++ b/www/js/services/utils-services.js @@ -0,0 +1,83 @@ +//var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; + +angular.module('cesium.utils.services', ['ngResource']) + +.factory('UIUtils', function($ionicLoading, $ionicPopup) { + function alertError(err, subtitle) { + var message = err.message || err; + return $ionicPopup.show({ + template: '<p>' + (message || 'Unknown error') + '</p>', + title: 'Application error', + subTitle: subtitle, + buttons: [ + { + text: '<b>OK</b>', + type: 'button-assertive' + } + ] + }); + } + + function alertInfo(message, subtitle) { + var message = err.message || err; + return $ionicPopup.show({ + template: '<p>' + message + '</p>', + title: 'Information', + subTitle: subtitle, + buttons: [ + { + text: '<b>OK</b>', + type: 'button-positive' + } + ] + }); + } + + function hideLoading(){ + $ionicLoading.hide(); + } + + function showLoading() { + $ionicLoading.show({ + template: 'Loading...' + }); + } + + function onError(msg) { + return function(err) { + console.error('>>>>>>>' , err); + alertError(msg + ': ' + err); + hideLoading(); + } + } + + return { + alert: { + error: alertError + }, + loading: { + show: showLoading, + hide: hideLoading + }, + onError: onError + }; +}) + +.factory('$localstorage', ['$window', 'CryptoUtils', '$q', function($window, CryptoUtils, $q) { + return { + set: function(key, value) { + $window.localStorage[key] = value; + }, + get: function(key, defaultValue) { + return $window.localStorage[key] || defaultValue; + }, + setObject: function(key, value) { + $window.localStorage[key] = JSON.stringify(value); + }, + getObject: function(key) { + return JSON.parse($window.localStorage[key] || '{}'); + } + } +}]) + +; diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js new file mode 100644 index 0000000000000000000000000000000000000000..248bddefb87933a89d1e1b7c3026227e3916b7d5 --- /dev/null +++ b/www/js/services/wallet-services.js @@ -0,0 +1,487 @@ +//var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; + +angular.module('cesium.wallet.services', ['ngResource', 'cesium.bma.services', 'cesium.crypto.services']) + +.factory('Wallet', ['CryptoUtils', 'BMA', '$q', function(CryptoUtils, BMA, $q) { + + Wallet = function(id) { + + var + + USE_RELATIVE_DEFAULT = true, + + createData = function() { + return { + pubkey: null, + keypair: { + signSk: null, + signPk: null + }, + balance: 0, + sources: null, + useRelative: USE_RELATIVE_DEFAULT, + currency: null, + currentUD: null, + history: {}, + requirements: null, + loaded: false + }; + }, + + data = createData(), + + reduceTx = function(txArray) { + var list = []; + txArray.forEach(function(tx) { + var issuerIndex = -1; + var issuer = tx.issuers.reduce(function(issuer, res, index) { + issuerIndex = (res == data.pubkey) ? index : issuerIndex; + return issuer + ((res != data.pubkey) ? ', ' + res : ''); + }, ', ').substring(2); + var amount = + tx.inputs.reduce(function(sum, input) { + var inputArray = input.split(':',5); + return sum - ((inputArray[0] == issuerIndex) ? parseInt(inputArray[4]) : 0); + }, 0); + amount += tx.outputs.reduce(function(sum, output) { + var outputArray = output.split(':',2); + return sum + ((outputArray[0] == data.pubkey) ? parseInt(outputArray[1]) : 0); + }, 0); + + list.push({ + time: ((tx.time != null && tx.time != "undefined") ? tx.time : 9999999), + amount: amount, + issuer: issuer, + comments: 'comments', + isUD: false, + hash: tx.hash, + block_number: tx.block_number + }); + }); + + return list; + }, + + login = function(salt, password) { + return $q(function(resolve, reject) { + CryptoUtils.connect(salt, password).then( + function(keypair) { + // Copy result to properties + data.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); + data.keypair = keypair; + resolve(); + } + ); + }); + }, + + logout = function(username, password) { + return $q(function(resolve, reject) { + data = createData(); + resolve(); + }); + }, + + isLogin = function() { + return data.pubkey != "undefined" + && data.pubkey != null; + }, + + isSourceEquals = function(arg1, arg2) { + return arg1.type == arg2.type + && arg1.fingerprint == arg2.fingerprint + && arg1.number == arg2.number + && arg1.amount == arg2.amount; + }, + + loadData = function() { + if (data.loaded) { + return refreshData(); + } + + return $q(function(resolve, reject){ + data.loaded = false; + + $q.all([ + + // Get currency parameters + BMA.currency.parameters() + .then(function(json){ + data.currency = json.currency; + }), + + // Get the UD informations + BMA.blockchain.stats.ud() + .then(function(res){ + if (res.result.blocks.length) { + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return BMA.blockchain.block({ block: lastBlockWithUD }) + .then(function(block){ + data.currentUD = block.dividend; + }); + } + }), + + // Get sources + BMA.tx.sources({pubkey: data.pubkey}) + .then(function(res){ + data.sources = res.sources; + + var balance = 0; + if (res.sources.length) { + for (var i=0; i<res.sources.length; i++) { + balance += res.sources[i].amount; + res.sources[i].consumed = false; + } + } + data.balance = balance; + }), + + // Get requirements + BMA.wot.requirements({pubkey: data.pubkey}) + .then(function(res){ + if (res.identities != "undefined" + && res.identities != null + && res.identities.length == 1) { + data.requirements = res.identities[0]; + data.uid = res.identities[0].uid; + } + }) + .catch(function(err) { + data.requirements = null; + }), + + // Get transactions + BMA.tx.history.all({pubkey: data.pubkey}) + .then(function(res){ + var list = reduceTx(res.history.sent); + list.push(reduceTx(res.history.received)); + list.push(reduceTx(res.history.sending)); + list.push(reduceTx(res.history.receiving)); + list.push(reduceTx(res.history.pending)); + + var history = []; + list.forEach(function(tx){ + history['T:'+ tx.block_number + tx.hash] = tx; + }); + var result = []; + _.keys(history).forEach(function(key) { + result.push(history[key]); + }) + data.history = result.sort(function(tx1, tx2) { + return tx2.time - tx1.time; + }); + }) + ]) + .then(function() { + data.loaded = true; + resolve(data); + }) + .catch(function(err) { + data.loaded = false; + reject(err); + }); + }); + }, + + refreshData = function() { + return $q(function(resolve, reject){ + $q.all([ + + // Get the UD informations + BMA.blockchain.stats.ud() + .then(function(res){ + if (res.result.blocks.length) { + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return BMA.blockchain.block({ block: lastBlockWithUD }) + .then(function(block){ + data.currentUD = block.dividend; + }); + } + }), + + // Get requirements + BMA.wot.requirements({pubkey: data.pubkey}) + .then(function(res){ + if (res.identities != "undefined" + && res.identities != null + && res.identities.length == 1) { + data.requirements = res.identities[0]; + data.uid = res.identities[0].uid; + } + else { + data.requirements = null; + } + }) + .catch(function(err) { + data.requirements = null; + }), + + // Get sources + BMA.tx.sources({pubkey: data.pubkey}) + .then(function(res){ + + var balance = 0; + if (res.sources.length) { + for (var i=0; i<res.sources.length; i++) { + res.sources[i].consumed = false; + if (data.sources.length) { + for (var j=0; j<data.sources.length; j++) { + if (isSourceEquals(res.sources[i], data.sources[j]) + && data.sources[j].consumed){ + res.sources[i].consumed = true; + break; + } + } + } + if (!res.sources[i].consumed){ + balance += res.sources[i].amount; + } + } + data.sources = res.sources; + } + data.balance = balance; + }) + ]) + .then(function() { + resolve(data); + }) + .catch(function(err) { + reject(err); + }); + }); + }, + + /** + * Send a new transaction + */ + transfer = function(destPub, amount, comments) { + return $q(function(resolve, reject) { + + if (!isLogin()){ + reject('Wallet required to be login first.'); return; + } + if (amount == null) { + reject('amount must not be null'); return; + } + amount = Math.round(amount); + if (amount <= 0) { + reject('amount must be greater than zero'); return; + } + if (amount > data.balance) { + reject('Not enought credit'); return; + } + + var tx = "Version: 1\n" + + "Type: Transaction\n" + + "Currency: " + data.currency + "\n" + + "Issuers:\n" + + data.pubkey + "\n" + + "Inputs:\n"; + var sourceAmount = 0; + var inputs = []; + for (var i = 0; i<data.sources.length; i++) { + var input = data.sources[i]; + if (input.consumed == "undefined" || !input.consumed){ + // INDEX:SOURCE:NUMBER:FINGERPRINT:AMOUNT + tx += "0:"+input.type+":"+ input.number+":" + + input.fingerprint+":" + + input.amount+"\n"; + sourceAmount += input.amount; + inputs.push(input); + if (sourceAmount >= amount) { + break; + } + } + } + + if (sourceAmount < amount) { + reject('Not enought sources (max amount: ' + +(data.useRelative ? (sourceAmount / data.currentUD)+' UD' : sourceAmount) + +'). Please wait next block computation.'); + return; + } + + tx += "Outputs:\n" + // ISSUERS:AMOUNT + + destPub +":" + amount + "\n"; + if (sourceAmount > amount) { + tx += data.pubkey+":"+(sourceAmount-amount)+"\n"; + } + + tx += "Comment: "+ (comments!=null?comments:"") + "\n"; + + + + CryptoUtils.sign(tx, data.keypair) + .then(function(signature) { + var signedTx = tx + signature + "\n"; + BMA.tx.process({transaction: signedTx}) + .then(function(result) { + data.balance -= amount; + for(var i=0;i<inputs.length;i++)inputs[i].consumed=true; + resolve(result); + }) + .catch(function(err){ + reject(err); + }); + }) + .catch(function(err){ + reject(err); + }); + }); + }, + + /** + * Send self certification + */ + self = function(uid) { + return $q(function(resolve, reject) { + + BMA.blockchain.current() + .then(function(block) { + // Create the self part to sign + var self = 'UID:' + uid + '\n' + + 'META:TS:' + (block.time+1) + '\n'; + + CryptoUtils.sign(self, data.keypair) + .then(function(signature) { + var signedSelf = self + signature + '\n'; + // Send self + BMA.wot.add({pubkey: data.pubkey, self: signedSelf, other: ''}) + .then(function(result) { + // Check requirements + BMA.wot.requirements({pubkey: data.pubkey}) + .then(function(res){ + if (res.identities != "undefined" + && res.identities != null + && res.identities.length == 1) { + data.requirements = res.identities[0]; + data.uid = uid; + resolve(); + } + else{ + reject(); + } + }) + .catch(function(err) { + reject(); + }) + }) + .catch(function(err){ + reject(err); + }); + }) + .catch(function(err){ + reject(err); + }); + }) + .catch(function(err) { + reject(err); + }); + }); + }, + + /** + * Send identity certification + */ + sign = function(uid, pubkey, timestamp, signature) { + return $q(function(resolve, reject) { + + BMA.blockchain.current() + .then(function(block) { + // Create the self part to sign + var self = 'UID:' + uid + '\n' + + 'META:TS:' + timestamp + '\n' + + signature /*+"\n"*/; + + var cert = self + '\n' + + 'META:TS:' + block.number + '-' + block.hash + '\n'; + + CryptoUtils.sign(cert, data.keypair) + .then(function(signature) { + var inlineCert = data.pubkey + + ':' + pubkey + + ':' + block.number + + ':' + signature + '\n'; + BMA.wot.add({pubkey: pubkey, self: self, other: inlineCert}) + .then(function(result) { + resolve(result); + }) + .catch(function(err){ + reject(err); + }); + }) + .catch(function(err){ + reject(err); + }); + }) + .catch(function(err) { + reject(err); + }); + }); + }, + + /** + * Serialize to JSON string + */ + toJson = function() { + return $q(function(resolve, reject) { + var json = JSON.stringify(data); + resolve(json); + }) + }, + + /** + * De-serialize from JSON string + */ + fromJson = function(json) { + return $q(function(resolve, reject) { + var obj = JSON.parse(json || '{}'); + if (obj.keypair != "undefined" + && obj.keypair != null) { + var keypair = obj.keypair; + + // Convert to Uint8Array type + var signPk = new Uint8Array(32); + for (var i = 0; i < 32; i++) signPk[i] = keypair.signPk[i]; + keypair.signPk = signPk; + + var signSk = new Uint8Array(64); + for (var i = 0; i < 64; i++) signSk[i] = keypair.signSk[i]; + keypair.signSk = signSk; + + data.pubkey = obj.pubkey; + data.keypair = keypair; + + resolve(); + } + else { + reject('Not a valid Wallet.data object'); + } + }) + }; + + return { + id: id, + data: data, + // auth + login: login, + logout: logout, + isLogin: isLogin, + loadData: loadData, + refreshData: refreshData, + // operations + transfer: transfer, + self: self, + sign: sign, + // serialization + toJson: toJson, + fromJson: fromJson + } + } + var service = Wallet('default'); + service.instance = service; + return service; +}]) +; diff --git a/www/templates/account/view_wallet.html b/www/templates/account/view_wallet.html index ee491dd0183f1cc5ddb5e72cf3f24b1102e8fb8e..3b82e5ebbd8095e958f4a35f826de6dbe6364427 100644 --- a/www/templates/account/view_wallet.html +++ b/www/templates/account/view_wallet.html @@ -5,13 +5,13 @@ <span class="item item-icon-left" ng-if="isMember"> <i class="icon ion-person"></i> - User - <span class="badge">{{walletData.uid}}</span> + <h2>{{walletData.uid}}</h2> + <p>Public key: {{walletData.pubkey | formatPubkey}}</p> </span> <span class="item item-icon-left" ng-if="!isMember"> <i class="icon ion-key"></i> - Pubkey + Public key <span class="badge">{{walletData.pubkey | formatPubkey}}</span> </span> diff --git a/www/templates/wot/view_identity.html b/www/templates/wot/view_identity.html index bc61831df776c8cf68dd41c589196e216bb73e75..d8cf130e76429858b168d130ccf6ceb5f8fbdef7 100644 --- a/www/templates/wot/view_identity.html +++ b/www/templates/wot/view_identity.html @@ -1,17 +1,12 @@ -<ion-view view-title="Identity {{identity.uid}}" left-buttons="leftButtons"> +<ion-view view-title="{{identity.uid}}" left-buttons="leftButtons"> <ion-content> <div class="scroll"> <div class="list"> <span class="item item-icon-left"> <i class="icon ion-person"></i> - Uid - <span class="badge badge-royal">{{identity.uid}}</span> - </span> - - <span class="item item-icon-left"> - <i class="icon ion-key"></i> - Public key - <span class="badge">{{identity.pub | formatPubkey}}</span> + <h2>{{identity.uid}}</h2> + <p>public key: {{identity.pub | formatPubkey}}</p> + <span class="badge"></span> </span> <span class="item item-icon-left">