<commit_before>/* globals $ */
var popoverLocalInit = function() {
  var options = {
    html: true,
    placement: function(context, source) {
      var position = $(source).offset();
      $(context).addClass($(source).attr('data-popover-class'));
      if ($(source).hasClass('a-js-popoverBig')) {
        return 'bottom';
      }
      if (position.left < 125) {
        return 'right';
      }
      if (position.left > ($(document).width() - $(source).width() - 125)) {
        return 'left';
      }
      return 'bottom';
    },
    content: function() {
      if ($(this).attr('data-popover-content')) {
        return $('#' + $(this).data('popover-content')).html();
      }
      return false;
    },
    template: '

'
  };

  $('[data-toggle="popover"]').popover(options);

  $('.a-js-togglePopoverIcons').each(function() {
    // $(this).find('i').eq(1).hide();
    $(this).find('.a-js-popoverIconExpanded').hide();
  });

  $('.a-js-popoverIconExpanded').on('click', function() {
    $('.a-js-popoverIconExpanded').hide();
    $('.a-js-popoverIconInitial').show();
    // $(this).hide();
    // $('.a-js-popoverIconInitial').show();
  });
  $('.a-js-popoverIconInitial').on('click', function() {
    $(this).hide();
    $(this).parent().find('.a-js-popoverIconExpanded').show();
  });
};

