<commit_before>'use strict';

angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {

  var root = {};

  root.showMenu = function(data) {
    $rootScope.$broadcast('incomingDataMenu.showMenu', data);
  };

  root.redir = function(data, serviceId, serviceData) {
    var originalAddress = null;
    var noPrefixInAddress = 0;

    if (data.toLowerCase().indexOf('bitcoin') < 0) {
      noPrefixInAddress = 1;
    }

    if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
      try {
        noPrefixInAddress = 0;

        if (data[0] == 'p' || data[0] == 'q') {
          data = 'bitcoincash:' + data;
        }
        var paramString = '';
        if (data.indexOf('?') >= 0) {
          paramString = data.substring(data.indexOf('?'));
          data = data.substring(0, data.indexOf('?'));
        }

        if (data.indexOf('BITCOINCASH:') >= 0) {
          data = data.toLowerCase();
        }
        originalAddress = data.replace('bitcoincash:', '');
        var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
        data = 'bitcoincash:' + legacyAddress + paramString;
      } catch (ex) {}
    }

    $log.debug('Processing incoming data: ' + data);

    function sanitizeUri(data) {
      // Fixes when a region uses comma to separate decimals
      var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
      var match = regex.exec(data);
      if (!match || match.length === 0) {
        return data;
      }
      var value = match[0].replace(',', '.');
      var newUri = data.replace(regex, value);

      // mobile devices, uris like copay://glidera
      newUri.replace('://', ':');

      return newUri;
    }

    function getParameterByName(name, url) {
      if (!url) return;
      name = name.replace(/[\[\]]/g, "\\$&");
      var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
      if (!results) return null;
      if (!results[2]) return '';
      return decodeURIComponent(results[2].replace(/\+/g, " "));
    }

    function checkPrivateKey(privateKey) {
      try {
        new bitcore.PrivateKey(privateKey, 'livenet');
      } catch (err) {
        return false;
      }
      return true;
    }

    function goSend(addr, amount, message, coin, serviceId, serviceData) {
      $state.go('tabs.send', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.send' ? false : true
      });
      // Timeout is required to enable the "Back" button
      $timeout(function() {
        var params = {};

        if (amount) {
          params.amount = amount;
        }

        if (addr) {
          params.toAddress = addr;
          params.displayAddress = originalAddress ? originalAddress : addr;
        }

        if (coin) {
          params.coin = coin;
        }

        if (noPrefixInAddress) {
          params.noPrefixInAddress = noPrefixInAddress;
        }

        if (serviceId) {
          params.thirdParty = [];
          params.thirdParty.id = serviceId;
          params.thirdParty.data = serviceData;
          sendFlowService.pushState(params);
          $state.transitionTo('tabs.send.amount', params);
        } else {
          sendFlowService.pushState(params);
          $state.transitionTo('tabs.send.origin', params);
        }
      }, 100);
    }
    // data extensions for Payment Protocol with non-backwards-compatible request
    if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
      var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
      data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
      if (coin == 'bch') {
        payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
          if (err) {
            var message = err.toString();
            if (typeof err.data === 'string') {
              // i.e. 'This invoice is no longer accepting payments'
              message = gettextCatalog.getString(err.data);
            }
            popupService.showAlert(gettextCatalog.getString('Error'), message)
          } else {
            handlePayPro(details, coin);
          }
        });
      } else {
        payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
          if (err) {
            popupService.showAlert(gettextCatalog.getString('Error'), err);
          } else {
            handlePayPro(details, coin);
          }
        });
      }
      return true;
    }

    data = sanitizeUri(data);

    // Bitcoin  URL
    if (bitcore.URI.isValid(data)) {
        var coin = 'btc';
        var parsed = new bitcore.URI(data);

        var addr = parsed.address ? parsed.address.toString() : '';
        var message = parsed.message;

        var amount = parsed.amount ? parsed.amount : '';

        if (parsed.r) {
          payproService.getPayProDetails(parsed.r, coin, function(err, details) {
            if (err) {
              if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
              else popupService.showAlert(gettextCatalog.getString('Error'), err);
            } else handlePayPro(details, coin);
          });
        } else {
          goSend(addr, amount, message, coin, serviceId, serviceData);
        }
        return true;
    // Cash URI
    } else if (bitcoreCash.URI.isValid(data)) {
        var coin = 'bch';
        var parsed = new bitcoreCash.URI(data);

        var addr = parsed.address ? parsed.address.toString() : '';
        var message = parsed.message;

        var amount = parsed.amount ? parsed.amount : '';

        // paypro not yet supported on cash
        if (parsed.r) {
          payproService.getPayProDetails(parsed.r, coin, function(err, details) {
            if (err) {
              if (addr && amount)
                goSend(addr, amount, message, coin, serviceId, serviceData);
              else
                popupService.showAlert(gettextCatalog.getString('Error'), err);
            }
            handlePayPro(details, coin);
          });
        } else {
          goSend(addr, amount, message, coin, serviceId, serviceData);
        }
        return true;

    // Cash URI with bitcoin (btc) address version number?
    } else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
        $log.debug('Handling bitcoincash URI with legacy address');
        var coin = 'bch';
        var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));

        var oldAddr = parsed.address ? parsed.address.toString() : '';
        if (!oldAddr) return false;

        var addr = '';

        var a = bitcore.Address(oldAddr).toObject();
        addr = bitcoreCash.Address.fromObject(a).toString();

        // Translate address
        $log.debug('address transalated to:' + addr);
        popupService.showConfirm(
          gettextCatalog.getString('Bitcoin cash Payment'),
          gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
          gettextCatalog.getString('OK'),
          gettextCatalog.getString('Cancel'),
          function(ret) {
            if (!ret) return false;

            var message = parsed.message;
            var amount = parsed.amount ? parsed.amount : '';

            // paypro not yet supported on cash
            if (parsed.r) {
              payproService.getPayProDetails(parsed.r, coin, function(err, details) {
                if (err) {
                  if (addr && amount)
                    goSend(addr, amount, message, coin, serviceId, serviceData);
                  else
                    popupService.showAlert(gettextCatalog.getString('Error'), err);
                }
                handlePayPro(details, coin);
              });
            } else {
              goSend(addr, amount, message, coin, serviceId, serviceData);
            }
          }
        );
      return true;
      // Plain URL
    } else if (/^https?:\/\//.test(data)) {
      payproService.getPayProDetails(data, coin, function(err, details) {
        if (err) {
          if ($state.includes('tabs.scan')) {
            root.showMenu({
              data: data,
              type: 'url'
            });
          }
          return;
        }
        handlePayPro(details);
        return true;
      });
      // Plain Address
    } else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'bitcoinAddress'
        });
      } else {
        goToAmountPage(data);
      }
    } else if (bitcoreCash.Address.isValid(data, 'livenet')) {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'bitcoinAddress',
          coin: 'bch',
        });
      } else {
        goToAmountPage(data, 'bch');
      }
    } else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
      var code = getParameterByName('code', data);
      $ionicHistory.nextViewOptions({
        disableAnimate: true
      });
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $ionicHistory.nextViewOptions({
          disableAnimate: true
        });
        $state.transitionTo('tabs.buyandsell.glidera', {
          code: code
        });
      });
      return true;

    } else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
      var code = getParameterByName('code', data);
      $ionicHistory.nextViewOptions({
        disableAnimate: true
      });
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $ionicHistory.nextViewOptions({
          disableAnimate: true
        });
        $state.transitionTo('tabs.buyandsell.coinbase', {
          code: code
        });
      });
      return true;

      // BitPayCard Authentication
    } else if (data && data.indexOf(appConfigService.name + '://') === 0) {

      // Disable BitPay Card
      if (!appConfigService._enabledExtensions.debitcard) return false;

      var secret = getParameterByName('secret', data);
      var email = getParameterByName('email', data);
      var otp = getParameterByName('otp', data);
      var reason = getParameterByName('r', data);

      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        switch (reason) {
          default:
            case '0':
            /* For BitPay card binding */
            $state.transitionTo('tabs.bitpayCardIntro', {
              secret: secret,
              email: email,
              otp: otp
            });
          break;
        }
      });
      return true;

      // Join
    } else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $state.transitionTo('tabs.add.join', {
          url: data
        });
      });
      return true;

      // Old join
    } else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $state.transitionTo('tabs.add.join', {
          url: data
        });
      });
      return true;
    } else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
      root.showMenu({
        data: data,
        type: 'privateKey'
      });
    } else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
      $state.go('tabs.home').then(function() {
        $state.transitionTo('tabs.add.import', {
          code: data
        });
      });
      return true;

    } else {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'text'
        });
      }
    }
    return false;
  };

  function goToAmountPage(toAddress, coin) {
    $state.go('tabs.send', {}, {
      'reload': true,
      'notify': $state.current.name == 'tabs.send' ? false : true
    });
    $timeout(function() {
      $state.transitionTo('tabs.send.origin', {
        toAddress: toAddress,
        coin: coin,
        noPrefix: 1
      });
    }, 100);
  }

  function handlePayPro(payProData, coin) {

    console.log(payProData);

    var toAddr = payProData.toAddress;
    var amount = payProData.amount;
    var paymentUrl = payProData.url;
    var expires = payProData.expires;
    var time = payProData.time;

    if (coin === 'bch') {
      var displayAddr = payProData.outputs[0].address;
      toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
      amount = payProData.outputs[0].amount;
      paymentUrl = payProData.paymentUrl;
      expires = Math.floor(new Date(expires).getTime() / 1000)
      time = Math.ceil(new Date(time).getTime() / 1000)
    }

    var name = payProData.domain;

    if (payProData.memo.indexOf('eGifter') > -1) {
      name = 'eGifter'
    } else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
      name = 'BitPay';
    }

    var thirdPartyData = {
      id: 'bip70',
      amount: amount,
      caTrusted: true,
      name: name,
      domain: payProData.domain,
      expires: expires,
      memo: payProData.memo,
      network: 'livenet',
      requiredFeeRate: payProData.requiredFeeRate,
      selfSigned: 0,
      time: time,
      displayAddress: displayAddr,
      toAddress: toAddr,
      url: paymentUrl,
      verified: true
    };

    var stateParams = {
      amount: thirdPartyData.amount,
      toAddress: thirdPartyData.toAddress,
      coin: coin,
      thirdParty: JSON.stringify(thirdPartyData)
    };

    // fee
    if (thirdPartyData.requiredFeeRate) {
      stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
    }

    scannerService.pausePreview();
    $state.go('tabs.send', {}, {
      'reload': true,
      'notify': $state.current.name == 'tabs.send' ? false : true
    }).then(function() {
      $timeout(function() {
        sendFlowService.pushState(stateParams); // Need to do more here
        $state.transitionTo('tabs.send.origin', stateParams);
      });
    });
  }

  return root;
});
<commit_msg>Incoming data working with sendFlowService.<commit_after>'use strict';

angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {

  var root = {};

  root.showMenu = function(data) {
    $rootScope.$broadcast('incomingDataMenu.showMenu', data);
  };

  root.redir = function(data, serviceId, serviceData) {
    var originalAddress = null;
    var noPrefixInAddress = 0;

    if (data.toLowerCase().indexOf('bitcoin') < 0) {
      noPrefixInAddress = 1;
    }

    if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
      try {
        noPrefixInAddress = 0;

        if (data[0] == 'p' || data[0] == 'q') {
          data = 'bitcoincash:' + data;
        }
        var paramString = '';
        if (data.indexOf('?') >= 0) {
          paramString = data.substring(data.indexOf('?'));
          data = data.substring(0, data.indexOf('?'));
        }

        if (data.indexOf('BITCOINCASH:') >= 0) {
          data = data.toLowerCase();
        }
        originalAddress = data.replace('bitcoincash:', '');
        var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
        data = 'bitcoincash:' + legacyAddress + paramString;
      } catch (ex) {}
    }

    $log.debug('Processing incoming data: ' + data);

    function sanitizeUri(data) {
      // Fixes when a region uses comma to separate decimals
      var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
      var match = regex.exec(data);
      if (!match || match.length === 0) {
        return data;
      }
      var value = match[0].replace(',', '.');
      var newUri = data.replace(regex, value);

      // mobile devices, uris like copay://glidera
      newUri.replace('://', ':');

      return newUri;
    }

    function getParameterByName(name, url) {
      if (!url) return;
      name = name.replace(/[\[\]]/g, "\\$&");
      var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
      if (!results) return null;
      if (!results[2]) return '';
      return decodeURIComponent(results[2].replace(/\+/g, " "));
    }

    function checkPrivateKey(privateKey) {
      try {
        new bitcore.PrivateKey(privateKey, 'livenet');
      } catch (err) {
        return false;
      }
      return true;
    }

    function goSend(addr, amount, message, coin, serviceId, serviceData) {
      $state.go('tabs.send', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.send' ? false : true
      });
      // Timeout is required to enable the "Back" button
      $timeout(function() {
        var params = sendFlowService.getState();

        if (amount) {
          params.amount = amount;
        }

        if (addr) {
          params.toAddress = addr;
          params.displayAddress = originalAddress ? originalAddress : addr;
        }

        if (coin) {
          params.coin = coin;
        }

        if (noPrefixInAddress) {
          params.noPrefixInAddress = noPrefixInAddress;
        }

        if (serviceId) {
          params.thirdParty = [];
          params.thirdParty.id = serviceId;
          params.thirdParty.data = serviceData;
          sendFlowService.pushState(params);
          $state.transitionTo('tabs.send.amount', params);
        } else {
          sendFlowService.pushState(params);
          $state.transitionTo('tabs.send.origin', params);
        }
      }, 100);
    }
    // data extensions for Payment Protocol with non-backwards-compatible request
    if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
      var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
      data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
      if (coin == 'bch') {
        payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
          if (err) {
            var message = err.toString();
            if (typeof err.data === 'string') {
              // i.e. 'This invoice is no longer accepting payments'
              message = gettextCatalog.getString(err.data);
            }
            popupService.showAlert(gettextCatalog.getString('Error'), message)
          } else {
            handlePayPro(details, coin);
          }
        });
      } else {
        payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
          if (err) {
            popupService.showAlert(gettextCatalog.getString('Error'), err);
          } else {
            handlePayPro(details, coin);
          }
        });
      }
      return true;
    }

    data = sanitizeUri(data);

    // Bitcoin  URL
    if (bitcore.URI.isValid(data)) {
        var coin = 'btc';
        var parsed = new bitcore.URI(data);

        var addr = parsed.address ? parsed.address.toString() : '';
        var message = parsed.message;

        var amount = parsed.amount ? parsed.amount : '';

        if (parsed.r) {
          payproService.getPayProDetails(parsed.r, coin, function(err, details) {
            if (err) {
              if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
              else popupService.showAlert(gettextCatalog.getString('Error'), err);
            } else handlePayPro(details, coin);
          });
        } else {
          goSend(addr, amount, message, coin, serviceId, serviceData);
        }
        return true;
    // Cash URI
    } else if (bitcoreCash.URI.isValid(data)) {
        var coin = 'bch';
        var parsed = new bitcoreCash.URI(data);

        var addr = parsed.address ? parsed.address.toString() : '';
        var message = parsed.message;

        var amount = parsed.amount ? parsed.amount : '';

        // paypro not yet supported on cash
        if (parsed.r) {
          payproService.getPayProDetails(parsed.r, coin, function(err, details) {
            if (err) {
              if (addr && amount)
                goSend(addr, amount, message, coin, serviceId, serviceData);
              else
                popupService.showAlert(gettextCatalog.getString('Error'), err);
            }
            handlePayPro(details, coin);
          });
        } else {
          goSend(addr, amount, message, coin, serviceId, serviceData);
        }
        return true;

    // Cash URI with bitcoin (btc) address version number?
    } else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
        $log.debug('Handling bitcoincash URI with legacy address');
        var coin = 'bch';
        var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));

        var oldAddr = parsed.address ? parsed.address.toString() : '';
        if (!oldAddr) return false;

        var addr = '';

        var a = bitcore.Address(oldAddr).toObject();
        addr = bitcoreCash.Address.fromObject(a).toString();

        // Translate address
        $log.debug('address transalated to:' + addr);
        popupService.showConfirm(
          gettextCatalog.getString('Bitcoin cash Payment'),
          gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
          gettextCatalog.getString('OK'),
          gettextCatalog.getString('Cancel'),
          function(ret) {
            if (!ret) return false;

            var message = parsed.message;
            var amount = parsed.amount ? parsed.amount : '';

            // paypro not yet supported on cash
            if (parsed.r) {
              payproService.getPayProDetails(parsed.r, coin, function(err, details) {
                if (err) {
                  if (addr && amount)
                    goSend(addr, amount, message, coin, serviceId, serviceData);
                  else
                    popupService.showAlert(gettextCatalog.getString('Error'), err);
                }
                handlePayPro(details, coin);
              });
            } else {
              goSend(addr, amount, message, coin, serviceId, serviceData);
            }
          }
        );
      return true;
      // Plain URL
    } else if (/^https?:\/\//.test(data)) {
      payproService.getPayProDetails(data, coin, function(err, details) {
        if (err) {
          if ($state.includes('tabs.scan')) {
            root.showMenu({
              data: data,
              type: 'url'
            });
          }
          return;
        }
        handlePayPro(details);
        return true;
      });
      // Plain Address
    } else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'bitcoinAddress'
        });
      } else {
        goToAmountPage(data);
      }
    } else if (bitcoreCash.Address.isValid(data, 'livenet')) {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'bitcoinAddress',
          coin: 'bch',
        });
      } else {
        goToAmountPage(data, 'bch');
      }
    } else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
      var code = getParameterByName('code', data);
      $ionicHistory.nextViewOptions({
        disableAnimate: true
      });
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $ionicHistory.nextViewOptions({
          disableAnimate: true
        });
        $state.transitionTo('tabs.buyandsell.glidera', {
          code: code
        });
      });
      return true;

    } else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
      var code = getParameterByName('code', data);
      $ionicHistory.nextViewOptions({
        disableAnimate: true
      });
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $ionicHistory.nextViewOptions({
          disableAnimate: true
        });
        $state.transitionTo('tabs.buyandsell.coinbase', {
          code: code
        });
      });
      return true;

      // BitPayCard Authentication
    } else if (data && data.indexOf(appConfigService.name + '://') === 0) {

      // Disable BitPay Card
      if (!appConfigService._enabledExtensions.debitcard) return false;

      var secret = getParameterByName('secret', data);
      var email = getParameterByName('email', data);
      var otp = getParameterByName('otp', data);
      var reason = getParameterByName('r', data);

      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        switch (reason) {
          default:
            case '0':
            /* For BitPay card binding */
            $state.transitionTo('tabs.bitpayCardIntro', {
              secret: secret,
              email: email,
              otp: otp
            });
          break;
        }
      });
      return true;

      // Join
    } else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $state.transitionTo('tabs.add.join', {
          url: data
        });
      });
      return true;

      // Old join
    } else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
      $state.go('tabs.home', {}, {
        'reload': true,
        'notify': $state.current.name == 'tabs.home' ? false : true
      }).then(function() {
        $state.transitionTo('tabs.add.join', {
          url: data
        });
      });
      return true;
    } else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
      root.showMenu({
        data: data,
        type: 'privateKey'
      });
    } else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
      $state.go('tabs.home').then(function() {
        $state.transitionTo('tabs.add.import', {
          code: data
        });
      });
      return true;

    } else {
      if ($state.includes('tabs.scan')) {
        root.showMenu({
          data: data,
          type: 'text'
        });
      }
    }
    return false;
  };

  function goToAmountPage(toAddress, coin) {
    $state.go('tabs.send', {}, {
      'reload': true,
      'notify': $state.current.name == 'tabs.send' ? false : true
    });
    $timeout(function() {
      $state.transitionTo('tabs.send.origin', {
        toAddress: toAddress,
        coin: coin,
        noPrefix: 1
      });
    }, 100);
  }

  function handlePayPro(payProData, coin) {

    console.log(payProData);

    var toAddr = payProData.toAddress;
    var amount = payProData.amount;
    var paymentUrl = payProData.url;
    var expires = payProData.expires;
    var time = payProData.time;

    if (coin === 'bch') {
      var displayAddr = payProData.outputs[0].address;
      toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
      amount = payProData.outputs[0].amount;
      paymentUrl = payProData.paymentUrl;
      expires = Math.floor(new Date(expires).getTime() / 1000)
      time = Math.ceil(new Date(time).getTime() / 1000)
    }

    var name = payProData.domain;

    if (payProData.memo.indexOf('eGifter') > -1) {
      name = 'eGifter'
    } else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
      name = 'BitPay';
    }

    var thirdPartyData = {
      id: 'bip70',
      amount: amount,
      caTrusted: true,
      name: name,
      domain: payProData.domain,
      expires: expires,
      memo: payProData.memo,
      network: 'livenet',
      requiredFeeRate: payProData.requiredFeeRate,
      selfSigned: 0,
      time: time,
      displayAddress: displayAddr,
      toAddress: toAddr,
      url: paymentUrl,
      verified: true
    };

    var stateParams = {
      amount: thirdPartyData.amount,
      toAddress: thirdPartyData.toAddress,
      coin: coin,
      thirdParty: JSON.stringify(thirdPartyData)
    };

    // fee
    if (thirdPartyData.requiredFeeRate) {
      stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
    }

    scannerService.pausePreview();
    $state.go('tabs.send', {}, {
      'reload': true,
      'notify': $state.current.name == 'tabs.send' ? false : true
    }).then(function() {
      $timeout(function() {
        sendFlowService.pushState(stateParams); // Need to do more here
        $state.transitionTo('tabs.send.origin', stateParams);
      });
    });
  }

  return root;
});
<|endoftext|>


