Skip to content
Snippets Groups Projects
app.js 18.71 KiB
// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.controllers' is found in controllers.js
angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht.translate', 'ngApi', 'angular-cache', 'angular.screenmatch',
  'FBAngular', // = angular-fullscreen
  // removeIf(no-device)
  'ngCordova',
  // endRemoveIf(no-device)
  'cesium.filters', 'cesium.config', 'cesium.platform', 'cesium.templates', 'cesium.translations', 'cesium.directives',
  // API dependencies :
  'cesium.services', 'cesium.api.demo.services', 'cesium.login.controllers', 'cesium.help.controllers'
])

  .config(function($stateProvider, $urlRouterProvider) {
    'ngInject';
    $stateProvider

      .state('app', {
        url: "/app",
        abstract: true,
        templateUrl: "templates/api/menu.html",
        controller: 'ApiCtrl'
      })

      .state('app.home', {
        url: "/home?result&service&cancel&node",
        views: {
          'menuContent': {
            templateUrl: "templates/api/home.html",
            controller: 'ApiDocCtrl'
          }
        }
      })

      .state('api', {
        url: "/v1",
        abstract: true,
        templateUrl: "templates/api/menu.html",
        controller: 'ApiCtrl'
      })

      .state('api.transfer', {
        cache: false,
        url: "/payment/:pubkey?name&amount&udAmount&comment&preferred_node&redirect_url&cancel_url&demo&error",
        views: {
          'menuContent': {
            templateUrl: "templates/api/transfer.html",
            controller: 'ApiTransferCtrl'
          }
        }
      });

    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/app/home');
  })

  .controller('ApiCtrl', function ($scope, $state, $translate, $ionicPopover, UIUtils, Modals, csSettings){
    'ngInject';

    // Fill locales
    $scope.locales = angular.copy(csSettings.locales);

    $scope.showAboutModal = function(e) {
      e.preventDefault(); // avoid to open link href
      Modals.showAbout();
    };
    $scope.showHome = function() {
      $state.go('app.home') ;
    };

    $scope.changeLanguage = function(langKey) {
      $translate.use(langKey);
      $scope.hideLocalesPopover();
      csSettings.data.locale = _.findWhere($scope.locales, {id: langKey});
    };

    /* -- show/hide locales popup -- */

    $scope.showLocalesPopover = function(event) {
      UIUtils.popover.show(event, {
        templateUrl: 'templates/api/popover_locales.html',
        scope: $scope,
        autoremove: true,
        afterShow: function(popover) {
          $scope.localesPopover = popover;
        }
      });
    };

    $scope.hideLocalesPopover = function() {
      if ($scope.localesPopover) {
        $scope.localesPopover.hide();
        $scope.localesPopover = null;
      }
    };
  })

  .controller('ApiDocCtrl', function ($scope, $rootScope, $state, $translate, $sce, csCurrency){
    'ngInject';

    $scope.loading = true;
    $scope.transferData = {
      pubkey: 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU',
      amount: 100,
      comment: 'REFERENCE',
      name: 'www.domain.com',
      preferred_node: undefined,
      redirect_url: 'http://www.domain.com/payment?ref={comment}&tx={tx}&node={node}',
      cancel_url: 'http://www.domain.com/payment?ref={comment}&cancel'
    };
    $scope.transferButton = {
      html: undefined,
      showParameters: false, // hide integration parameters, by default
      icons: [
        {
          label: 'API.DOC.TRANSFER.EXAMPLE_BUTTON_ICON_NONE'
        },
        {
          label: 'API.DOC.TRANSFER.EXAMPLE_BUTTON_ICON_DUNITER',
          filename: '../img/logo_duniter_32px.png'
        },
        {
          label: 'API.DOC.TRANSFER.EXAMPLE_BUTTON_ICON_CESIUM',
          filename: '../img/logo_32px.png'
        },
        {
          label: 'API.DOC.TRANSFER.EXAMPLE_BUTTON_ICON_G1_COLOR',
          filename: '../img/logo_g1_32px.png'
        },
        {
          label: 'API.DOC.TRANSFER.EXAMPLE_BUTTON_ICON_G1_BLACK',
          filename: '../img/logo_g1_32px_black.png'
        }
      ],
      style: {
        enable: false,
        text: 'Ğ1 pubkey',
        bgColor: '#fbc14c',
        fontColor: 'black',
        width: undefined
      }
    };
    $scope.transferButton.style.icon = $scope.transferButton.icons[1/*Duniter icon*/];
    $scope.transferDemoUrl = $rootScope.rootPath + $state.href('api.transfer', angular.merge({}, $scope.transferData, {
        demo: true,
        redirect_url: $rootScope.rootPath + '#/app/home?service=payment&result={tx}&node={node}',
        cancel_url: $rootScope.rootPath + '#/app/home?service=payment&cancel'
      }));

    $scope.enter = function(e, state) {
      if (!$scope.loading) return; // already enter

      $scope.result = {};
      if (state.stateParams && state.stateParams.service) {
        $scope.result.type = state.stateParams.service;
      }
      if (state.stateParams && state.stateParams.result) {
        $scope.result.content = state.stateParams.result;
      }
      if (state.stateParams && state.stateParams.cancel) {
        $scope.result.cancelled = true;
      }
      if (state.stateParams && state.stateParams.node) {
        $scope.result.node = state.stateParams.node;
      }

      csCurrency.get()
        .then(function(currency) {
          return $translate('API.DOC.TRANSFER.EXAMPLE_BUTTON_DEFAULT_TEXT', {currency: currency.name});
        })
        .then(function(buttonText) {
          $scope.transferButton.style.text = buttonText;
          $scope.loading = false;

          // compute HTML button
          $scope.computeTransferButtonHtml();
        });

    };
    $scope.$on('$ionicView.enter', $scope.enter);

    // watch from update
    $scope.computeTransferButtonHtml = function() {
      if ($scope.loading) return; // skip if loading

      // Compute URL
      var url = $rootScope.rootPath + $state.href('api.transfer', $scope.transferData);

      var html;
      // Compute HTML: simple button
      if (!$scope.transferButton.style.enable){
        html = '<a href="'+url+'">\n'+
          '  <img src="'+$rootScope.rootPath + '../img/duniter_button.svg">\n'+
          '</a>';
      }
      // Compute HTML: advanced button
      else {
        html = '<a href="'+url+'">\n'+
          '  <div style="border-radius: 5px; min-height: 42px; text-align: center; padding: 5px; line-height: 30px; ';
        if ($scope.transferButton.style.width) {
          html += 'max-width: '+$scope.transferButton.style.width+'; ';
        }
        if ($scope.transferButton.style.bgColor) {
          html += 'background-color: '+$scope.transferButton.style.bgColor+'; ';
        }
        if ($scope.transferButton.style.fontColor) {
          html += 'color: '+$scope.transferButton.style.fontColor+'; ';
        }
        html += '">\n';
        if ($scope.transferButton.style.icon && $scope.transferButton.style.icon.filename) {
          html += '    <img style="vertical-align: middle;" src="'+$rootScope.rootPath + $scope.transferButton.style.icon.filename+'">\n';
        }
        html += '    ' + $scope.transferButton.style.text + '\n' +
          '  </div>\n' +
          '</a>';
      }

      if ($scope.transferButton.html != html) {
        $scope.transferButton.html = html;
        $scope.$broadcast('$$rebind::transferButton'); // force rebind
      }
    };
    $scope.$watch('transferData', $scope.computeTransferButtonHtml, true);
    $scope.$watch('transferButton.style', $scope.computeTransferButtonHtml, true);
  })

  .controller('ApiTransferCtrl', function ($scope, $rootScope, $timeout, $controller, $state, $q, $translate, $filter,
                                           $window, $ionicHistory, BMA, CryptoUtils, UIUtils, csSettings, csCurrency,
                                           csPlatform, csTx, csWallet, csDemoWallet){
    'ngInject';

    // Initialize the super class and extend it.
    angular.extend(this, $controller('AuthCtrl', {$scope: $scope}));

    $scope.loading = true;
    $scope.transferData = {
      amount: undefined,
      amounts: undefined,
      comment: undefined,
      pubkey: undefined,
      name: undefined,
      redirect_url: undefined,
      cancel_url: undefined,
      node: undefined
    };

    $scope.enter = function(e, state) {
      $rootScope.errorState = state.stateName;

      if (!$scope.loading) return; // already enter

      if (state.stateParams && state.stateParams.amount) {
        var amountStr = state.stateParams.amount.trim();
        var amounts = ((amountStr.indexOf('|') !== -1) && amountStr.split('|')) ||
          ((amountStr.indexOf(' ') !== -1) && amountStr.split(' ')) ||
          ((amountStr.indexOf(';') !== -1) && amountStr.split(';'));
        if (amounts) {
          $scope.transferData.amounts = amounts.reduce(function(res, amountStr) {
            var amount = normalizeAmount(amountStr);
            return amount > 0 ? res.concat(amount) : res;
          }, []);
          if ($scope.transferData.amounts.length === 1) {
            $scope.transferData.amount  = $scope.transferData.amounts[0];
            delete $scope.transferData.amounts;
          }
          else {
            $scope.transferData.amounts.sort();
          }
        }
        else {
          $scope.transferData.amount  = normalizeAmount(amountStr);
        }
      }
      if (state.stateParams && state.stateParams.pubkey) {
        $scope.transferData.pubkey = state.stateParams.pubkey;
      }
      if (state.stateParams && state.stateParams.name) {
        $scope.transferData.name= state.stateParams.name;
      }
      if (state.stateParams && state.stateParams.comment) {
        $scope.transferData.comment = state.stateParams.comment;
      }
      if (state.stateParams && state.stateParams.redirect_url) {
        $scope.transferData.redirect_url = state.stateParams.redirect_url;
      }
      if (state.stateParams && state.stateParams.cancel_url) {
        $scope.transferData.cancel_url = state.stateParams.cancel_url;
      }
      if (state.stateParams && state.stateParams.demo) {
        $scope.demo = true;
      }

      if (state.stateParams && state.stateParams.preferred_node) {
        var
          isHttpsMode = $window.location.protocol === 'https:',
          useSsl = isHttpsMode,
          preferredNode = state.stateParams.preferred_node;
        var matches = /^(?:(http[s]?:)\/\/)(.*)$/.exec(preferredNode);
        if (matches) {
          useSsl = matches[1] === 'https:';
          preferredNode = matches[2];
        }
        var parts = preferredNode.split(':');
        if (parts.length >= 1) {
          var port = parts[1] || (useSsl ? 443 : 80);
          $scope.node = {
            host: parts[0],
            port: port,
            useSsl: useSsl || (port == 443)
          };

          // Add a fallback node that use SSL
          if (!$scope.node.useSsl) {
            var node = angular.copy($scope.node);
            node.useSsl = true;
            node.port = 443;
            csSettings.data.fallbackNodes = csSettings.data.fallbackNodes || [];
            csSettings.data.fallbackNodes.splice(0,0,node);
          }
        }
        else {
          console.warn("[api] Invalid preferred node address: {0}. Using default node." + state.stateParams.preferred_node);
        }
      }

      // Start
      return $scope.start();
    };
    $scope.$on('$ionicView.enter', $scope.enter);


    $scope.start = function() {
      if ($scope.starting) return;

      $scope.starting = true;
      $scope.loading = true;

      // Set BMA node
      if (!$scope.error && $scope.node && !BMA.node.same($scope.node)) {
        console.debug("[api] Using preferred node: {0}:{1}".format($scope.node.host, $scope.node.port));
        BMA.stop();
        BMA.copy($scope.node);
        $scope.node.server = BMA.server;
      }

      // Start platform (or restart) platform
      // This will start the BMA node
      return csPlatform.restart()
        .then(csCurrency.get)
        .then(function(currency) {
          $scope.currency = currency;
          $scope.node = currency.node;
          $scope.loading = false;
          $scope.error = false;
          $scope.starting = false;
          // Reset history cache
          $ionicHistory.clearCache();
        })
        // Error during load (BMA not alive ?)
        .catch(function(err) {
          console.error(err && err.message || err);
          $scope.error = true;
          $scope.loading = false;
          $scope.starting = false;

          // Make sure to retry if user choose a fallback node
          var unsubscribe = BMA.api.node.on.start($scope, function() {
            $scope.start();
            unsubscribe();
          });
        })
        ;
    };

    function normalizeAmount(amountStr) {
      return parseFloat((amountStr||'0').trim().replace(new RegExp('[.,]'), '.')).toFixed(2) * 100;
    }

    function onLogin(authData) {

      // User cancelled
      if (!authData) return $scope.onCancel();

      // Make sure amount require fields
      if (!$scope.transferData.amount || !$scope.transferData.pubkey) {
        $scope.form.$submitted=true;
        UIUtils.loading.hide();
        return $q.reject();
      }

      // Avoid multiple click
      if ($scope.sending) return;
      $scope.sending = true;
      delete $scope.transferData.error;

      var wallet = $scope.demo ? csDemoWallet.instance(authData) : csWallet.instance('api', BMA);

      UIUtils.loading.show();

      wallet.start({restore: false/*skip restore from local storage*/})
        .then(function() {
          return wallet.login({
            auth: true,
            authData: authData,
            minData: true,
            sources: true,
            tx: {enable: false}
          });
        })
        .then(function(walletData) {
          if (!walletData) return;

          $scope.login = true;

          UIUtils.loading.hide();
          return $scope.askTransferConfirm(walletData);
        })
        .then(function(confirm) {
          if (!confirm) return;

          // sent transfer
          return UIUtils.loading.show()
            .then(function(){
              var amount = parseInt($scope.transferData.amount); // remove 2 decimals on quantitative mode
              return wallet.transfer($scope.transferData.pubkey, amount, $scope.transferData.comment, false /*always quantitative mode*/);
            })
            .then(function(txRes) {

              UIUtils.loading.hide();
              return txRes;
            })
            .catch(function(err) {
              UIUtils.onError()(err);
              return false;
            });
        })
        .then(function(txRes) {
          if (txRes) {
            return $scope.onSuccess(txRes);
          }
          else {
            $scope.sending = false;
          }
        })
        .catch(function(err){
          // when user cancel
          if (err && err === 'CANCELLED') {
            return $scope.onCancel();
          }
          // When wallet is empty
          if (err && err === 'RETRY') {
            $scope.sending = false;
            return;
          }
          $scope.sending = false;
          UIUtils.onError()(err);
        });
    }


    $scope.askTransferConfirm = function(walletData) {
      return $translate(['COMMON.UD', 'COMMON.EMPTY_PARENTHESIS'])
        .then(function(translations) {
          return $translate('CONFIRM.TRANSFER', {
            from: walletData.isMember ? walletData.uid : $filter('formatPubkey')(walletData.pubkey),
            to: $scope.transferData.name || $filter('formatPubkey')($scope.transferData.pubkey),
            amount: $scope.transferData.amount / 100,
            unit: $filter('abbreviate')($rootScope.currency.name),
            comment: (!$scope.transferData.comment || $scope.transferData.comment.trim().length === 0) ? translations['COMMON.EMPTY_PARENTHESIS'] : $scope.transferData.comment
          });
        })
        .then(UIUtils.alert.confirm);
    };

    $scope.onSuccess = function(txRes) {
      if (!$scope.transferData.redirect_url) {
        return UIUtils.toast.show('INFO.TRANSFER_SENT');
      }

      return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING'))
        .then(function(message){
          return UIUtils.loading.show({template: message});
        })
        .then(function() {
          var url = $scope.transferData.redirect_url;
          // Make replacements
          url = url.replace(/\{pubkey\}/g, $scope.transferData.pubkey);
          url = url.replace(/\{hash\}/g, txRes.hash||'');
          url = url.replace(/\{comment\}/g, $scope.transferData.comment||'');
          url = url.replace(/\{amount\}/g, $scope.transferData.amount && ($scope.transferData.amount/100).toString() || '');
          url = url.replace(/\{tx\}/g, encodeURI(txRes.tx));
          url = url.replace(/\{node\}/g, encodeURI(BMA.host+':'+BMA.port));

          return $scope.redirectToUrl(url, 2500);
        });
    };

    $scope.onCancel = function() {
      if (!$scope.transferData.cancel_url) {
        $scope.formData.salt = undefined;
        $scope.formData.password = undefined;
        return; // nothing to do
      }

      return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING'))
        .then(function(message){
          return UIUtils.loading.show({template: message});
        })
        .then(function() {
          var url = $scope.transferData.cancel_url;
          // Make replacements - fix #548
          url = url.replace(/\{pubkey\}/g, $scope.transferData.pubkey);
          url = url.replace(/\{comment\}/g, $scope.transferData.comment||'');
          url = url.replace(/\{amount\}/g, $scope.transferData.amount && ($scope.transferData.amount/100).toString() || '');

          return $scope.redirectToUrl(url, 1500);
        });
    };

    $scope.redirectToUrl = function(url, timeout) {
      if (!url) return;

      return $timeout(function() {
        // if iframe: send to parent
        if (window.top && window.top.location) {
          window.top.location.href = url;
        }
        else if (parent && parent.document && parent.document.location) {
          parent.document.location.href = url;
        }
        else {
          window.location.assign(url);
        }
        return UIUtils.loading.hide();
      }, timeout||0);
    };

    /* -- methods need by Login controller -- */

    $scope.closeModal = onLogin;

  })

  .run(function(csSettings) {
    'ngInject';

    csSettings.data.rememberMe = false;
    csSettings.data.useLocalStorage = false;
    // Force auth idle to 30s
    csSettings.data.keepAuthIdle = 30;

    //csPlatform.start();
  })
;