var forceFocusTriggerElement;
var popoverGlobalInit = function() {
  $('body').on('show.bs.popover', '[data-toggle="popover"].a-js-tabable-popover', function(e) {
    var triggerElement = this;
    $(triggerElement).closest('.a-modal').scrollTop(0);
  });

  $('body').on('shown.bs.popover', '[data-toggle="popover"].a-js-tabable-popover', function(e) {
    var triggerElement = this;
    setTimeout(function() {
      $(triggerElement).after($($(triggerElement).data('bs.popover').tip));
      $(triggerElement).closest('.a-modal').one('scroll', function() {
        $('[data-toggle="popover"]').popover('hide');
      });
    }, 0);
  });

  $('body').on('shown.bs.popover', '[data-toggle="popover"].a-js-popover-forceFocus', function(e) {
    $('body').append($('<button class="sr-only a-js-popoverTrick">ignoreme</button>'));
    forceFocusTriggerElement = this;
    $(forceFocusTriggerElement).one('blur', function() {
      var that = this;
      if (forceFocusTriggerElement) {
        $($(this).data('bs.popover').tip).find('button,input,a,textarea').filter(':visible:first').focus();
      }
    });
  });

  $('body').on('hidden.bs.popover', '[data-toggle="popover"].a-js-popover-forceFocus', function(e) {
    $('body').find('.a-js-popoverTrick').remove();
  });

  // Hide all existing popovers when opening a new popover
  $('body').on('click', '[data-toggle="popover"]', function(e) {
    $('[data-toggle="popover"]').not(this).popover('hide');
  });

  // Hide all existing popovers when focusing a new element
  // which is not the open popover or any of its content
  $('body').on('blur', '[data-toggle="popover"], .popover *', function(e) {
    setTimeout(function() {
      var $focused = $(':focus');
      if ((($focused.length !== 0 || forceFocusTriggerElement)
        && !$focused.hasClass('popover')
        && !$focused.parents('.popover').length >= 1) || $focused.hasClass('a-js-popoverTrick')) {
        if (forceFocusTriggerElement) {
          $(forceFocusTriggerElement).focus();
          forceFocusTriggerElement = false;
        }

        $('[data-toggle="popover"]').popover('hide');
      }
    }, 0);
  });

  // Hide popovers when clicking on something else than the trigger element
  // and the popover itself
  $('body').on('click', function(e) {
    if ($(e.target).data('toggle') !== 'popover'
      && $(e.target).parents('[data-toggle="popover"]').length === 0
      && $(e.target).parents('.popover.show').length === 0) {
      $('[data-toggle="popover"]').popover('hide');
      forceFocusTriggerElement = false;
      $(this).parent().find('.a-js-popoverIconInitial').show();
      $(this).parent().find('.a-js-popoverIconExpanded').hide();
    }
  });

  function adjustBig() {
    if ($('.popover-big').length > 0) {
      $('.popover-big').attr('style',
        $('.popover-big').attr('style').replace(
          /translateX\(.*?\)/, 'translateX(0px)'
        )
      );
    }
  }

  // $('body').on('shown.bs.popover', '.a-js-togglePopoverIcons', function(e) {
  //   $(e.target).find('.a-js-popoverIconInitial').hide();
  //   $(e.target).find('.a-js-popoverIconExpanded').show();
  //   // $(e.target).find('i').eq(0).hide();
  //   // $(e.target).find('i').eq(1).show();
  // });

  // $('body').on('hidden.bs.popover', '.a-js-togglePopoverIcons', function(e) {
  //   $(e.target).find('.a-js-popoverIconInitial').show();
  //   $(e.target).find('.a-js-popoverIconExpanded').hide();
  //   // $(e.target).find('i').eq(0).show();
  //   // $(e.target).find('i').eq(1).hide();
  // });

  $('body').on('shown.bs.popover', '.a-js-persistPopover', function() {
    // Adjust the popover arrow correctly as the popover fills the full width
    $('body').append(
      '');
    $('html, body').animate({
      scrollTop: $('.a-js-persistPopover').offset().top - 50
    }, 250);
    adjustBig();
  });

  $(window).scroll(adjustBig);
  $(window).resize(adjustBig);
};
<commit_msg>Fix popover in modal (#69)<commit_after>/* globals $ */
var popoverLocalInit = function() {
  var options = {
    html: true,
    placement: function(context, source) {
      var position = $(source).offset();
      $(context).addClass($(source).attr('data-popover-class'));
      if ($(source).hasClass('a-js-popoverBig')) {
        return 'bottom';
      }
      if (position.left < 125) {
        return 'right';
      }
      if (position.left > ($(document).width() - $(source).width() - 125)) {
        return 'left';
      }
      return 'bottom';
    },
    content: function() {
      if ($(this).attr('data-popover-content')) {
        return $('#' + $(this).data('popover-content')).html();
      }
      return false;
    },
    template: '

'
  };

  $('[data-toggle="popover"]').popover(options);

  $('.a-js-togglePopoverIcons').each(function() {
    // $(this).find('i').eq(1).hide();
    $(this).find('.a-js-popoverIconExpanded').hide();
  });

  $('.a-js-popoverIconExpanded').on('click', function() {
    $('.a-js-popoverIconExpanded').hide();
    $('.a-js-popoverIconInitial').show();
    // $(this).hide();
    // $('.a-js-popoverIconInitial').show();
  });
  $('.a-js-popoverIconInitial').on('click', function() {
    $(this).hide();
    $(this).parent().find('.a-js-popoverIconExpanded').show();
  });
};

var forceFocusTriggerElement;
var popoverGlobalInit = function() {
  $('body').on('show.bs.popover', '[data-toggle="popover"].a-js-tabable-popover', function(e) {
    var triggerElement = this;
    $(triggerElement).closest('.a-modal').scrollTop(0);
  });

  $('body').on('shown.bs.popover', '[data-toggle="popover"].a-js-tabable-popover', function(e) {
    var triggerElement = this;
    setTimeout(function() {
      $(triggerElement).after($($(triggerElement).data('bs.popover').tip));
      $(triggerElement).closest('.a-modal').one('scroll', function() {
        $('[data-toggle="popover"]').popover('hide');
      });
    }, 0);
  });

  $('body').on('shown.bs.popover', '[data-toggle="popover"].a-js-popover-forceFocus', function(e) {
    $('body').append($('<button class="sr-only a-js-popoverTrick">ignoreme</button>'));
    forceFocusTriggerElement = this;
    $(forceFocusTriggerElement).one('blur', function() {
      var that = this;
      if (forceFocusTriggerElement) {
        $($(this).data('bs.popover').tip).find('button,input,a,textarea').filter(':visible:first').focus();
      }
    });
  });

  $('body').on('hidden.bs.popover', '[data-toggle="popover"].a-js-popover-forceFocus', function(e) {
    $('body').find('.a-js-popoverTrick').remove();
  });

  // Hide all existing popovers when opening a new popover
  $('body').on('click', '[data-toggle="popover"]', function(e) {
    $('[data-toggle="popover"]').not(this).popover('hide');
  });

  // Hide all existing popovers when focusing a new element
  // which is not the open popover or any of its content
  $('body').on('blur', '[data-toggle="popover"], .popover *', function(e) {
    setTimeout(function() {
      var $focused = $(':focus');
      if ((($focused.length !== 0 || forceFocusTriggerElement)
        && !$focused.hasClass('popover')
        && !$focused.parents('.popover').length >= 1) || $focused.hasClass('a-js-popoverTrick')) {
        if (forceFocusTriggerElement) {
          $(forceFocusTriggerElement).focus();
          forceFocusTriggerElement = false;
        }
        // disable blur when in modal to allow use of non-original scrollbar
        if ($('.modal.show').length > 0) {
          $('[data-toggle="popover"]').popover('hide');
        }
      }
    }, 0);
  });

  // Hide popovers when clicking on something else than the trigger element
  // and the popover itself
  $('body').on('click', function(e) {
    if ($(e.target).data('toggle') !== 'popover'
      && $(e.target).parents('[data-toggle="popover"]').length === 0
      && $(e.target).parents('.popover.show').length === 0) {
      $('[data-toggle="popover"]').popover('hide');
      forceFocusTriggerElement = false;
      $(this).parent().find('.a-js-popoverIconInitial').show();
      $(this).parent().find('.a-js-popoverIconExpanded').hide();
    }
  });

  function resetTranslate() {
    $('.popover-big').attr('style', $('.popover-big').attr('style').replace(/translateX\(.*?\)/, 'translateX(0px)'));
  }

  function adjustBig() {
    var modalHeight;
    var padding;
    if ($('.popover-big').length > 0) {
      if ($('.modal.show').length > 0) {
        // Add padding to make sure modal is big enough to contain popover
        modalHeight = $('.modal-dialog').height() + $('.modalPage').height();
        padding = ($('.popover').offset().top + $('.modal').scrollTop() + $('.popover').height() + 5) - modalHeight;
        $('.modalPage').css('padding-bottom', padding + 'px');
        // tranlate is somehow added by Bootstrap later when in modal??
        setTimeout(resetTranslate, 0);
      } else {
        resetTranslate();
      }
    }
  }

  $('body').on('shown.bs.popover', '.a-js-persistPopover', function() {
    $('.popover-arrow').html('');
    $('html, body').animate({
      scrollTop: $('.a-js-persistPopover').offset().top - 50
    }, 250);

    // bind scroll wheel to modal popover
    if ($('.modal.show').length > 0) {
      $('.popover-big').bind('wheel', function(e) {
        var scrollTo;
        if (e.originalEvent.deltaY > 0 || e.originalEvent.deltaY < 0) {
          scrollTo = (e.originalEvent.deltaY) + $('.modal').scrollTop();
          $('.modal').scrollTop(scrollTo);
        }
      });
    }

    adjustBig();
  });

  // clean up modal page fix
  $('body').on('hidden.bs.popover', 'a-js-persistPopover', function(e) {
    $('.modalPage').css('padding-bottom', '0px');
  });

  $(window).scroll(adjustBig);
  $('.modal').scroll(adjustBig);
  $(window).resize(adjustBig);
};
<|endoftext|>


<commit_before>var extend = require('xtend');
var Route = require('./Route');
var Request = require('./Request');
var Response = require('./Response');
var Unhandled = require('./errors/Unhandled');
var delayed = require('./utils/delayed');
var getDefaultHistory = require('./history/getHistory');
var series = require('run-series');
var inherits = require('inherits');
var EventEmitter = require('wolfy87-eventemitter');
var attach = require('./attach');
var LinkHijacker = require('./LinkHijacker');
var withoutResults = require('./utils/withoutResults');
var thunkifyAll = require('./utils/thunkifyAll');

function Router(opts) {
  if (opts) {
    this.state = extend(opts.initialState);
    this.history = opts.history;
  }
  this.url = this.constructor.url.bind(this.constructor);
  // TODO: Should we add the properties from the class (i.e. rootURL) to the instance?
}

inherits(Router, EventEmitter);

/**
 * Expose the `Unhandled` error so it can be used in handlers more easily.
 */
Router.Unhandled = Unhandled;

function throwNoEngine() {
  throw new Error('You are attempting to render but your router has no rendering engine.');
}

Router.engine = {
  renderInto: throwNoEngine,
  renderToString: throwNoEngine
};

/**
 * Add a route to the router class. This API can be used in a manner similar
 * to many server-side JS routing libraries—by chainging calls to `route`—
 * however, it's more likely that you'll want to pass your routes to the
 * router class constructor using JSX.
 *
 * This function accepts either `(path, handlers...)`, or
 * `(name, path, handlers...)`.
 */
Router.route = function() {
  var RouterClass = this;
  var args = Array.prototype.slice.call(arguments, 0);
  var name, path, handlers;

  if (typeof args[1] !== 'function') name = args.shift();
  path = args.shift();
  handlers = args;

  var route = new Route(path);

  if (name) {
    if (RouterClass.namedRoutes[name])
      throw new Error('Route with name "' + name + '" already exists.');
    RouterClass.namedRoutes[name] = route;
  }

  // Create and register a middleware to represent this route.
  RouterClass.use(function(req, next) {
    // If this route doesn't match, skip it.
    var match = route.match(req.path);
    if (!match) return next();

    req.params = match;

    series(thunkifyAll(handlers, this, [req]), withoutResults(next));
  });

  // For chaining!
  return this;
};

Router.prototype.setState = function(state) {
  this.state = extend(this.state, state);
  this.emit('stateChange');
};

Router.prototype.setView = function(view) {
  this.view = view;
  this.emit('viewChange');
};

Router.prototype.render = function() {
  if (!this.view) {
    throw new Error('You must set a view before rendering');
  }
  return this.view(extend(this.state));
};

Router.finalMiddleware = [
  // If we've exhausted the middleware without handling the request, call the
  // `unhandled()` method. Going through `unhandled` instead of just creating an
  // error in the dispatch callback means we're always going through the same
  // process, getting the same events, etc.
  function(req, next) {
    if (!this.error && !this.ended) return this.unhandled();
    next();
  }
];

/**
 *
 * @param {string} url
 * @param {function?} callback
 */
Router.prototype.dispatch = function(url, opts, callback) {
  var RouterClass = this.constructor;

  if (typeof arguments[1] === 'function') {
    callback = arguments[1];
    opts = null;
  }

  // Wrap the callback, imposing a delay to force asynchronousness in
  // case the user calls it synchronously.
  var cb = function(err) {
    // Clean up listeners
    res.off('error', cb).off('end', cb);

    this._currentResponse = null;
    if (callback) callback(err, err ? null : res);
  }.bind(this);

  // Resolve the URL to our root.
  var outsideRoot = true;
  if (url.slice(0, RouterClass.rootURL.length) === RouterClass.rootURL) {
    url = url.slice(RouterClass.rootURL.length);
    outsideRoot = false;
  }

  var req = new Request(url, opts);
  var res = new Response(req, this)
    .on('error', cb)
    .on('end', cb);

  if (outsideRoot) {
    delayed(function() {
      res.unhandled('URL not within router root: ' + url);
    })();
    return res;
  }

  if (this._currentResponse) {
    this._currentResponse.request.cancel();
  }
  this._currentResponse = res;

  // Force async behavior so you have a chance to add listeners to the
  // request object.
  var middleware = RouterClass.middleware.concat(RouterClass.finalMiddleware);
  delayed(function() {
    series(thunkifyAll(middleware, res, [req]), function(err) {
      if (err) {
        series(thunkifyAll(RouterClass.errorMiddleware, res, [err, req]), function(err) {
          if (err) res['throw'](err);
          else res.unhandled();
        });
      }
    });
  })();

  return res;
};

Router.prototype.captureClicks = function(el, opts) {
  return new LinkHijacker(this, el, opts);
};

Router.use = function(middleware) {
  var RouterClass = this,
      middlewares = middleware.length === 3 ? RouterClass.errorMiddleware : RouterClass.middleware;
  middlewares.push(middleware);
  return this;
};

Router.extend = function(opts) {
  var SuperClass = this;
  var NewRouter = function(opts) {
    if (!(this instanceof NewRouter)) {
      return new NewRouter(opts);
    }
    SuperClass.call(this, opts);
  };
  inherits(NewRouter, SuperClass);

  // Add "static" props to the new router.
  for (var k in SuperClass) {
    if (SuperClass.hasOwnProperty(k)) {
      NewRouter[k] = SuperClass[k];
    }
  }

  NewRouter.engine = opts && opts.engine;
  NewRouter.rootURL = opts && opts.rootURL || '';
  NewRouter.middleware = [];
  NewRouter.errorMiddleware = [];
  NewRouter.namedRoutes = {};

  return NewRouter;
};

Router.attach = function(element, opts) {
  return attach(this, element, opts);
};

/**
 * Extensions are just functions that mutate the router in any way they want
 * and return it. This function is just a prettier way to use them than calling
 * them directly.
 */
Router.setup = function(extension) {
  var router = extension(this);
  if (!router)
    throw new Error('Invalid extension: extension did not return router.');
  return router;
};

Router.url = function(name, params) {
  var route = this.namedRoutes[name];
  if (!route)
    throw new Error('There is no route named "' + name + '".');
  return route.url(params);
};

module.exports = Router;
<commit_msg>Pass errors from previous error middleware<commit_after>var extend = require('xtend');
var Route = require('./Route');
var Request = require('./Request');
var Response = require('./Response');
var Unhandled = require('./errors/Unhandled');
var delayed = require('./utils/delayed');
var getDefaultHistory = require('./history/getHistory');
var series = require('run-series');
var inherits = require('inherits');
var EventEmitter = require('wolfy87-eventemitter');
var attach = require('./attach');
var LinkHijacker = require('./LinkHijacker');
var withoutResults = require('./utils/withoutResults');
var thunkifyAll = require('./utils/thunkifyAll');

function Router(opts) {
  if (opts) {
    this.state = extend(opts.initialState);
    this.history = opts.history;
  }
  this.url = this.constructor.url.bind(this.constructor);
  // TODO: Should we add the properties from the class (i.e. rootURL) to the instance?
}

inherits(Router, EventEmitter);

/**
 * Expose the `Unhandled` error so it can be used in handlers more easily.
 */
Router.Unhandled = Unhandled;

function throwNoEngine() {
  throw new Error('You are attempting to render but your router has no rendering engine.');
}

Router.engine = {
  renderInto: throwNoEngine,
  renderToString: throwNoEngine
};

/**
 * Add a route to the router class. This API can be used in a manner similar
 * to many server-side JS routing libraries—by chainging calls to `route`—
 * however, it's more likely that you'll want to pass your routes to the
 * router class constructor using JSX.
 *
 * This function accepts either `(path, handlers...)`, or
 * `(name, path, handlers...)`.
 */
Router.route = function() {
  var RouterClass = this;
  var args = Array.prototype.slice.call(arguments, 0);
  var name, path, handlers;

  if (typeof args[1] !== 'function') name = args.shift();
  path = args.shift();
  handlers = args;

  var route = new Route(path);

  if (name) {
    if (RouterClass.namedRoutes[name])
      throw new Error('Route with name "' + name + '" already exists.');
    RouterClass.namedRoutes[name] = route;
  }

  // Create and register a middleware to represent this route.
  RouterClass.use(function(req, next) {
    // If this route doesn't match, skip it.
    var match = route.match(req.path);
    if (!match) return next();

    req.params = match;

    series(thunkifyAll(handlers, this, [req]), withoutResults(next));
  });

  // For chaining!
  return this;
};

Router.prototype.setState = function(state) {
  this.state = extend(this.state, state);
  this.emit('stateChange');
};

Router.prototype.setView = function(view) {
  this.view = view;
  this.emit('viewChange');
};

Router.prototype.render = function() {
  if (!this.view) {
    throw new Error('You must set a view before rendering');
  }
  return this.view(extend(this.state));
};

Router.finalMiddleware = [
  // If we've exhausted the middleware without handling the request, call the
  // `unhandled()` method. Going through `unhandled` instead of just creating an
  // error in the dispatch callback means we're always going through the same
  // process, getting the same events, etc.
  function(req, next) {
    if (!this.error && !this.ended) return this.unhandled();
    next();
  }
];

/**
 *
 * @param {string} url
 * @param {function?} callback
 */
Router.prototype.dispatch = function(url, opts, callback) {
  var RouterClass = this.constructor;

  if (typeof arguments[1] === 'function') {
    callback = arguments[1];
    opts = null;
  }

  // Wrap the callback, imposing a delay to force asynchronousness in
  // case the user calls it synchronously.
  var cb = function(err) {
    // Clean up listeners
    res.off('error', cb).off('end', cb);

    this._currentResponse = null;
    if (callback) callback(err, err ? null : res);
  }.bind(this);

  // Resolve the URL to our root.
  var outsideRoot = true;
  if (url.slice(0, RouterClass.rootURL.length) === RouterClass.rootURL) {
    url = url.slice(RouterClass.rootURL.length);
    outsideRoot = false;
  }

  var req = new Request(url, opts);
  var res = new Response(req, this)
    .on('error', cb)
    .on('end', cb);

  if (outsideRoot) {
    delayed(function() {
      res.unhandled('URL not within router root: ' + url);
    })();
    return res;
  }

  if (this._currentResponse) {
    this._currentResponse.request.cancel();
  }
  this._currentResponse = res;

  // Force async behavior so you have a chance to add listeners to the
  // request object.
  var middleware = RouterClass.middleware.concat(RouterClass.finalMiddleware);
  delayed(function() {
    series(thunkifyAll(middleware, res, [req]), function(err) {
      if (err) {
        // Create a wrapper for each of the error middlewares that receives the
        // error from the previous one. This way, error middleware can pass a
        // different error than it received, and this new error can be handled
        // by subsequent error middleware. If an error middleware does not pass
        // an error or end the request, the request will be considered
        // unhandled.
        var previousError = err;
        var errorMiddleware = RouterClass.errorMiddleware.map(function(fn) {
          return function(done) {
            var newDone = function(err, result) {
              if (err) {
                previousError = err;
                done();
              } else {
                res.unhandled();
              }
            };
            try {
              fn.call(res, previousError, req, newDone);
            } catch (err) {
              newDone(err);
            }
          };
        });
        series(errorMiddleware, function(err) {
          if (err) res['throw'](err);
        });
      }
    });
  })();

  return res;
};

Router.prototype.captureClicks = function(el, opts) {
  return new LinkHijacker(this, el, opts);
};

Router.use = function(middleware) {
  var RouterClass = this,
      middlewares = middleware.length === 3 ? RouterClass.errorMiddleware : RouterClass.middleware;
  middlewares.push(middleware);
  return this;
};

Router.extend = function(opts) {
  var SuperClass = this;
  var NewRouter = function(opts) {
    if (!(this instanceof NewRouter)) {
      return new NewRouter(opts);
    }
    SuperClass.call(this, opts);
  };
  inherits(NewRouter, SuperClass);

  // Add "static" props to the new router.
  for (var k in SuperClass) {
    if (SuperClass.hasOwnProperty(k)) {
      NewRouter[k] = SuperClass[k];
    }
  }

  NewRouter.engine = opts && opts.engine;
  NewRouter.rootURL = opts && opts.rootURL || '';
  NewRouter.middleware = [];
  NewRouter.errorMiddleware = [];
  NewRouter.namedRoutes = {};

  return NewRouter;
};

Router.attach = function(element, opts) {
  return attach(this, element, opts);
};

/**
 * Extensions are just functions that mutate the router in any way they want
 * and return it. This function is just a prettier way to use them than calling
 * them directly.
 */
Router.setup = function(extension) {
  var router = extension(this);
  if (!router)
    throw new Error('Invalid extension: extension did not return router.');
  return router;
};

Router.url = function(name, params) {
  var route = this.namedRoutes[name];
  if (!route)
    throw new Error('There is no route named "' + name + '".');
  return route.url(params);
};

module.exports = Router;
<|endoftext|>


<commit_before>/* jshint node:true */
'use strict';

var events = require('events');
var util = require('util');
var extend = require('util')._extend

var request = require('request');
var xml2js = require('xml2js');

function Sender() {
  var self = this;

  events.EventEmitter.call(self);

  self.device = null;
  self.appID = null;
  self.appURL = null;

  self.token = null;
  self.state = null;
}

util.inherits(Sender, events.EventEmitter);

Sender.prototype.launchApp = function (cb) {
  var self = this;

  var uri = util.format('%s/%s', self.device.applicationUrl, self.appID);
  var body = {
    type: 'launch', // launch, relaunch, join
    app_info: {
      url: self.appURL,
      useIpc: false, // needs WebSocket channel
      maxInactive: -1 // when useIpc is false, receiver running time in ms, -1 is forever
    }
  };
  request({uri: uri, method: 'POST', body: body, json: true}, function (err, res, body) {
    self.token = body.token;
    // TODO: handle body.interval, the heartbeat interval

    // NB: res.statusCode === 201 launched by request, === 200 already running

    cb(err);
  });
};

Sender.prototype.getState = function (cb) {
  var self = this;

  var uri = util.format('%s/%s', self.device.applicationUrl, self.appID);
  var headers = {
    'Accept': 'application/xml; charset=utf8'
  };
  if (self.token) {
    extend(headers, {'Authorization': self.token});
  }
  request({uri: uri, method: 'GET', headers: headers}, function (err, res, body) {
    var parser = new xml2js.Parser();
    parser.parseString(body, function (err, result) {
//      console.log(util.inspect(body));
//      console.log(util.inspect(result));
      var state = result.service.state[0];
      if (state === 'starting' || state === 'running' || state === 'stopped') {
        self.state = state;
      }

      cb(err, self.state);
    });
  });
};

Sender.prototype.closeApp = function (cb) {
  var self = this;

  var uri = util.format('%s/%s/run', self.device.applicationUrl, self.appID);
  var headers = {
    'Accept': 'application/xml; charset=utf8',
    'Authorization': self.token
  };
  request({uri: uri, method: 'DELETE', headers: headers}, function (err, res, body) {
    cb(err);
  });
};

module.exports = Sender;
<commit_msg>add basic system control support<commit_after>/* jshint node:true */
'use strict';

var events = require('events');
var util = require('util');
var extend = require('util')._extend

var request = require('request');
var xml2js = require('xml2js');

function Sender() {
  var self = this;

  events.EventEmitter.call(self);

  self.device = null;
  self.appID = null;
  self.appURL = null;

  self.token = null;
  self.state = null;
}

util.inherits(Sender, events.EventEmitter);

Sender.prototype.launchApp = function (cb) {
  var self = this;

  var uri = util.format('%s/%s', self.device.applicationUrl, self.appID);
  var body = {
    type: 'launch', // launch, relaunch, join
    app_info: {
      url: self.appURL,
      useIpc: false, // needs WebSocket channel
      maxInactive: -1 // when useIpc is false, receiver running time in ms, -1 is forever
    }
  };
  request({uri: uri, method: 'POST', body: body, json: true}, function (err, res, body) {
    self.token = body.token;
    // TODO: handle body.interval, the heartbeat interval

    // NB: res.statusCode === 201 launched by request, === 200 already running

    cb(err);
  });
};

Sender.prototype.getState = function (cb) {
  var self = this;

  var uri = util.format('%s/%s', self.device.applicationUrl, self.appID);
  var headers = {
    'Accept': 'application/xml; charset=utf8'
  };
  if (self.token) {
    extend(headers, {'Authorization': self.token});
  }
  request({uri: uri, method: 'GET', headers: headers}, function (err, res, body) {
    var parser = new xml2js.Parser();
    parser.parseString(body, function (err, result) {
//      console.log(util.inspect(body));
//      console.log(util.inspect(result));
      var state = result.service.state[0];
      if (state === 'starting' || state === 'running' || state === 'stopped') {
        self.state = state;
      }

      cb(err, self.state);
    });
  });
};

Sender.prototype.closeApp = function (cb) {
  var self = this;

  var uri = util.format('%s/%s/run', self.device.applicationUrl, self.appID);
  var headers = {
    'Accept': 'application/xml; charset=utf8',
    'Authorization': self.token
  };
  request({uri: uri, method: 'DELETE', headers: headers}, function (err, res, body) {
    cb(err);
  });
};

Sender.prototype.systemControl = function (type, value, cb) {
  var self = this

  if (typeof value === 'function') {
    cb = value;
  }

  // really odd REST semantics https://github.com/openflint/flingd-coffee/issues/2
  var uri = util.format('%s/system/control', self.device.applicationUrl.replace(/\/apps$/, ''));
  var body = {
    type: type
  };
  if (type === 'SET_VOLUME') {
    extend(body, {level: value});
  } else if (type === 'SET_MUTED') {
    extend(body, {muted: value});
  }
  request({uri: uri, method: 'POST', body: body, json: true}, function (err, res, body) {
    // only the GET_* requests return content https://github.com/openflint/flingd-coffee/issues/1
    var result = undefined;
    if (body && body.success) {
      if (body.request_type === 'GET_VOLUME') {
          result = body.level;
      } else if (body.request_type === 'GET_MUTED') {
        result = body.muted;
      }
    }

    cb(err, result);
  });
};

module.exports = Sender;
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var http = require('http');
var url = require('url');
var connect = require('connect');
var spawn = require('child_process').spawn;
var Promise = require('bluebird');
var SourceNode = require('source-map').SourceNode;
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var getReactNativeExternals = require('./getReactNativeExternals');

var ENTRY_JS = 'global.React = require("react-native");';
var SOURCEMAP_REGEX = /\/\/[#@] sourceMappingURL=([^\s'"]*)/;

function fetch(uri) {
  return new Promise(function(resolve, reject) {
    var parts = url.parse(uri);
    var buffer = '';
    var handler = function(res) {
      res.on('data', function(chunk) {
        buffer += chunk;
      });
      res.on('end', function() {
        if (res.statusCode === 200) {
          resolve(buffer);
        } else {
          reject(buffer);
        }
      });
    };
    http.request(parts, handler).end();
  });
}

/**
 * Create a new server with the following options:
 * {String} hostname (default localhost)
 * {Number} port (default 8080)
 * {Number} packagerPort (default 8081)
 * {Number} webpackPort (default 8082)
 * {String} entry (default index.ios)
 * {String} webpackConfigPath (default ./webpack.config.js)
 * {Boolean} hot enable react-hot-loader (default false)
 *
 * @constructor
 * @param {Object} options
 */
function Server(options) {
  if (!options) options = {};

  // Default options
  this.hostname = options.hostname || 'localhost';
  this.port = options.port || 8080;
  this.packagerPort = options.packagerPort || 8081;
  this.webpackPort = options.webpackPort || 8082;
  this.entry = options.entry || 'index.ios';
  this.hot = (options.hot === true);
  this.webpackConfigPath = options.webpackConfigPath || 'webpack.config.js';
  this.webpackConfigPath = path.resolve(process.cwd(), this.webpackConfigPath);

  // Check for webpack config.
  if (!fs.existsSync(this.webpackConfigPath)) {
    throw new Error('Must specify webpackConfigPath or create ./webpack.config.js');
    process.exit(1);
  }

  // Check for local react-native.
  if (!fs.existsSync(path.resolve(process.cwd(), 'node_modules/react-native'))) {
    throw new Error('Could not find react-native. Try `npm install react-native`.');
    process.exit(1);
  }

  // Construct resource URLs up-front
  this.webpackBaseURL = 'http://' + this.hostname + ':' + this.webpackPort;
  this.packagerBaseURL = 'http://' + this.hostname + ':' + this.packagerPort;
  this.reactCodeURL = this.packagerBaseURL + '/' + this.entry + '.bundle';
  this.reactMapURL = this.packagerBaseURL + '/' + this.entry + '.map';
  this.appCodeURL = this.webpackBaseURL + '/' + this.entry + '.js';
  this.appMapURL = this.webpackBaseURL + '/' + this.entry + '.js.map';

  // Setup the connect server
  this.server = connect();
  this.server.use(this.handleRequest.bind(this));

  // Create a stub entry module for the RN packager.
  this.entryDir = path.resolve(__dirname, '../_entry');
  var entryFile = path.join(this.entryDir, this.entry + '.js');
  if (!fs.existsSync(this.entryDir)) {
    fs.mkdirSync(this.entryDir);
  }
  fs.writeFileSync(entryFile, ENTRY_JS, 'utf-8');

  // Make sure to clean up when the process is terminated.
  process.on('exit', this.handleProcessExit.bind(this));
  process.on('SIGINT', process.exit.bind(process));

  // Spin up the RN packager and webpack servers.
  this._startWebpackDevServer();
  this._startPackageServer();
}

Server.prototype = {

  start: function() {
    var hostname = this.hostname;
    var port = this.port;
    http.createServer(this.server).listen(this.port, function() {
      console.log('Server listening at http://%s:%s', hostname, port);
    });
  },

  handleRequest: function(req, res, next) {
    switch (req.url) {
      case ('/' + this.entry + '.bundle'):
        this.handleBundleRequest(req, res, next);
        break;
      case ('/' + this.entry + '.map'):
        this.handleMapRequest(req, res, next);
        break;
      default:
        res.writeHead(404);
        res.end('Cannot GET ' + req.url);
    }
  },

  handleBundleRequest: function(req, res, next) {
    var createBundleCode = this._createBundleCode.bind(this);
    Promise.props({
      reactCode: fetch(this.reactCodeURL),
      appCode: fetch(this.appCodeURL),
    }).then(function(r) {
      return createBundleCode(r.reactCode, r.appCode);
    }).then(function(bundleCode) {
      res.writeHead(200);
      res.end(bundleCode);
    }).catch(next);
  },

  handleMapRequest: function(req, res, next) {
    var createBundleMap = this._createBundleMap.bind(this);
    Promise.props({
      reactCode: fetch(this.reactCodeURL),
      reactMap: fetch(this.reactMapURL),
      appCode: fetch(this.appCodeURL),
      appMap: fetch(this.appMapURL),
    }).then(function(r) {
      return createBundleMap(r.reactCode, r.reactMap, r.appCode, r.appMap);
    }).then(function(bundleMap) {
      res.writeHead(200);
      res.end(bundleMap);
    }).catch(next);
  },

  handleProcessExit: function() {
    // Clean up temp files
    var entryDir = this.entryDir;
    if (fs.existsSync(entryDir)) {
      fs.readdirSync(entryDir).forEach(function(file) {
        fs.unlinkSync(path.join(entryDir, file));
      });
      fs.rmdirSync(entryDir);
    }

    // Kill the package server
    if (this.packageServer) {
      this.packageServer.kill();
    }
  },

  _createBundleCode: function(reactCode, appCode) {
    reactCode = reactCode.replace(SOURCEMAP_REGEX, '');
    appCode = appCode.replace(SOURCEMAP_REGEX, '');
    return reactCode + appCode + '//# sourceMappingURL=/' + this.entry + '.map';
  },

  _createBundleMap: function(reactCode, reactMap, appCode, appMap) {
    var node = new SourceNode();
    var map;

    node.add(SourceNode.fromStringWithSourceMap(
      reactCode,
      new SourceMapConsumer(reactMap)
    ));
    node.add(SourceNode.fromStringWithSourceMap(
      appCode,
      new SourceMapConsumer(appMap)
    ));

    node = node.join('');
    map = node.toStringWithSourceMap().map;

    return JSON.stringify(map);
  },

  _startPackageServer: function() {
    // Easier to just shell out to the packager than use the JS API.
    var cmd = './node_modules/react-native/packager/packager.sh';
    var args = [
      '--projectRoots', path.resolve(process.cwd(), 'node_modules/react-native'),
      '--root', this.entryDir,
    ];
    var opts = {stdio: 'inherit'};
    this.packageServer = spawn(cmd, args, opts);
  },

  _startWebpackDevServer: function() {
    var webpackConfig = require(this.webpackConfigPath);
    var webpackURL = 'http://' + this.hostname + ':' + this.webpackPort;
    var hot = this.hot;
    var publicPath = hot ? (webpackURL + '/') : null;
    var reactNativePath = path.resolve(process.cwd(), 'node_modules/react-native');
    var reactNativeExternals = getReactNativeExternals(path.join(reactNativePath, 'Libraries'));

    // Inject react native externals
    webpackConfig.externals = webpackConfig.externals || {};
    for (var mod in reactNativeExternals) {
      webpackConfig.externals[mod] = reactNativeExternals[mod];
    }

    // By default webpack uses webpack://[resource-path]?[hash] in the source
    // map which is handled by its dev server. Use absolute path instead so
    // React Native's exception manager can load the source maps.
    webpackConfig.output = webpackConfig.output || {};
    if (!webpackConfig.output.devtoolModuleFilenameTemplate) {
      webpackConfig.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
    }

    if (hot) {
      // Ensure react-hot-loader is installed.
      if (!fs.existsSync(path.resolve(process.cwd(), 'node_modules/react-hot-loader'))) {
        console.error('Please install react-hot-loader first.');
        process.exit(1);
      }

      // Setup alias into project's node_modules/react-hot-loader. We can't use
      // require('react-hot-loader') in the hot entry.js since webpack will look
      // in our own node_modules directory and we would have the same problem
      // with requiring external React.
      webpackConfig.resolve = webpackConfig.resolve || {};
      webpackConfig.resolve.alias = webpackConfig.resolve.alias || {};
      webpackConfig.resolve.alias['react-hot-loader/Injection'] =
        path.resolve(process.cwd(), 'node_modules/react-hot-loader/Injection');

      // Restore document.createElement (see: InitializeJavaScriptAppEngine.js)
      webpackConfig.plugins = webpackConfig.plugins || [];
      webpackConfig.plugins.unshift(
        new webpack.BannerPlugin(
          'if (GLOBAL && GLOBAL.document) {\n' +
          '  document.createElement = function() {\n' +
          '    return HTMLDocument.prototype.createElement.apply(document, arguments);\n' +
          '  };\n' +
          '}\n',
          {raw: true, entryOnly: true}
        )
      );
    }

    this.webpackServer = new WebpackDevServer(webpack(webpackConfig), {
      hot: hot,
      publicPath: publicPath,
      headers: {
        'Access-Control-Allow-Origin': '*'
      },
      stats: {colors: true}
    });

    this.webpackServer.listen(this.webpackPort, this.hostname, function() {
      console.log('Webpack dev server listening at ', webpackURL);
    });
  },

};

module.exports = Server;<commit_msg>Prevents GLOBAL ReferenceError in hot browsers<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var http = require('http');
var url = require('url');
var connect = require('connect');
var spawn = require('child_process').spawn;
var Promise = require('bluebird');
var SourceNode = require('source-map').SourceNode;
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var getReactNativeExternals = require('./getReactNativeExternals');

var ENTRY_JS = 'global.React = require("react-native");';
var SOURCEMAP_REGEX = /\/\/[#@] sourceMappingURL=([^\s'"]*)/;

function fetch(uri) {
  return new Promise(function(resolve, reject) {
    var parts = url.parse(uri);
    var buffer = '';
    var handler = function(res) {
      res.on('data', function(chunk) {
        buffer += chunk;
      });
      res.on('end', function() {
        if (res.statusCode === 200) {
          resolve(buffer);
        } else {
          reject(buffer);
        }
      });
    };
    http.request(parts, handler).end();
  });
}

/**
 * Create a new server with the following options:
 * {String} hostname (default localhost)
 * {Number} port (default 8080)
 * {Number} packagerPort (default 8081)
 * {Number} webpackPort (default 8082)
 * {String} entry (default index.ios)
 * {String} webpackConfigPath (default ./webpack.config.js)
 * {Boolean} hot enable react-hot-loader (default false)
 *
 * @constructor
 * @param {Object} options
 */
function Server(options) {
  if (!options) options = {};

  // Default options
  this.hostname = options.hostname || 'localhost';
  this.port = options.port || 8080;
  this.packagerPort = options.packagerPort || 8081;
  this.webpackPort = options.webpackPort || 8082;
  this.entry = options.entry || 'index.ios';
  this.hot = (options.hot === true);
  this.webpackConfigPath = options.webpackConfigPath || 'webpack.config.js';
  this.webpackConfigPath = path.resolve(process.cwd(), this.webpackConfigPath);

  // Check for webpack config.
  if (!fs.existsSync(this.webpackConfigPath)) {
    throw new Error('Must specify webpackConfigPath or create ./webpack.config.js');
    process.exit(1);
  }

  // Check for local react-native.
  if (!fs.existsSync(path.resolve(process.cwd(), 'node_modules/react-native'))) {
    throw new Error('Could not find react-native. Try `npm install react-native`.');
    process.exit(1);
  }

  // Construct resource URLs up-front
  this.webpackBaseURL = 'http://' + this.hostname + ':' + this.webpackPort;
  this.packagerBaseURL = 'http://' + this.hostname + ':' + this.packagerPort;
  this.reactCodeURL = this.packagerBaseURL + '/' + this.entry + '.bundle';
  this.reactMapURL = this.packagerBaseURL + '/' + this.entry + '.map';
  this.appCodeURL = this.webpackBaseURL + '/' + this.entry + '.js';
  this.appMapURL = this.webpackBaseURL + '/' + this.entry + '.js.map';

  // Setup the connect server
  this.server = connect();
  this.server.use(this.handleRequest.bind(this));

  // Create a stub entry module for the RN packager.
  this.entryDir = path.resolve(__dirname, '../_entry');
  var entryFile = path.join(this.entryDir, this.entry + '.js');
  if (!fs.existsSync(this.entryDir)) {
    fs.mkdirSync(this.entryDir);
  }
  fs.writeFileSync(entryFile, ENTRY_JS, 'utf-8');

  // Make sure to clean up when the process is terminated.
  process.on('exit', this.handleProcessExit.bind(this));
  process.on('SIGINT', process.exit.bind(process));

  // Spin up the RN packager and webpack servers.
  this._startWebpackDevServer();
  this._startPackageServer();
}

Server.prototype = {

  start: function() {
    var hostname = this.hostname;
    var port = this.port;
    http.createServer(this.server).listen(this.port, function() {
      console.log('Server listening at http://%s:%s', hostname, port);
    });
  },

  handleRequest: function(req, res, next) {
    switch (req.url) {
      case ('/' + this.entry + '.bundle'):
        this.handleBundleRequest(req, res, next);
        break;
      case ('/' + this.entry + '.map'):
        this.handleMapRequest(req, res, next);
        break;
      default:
        res.writeHead(404);
        res.end('Cannot GET ' + req.url);
    }
  },

  handleBundleRequest: function(req, res, next) {
    var createBundleCode = this._createBundleCode.bind(this);
    Promise.props({
      reactCode: fetch(this.reactCodeURL),
      appCode: fetch(this.appCodeURL),
    }).then(function(r) {
      return createBundleCode(r.reactCode, r.appCode);
    }).then(function(bundleCode) {
      res.writeHead(200);
      res.end(bundleCode);
    }).catch(next);
  },

  handleMapRequest: function(req, res, next) {
    var createBundleMap = this._createBundleMap.bind(this);
    Promise.props({
      reactCode: fetch(this.reactCodeURL),
      reactMap: fetch(this.reactMapURL),
      appCode: fetch(this.appCodeURL),
      appMap: fetch(this.appMapURL),
    }).then(function(r) {
      return createBundleMap(r.reactCode, r.reactMap, r.appCode, r.appMap);
    }).then(function(bundleMap) {
      res.writeHead(200);
      res.end(bundleMap);
    }).catch(next);
  },

  handleProcessExit: function() {
    // Clean up temp files
    var entryDir = this.entryDir;
    if (fs.existsSync(entryDir)) {
      fs.readdirSync(entryDir).forEach(function(file) {
        fs.unlinkSync(path.join(entryDir, file));
      });
      fs.rmdirSync(entryDir);
    }

    // Kill the package server
    if (this.packageServer) {
      this.packageServer.kill();
    }
  },

  _createBundleCode: function(reactCode, appCode) {
    reactCode = reactCode.replace(SOURCEMAP_REGEX, '');
    appCode = appCode.replace(SOURCEMAP_REGEX, '');
    return reactCode + appCode + '//# sourceMappingURL=/' + this.entry + '.map';
  },

  _createBundleMap: function(reactCode, reactMap, appCode, appMap) {
    var node = new SourceNode();
    var map;

    node.add(SourceNode.fromStringWithSourceMap(
      reactCode,
      new SourceMapConsumer(reactMap)
    ));
    node.add(SourceNode.fromStringWithSourceMap(
      appCode,
      new SourceMapConsumer(appMap)
    ));

    node = node.join('');
    map = node.toStringWithSourceMap().map;

    return JSON.stringify(map);
  },

  _startPackageServer: function() {
    // Easier to just shell out to the packager than use the JS API.
    var cmd = './node_modules/react-native/packager/packager.sh';
    var args = [
      '--projectRoots', path.resolve(process.cwd(), 'node_modules/react-native'),
      '--root', this.entryDir,
    ];
    var opts = {stdio: 'inherit'};
    this.packageServer = spawn(cmd, args, opts);
  },

  _startWebpackDevServer: function() {
    var webpackConfig = require(this.webpackConfigPath);
    var webpackURL = 'http://' + this.hostname + ':' + this.webpackPort;
    var hot = this.hot;
    var publicPath = hot ? (webpackURL + '/') : null;
    var reactNativePath = path.resolve(process.cwd(), 'node_modules/react-native');
    var reactNativeExternals = getReactNativeExternals(path.join(reactNativePath, 'Libraries'));

    // Inject react native externals
    webpackConfig.externals = webpackConfig.externals || {};
    for (var mod in reactNativeExternals) {
      webpackConfig.externals[mod] = reactNativeExternals[mod];
    }

    // By default webpack uses webpack://[resource-path]?[hash] in the source
    // map which is handled by its dev server. Use absolute path instead so
    // React Native's exception manager can load the source maps.
    webpackConfig.output = webpackConfig.output || {};
    if (!webpackConfig.output.devtoolModuleFilenameTemplate) {
      webpackConfig.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
    }

    if (hot) {
      // Ensure react-hot-loader is installed.
      if (!fs.existsSync(path.resolve(process.cwd(), 'node_modules/react-hot-loader'))) {
        console.error('Please install react-hot-loader first.');
        process.exit(1);
      }

      // Setup alias into project's node_modules/react-hot-loader. We can't use
      // require('react-hot-loader') in the hot entry.js since webpack will look
      // in our own node_modules directory and we would have the same problem
      // with requiring external React.
      webpackConfig.resolve = webpackConfig.resolve || {};
      webpackConfig.resolve.alias = webpackConfig.resolve.alias || {};
      webpackConfig.resolve.alias['react-hot-loader/Injection'] =
        path.resolve(process.cwd(), 'node_modules/react-hot-loader/Injection');

      // Restore document.createElement (see: InitializeJavaScriptAppEngine.js)
      webpackConfig.plugins = webpackConfig.plugins || [];
      webpackConfig.plugins.unshift(
        new webpack.BannerPlugin(
          'if (typeof(GLOBAL) !== \'undefined\' && GLOBAL.document) {\n' +
          '  document.createElement = function() {\n' +
          '    return HTMLDocument.prototype.createElement.apply(document, arguments);\n' +
          '  };\n' +
          '}\n',
          {raw: true, entryOnly: true}
        )
      );
    }

    this.webpackServer = new WebpackDevServer(webpack(webpackConfig), {
      hot: hot,
      publicPath: publicPath,
      headers: {
        'Access-Control-Allow-Origin': '*'
      },
      stats: {colors: true}
    });

    this.webpackServer.listen(this.webpackPort, this.hostname, function() {
      console.log('Webpack dev server listening at ', webpackURL);
    });
  },

};

module.exports = Server;
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2019 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/recaptcha-enterprise',
    theme: 'lumen',
    default: {
      "outputSourceFiles": false
    }
  },
  markdown: {
    idInHeadings: true
  }
};
<commit_msg>chore: correct .jsdoc.js protos and double quotes (#123)<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src',
      'protos'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2019 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/recaptcha-enterprise',
    theme: 'lumen',
    default: {
      outputSourceFiles: false
    }
  },
  markdown: {
    idInHeadings: true
  }
};
<|endoftext|>