<commit_before>var PagedReadableStream = require('./stream/paged-readable-stream'),
	util = require('util');

// TODO: provide option to buffer get/set and use getMulti/setMulti

var get = function (connection, options) {
	options = options || {};
	var includeKey = options.includeKey;

	return streamy.map(function (key, callback) {
		connection.get(key, options, includeKey ? function (error, result) {
			if (result != null)
				result.key = key;
			callback(error, result);
		} : callback);
	});
};

var set = function (connection, options) {
	options = options || {};
	var includeResult = options.includeResult;

	return streamy.map(function (pair, callback) {
		connection.set(pair.key, pair.value, options, function (error, result) {
			callback(error, includeResult ? {
				key: pair.key,
				value: pair.value,
				result: result
			} : pair);
		});
	});
};

var remove = function (connection, options) {
	options = options || {};
	var includeResult = options.includeResult;

	return streamy.map(function (key, callback) {
		connection.remove(key, options, function (error, result) {
			callback(error, includeResult ? {
				key: key,
				result: result
			} : key);
		});
	});
};

var ViewQueryStream = function (viewQuery, options) {
	options = options || {};

	PagedReadableStream.call(this, options);

	this._pageSize = options.bufferSize || 32;
	this._viewQuery = viewQuery;
	this._page = 0;
};

util.inherits(ViewQueryStream, PagedReadableStream);

ViewQueryStream.prototype._pushResults = function (push, results) {
	if (!results)
		return;
	results.forEach(push);
};

ViewQueryStream.prototype._more = function (push, callback) {
	var vq = this._viewQuery.q, // where its parameters are stored
		ps = this._pageSize,
		p = this._page,
		limit = vq.limit == null ? ps : Math.max(vq.limit - p * ps, 0),
		q = p == 0 ? { limit: limit } : { limit: limit, startkey: this._lastKey,
			startkey_docid: this._lastDocId, skip: 1 };

	this._viewQuery.query(q, (function (error, results) {
		var empty = !results || results.length == 0,
			last = !empty && results[results.length - 1];

		this._lastKey = last && last.key;
		this._lastDocId = last && last.docid;
		this._page++;

		this._pushResults(push, results);
		callback(empty);

	}).bind(this));
};

// var viewQuery = connection.view(ddoc, view, query);
var view = function (viewQuery, options) {
	return new ViewQueryStream(viewQuery, options);
};

module.exports = {
	get: get,
	set: set,
	remove: remove,
	view: view
};
<commit_msg>Fix couch.<commit_after>var PagedReadableStream = require('./stream/paged-readable-stream'),
	map = require('./map'),
	util = require('util');

// TODO: provide option to buffer get/set and use getMulti/setMulti

var get = function (connection, options) {
	options = options || {};
	var includeKey = options.includeKey;

	return map(function (key, callback) {
		connection.get(key, options, includeKey ? function (error, result) {
			if (result != null)
				result.key = key;
			callback(error, result);
		} : callback);
	});
};

var set = function (connection, options) {
	options = options || {};
	var includeResult = options.includeResult;

	return map(function (pair, callback) {
		connection.set(pair.key, pair.value, options, function (error, result) {
			callback(error, includeResult ? {
				key: pair.key,
				value: pair.value,
				result: result
			} : pair);
		});
	});
};

var remove = function (connection, options) {
	options = options || {};
	var includeResult = options.includeResult;

	return map(function (key, callback) {
		connection.remove(key, options, function (error, result) {
			callback(error, includeResult ? {
				key: key,
				result: result
			} : key);
		});
	});
};

var ViewQueryStream = function (viewQuery, options) {
	options = options || {};

	PagedReadableStream.call(this, options);

	this._pageSize = options.bufferSize || 32;
	this._viewQuery = viewQuery;
	this._page = 0;
};

util.inherits(ViewQueryStream, PagedReadableStream);

ViewQueryStream.prototype._pushResults = function (push, results) {
	if (!results)
		return;
	results.forEach(push);
};

ViewQueryStream.prototype._more = function (push, callback) {
	var vq = this._viewQuery.q, // where its parameters are stored
		ps = this._pageSize,
		p = this._page,
		limit = vq.limit == null ? ps : Math.max(vq.limit - p * ps, 0),
		q = p == 0 ? { limit: limit } : { limit: limit, startkey: this._lastKey,
			startkey_docid: this._lastDocId, skip: 1 };

	this._viewQuery.query(q, (function (error, results) {
		var empty = !results || results.length == 0,
			last = !empty && results[results.length - 1];

		this._lastKey = last && last.key;
		this._lastDocId = last && last.docid;
		this._page++;

		this._pushResults(push, results);
		callback(empty);

	}).bind(this));
};

// var viewQuery = connection.view(ddoc, view, query);
var view = function (viewQuery, options) {
	return new ViewQueryStream(viewQuery, options);
};

module.exports = {
	get: get,
	set: set,
	remove: remove,
	view: view
};
<|endoftext|>


<commit_before>var plugin = require('./plugin');

var SpecsSets = function (config) {
    this._config = config;

    config.includeConfig('enb-magic-factory');
};

SpecsSets.prototype.createConfigurator = function (taskName) {
    var sets = this._config.module('enb-magic-factory');

    return plugin(sets.createHelper(taskName));
};

module.exports = function (config) {
    config.registerModule('enb-bem-specs', new SpecsSets(config));
};
<commit_msg>Fixed `enb-magic-factory` include<commit_after>var plugin = require('./plugin');

var SpecsSets = function (config) {
    var magicFilename = require.resolve('enb-magic-factory');
    var includedFilenames = config.getIncludedConfigFilenames();

    this._config = config;

    if (includedFilenames.indexOf(magicFilename) === -1) {
        config.includeConfig('enb-magic-factory');
    }
};

SpecsSets.prototype.createConfigurator = function (taskName) {
    var sets = this._config.module('enb-magic-factory');

    return plugin(sets.createHelper(taskName));
};

module.exports = function (config) {
    config.registerModule('enb-bem-specs', new SpecsSets(config));
};
<|endoftext|>