<commit_before>/*global JSON*/
var EventEmitter = require('events').EventEmitter,
    util = require('util'),
    urlModule = require('url'),
    _ = require('underscore'),
    lines = require('lines'),
    fs = require('fs'),
    httpErrors = require('httperrors'),
    socketErrors = require('socketerrors'),
    os = require('os'),
    passError = require('passerror'),
    http = require('http'),
    https = require('https');

function isContentTypeJson(contentType) {
    return /^application\/json\b|\+json\b/i.test(contentType);
}

function resolveCertKeyOrCa(value) {
    if (typeof value === 'string') {
        return fs.readFileSync(value.replace(/\{hostname\}/g, os.hostname()));
    } else if (Array.isArray(value)) {
        // An array of ca file names
        return value.map(resolveCertKeyOrCa);
    } else {
        return value;
    }
}

/*
 * config.url            {String}             The base url for all requests
 * config.numRetries     {Number}  (optional) The number of times to retry an operation if it fails due to a non-HTTP error such
 *                                           as a socket timeout. Defaults to 0.
 * config.agent          {Object}  (optional) The HTTP/HTTPS agent to use. Defaults to creating a new one with the below options.
 * config.maxSockets     {Number}  (optional) The maximum number of simultaneous connections to support. Only used if config.agent isn't provided.
 * config.keepAlive      {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.keepAliveMsecs {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.maxSockets     {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.maxFreeSockets {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.cert           {Buffer}  (optional) The certificate to use. Only used if config.agent isn't provided.
 * config.key            {Buffer}  (optional) The certificate key to use. Only used if config.agent isn't provided.
 * config.ca             {Buffer}  (optional) The certificate authority (CA) to use. Only used if config.agent isn't provided.
 */
function Teepee(config) {
    EventEmitter.call(this);

    this._userSuppliedConfigOptionNames = [];
    if (config) {
        Object.keys(config).forEach(function (key) {
            if (key === 'cert' || key === 'key' || key === 'ca') {
                this[key] = resolveCertKeyOrCa(config[key]);
                this._userSuppliedConfigOptionNames.push(key);
            } else if (typeof this[key] === 'undefined') {
                this[key] = config[key];
                this._userSuppliedConfigOptionNames.push(key);
            } else {
                // Ignore unsupported property that would overwrite or shadow for a built-in property or method
            }
        }, this);
    }

    // Ensure no trailing slash:
    if (Array.isArray(this.url)) {
        this.url = this.url.map(function (url) {
            return url.replace(/\/$/, '');
        });
    } else if (typeof this.url === 'string') {
        this.url = this.url.replace(/\/$/, '');
    } else {
        throw new Error('config.url is required');
    }

    this.numRetries = this.numRetries || 0;
}

util.inherits(Teepee, EventEmitter);

Teepee.prototype.extractNonRequestOptions = function (obj) {
    var result = {};
    if (obj) {
        Object.keys(obj).forEach(function (key) {
            if (key !== 'method' && key !== 'headers' && key !== 'path' && key !== 'query' && key !== 'streamRows' && key !== 'eventEmitter' && key !== 'url' && key !== 'path') {
                result[key] = obj[key];
            }
        });
    }
    return result;
};