<commit_before>define(["js/data/DataSource", "js/core/Base", "js/data/Model", "underscore", "flow", "JSON"], function (DataSource, Base, Model, _, flow, JSON) {

    var rIdExtractor = /http.+\/([^/]+)$/;

    var RestDataSource = DataSource.inherit("js.data.RestDataSource", {

        initializeFormatProcessors: function () {

            var jsonProcessor = new DataSource.JsonFormatProcessor();
            jsonProcessor.regex = /json/;

            this.$formatProcessors.push(jsonProcessor);

        },

        defaults: {
            endPoint: null,
            gateway: null
        },

        initialize: function () {

            if (!this.$.endPoint) {
                console.warn("No end-point for RestDataSource defined");
            }

            if (!this.$.gateway) {
                this.$.gateway = this.$.endPoint;
            }

        },

        /***
         * creates the context as RestContext
         *
         * @param properties
         * @param parentContext
         * @return {js.core.RestDataSource.RestContext}
         */
        createContext: function (properties, parentContext) {
            return new RestDataSource.RestContext(this, properties, parentContext);
        },

        /***
         * global query parameter for each REST action
         * @action {String} Rest action [GET, PUT, DELETE, POST]
         * @return {Object}
         */
        getQueryParameter: function (action) {
            return {};
        },

        getRestPathForAlias: function (alias) {

            var typeConfig,
                i;

            // search via alias
            for (i = 0; i < this.$configuredTypes.length; i++) {
                typeConfig = this.$configuredTypes[i];
                if (typeConfig.$.alias == alias) {
                    return typeConfig.$.path;
                }
            }

            return null;
        },

        getPathComponentsForModel: function (model) {

            if (model) {
                var path = this.getRestPathForAlias(model.$alias);

                if (path) {

                    var ret = [path];

                    if (model._status() === Model.STATE.CREATED) {
                        ret.push(model.$.id);
                    }

                    return ret;
                }
            }

            return null;
        },
        _buildUriForModel: function(model){
            // map model to url
            var modelPathComponents = this.getPathComponentsForModel(model);

            if (!modelPathComponents) {
                callback("path for model unknown", null, options);
                return;
            }

            // build uri
            var uri = [this.$.gateway];
            uri = uri.concat(model.$context.getPathComponents());
            uri = uri.concat(modelPathComponents);

            if (this.$.suffix) {
                uri[uri.length - 1] = uri[uri.length - 1] + "." + this.$.suffix;
            }

            return uri.join("/");
        },
        /**
         *
         * @param model
         * @param options
         * @param callback function(err, model, options)
         */
        loadModel: function (model, options, callback) {
            var self = this;

            // create url
            var url = this._buildUriForModel(model);

            // get queryParameter
            var params = _.defaults(model.$context.getQueryParameter(),
                this.getQueryParameter(RestDataSource.METHOD.GET));

            flow()
                .seq("xhr", function(cb) {
                    // send request
                    self.$stage.$applicationContext.ajax(url, {
                        type: RestDataSource.METHOD.GET,
                        queryParameter: params
                    }, cb);
                })
                .seq(function(cb) {
                    var xhr = this.vars.xhr;

                    if (xhr.status === 200 || xhr.status === 304) {
                        // find processor that matches the content-type
                        var contentType = xhr.getResponseHeader("Content-Type");
                        var formatProcessor = self.getFormatProcessorForContentType(contentType);

                        if (!formatProcessor) {
                            callback("No formatProcessor for content type '" + contentType + "' found", null, options);
                            return;
                        }

                        // deserialize data with format processor
                        var data = formatProcessor.deserialize(xhr.responses.text);

                        // parse data inside model
                        data = model.parse(data, DataSource.ACTION.LOAD, options);

                        // set data
                        model.set(data);

                        cb(null, model, options);

                    } else {
                        // TODO: better error handling
                        cb("Got status code " + xhr.status + " for '" + url + "'", xhr, null, options);
                    }
                })
                .exec(function(err) {
                    if (callback) {
                        callback(err, model, options);
                    }
                });

        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleSaveError: function (request, xhr, callback) {
            if (callback) {
                callback({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleCreationSuccess: function (request, xhr, callback) {

            var model = request.model;

            var cb = function(err) {
                if (callback) {
                    callback(err, model, request.options);
                }
            };

            try { // get location header
                var location = xhr.getResponseHeader('Location');

                if (location) {
                    // extract id
                    var id = this.extractIdFromLocation(location, request);

                    if (id || id === 0) {
                        model.set('id', id);
                        // TODO: ask processor if i should call save again to put content
                        cb(null);
                    } else {
                        cb("Id couldn't be extracted");
                    }

                } else {
                    cb('Location header not found');
                }

            } catch (e) {
                cb(e || true);
            }
        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleUpdateSuccess: function (request, xhr, callback) {

            var model = request.model;

            if (callback) {
                callback(null, model, request.options);
            }
        },

        extractIdFromLocation: function(location, request) {
            var param = rIdExtractor.exec(location);

            if (param) {
                return param[1];
            }

            return null;
        },

        saveModel: function (model, options, callback) {

            var action = DataSource.ACTION.UPDATE,
                method = RestDataSource.METHOD.PUT;

            if (model._status() === Model.STATE.NEW) {
                action = DataSource.ACTION.CREATE;
                method = RestDataSource.METHOD.POST;
            }

            var processor = this.getProcessorForModel(model, options);
            var formatProcessor = this.getFormatProcessor(action);
            var self = this;

            // call save of the processor to save submodels
            flow()
                .seq(function(cb) {
                    processor.saveSubModels(model, options, cb)
                })
                .seq(function(cb) {
                    // create url
                    var url = self._buildUriForModel(model);

                    // get queryParameter
                    var params = _.defaults(model.$context.getQueryParameter(),
                        self.getQueryParameter(method));

                    // TODO: create hook, which can modify url and queryParameter

                    // compose data in model and in processor
                    var data = model.compose(action, options);

                    // format payload
                    var payload = formatProcessor.serialize(data);

                    // send request
                    self.$stage.$applicationContext.ajax(url, {
                        type: method,
                        queryParameter: params,
                        data: payload,
                        contentType: formatProcessor.getContentType()
                    }, function (err, xhr) {

                        var request = {
                            url: url,
                            queryParameter: params,
                            model: model,
                            options: options
                        };

                        if (!err && action === DataSource.ACTION.CREATE && xhr.status === 201) {
                            self.handleCreationSuccess(request, xhr, cb)
                        } else if (!err && action === DataSource.ACTION.UPDATE && xhr.status === 200) {
                            self.handleUpdateSuccess(request, xhr, cb);
                        }
                        else {
                            // error handling
                            self.handleSaveError(request, xhr, cb);
                        }

                    });
                })
                .exec(function(err){
                    callback(err, model, options);
                })

        },

        loadCollectionPage: function (page, options, callback) {

            var rootCollection = page.getRootCollection();
            var modelPathComponents = rootCollection.$options.path ?
                rootCollection.$options.path : this.getRestPathForAlias(rootCollection.$alias);

            if (!modelPathComponents) {
                callback("path for model unknown", null, options);
                return;
            }

            // build uri
            var uri = [this.$.gateway];
            uri = uri.concat(rootCollection.$context.getPathComponents());
            uri = uri.concat(modelPathComponents);

            if (this.$.suffix) {
                uri[uri.length - 1] = uri[uri.length - 1] + "." + this.$.suffix;
            }

            var params = {};

            _.defaults(params, (options || {}).params);

            if (page.$limit) {
                params.limit = page.$limit;
            }

            if (page.$offset) {
                params.offset = page.$offset;
            }

            // get queryParameter
            params = _.defaults(params, page.$collection.getQueryParameters(RestDataSource.METHOD.GET), rootCollection.$context.getQueryParameter(), this.getQueryParameter(RestDataSource.METHOD.GET));

            // create url
            var url = uri.join("/");

            var self = this;

            // send request
            this.$stage.$applicationContext.ajax(url, {
                type: RestDataSource.METHOD.GET,
                queryParameter: params
            }, function (err, xhr) {
                if (!err && (xhr.status == 200 || xhr.status == 304)) {
                    // find processor that matches the content-type
                    var contentType = xhr.getResponseHeader("Content-Type");
                    var processor = self.getFormatProcessorForContentType(contentType);

                    if (!processor) {
                        callback("No processor for content type '" + contentType + "' found", null, options);
                        return;
                    }

                    try {
                        // deserialize data with processor
                        var payload = processor.deserialize(xhr.responses.text);

                        // extract meta data
                        var metaData = self.extractListMetaData(page, payload, options);

                        if (metaData && metaData.count) {
                            // set itemsCount in collection for page calculation
                            page.$collection.set('$itemsCount', metaData.count);
                        }

                        // extract data from list result
                        var data = self.extractListData(page, payload, options);

                        data = page.parse(data);
                        page.add(data);

                        callback(null, page, options)

                    } catch (e) {
                        self.log(e, 'error');
                        callback(e, page, options);
                    }

                } else {
                    // TODO: better error handling
                    err = err || "wrong status code";
                    callback(err, page, options);
                }
            });
        },

        getFormatProcessorForContentType: function(contentType) {
            for (var i = 0; i < this.$formatProcessors.length; i++) {
                var processorEntry = this.$formatProcessors[i];
                if (processorEntry.regex.test(contentType)) {
                    return processorEntry;
                }
            }

            return null;
        }

    });

    RestDataSource.RestContext = DataSource.Context.inherit("js.data.RestDataSource.Context", {

        createCollection: function (factory, options, type) {
            options = options || {};
            _.defaults(options, {
                pageSize: this.$datasource.$.collectionPageSize || 100
            });

            return this.callBase(factory, options, type);
        },

        getQueryParameter: function () {
            return {};
        }
    });

    RestDataSource.METHOD = {
        GET: 'GET',
        POST: 'POST',
        PUT: 'PUT',
        DELETE: 'DELETE'
    };

    // TODO: implement XmlProcessor
    RestDataSource.XmlFormatProcessor = DataSource.FormatProcessor.inherit("js.data.RestDataSource.XmlFormatProcessor", {
        serialize: function (data) {
            throw "not implemented";
        },
        deserialize: function (responses) {
            throw "not implemented";
        }
    });

    return RestDataSource;
});<commit_msg>added Context handling by configuration in RestDataSource<commit_after>define(["js/data/DataSource", "js/core/Base", "js/data/Model", "underscore", "flow", "JSON"], function (DataSource, Base, Model, _, flow, JSON) {

    var rIdExtractor = /http.+\/([^/]+)$/;

    var RestDataSource = DataSource.inherit("js.data.RestDataSource", {

        defaults: {
            endPoint: null,
            gateway: null
        },

        ctor: function(){
            this.callBase();
        },

        initialize: function () {

            if (!this.$.endPoint) {
                console.warn("No end-point for RestDataSource defined");
            }

            if (!this.$.gateway) {
                this.$.gateway = this.$.endPoint;
            }

        },

        initializeFormatProcessors: function () {

            var jsonProcessor = new DataSource.JsonFormatProcessor();
            jsonProcessor.regex = /json/;

            this.$formatProcessors.push(jsonProcessor);

        },

        /***
         * creates the context as RestContext
         *
         * @param properties
         * @param parentContext
         * @return {js.core.RestDataSource.RestContext}
         */
        createContext: function (properties, parentContext) {
            return new RestDataSource.RestContext(this, properties, parentContext);
        },

        /***
         * global query parameter for each REST action
         * @action {String} Rest action [GET, PUT, DELETE, POST]
         * @return {Object}
         */
        getQueryParameter: function (action) {
            return {};
        },

        getPathComponentsForModel: function (model) {

            if (model) {
                var config = this.$dataSourceConfiguration.getConfigurationForModelClassName(model.constructor.name);
                if (config) {

                    var path = config.$.path;

                    if (path) {
                        var ret = [path];

                        if (!model.isNew()) {
                            ret.push(model.$.id);
                        }

                        return ret;
                    }
                }
            }

            return null;
        },

        _buildUriForModel: function(model){
            // map model to url
            var modelPathComponents = this.getPathComponentsForModel(model);

            if (!modelPathComponents) {
                callback("path for model unknown", null, options);
                return;
            }

            // build uri
            var uri = [this.$.gateway];
            uri = uri.concat(model.$context.getPathComponents());
            uri = uri.concat(modelPathComponents);

            if (this.$.suffix) {
                uri[uri.length - 1] = uri[uri.length - 1] + "." + this.$.suffix;
            }

            return uri.join("/");
        },
        /**
         *
         * @param model
         * @param options
         * @param callback function(err, model, options)
         */
        loadModel: function (model, options, callback) {
            var self = this;

            // create url
            var url = this._buildUriForModel(model);

            // get queryParameter
            var params = _.defaults(model.$context.getQueryParameter(),
                this.getQueryParameter(RestDataSource.METHOD.GET));

            flow()
                .seq("xhr", function(cb) {
                    // send request
                    self.$stage.$applicationContext.ajax(url, {
                        type: RestDataSource.METHOD.GET,
                        queryParameter: params
                    }, cb);
                })
                .seq(function(cb) {
                    var xhr = this.vars.xhr;

                    if (xhr.status === 200 || xhr.status === 304) {
                        // find processor that matches the content-type
                        var contentType = xhr.getResponseHeader("Content-Type");
                        var formatProcessor = self.getFormatProcessorForContentType(contentType);

                        if (!formatProcessor) {
                            callback("No formatProcessor for content type '" + contentType + "' found", null, options);
                            return;
                        }

                        // deserialize data with format processor
                        var data = formatProcessor.deserialize(xhr.responses.text);

                        // parse data inside model
                        data = model.parse(data, DataSource.ACTION.LOAD, options);

                        // set data
                        model.set(data);

                        cb(null, model, options);

                    } else {
                        // TODO: better error handling
                        cb("Got status code " + xhr.status + " for '" + url + "'", xhr, null, options);
                    }
                })
                .exec(function(err) {
                    if (callback) {
                        callback(err, model, options);
                    }
                });

        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleSaveError: function (request, xhr, callback) {
            if (callback) {
                callback({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleCreationSuccess: function (request, xhr, callback) {

            var model = request.model;

            var cb = function(err) {
                if (callback) {
                    callback(err, model, request.options);
                }
            };

            try { // get location header
                var location = xhr.getResponseHeader('Location');

                if (location) {
                    // extract id
                    var id = this.extractIdFromLocation(location, request);

                    if (id || id === 0) {
                        model.set('id', id);
                        model.$context.addEntityToCache(model);
                        // TODO: ask processor if i should call save again to put content
                        cb(null);
                    } else {
                        cb("Id couldn't be extracted");
                    }

                } else {
                    cb('Location header not found');
                }

            } catch (e) {
                cb(e || true);
            }
        },

        /***
         *
         * @param request.url
         * @param request.queryParameter
         * @param request.model
         * @param request.options
         * @param xhr
         * @param callback
         */
        handleUpdateSuccess: function (request, xhr, callback) {

            var model = request.model;

            if (callback) {
                callback(null, model, request.options);
            }
        },

        extractIdFromLocation: function(location, request) {
            var param = rIdExtractor.exec(location);

            if (param) {
                return param[1];
            }

            return null;
        },

        saveModel: function (model, options, callback) {

            var action = DataSource.ACTION.UPDATE,
                method = RestDataSource.METHOD.PUT;

            if (model._status() === Model.STATE.NEW) {
                action = DataSource.ACTION.CREATE;
                method = RestDataSource.METHOD.POST;
            }

            var processor = this.getProcessorForModel(model, options);
            var formatProcessor = this.getFormatProcessor(action);
            var self = this;

            // call save of the processor to save submodels
            flow()
//                .seq(function(cb) {
//                    processor.saveSubModels(model, options, cb)
//                })
                .seq(function(cb) {
                    // create url
                    var url = self._buildUriForModel(model);

                    // get queryParameter
                    var params = _.defaults(model.$context.getQueryParameter(),
                        self.getQueryParameter(method));

                    // TODO: create hook, which can modify url and queryParameter

                    // compose data in model and in processor
                    var data = model.compose(action, options);

                    // format payload
                    var payload = formatProcessor.serialize(data);

                    // send request
                    self.$stage.$applicationContext.ajax(url, {
                        type: method,
                        queryParameter: params,
                        data: payload,
                        contentType: formatProcessor.getContentType()
                    }, function (err, xhr) {

                        var request = {
                            url: url,
                            queryParameter: params,
                            model: model,
                            options: options
                        };

                        if (!err && action === DataSource.ACTION.CREATE && xhr.status === 201) {
                            self.handleCreationSuccess(request, xhr, cb)
                        } else if (!err && action === DataSource.ACTION.UPDATE && xhr.status === 200) {
                            self.handleUpdateSuccess(request, xhr, cb);
                        }
                        else {
                            // error handling
                            self.handleSaveError(request, xhr, cb);
                        }

                    });
                })
                .exec(function(err){
                    callback && callback(err, model, options);
                })

        },

        loadCollectionPage: function (page, options, callback) {

            var rootCollection = page.getRootCollection();
            var config =  this.$dataSourceConfiguration.getConfigurationForModelClassName(rootCollection.$modelFactory.prototype.constructor.name);

            if(!config){
                throw new Error("Couldnt find path config for " + rootCollection.$modelFactory.prototype.constructor.name);
            }
            var modelPathComponents = [config.$.path];

            if (!modelPathComponents) {
                callback("path for model unknown", null, options);
                return;
            }

            // build uri
            var uri = [this.$.gateway];
            uri = uri.concat(rootCollection.$context.getPathComponents());
            uri = uri.concat(modelPathComponents);

            if (this.$.suffix) {
                uri[uri.length - 1] = uri[uri.length - 1] + "." + this.$.suffix;
            }

            var params = {};

            _.defaults(params, (options || {}).params);

            if (page.$limit) {
                params.limit = page.$limit;
            }

            if (page.$offset) {
                params.offset = page.$offset;
            }

            // get queryParameter
            params = _.defaults(params, page.$collection.getQueryParameters(RestDataSource.METHOD.GET), rootCollection.$context.getQueryParameter(), this.getQueryParameter(RestDataSource.METHOD.GET));

            // create url
            var url = uri.join("/");

            var self = this;

            // send request
            this.$stage.$applicationContext.ajax(url, {
                type: RestDataSource.METHOD.GET,
                queryParameter: params
            }, function (err, xhr) {
                if (!err && (xhr.status == 200 || xhr.status == 304)) {
                    // find processor that matches the content-type
                    var contentType = xhr.getResponseHeader("Content-Type");
                    var processor = self.getFormatProcessorForContentType(contentType);

                    if (!processor) {
                        callback("No processor for content type '" + contentType + "' found", null, options);
                        return;
                    }

                    try {
                        // deserialize data with processor
                        var payload = processor.deserialize(xhr.responses.text);

                        // extract meta data
                        var metaData = self.extractListMetaData(page, payload, options);

                        if (metaData && metaData.count) {
                            // set itemsCount in collection for page calculation
                            page.$collection.set('$itemsCount', metaData.count);
                        }

                        // extract data from list result
                        var data = self.extractListData(page, payload, options);

                        data = page.parse(data);
                        page.add(data);

                        callback(null, page, options)

                    } catch (e) {
                        self.log(e, 'error');
                        callback(e, page, options);
                    }

                } else {
                    // TODO: better error handling
                    err = err || "wrong status code";
                    callback(err, page, options);
                }
            });
        },

        getFormatProcessorForContentType: function(contentType) {
            for (var i = 0; i < this.$formatProcessors.length; i++) {
                var processorEntry = this.$formatProcessors[i];
                if (processorEntry.regex.test(contentType)) {
                    return processorEntry;
                }
            }

            return null;
        }

    });

    RestDataSource.RestContext = DataSource.Context.inherit("js.data.RestDataSource.Context", {
        ctor: function(dataSource, properties, parentContext){
            this.$contextModel = properties;
            this.callBase(dataSource, properties, parentContext);
        },

        createContextCacheId: function (contextModel) {
            return contextModel.constructor.name + "_" + contextModel.$.id;
        },

        getPathComponents: function(){

            if (!this.$parent) {
                // rootContext
                return [];
            }

            if (!this.$contextModel) {
                throw new Error("ContextModel missing for non-root-Context");
            }

            var configuration = this.$datasource.getConfigurationForModelClassName(this.$contextModel.constructor.name);
            return [configuration.$.path, this.$contextModel.$.id];
        },

        createCollection: function (factory, options, type) {
            options = options || {};
            _.defaults(options, {
                pageSize: this.$datasource.$.collectionPageSize || 100
            });

            return this.callBase(factory, options, type);
        },

        getQueryParameter: function () {
            return {};
        }
    });

    RestDataSource.METHOD = {
        GET: 'GET',
        POST: 'POST',
        PUT: 'PUT',
        DELETE: 'DELETE'
    };

    // TODO: implement XmlProcessor
    RestDataSource.XmlFormatProcessor = DataSource.FormatProcessor.inherit("js.data.RestDataSource.XmlFormatProcessor", {
        serialize: function (data) {
            throw "not implemented";
        },
        deserialize: function (responses) {
            throw "not implemented";
        }
    });

    return RestDataSource;
});<|endoftext|>


<commit_before>
module.exports = exports = build

/**
 * Module dependencies.
 */

var fs = require('fs')
  , path = require('path')
  , glob = require('glob')
  , which = require('which')
  , asyncEmit = require('./util/asyncEmit')
  , createHook = require('./util/hook')
  , win = process.platform == 'win32'

exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'

function build (gyp, argv, callback) {

  gyp.verbose('build args', argv)
  var command = win ? 'msbuild' : 'make'
    , buildDir = path.resolve('build')
    , configPath = path.resolve(buildDir, 'config.gypi')
    , config
    , emitter

  createHook('gyp-build.js', function (err, _e) {
    if (err) return callback(err)
    emitter = _e
    loadConfigGypi()
  })

  /**
   * Load the "config.gypi" file that was generated during "configure".
   */

  function loadConfigGypi () {
    fs.readFile(configPath, 'utf8', function (err, data) {
      if (err) {
        if (err.code == 'ENOENT') {
          callback(new Error('You must run `node-gyp configure` first!'))
        } else {
          callback(err)
        }
        return
      }
      config = JSON.parse(data.replace(/\#.+\n/, ''))
      if (win) {
        findSolutionFile()
      } else {
        doWhich()
      }
    })
  }

  /**
   * On Windows, find the first build/*.sln file.
   */

  function findSolutionFile () {
    glob('build/*.sln', function (err, files) {
      if (err) return callback(err)
      if (files.length === 0) {
        return callback(new Error('Could not find *.sln file. Did you run "configure"?'))
      }
      guessedSolution = files[0]
      gyp.verbose('found first Solution file', guessedSolution)
      doWhich()
    })
  }

  function doWhich () {
    // First make sure we have the build command in the PATH
    which(command, function (err, execPath) {
      if (err) {
        if (win && /not found/.test(err.message)) {
          // On windows and no 'msbuild' found. Let's guess where it is
          guessMsbuild()
        } else {
          // Some other error or 'make' not found on Unix, report that to the user
          callback(err)
        }
        return
      }
      gyp.verbose('`which` succeeded for `' + command + '`', execPath)
      build()
    })
  }

  /**
   * Guess the location of the "msbuild.exe" file on Windows.
   */

  function guessMsbuild () {
    gyp.verbose('could not find "msbuild.exe". guessing location')
    // This is basically just hard-coded. If this causes problems
    // then we'll think of something more clever.
    var windir = process.env.WINDIR || process.env.SYSTEMROOT || 'C:\\WINDOWS'
      , frameworkDir = path.resolve(windir, 'Microsoft.NET', 'Framework')
      , versionDir = path.resolve(frameworkDir, 'v4.0.30319') // This is probably the most brittle part...
      , msbuild = path.resolve(versionDir, 'msbuild.exe')
    fs.stat(msbuild, function (err, stat) {
      if (err) {
        if (err.code == 'ENOENT') {
          callback(new Error('Can\'t find "msbuild.exe". Do you have Microsoft Visual Studio C++ 2010 installed?'))
        } else {
          callback(err)
        }
        return
      }
      command = msbuild
      build()
    })
  }

  /**
   * Actually spawn the process and compile the module.
   */

  function build () {
    var buildType = config.target_defaults.default_configuration
      , platform = config.variables.target_arch == 'x64' ? 'x64' : 'Win32'

    if (gyp.opts.debug) {
      buildType = 'Debug'
    }

    // Enable Verbose build
    if (!win && gyp.opts.verbose) {
      argv.push('V=1')
    }
    if (win && !gyp.opts.verbose) {
      argv.push('/clp:Verbosity=minimal')
    }

    // Turn off the Microsoft logo on Windows
    if (win) {
      argv.push('/nologo')
    }

    // Specify the build type, Release by default
    if (win) {
      argv.push('/p:Configuration=' + buildType + ';Platform=' + platform)
    } else {
      argv.push('BUILDTYPE=' + buildType)
      // Invoke the Makefile in the 'build' dir.
      argv.push('-C')
      argv.push('build')
    }

    if (win) {
      // did the user specify their own .sln file?
      var hasSln = argv.some(function (arg) {
        return path.extname(arg) == '.sln'
      })
      if (!hasSln) {
        argv.unshift(gyp.opts.solution || guessedSolution)
      }
    }

    asyncEmit(emitter, 'before', function (err) {
      if (err) return callback(err)
      var proc = gyp.spawn(command, argv)
      proc.on('exit', onExit)
    })
  }

  /**
   * Invoked after the make/msbuild command exits.
   */

  function onExit (code, signal) {
    asyncEmit(emitter, 'after', function (err) {
      if (err) return callback(err)
      if (code !== 0) {
        return callback(new Error('`' + command + '` failed with exit code: ' + code))
      }
      if (signal) {
        return callback(new Error('`' + command + '` got signal: ' + signal))
      }
      callback()
    })
  }

}
<commit_msg>build: comments<commit_after>
module.exports = exports = build

/**
 * Module dependencies.
 */

var fs = require('fs')
  , path = require('path')
  , glob = require('glob')
  , which = require('which')
  , asyncEmit = require('./util/asyncEmit')
  , createHook = require('./util/hook')
  , win = process.platform == 'win32'

exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'

function build (gyp, argv, callback) {

  gyp.verbose('build args', argv)
  var command = win ? 'msbuild' : 'make'
    , buildDir = path.resolve('build')
    , configPath = path.resolve(buildDir, 'config.gypi')
    , config
    , emitter

  createHook('gyp-build.js', function (err, _e) {
    if (err) return callback(err)
    emitter = _e
    loadConfigGypi()
  })

  /**
   * Load the "config.gypi" file that was generated during "configure".
   */

  function loadConfigGypi () {
    fs.readFile(configPath, 'utf8', function (err, data) {
      if (err) {
        if (err.code == 'ENOENT') {
          callback(new Error('You must run `node-gyp configure` first!'))
        } else {
          callback(err)
        }
        return
      }
      config = JSON.parse(data.replace(/\#.+\n/, ''))
      if (win) {
        findSolutionFile()
      } else {
        doWhich()
      }
    })
  }

  /**
   * On Windows, find the first build/*.sln file.
   */

  function findSolutionFile () {
    glob('build/*.sln', function (err, files) {
      if (err) return callback(err)
      if (files.length === 0) {
        return callback(new Error('Could not find *.sln file. Did you run "configure"?'))
      }
      guessedSolution = files[0]
      gyp.verbose('found first Solution file', guessedSolution)
      doWhich()
    })
  }

  /**
   * Uses node-which to locate the msbuild / make executable.
   */

  function doWhich () {
    // First make sure we have the build command in the PATH
    which(command, function (err, execPath) {
      if (err) {
        if (win && /not found/.test(err.message)) {
          // On windows and no 'msbuild' found. Let's guess where it is
          guessMsbuild()
        } else {
          // Some other error or 'make' not found on Unix, report that to the user
          callback(err)
        }
        return
      }
      gyp.verbose('`which` succeeded for `' + command + '`', execPath)
      build()
    })
  }

  /**
   * Guess the location of the "msbuild.exe" file on Windows.
   */

  function guessMsbuild () {
    gyp.verbose('could not find "msbuild.exe". guessing location')
    // This is basically just hard-coded. If this causes problems
    // then we'll think of something more clever.
    var windir = process.env.WINDIR || process.env.SYSTEMROOT || 'C:\\WINDOWS'
      , frameworkDir = path.resolve(windir, 'Microsoft.NET', 'Framework')
      , versionDir = path.resolve(frameworkDir, 'v4.0.30319') // This is probably the most brittle part...
      , msbuild = path.resolve(versionDir, 'msbuild.exe')
    fs.stat(msbuild, function (err, stat) {
      if (err) {
        if (err.code == 'ENOENT') {
          callback(new Error('Can\'t find "msbuild.exe". Do you have Microsoft Visual Studio C++ 2010 installed?'))
        } else {
          callback(err)
        }
        return
      }
      command = msbuild
      build()
    })
  }

  /**
   * Actually spawn the process and compile the module.
   */

  function build () {
    var buildType = config.target_defaults.default_configuration
      , platform = config.variables.target_arch == 'x64' ? 'x64' : 'Win32'

    if (gyp.opts.debug) {
      buildType = 'Debug'
    }

    // Enable Verbose build
    if (!win && gyp.opts.verbose) {
      argv.push('V=1')
    }
    if (win && !gyp.opts.verbose) {
      argv.push('/clp:Verbosity=minimal')
    }

    // Turn off the Microsoft logo on Windows
    if (win) {
      argv.push('/nologo')
    }

    // Specify the build type, Release by default
    if (win) {
      argv.push('/p:Configuration=' + buildType + ';Platform=' + platform)
    } else {
      argv.push('BUILDTYPE=' + buildType)
      // Invoke the Makefile in the 'build' dir.
      argv.push('-C')
      argv.push('build')
    }

    if (win) {
      // did the user specify their own .sln file?
      var hasSln = argv.some(function (arg) {
        return path.extname(arg) == '.sln'
      })
      if (!hasSln) {
        argv.unshift(gyp.opts.solution || guessedSolution)
      }
    }

    asyncEmit(emitter, 'before', function (err) {
      if (err) return callback(err)
      var proc = gyp.spawn(command, argv)
      proc.on('exit', onExit)
    })
  }

  /**
   * Invoked after the make/msbuild command exits.
   */

  function onExit (code, signal) {
    asyncEmit(emitter, 'after', function (err) {
      if (err) return callback(err)
      if (code !== 0) {
        return callback(new Error('`' + command + '` failed with exit code: ' + code))
      }
      if (signal) {
        return callback(new Error('`' + command + '` got signal: ' + signal))
      }
      callback()
    })
  }

}
<|endoftext|>


<commit_before>if(window.jQuery) {
  (function ( $ ) {
    $.fn.snabbt = function(arg1, arg2) {

      return snabbt(this.get(), arg1, arg2);
    };
  }( jQuery ));
}
<commit_msg>Fix jquery bug after umd fixes<commit_after>if(window.jQuery) {
  (function ( $ ) {
    $.fn.snabbt = function(arg1, arg2) {
      return snabbtjs.snabbt(this.get(), arg1, arg2);
    };
  }( jQuery ));
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import type { AddressResult, LibSymbolicationRequest } from './symbol-store';
import { SymbolsNotFoundError } from './errors';

// This file handles requesting symbolication information from the Mozilla
// symbol server using the API described on
// https://tecken.readthedocs.io/en/latest/symbolication.html .
// Specifically, it uses version 5 of the API, which was implemented in
// https://bugzilla.mozilla.org/show_bug.cgi?id=1377479 .

type APIFoundModulesV5 = {
  // For every requested library in the memoryMap, this object contains a string
  // key of the form `${debugName}/${breakpadId}`. The value is null if no
  // address with the module index was requested, and otherwise a boolean that
  // says whether the symbol server had symbols for this library.
  [string]: null | boolean,
};

type APIInlineFrameInfoV5 = {
  // The name of the function this inline frame was in, if known.
  function?: string,
  // The path of the file that contains the function this inline frame was in, optional.
  file?: string,
  // The line number that contains the source code for this inline frame that
  // contributed to the instruction at the looked-up address, optional.
  // e.g. 543
  line?: number,
};

type APIFrameInfoV5 = {
  // The hex version of the address that we requested (e.g. "0x5ab").
  module_offset: string,
  // The debugName of the library that this frame was in.
  module: string,
  // The index of this APIFrameInfo in its enclosing APIStack.
  frame: number,
  // The name of the function this frame was in, if symbols were found.
  function?: string,
  // The hex offset between the requested address and the start of the function,
  // e.g. "0x3c".
  function_offset?: string,
  // The path of the file that contains the function this frame was in, optional.
  // As of June 2021, this is only supported on the staging symbolication server
  // ("Eliot") but not on the implementation that's currently in production ("Tecken").
  // e.g. "hg:hg.mozilla.org/mozilla-central:js/src/vm/Interpreter.cpp:24938c537a55f9db3913072d33b178b210e7d6b5"
  file?: string,
  // The line number that contains the source code that generated the instructions at the address, optional.
  // (Same support as file.)
  // e.g. 543
  line?: number,
  // Information about functions that were inlined at this address.
  // Ordered from inside to outside.
  // As of November 2021, this is only supported by profiler-symbol-server.
  // Adding this functionality to the Mozilla symbol server is tracked in
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1636194
  inlines?: APIInlineFrameInfoV5[],
};

type APIStackV5 = APIFrameInfoV5[];

type APIJobResultV5 = {
  found_modules: APIFoundModulesV5,
  stacks: APIStackV5[],
};

type APIResultV5 = {
  results: APIJobResultV5[],
};

// Make sure that the JSON blob we receive from the API conforms to our flow
// type definition.
function _ensureIsAPIResultV5(result: any): APIResultV5 {
  if (!(result instanceof Object) || !('results' in result)) {
    throw new Error('Expected an object with property `results`');
  }
  const results = result.results;
  if (!Array.isArray(results)) {
    throw new Error('Expected `results` to be an array');
  }
  for (const jobResult of results) {
    if (
      !(jobResult instanceof Object) ||
      !('found_modules' in jobResult) ||
      !('stacks' in jobResult)
    ) {
      throw new Error(
        'Expected jobResult to have `found_modules` and `stacks` properties'
      );
    }
    const found_modules = jobResult.found_modules;
    if (!(found_modules instanceof Object)) {
      throw new Error('Expected `found_modules` to be an object');
    }
    const stacks = jobResult.stacks;
    if (!Array.isArray(stacks)) {
      throw new Error('Expected `stacks` to be an array');
    }
    for (const stack of stacks) {
      if (!Array.isArray(stack)) {
        throw new Error('Expected `stack` to be an array');
      }
      for (const frameInfo of stack) {
        if (!(frameInfo instanceof Object)) {
          throw new Error('Expected `frameInfo` to be an object');
        }
        if (
          !('module_offset' in frameInfo) ||
          !('module' in frameInfo) ||
          !('frame' in frameInfo)
        ) {
          throw new Error(
            'Expected frameInfo to have `module_offset`, `module` and `frame` properties'
          );
        }
        if ('file' in frameInfo && typeof frameInfo.file !== 'string') {
          throw new Error('Expected frameInfo.file to be a string, if present');
        }
        if ('line' in frameInfo && typeof frameInfo.line !== 'number') {
          throw new Error('Expected frameInfo.line to be a number, if present');
        }
        if ('inlines' in frameInfo) {
          const inlines = frameInfo.inlines;
          if (!Array.isArray(inlines)) {
            throw new Error('Expected `inlines` to be an array');
          }
          for (const inlineFrame of inlines) {
            if (!(inlineFrame instanceof Object)) {
              throw new Error('Expected `inlineFrame` to be an object');
            }
          }
        }
      }
    }
  }
  return result;
}

function getV5ResultForLibRequest(
  request: LibSymbolicationRequest,
  addressArray: number[],
  json: APIJobResultV5
): Map<number, AddressResult> {
  const { lib } = request;
  const { debugName, breakpadId } = lib;

  if (!json.found_modules[`${debugName}/${breakpadId}`]) {
    throw new SymbolsNotFoundError(
      `The symbol server does not have symbols for ${debugName}/${breakpadId}.`,
      lib
    );
  }

  const addressInfo = json.stacks[0];
  if (addressInfo.length !== addressArray.length) {
    throw new SymbolsNotFoundError(
      'The result from the symbol server has an unexpected length.',
      lib
    );
  }

  const results = new Map();
  for (let i = 0; i < addressInfo.length; i++) {
    const address = addressArray[i];
    const info = addressInfo[i];
    let addressResult;
    if (info.function !== undefined && info.function_offset !== undefined) {
      const name = info.function;
      const functionOffset = parseInt(info.function_offset.substr(2), 16);

      // Some symbolication API implementations return an inline stack for each
      // address. These are function calls that were inlined into the outer
      // function by the compiler.
      let inlines;
      if (info.inlines !== undefined) {
        const inlineCount = info.inlines.length;
        inlines = info.inlines.map(({ function: name, file, line }, i) => {
          const depth = inlineCount - i;
          return {
            name:
              name ??
              `<unknown at ${info.module_offset} at inline depth ${depth}>`,
            file,
            line,
          };
        });
      }

      addressResult = {
        name,
        symbolAddress: address - functionOffset,
        file: info.file,
        line: info.line,
        inlines,
      };
    } else {
      // This can happen if the address is between functions, or before the first
      // or after the last function.
      addressResult = {
        name: `<unknown at ${info.module_offset}>`,
        symbolAddress: address,
      };
    }
    results.set(address, addressResult);
  }
  return results;
}

// Request symbols for the given addresses and libraries using the Mozilla
// symbolication API.
// Returns an array of promises, one promise per LibSymbolicationRequest in
// requests. If the server does not have symbol information for a given library,
// the promise for that library will fail.
// That's the reason why this function does not return just one promise: We want
// to indicate failure status for each library independently. Under the hood,
// only one request is made to the server.
export function requestSymbols(
  requests: LibSymbolicationRequest[],
  symbolsUrl: string
): Array<Promise<Map<number, AddressResult>>> {
  // For each request, turn its set of addresses into an array.
  // We need there to be a defined order in each addressArray so that we can
  // match the results to the request.
  const requestsWithAddressArrays = requests.map((request) => ({
    request,
    addressArray: Array.from(request.addresses),
  }));

  // Construct the API request body. We make one "job" per LibSymbolicatioRequest.
  // Each "job" has a module list with just one "module" (the lib), and a list
  // of stacks with just one "stack", which contains all addresses for that lib.
  const body = {
    jobs: requestsWithAddressArrays.map(({ request, addressArray }) => {
      const { debugName, breakpadId } = request.lib;
      return {
        memoryMap: [[debugName, breakpadId]],
        stacks: [addressArray.map((addr) => [0, addr])],
      };
    }),
  };

  const jsonPromise = fetch(symbolsUrl + '/symbolicate/v5', {
    body: JSON.stringify(body),
    method: 'POST',
    mode: 'cors',
    // Use a profiler-specific user agent, so that the symbolication server knows
    // what's making this request.
    headers: new Headers({
      'User-Agent': `FirefoxProfiler/1.0 (+${location.origin}) ${navigator.userAgent}`,
    }),
  })
    .then((response) => response.json())
    .then(_ensureIsAPIResultV5);

  return requestsWithAddressArrays.map(async function (
    { request, addressArray },
    requestIndex
  ) {
    const { lib } = request;

    let json;
    try {
      json = (await jsonPromise).results[requestIndex];
    } catch (error) {
      throw new SymbolsNotFoundError(
        'There was a problem with the JSON returned by the symbolication API.',
        lib,
        error
      );
    }

    return getV5ResultForLibRequest(request, addressArray, json);
  });
}
<commit_msg>Remove User-Agent header again. (Merge PR #3685)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import type { AddressResult, LibSymbolicationRequest } from './symbol-store';
import { SymbolsNotFoundError } from './errors';

// This file handles requesting symbolication information from the Mozilla
// symbol server using the API described on
// https://tecken.readthedocs.io/en/latest/symbolication.html .
// Specifically, it uses version 5 of the API, which was implemented in
// https://bugzilla.mozilla.org/show_bug.cgi?id=1377479 .

type APIFoundModulesV5 = {
  // For every requested library in the memoryMap, this object contains a string
  // key of the form `${debugName}/${breakpadId}`. The value is null if no
  // address with the module index was requested, and otherwise a boolean that
  // says whether the symbol server had symbols for this library.
  [string]: null | boolean,
};

type APIInlineFrameInfoV5 = {
  // The name of the function this inline frame was in, if known.
  function?: string,
  // The path of the file that contains the function this inline frame was in, optional.
  file?: string,
  // The line number that contains the source code for this inline frame that
  // contributed to the instruction at the looked-up address, optional.
  // e.g. 543
  line?: number,
};

type APIFrameInfoV5 = {
  // The hex version of the address that we requested (e.g. "0x5ab").
  module_offset: string,
  // The debugName of the library that this frame was in.
  module: string,
  // The index of this APIFrameInfo in its enclosing APIStack.
  frame: number,
  // The name of the function this frame was in, if symbols were found.
  function?: string,
  // The hex offset between the requested address and the start of the function,
  // e.g. "0x3c".
  function_offset?: string,
  // The path of the file that contains the function this frame was in, optional.
  // As of June 2021, this is only supported on the staging symbolication server
  // ("Eliot") but not on the implementation that's currently in production ("Tecken").
  // e.g. "hg:hg.mozilla.org/mozilla-central:js/src/vm/Interpreter.cpp:24938c537a55f9db3913072d33b178b210e7d6b5"
  file?: string,
  // The line number that contains the source code that generated the instructions at the address, optional.
  // (Same support as file.)
  // e.g. 543
  line?: number,
  // Information about functions that were inlined at this address.
  // Ordered from inside to outside.
  // As of November 2021, this is only supported by profiler-symbol-server.
  // Adding this functionality to the Mozilla symbol server is tracked in
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1636194
  inlines?: APIInlineFrameInfoV5[],
};

type APIStackV5 = APIFrameInfoV5[];

type APIJobResultV5 = {
  found_modules: APIFoundModulesV5,
  stacks: APIStackV5[],
};

type APIResultV5 = {
  results: APIJobResultV5[],
};

// Make sure that the JSON blob we receive from the API conforms to our flow
// type definition.
function _ensureIsAPIResultV5(result: any): APIResultV5 {
  if (!(result instanceof Object) || !('results' in result)) {
    throw new Error('Expected an object with property `results`');
  }
  const results = result.results;
  if (!Array.isArray(results)) {
    throw new Error('Expected `results` to be an array');
  }
  for (const jobResult of results) {
    if (
      !(jobResult instanceof Object) ||
      !('found_modules' in jobResult) ||
      !('stacks' in jobResult)
    ) {
      throw new Error(
        'Expected jobResult to have `found_modules` and `stacks` properties'
      );
    }
    const found_modules = jobResult.found_modules;
    if (!(found_modules instanceof Object)) {
      throw new Error('Expected `found_modules` to be an object');
    }
    const stacks = jobResult.stacks;
    if (!Array.isArray(stacks)) {
      throw new Error('Expected `stacks` to be an array');
    }
    for (const stack of stacks) {
      if (!Array.isArray(stack)) {
        throw new Error('Expected `stack` to be an array');
      }
      for (const frameInfo of stack) {
        if (!(frameInfo instanceof Object)) {
          throw new Error('Expected `frameInfo` to be an object');
        }
        if (
          !('module_offset' in frameInfo) ||
          !('module' in frameInfo) ||
          !('frame' in frameInfo)
        ) {
          throw new Error(
            'Expected frameInfo to have `module_offset`, `module` and `frame` properties'
          );
        }
        if ('file' in frameInfo && typeof frameInfo.file !== 'string') {
          throw new Error('Expected frameInfo.file to be a string, if present');
        }
        if ('line' in frameInfo && typeof frameInfo.line !== 'number') {
          throw new Error('Expected frameInfo.line to be a number, if present');
        }
        if ('inlines' in frameInfo) {
          const inlines = frameInfo.inlines;
          if (!Array.isArray(inlines)) {
            throw new Error('Expected `inlines` to be an array');
          }
          for (const inlineFrame of inlines) {
            if (!(inlineFrame instanceof Object)) {
              throw new Error('Expected `inlineFrame` to be an object');
            }
          }
        }
      }
    }
  }
  return result;
}

function getV5ResultForLibRequest(
  request: LibSymbolicationRequest,
  addressArray: number[],
  json: APIJobResultV5
): Map<number, AddressResult> {
  const { lib } = request;
  const { debugName, breakpadId } = lib;

  if (!json.found_modules[`${debugName}/${breakpadId}`]) {
    throw new SymbolsNotFoundError(
      `The symbol server does not have symbols for ${debugName}/${breakpadId}.`,
      lib
    );
  }

  const addressInfo = json.stacks[0];
  if (addressInfo.length !== addressArray.length) {
    throw new SymbolsNotFoundError(
      'The result from the symbol server has an unexpected length.',
      lib
    );
  }

  const results = new Map();
  for (let i = 0; i < addressInfo.length; i++) {
    const address = addressArray[i];
    const info = addressInfo[i];
    let addressResult;
    if (info.function !== undefined && info.function_offset !== undefined) {
      const name = info.function;
      const functionOffset = parseInt(info.function_offset.substr(2), 16);

      // Some symbolication API implementations return an inline stack for each
      // address. These are function calls that were inlined into the outer
      // function by the compiler.
      let inlines;
      if (info.inlines !== undefined) {
        const inlineCount = info.inlines.length;
        inlines = info.inlines.map(({ function: name, file, line }, i) => {
          const depth = inlineCount - i;
          return {
            name:
              name ??
              `<unknown at ${info.module_offset} at inline depth ${depth}>`,
            file,
            line,
          };
        });
      }

      addressResult = {
        name,
        symbolAddress: address - functionOffset,
        file: info.file,
        line: info.line,
        inlines,
      };
    } else {
      // This can happen if the address is between functions, or before the first
      // or after the last function.
      addressResult = {
        name: `<unknown at ${info.module_offset}>`,
        symbolAddress: address,
      };
    }
    results.set(address, addressResult);
  }
  return results;
}

// Request symbols for the given addresses and libraries using the Mozilla
// symbolication API.
// Returns an array of promises, one promise per LibSymbolicationRequest in
// requests. If the server does not have symbol information for a given library,
// the promise for that library will fail.
// That's the reason why this function does not return just one promise: We want
// to indicate failure status for each library independently. Under the hood,
// only one request is made to the server.
export function requestSymbols(
  requests: LibSymbolicationRequest[],
  symbolsUrl: string
): Array<Promise<Map<number, AddressResult>>> {
  // For each request, turn its set of addresses into an array.
  // We need there to be a defined order in each addressArray so that we can
  // match the results to the request.
  const requestsWithAddressArrays = requests.map((request) => ({
    request,
    addressArray: Array.from(request.addresses),
  }));

  // Construct the API request body. We make one "job" per LibSymbolicatioRequest.
  // Each "job" has a module list with just one "module" (the lib), and a list
  // of stacks with just one "stack", which contains all addresses for that lib.
  const body = {
    jobs: requestsWithAddressArrays.map(({ request, addressArray }) => {
      const { debugName, breakpadId } = request.lib;
      return {
        memoryMap: [[debugName, breakpadId]],
        stacks: [addressArray.map((addr) => [0, addr])],
      };
    }),
  };

  const jsonPromise = fetch(symbolsUrl + '/symbolicate/v5', {
    body: JSON.stringify(body),
    method: 'POST',
    mode: 'cors',
  })
    .then((response) => response.json())
    .then(_ensureIsAPIResultV5);

  return requestsWithAddressArrays.map(async function (
    { request, addressArray },
    requestIndex
  ) {
    const { lib } = request;

    let json;
    try {
      json = (await jsonPromise).results[requestIndex];
    } catch (error) {
      throw new SymbolsNotFoundError(
        'There was a problem with the JSON returned by the symbolication API.',
        lib,
        error
      );
    }

    return getV5ResultForLibRequest(request, addressArray, json);
  });
}
<|endoftext|>