Teepee.prototype.preprocessQueryStringParameterValue = function (queryStringParameterValue) {
    return queryStringParameterValue;
};

Teepee.prototype.stringifyJsonRequestBody = JSON.stringify;

Teepee.prototype._addQueryStringToUrl = function (url, query) {
    if (typeof query !== 'undefined') {
        url += (url.indexOf('?') !== -1) ? '&' : '?';
        if (typeof query === 'string') {
            url += query;
        } else {
            // Assume object
            var params = [];
            Object.keys(query).forEach(function (key) {
                var value = query[key];
                if (Array.isArray(value)) {
                    // Turn query: {foo: ['a', 'b']} into ?foo=a&foo=b
                    value.forEach(function (valueArrayItem) {
                        params.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.preprocessQueryStringParameterValue(valueArrayItem)));
                    }, this);
                } else if (typeof value !== 'undefined') {
                    params.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.preprocessQueryStringParameterValue(value)));
                }
            }, this);
            url += params.join('&');
        }
    }
    return url;
};

Teepee.prototype.getPlaceholderValue = function (placeholderName, requestOptions) {
    if (typeof requestOptions[placeholderName] !== 'undefined') {
        return requestOptions[placeholderName];
    } else {
        var type = typeof this[placeholderName];
        if (type === 'undefined') {
            return '{' + placeholderName + '}';
        } else {
            var value = this[placeholderName];
            if (typeof value === 'function') {
                return value.call(this, requestOptions, placeholderName);
            } else {
                return String(value);
            }
        }
    }
};

Teepee.prototype.expandUrl = function (url, requestOptions) {
    requestOptions = requestOptions || {};
    var that = this;
    return url.replace(/\{((?:[^\{\}]+|\{\w+\})*)\}/g, function ($0, placeholderName) {
        if (/^\w+$/.test(placeholderName)) {
            return that.getPlaceholderValue(placeholderName, requestOptions, $0);
        } else {
            var methodName = '__placeholder_fn_' + placeholderName;
            if (!that[methodName]) {
                /*jshint evil:true*/
                that[methodName] = new Function('requestOptions', 'return ' + placeholderName.replace(/\{(\w+)\}/g, 'this.getPlaceholderValue("$1", requestOptions)') + ';');
                /*jshint evil:false*/
            }
            return that[methodName](requestOptions);
        }
    });
};

Teepee.prototype.getAgent = function () {
    if (!this.agent) {
        var agentOptions = {};
        // Pass all instance variables that originate from the user-supplied config object to the Agent constructor:
        this._userSuppliedConfigOptionNames.forEach(function (userSuppliedConfigOptionName) {
            var value = this[userSuppliedConfigOptionName];
            if (typeof value !== 'undefined') {
                agentOptions[userSuppliedConfigOptionName] = value;
            }
        }, this);
        var Agent = this.Agent || require(urlModule.parse(this.url).protocol.replace(/:$/, '')).Agent;
        this.agent = new Agent(agentOptions);
    }
    return this.agent;
};

/*
 * Perform a request
 *
 * options.headers    {Object} (optional) The HTTP headers for the request.
 * options.path       {String} (optional) The path relative to the database url.
 * options.query      {Object} (optional) The query string (objects will be JSON.stringify'ed)
 * options.body       {String|Object|Buffer|Stream} (optional) What to send. Streams are streamed, objects will be serialized as JSON,
 *                                                             and buffers are sent as-is.
 * options.numRetries {Number} (optional) The number of times to retry an operation if it fails due to a non-HTTP error such
 *                                        as a socket timeout. Defaults to the numRetries parameter given to the constructor
 *                                        (which defaults to 0). Has no effect with the onResponse and streamRows options.
 * options.streamRows {Boolean} (optional) If specified, an event emitter will be returned that emits error/metadata/row/end events.
 *                                         This is useful for big responses that you don't want to parse in one go. Defaults to false.
 */
Teepee.prototype.request = function (options, cb) {
    var that = this,
        numRetriesLeft = options.streamRows ? 0 : typeof options.numRetries !== 'undefined' ? options.numRetries : this.numRetries,
        retry = typeof options.retry !== 'undefined' ? options.retry : this.retry,
        headers = _.extend({}, options.headers),
        url = this.expandUrl(this.url, options);
    if (options.path) {
        if (/^[\/\.]/.test(options.path)) {
            // options.path is root-relative or begins with a dot, treat it as a relative url that needs to be resolved:
            url = urlModule.resolve(url + '/', options.path);
        } else {
            url += '/' + options.path;
        }
    }
    url = this._addQueryStringToUrl(url, options.query);

    var body = options.body;
    if (typeof body === 'object' && !Buffer.isBuffer(body) && typeof body.pipe !== 'function') {
        body = this.stringifyJsonRequestBody(body);
        headers['content-type'] = 'application/json';
    }

    if (!('accept' in headers)) {
        headers.accept = 'application/json';
    }

    var eventEmitter = new EventEmitter(),
        urlObj = urlModule.parse(url),
        httpModule = (urlObj.protocol === 'https:' ? https : http),
        requestOptions = {
            host: urlObj.hostname,
            port: urlObj.port === null ? 80 : parseInt(urlObj.port, 10),
            method: options.method || 'GET',
            path: urlObj.path,
            headers: headers,
            agent: this.getAgent()
        },
        done = false,
        currentRequest;

    eventEmitter.abort = function () {
        done = true;
        if (currentRequest) {
            currentRequest.abort();
            currentRequest = null;
        }
    };

    function handleError(err, response) {
        var socketError;

        if (!done) {
            done = true;

            // if what came up was a plain error convert it to an httpError
            if (!err.statusCode) {
                socketError = socketErrors(err);

                if (!socketError.NotSocketError) {
                    err = socketError;
                } else {
                    // convert to a 500 internal server error
                    err = new httpErrors[500](err.message);
                }
            }

            that.emit('failedRequest', { url: url, requestOptions: requestOptions, response: response, err: err, numRetriesLeft: numRetriesLeft });
            if (cb) {
                cb(err);
            } else {
                eventEmitter.emit('error', err);
            }
        }
    }

    function handleSuccess(response) {
        if (!done) {
            done = true;
            that.emit('successfulRequest', { url: url, requestOptions: requestOptions, response: response });
            eventEmitter.emit('end');
            if (cb) {
                cb(null, response, response && response.body);
            }
        }
    }

    if (this.preprocessRequestOptions) {
        this.preprocessRequestOptions(requestOptions, options, passError(handleError, dispatchRequest));
    } else {
        dispatchRequest();
    }

    function dispatchRequest() {
        currentRequest = httpModule.request(requestOptions);
        if (eventEmitter.listeners('request').length > 0) {
            numRetriesLeft = 0;
            eventEmitter.emit('request', currentRequest);
        }
        if (body && typeof body.pipe === 'function') {
            numRetriesLeft = 0;
            body.pipe(currentRequest);
        } else {
            currentRequest.end(body);
        }
        currentRequest.on('error', function (err) {
            currentRequest = null;
            if (done) {
                return;
            }
            // Non-HTTP error (ECONNRESET, ETIMEDOUT, etc.)
            // Try again (up to numRetriesLeft times). Warning: This doesn't work when piping into the returned request,
            // so please specify numRetriesLeft:0 if you intend to do that.
            if (numRetriesLeft > 0) {
                numRetriesLeft -= 1;
                dispatchRequest();
            } else {
                handleError(err);
            }
        }).on('response', function (response) {
            var responseError;

            if (done) {
                return;
            }

            function returnSuccessOrError(err) {
                err = err || responseError;

                if (!err) {
                    handleSuccess(response);
                } else {
                    handleError(err, response);
                }
            }

            response.cacheInfo = { headers: {} };
            if (response.statusCode >= 400) {
                if (numRetriesLeft > 0 && Array.isArray(retry) && retry.indexOf(response.statusCode) !== -1) {
                    response
                        .on('data', function () {})
                        .on('error', function () {});
                    numRetriesLeft -= 1;
                    dispatchRequest();
                    return;
                }
                responseError = new httpErrors[response.statusCode]();
            } else if (response.statusCode === 304) {
                response.cacheInfo.notModified = true;
                body = null;
            }
            numRetriesLeft = 0;
            ['last-modified', 'etag', 'expires', 'cache-control', 'content-type'].forEach(function (headerName) {
                if (headerName in response.headers) {
                    response.cacheInfo.headers[headerName] = response.headers[headerName];
                }
            });

            if (!responseError) {
                eventEmitter.emit('response', response);
            }

            if (options.streamRows) {
                response.setEncoding('utf-8');
                lines(response);
                response.on('line', function (str) {
                    if (done) {
                        return;
                    }

                    // use a regex to match the JSON metadata row and emit it
                    // both populated and empty result cases are handled
                    var matchFirstLine = str.match(/^\{(.*)"(?:rows|results)":\s*\[(?:\]\}|)$/);
                    if (matchFirstLine) {
                        if (matchFirstLine[1] !== "") {
                            eventEmitter.emit('metadata', JSON.parse('{' + matchFirstLine[1].replace(/\,\s*$/, '') + '}'));
                        }
                    } else {
                        var matchLastLine = str.match(/^(".*)\}$/);
                        if (matchLastLine) {
                            eventEmitter.emit('metadata', JSON.parse('{' + matchLastLine[1] + '}'));
                        } else if (str === ']}' || str === '' || str === '],') {
                            return;
                        } else {
                            str = str.replace(/,\r?$/, '');
                            var row;
                            try {
                                row = JSON.parse(str);
                            } catch (e) {
                                handleError(httpErrors.InternalServerError("Couldn't parse line: " + str), response);
                            }
                            eventEmitter.emit('row', row);
                        }
                    }
                }).on('error', returnSuccessOrError).on('end', returnSuccessOrError);
            } else if (cb) {
                var responseBodyChunks = [];
                response.on('data', function (responseBodyChunk) {
                    responseBodyChunks.push(responseBodyChunk);
                }).on('error', returnSuccessOrError).on('end', function () {
                    currentRequest = null;
                    if (responseBodyChunks.length > 0) {
                        response.body = Buffer.concat(responseBodyChunks);
                        if (isContentTypeJson(response.headers['content-type'])) {
                            try {
                                response.body = JSON.parse(response.body.toString('utf-8'));
                            } catch (e) {
                                return handleError(new httpErrors.BadGateway('Error parsing JSON response body'), response);
                            }
                        }
                    }
                    returnSuccessOrError();
                });
            } else {
                if (responseError) {
                    response.on('data', function () {});
                }
                returnSuccessOrError();
            }
        });
    }

    return eventEmitter;
};

Teepee.prototype.quit = function () {
    // agent.destroy became available in node.js 0.12:
    if (this.agent && this.agent.destroy) {
        this.agent.destroy();
    }
};

// Expose the httpErrors module so that test suites for modules using Teepee can use the correct constructors:
Teepee.httpErrors = httpErrors;

module.exports = Teepee;
<commit_msg>Disable chunked encoding for non-streamed requests<commit_after>/*global JSON*/
var EventEmitter = require('events').EventEmitter,
    util = require('util'),
    urlModule = require('url'),
    _ = require('underscore'),
    lines = require('lines'),
    fs = require('fs'),
    httpErrors = require('httperrors'),
    socketErrors = require('socketerrors'),
    os = require('os'),
    passError = require('passerror'),
    http = require('http'),
    https = require('https');

function isContentTypeJson(contentType) {
    return /^application\/json\b|\+json\b/i.test(contentType);
}

function resolveCertKeyOrCa(value) {
    if (typeof value === 'string') {
        return fs.readFileSync(value.replace(/\{hostname\}/g, os.hostname()));
    } else if (Array.isArray(value)) {
        // An array of ca file names
        return value.map(resolveCertKeyOrCa);
    } else {
        return value;
    }
}

/*
 * config.url            {String}             The base url for all requests
 * config.numRetries     {Number}  (optional) The number of times to retry an operation if it fails due to a non-HTTP error such
 *                                           as a socket timeout. Defaults to 0.
 * config.agent          {Object}  (optional) The HTTP/HTTPS agent to use. Defaults to creating a new one with the below options.
 * config.maxSockets     {Number}  (optional) The maximum number of simultaneous connections to support. Only used if config.agent isn't provided.
 * config.keepAlive      {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.keepAliveMsecs {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.maxSockets     {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.maxFreeSockets {Number}  (optional) Passed to the Agent constructor. Only used if config.agent isn't provided.
 * config.cert           {Buffer}  (optional) The certificate to use. Only used if config.agent isn't provided.
 * config.key            {Buffer}  (optional) The certificate key to use. Only used if config.agent isn't provided.
 * config.ca             {Buffer}  (optional) The certificate authority (CA) to use. Only used if config.agent isn't provided.
 */
function Teepee(config) {
    EventEmitter.call(this);

    this._userSuppliedConfigOptionNames = [];
    if (config) {
        Object.keys(config).forEach(function (key) {
            if (key === 'cert' || key === 'key' || key === 'ca') {
                this[key] = resolveCertKeyOrCa(config[key]);
                this._userSuppliedConfigOptionNames.push(key);
            } else if (typeof this[key] === 'undefined') {
                this[key] = config[key];
                this._userSuppliedConfigOptionNames.push(key);
            } else {
                // Ignore unsupported property that would overwrite or shadow for a built-in property or method
            }
        }, this);
    }

    // Ensure no trailing slash:
    if (Array.isArray(this.url)) {
        this.url = this.url.map(function (url) {
            return url.replace(/\/$/, '');
        });
    } else if (typeof this.url === 'string') {
        this.url = this.url.replace(/\/$/, '');
    } else {
        throw new Error('config.url is required');
    }

    this.numRetries = this.numRetries || 0;
}

util.inherits(Teepee, EventEmitter);

Teepee.prototype.extractNonRequestOptions = function (obj) {
    var result = {};
    if (obj) {
        Object.keys(obj).forEach(function (key) {
            if (key !== 'method' && key !== 'headers' && key !== 'path' && key !== 'query' && key !== 'streamRows' && key !== 'eventEmitter' && key !== 'url' && key !== 'path') {
                result[key] = obj[key];
            }
        });
    }
    return result;
};

Teepee.prototype.preprocessQueryStringParameterValue = function (queryStringParameterValue) {
    return queryStringParameterValue;
};

Teepee.prototype.stringifyJsonRequestBody = JSON.stringify;

Teepee.prototype._addQueryStringToUrl = function (url, query) {
    if (typeof query !== 'undefined') {
        url += (url.indexOf('?') !== -1) ? '&' : '?';
        if (typeof query === 'string') {
            url += query;
        } else {
            // Assume object
            var params = [];
            Object.keys(query).forEach(function (key) {
                var value = query[key];
                if (Array.isArray(value)) {
                    // Turn query: {foo: ['a', 'b']} into ?foo=a&foo=b
                    value.forEach(function (valueArrayItem) {
                        params.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.preprocessQueryStringParameterValue(valueArrayItem)));
                    }, this);
                } else if (typeof value !== 'undefined') {
                    params.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.preprocessQueryStringParameterValue(value)));
                }
            }, this);
            url += params.join('&');
        }
    }
    return url;
};

Teepee.prototype.getPlaceholderValue = function (placeholderName, requestOptions) {
    if (typeof requestOptions[placeholderName] !== 'undefined') {
        return requestOptions[placeholderName];
    } else {
        var type = typeof this[placeholderName];
        if (type === 'undefined') {
            return '{' + placeholderName + '}';
        } else {
            var value = this[placeholderName];
            if (typeof value === 'function') {
                return value.call(this, requestOptions, placeholderName);
            } else {
                return String(value);
            }
        }
    }
};

Teepee.prototype.expandUrl = function (url, requestOptions) {
    requestOptions = requestOptions || {};
    var that = this;
    return url.replace(/\{((?:[^\{\}]+|\{\w+\})*)\}/g, function ($0, placeholderName) {
        if (/^\w+$/.test(placeholderName)) {
            return that.getPlaceholderValue(placeholderName, requestOptions, $0);
        } else {
            var methodName = '__placeholder_fn_' + placeholderName;
            if (!that[methodName]) {
                /*jshint evil:true*/
                that[methodName] = new Function('requestOptions', 'return ' + placeholderName.replace(/\{(\w+)\}/g, 'this.getPlaceholderValue("$1", requestOptions)') + ';');
                /*jshint evil:false*/
            }
            return that[methodName](requestOptions);
        }
    });
};

Teepee.prototype.getAgent = function () {
    if (!this.agent) {
        var agentOptions = {};
        // Pass all instance variables that originate from the user-supplied config object to the Agent constructor:
        this._userSuppliedConfigOptionNames.forEach(function (userSuppliedConfigOptionName) {
            var value = this[userSuppliedConfigOptionName];
            if (typeof value !== 'undefined') {
                agentOptions[userSuppliedConfigOptionName] = value;
            }
        }, this);
        var Agent = this.Agent || require(urlModule.parse(this.url).protocol.replace(/:$/, '')).Agent;
        this.agent = new Agent(agentOptions);
    }
    return this.agent;
};

/*
 * Perform a request
 *
 * options.headers    {Object} (optional) The HTTP headers for the request.
 * options.path       {String} (optional) The path relative to the database url.
 * options.query      {Object} (optional) The query string (objects will be JSON.stringify'ed)
 * options.body       {String|Object|Buffer|Stream} (optional) What to send. Streams are streamed, objects will be serialized as JSON,
 *                                                             and buffers are sent as-is.
 * options.numRetries {Number} (optional) The number of times to retry an operation if it fails due to a non-HTTP error such
 *                                        as a socket timeout. Defaults to the numRetries parameter given to the constructor
 *                                        (which defaults to 0). Has no effect with the onResponse and streamRows options.
 * options.streamRows {Boolean} (optional) If specified, an event emitter will be returned that emits error/metadata/row/end events.
 *                                         This is useful for big responses that you don't want to parse in one go. Defaults to false.
 */
Teepee.prototype.request = function (options, cb) {
    var that = this,
        numRetriesLeft = options.streamRows ? 0 : typeof options.numRetries !== 'undefined' ? options.numRetries : this.numRetries,
        retry = typeof options.retry !== 'undefined' ? options.retry : this.retry,
        headers = _.extend({}, options.headers),
        url = this.expandUrl(this.url, options);
    if (options.path) {
        if (/^[\/\.]/.test(options.path)) {
            // options.path is root-relative or begins with a dot, treat it as a relative url that needs to be resolved:
            url = urlModule.resolve(url + '/', options.path);
        } else {
            url += '/' + options.path;
        }
    }
    url = this._addQueryStringToUrl(url, options.query);

    var body = options.body;
    if (Buffer.isBuffer(body)) {
        headers['content-length'] = body.length; // Disables chunked encoding for buffered body
    } else if (typeof body === 'object' && typeof body.pipe !== 'function') {
        body = this.stringifyJsonRequestBody(body);
        headers['content-type'] = 'application/json';
        headers['content-length'] = Buffer.byteLength(body); // Disables chunked encoding for json body
    } else if (!body) {
        headers['content-length'] = 0; // Disables chunked encoding if there is no body
    }

    if (!('accept' in headers)) {
        headers.accept = 'application/json';
    }

    var eventEmitter = new EventEmitter(),
        urlObj = urlModule.parse(url),
        httpModule = (urlObj.protocol === 'https:' ? https : http),
        requestOptions = {
            host: urlObj.hostname,
            port: urlObj.port === null ? 80 : parseInt(urlObj.port, 10),
            method: options.method || 'GET',
            path: urlObj.path,
            headers: headers,
            agent: this.getAgent()
        },
        done = false,
        currentRequest;

    eventEmitter.abort = function () {
        done = true;
        if (currentRequest) {
            currentRequest.abort();
            currentRequest = null;
        }
    };

    function handleError(err, response) {
        var socketError;

        if (!done) {
            done = true;

            // if what came up was a plain error convert it to an httpError
            if (!err.statusCode) {
                socketError = socketErrors(err);

                if (!socketError.NotSocketError) {
                    err = socketError;
                } else {
                    // convert to a 500 internal server error
                    err = new httpErrors[500](err.message);
                }
            }

            that.emit('failedRequest', { url: url, requestOptions: requestOptions, response: response, err: err, numRetriesLeft: numRetriesLeft });
            if (cb) {
                cb(err);
            } else {
                eventEmitter.emit('error', err);
            }
        }
    }

    function handleSuccess(response) {
        if (!done) {
            done = true;
            that.emit('successfulRequest', { url: url, requestOptions: requestOptions, response: response });
            eventEmitter.emit('end');
            if (cb) {
                cb(null, response, response && response.body);
            }
        }
    }

    if (this.preprocessRequestOptions) {
        this.preprocessRequestOptions(requestOptions, options, passError(handleError, dispatchRequest));
    } else {
        dispatchRequest();
    }

    function dispatchRequest() {
        currentRequest = httpModule.request(requestOptions);
        if (eventEmitter.listeners('request').length > 0) {
            numRetriesLeft = 0;
            eventEmitter.emit('request', currentRequest);
        }
        if (body && typeof body.pipe === 'function') {
            numRetriesLeft = 0;
            body.pipe(currentRequest);
        } else {
            currentRequest.end(body);
        }
        currentRequest.on('error', function (err) {
            currentRequest = null;
            if (done) {
                return;
            }
            // Non-HTTP error (ECONNRESET, ETIMEDOUT, etc.)
            // Try again (up to numRetriesLeft times). Warning: This doesn't work when piping into the returned request,
            // so please specify numRetriesLeft:0 if you intend to do that.
            if (numRetriesLeft > 0) {
                numRetriesLeft -= 1;
                dispatchRequest();
            } else {
                handleError(err);
            }
        }).on('response', function (response) {
            var responseError;

            if (done) {
                return;
            }

            function returnSuccessOrError(err) {
                err = err || responseError;

                if (!err) {
                    handleSuccess(response);
                } else {
                    handleError(err, response);
                }
            }

            response.cacheInfo = { headers: {} };
            if (response.statusCode >= 400) {
                if (numRetriesLeft > 0 && Array.isArray(retry) && retry.indexOf(response.statusCode) !== -1) {
                    response
                        .on('data', function () {})
                        .on('error', function () {});
                    numRetriesLeft -= 1;
                    dispatchRequest();
                    return;
                }
                responseError = new httpErrors[response.statusCode]();
            } else if (response.statusCode === 304) {
                response.cacheInfo.notModified = true;
                body = null;
            }
            numRetriesLeft = 0;
            ['last-modified', 'etag', 'expires', 'cache-control', 'content-type'].forEach(function (headerName) {
                if (headerName in response.headers) {
                    response.cacheInfo.headers[headerName] = response.headers[headerName];
                }
            });

            if (!responseError) {
                eventEmitter.emit('response', response);
            }

            if (options.streamRows) {
                response.setEncoding('utf-8');
                lines(response);
                response.on('line', function (str) {
                    if (done) {
                        return;
                    }

                    // use a regex to match the JSON metadata row and emit it
                    // both populated and empty result cases are handled
                    var matchFirstLine = str.match(/^\{(.*)"(?:rows|results)":\s*\[(?:\]\}|)$/);
                    if (matchFirstLine) {
                        if (matchFirstLine[1] !== "") {
                            eventEmitter.emit('metadata', JSON.parse('{' + matchFirstLine[1].replace(/\,\s*$/, '') + '}'));
                        }
                    } else {
                        var matchLastLine = str.match(/^(".*)\}$/);
                        if (matchLastLine) {
                            eventEmitter.emit('metadata', JSON.parse('{' + matchLastLine[1] + '}'));
                        } else if (str === ']}' || str === '' || str === '],') {
                            return;
                        } else {
                            str = str.replace(/,\r?$/, '');
                            var row;
                            try {
                                row = JSON.parse(str);
                            } catch (e) {
                                handleError(httpErrors.InternalServerError("Couldn't parse line: " + str), response);
                            }
                            eventEmitter.emit('row', row);
                        }
                    }
                }).on('error', returnSuccessOrError).on('end', returnSuccessOrError);
            } else if (cb) {
                var responseBodyChunks = [];
                response.on('data', function (responseBodyChunk) {
                    responseBodyChunks.push(responseBodyChunk);
                }).on('error', returnSuccessOrError).on('end', function () {
                    currentRequest = null;
                    if (responseBodyChunks.length > 0) {
                        response.body = Buffer.concat(responseBodyChunks);
                        if (isContentTypeJson(response.headers['content-type'])) {
                            try {
                                response.body = JSON.parse(response.body.toString('utf-8'));
                            } catch (e) {
                                return handleError(new httpErrors.BadGateway('Error parsing JSON response body'), response);
                            }
                        }
                    }
                    returnSuccessOrError();
                });
            } else {
                if (responseError) {
                    response.on('data', function () {});
                }
                returnSuccessOrError();
            }
        });
    }

    return eventEmitter;
};

Teepee.prototype.quit = function () {
    // agent.destroy became available in node.js 0.12:
    if (this.agent && this.agent.destroy) {
        this.agent.destroy();
    }
};

// Expose the httpErrors module so that test suites for modules using Teepee can use the correct constructors:
Teepee.httpErrors = httpErrors;

module.exports = Teepee;
<|endoftext|>


<commit_before>/*
	The MIT License (MIT)

	Copyright (c) 2018 Fernando Bevilacqua

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
	the Software, and to permit persons to whom the Software is furnished to do so,
	subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
	FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
	IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
	CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// Namespace for IDE (web)
var IdeWeb = IdeWeb || {};

// Namespace for panels
IdeWeb.Panel = IdeWeb.Panel || {};

/**
 * Show info about the currently authenticated user.
 */
IdeWeb.Panel.Credentials = function() {
    // Call constructor of base class
    Codebot.Panel.call(this, 'User information');
};

// Lovely pants-in-the-head javascript boilerplate for OOP.
IdeWeb.Panel.Credentials.prototype = Object.create(Codebot.Panel.prototype);
IdeWeb.Panel.Credentials.prototype.constructor = IdeWeb.Panel.Credentials;

IdeWeb.Panel.Credentials.prototype.render = function() {
    Codebot.Panel.prototype.render.call(this);

    var aIde = this.getContext().plugins.get('cc.codebot.ide.web');
    var aUser = this.getParams().user;
    var aContent = '';

    // Show some nice about info about Codebot
    this.row(
        '
' +
            '' +
            '
User' + aUser.id + '
 ' +
            '
' + aUser.email + '
' +
        '
'
    );

    this.divider('Authentication');
    aContent = '
';
    aContent += '
 Sign out
';
    aContent += '
';
    this.row(aContent);

    this.divider('Account');
    aContent = '
';
    aContent += '
 Edit info
';
    aContent += '
';
    this.row(aContent);

    this.divider('Personal data');
    aContent = '
';
    aContent += '
 Download
';
    aContent += '
 Delete everything
';
    aContent += '
';
    this.row(aContent);
};
<commit_msg>Logout option works<commit_after>/*
	The MIT License (MIT)

	Copyright (c) 2018 Fernando Bevilacqua

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
	the Software, and to permit persons to whom the Software is furnished to do so,
	subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
	FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
	IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
	CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// Namespace for IDE (web)
var IdeWeb = IdeWeb || {};

// Namespace for panels
IdeWeb.Panel = IdeWeb.Panel || {};

/**
 * Show info about the currently authenticated user.
 */
IdeWeb.Panel.Credentials = function() {
    // Call constructor of base class
    Codebot.Panel.call(this, 'User information');
};

// Lovely pants-in-the-head javascript boilerplate for OOP.
IdeWeb.Panel.Credentials.prototype = Object.create(Codebot.Panel.prototype);
IdeWeb.Panel.Credentials.prototype.constructor = IdeWeb.Panel.Credentials;

IdeWeb.Panel.Credentials.prototype.render = function() {
    Codebot.Panel.prototype.render.call(this);

    var aIde = this.getContext().plugins.get('cc.codebot.ide.web');
    var aUser = this.getParams().user;
    var aContent = '';

    // Show some nice about info about Codebot
    this.row(
        '
' +
            '' +
            '
User' + aUser.id + '
 ' +
            '
' + aUser.email + '
' +
        '
'
    );

    this.divider('Authentication');
    aContent = '
';
    aContent += '
 Sign out
';
    aContent += '
';
    this.row(aContent);

    this.divider('Account');
    aContent = '
';
    aContent += '
 Edit info
';
    aContent += '
';
    this.row(aContent);

    this.divider('Personal data');
    aContent = '
';
    aContent += '
 Download
';
    aContent += '
 Delete everything
';
    aContent += '
';
    this.row(aContent);

    // Make all links clickable
    var aSelf = this;
    this.container.find('a.clickable').each(function(theIndex, theElement) {
        $(theElement).click(function() {
            var aAction = $(this).data('action');
            aSelf.handleActionClick(aAction);
        });
    });
};

IdeWeb.Panel.Credentials.prototype.handleActionClick = function(theAction) {
    var aIde = this.getContext().plugins.get('cc.codebot.ide.web');

    if(theAction == 'logout') {
        this.getContext().setLoadingScreenVisibility(true);

        aIde.api('credentials', 'logout', null, function(theData) {
            console.debug(theData);
            window.location.replace('./plugins/ide-web/login/');
        }, this);
    } else {
        alert('Sorry, this option is not available at the moment. (action=' + theAction + ')');
    }
};
<|endoftext|>