<commit_before>import { delegate } from './util';
import EventEmitter from './emitter';

const symComponent = Symbol('component');
const symRoot = Symbol('root');
const symChildren = Symbol('children');
const symInitChildren = Symbol('init-children');
const map = Array.prototype.map;

function findParent(child) {
  let ancestor = child.root;
  while (ancestor = ancestor.parentNode) {
    if (ancestor[symComponent]) {
      return ancestor[symComponent];
    }
  }

  return null;
}

export class VComponent extends EventEmitter {
  constructor(root) {
    super();

    if (this.constructor.get(root)) {
      throw new Error('cannot construct twice');
    }
    this[symRoot] = root;
    this[symRoot][symComponent] = this;
    this[symChildren] = {};
    this.name = this[symRoot].dataset.vceName
      || this.name
      || this.constructor.vceName
      || this[symRoot].dataset.vce;

    this.renderChildren()
      .then(this.bindDomEvents.bind(this))
      .then(this.init.bind(this));
  }

  removeChild(child) {
    const name = child.name;
    const idx = this[symChildren][name].indexOf(child);
    if (idx === -1) {
      throw new Error('child not found');
    }

    child.off();
    child.root.parentNode.removeChild(child.root);
    this[symChildren][name].splice(idx, 1);
  }

  renderChildren() {
    return this.constructor.render(this.root).then(
      this[symInitChildren].bind(this),
    );
  }

  [symInitChildren](children) {
    const myChildren = {};
    children.forEach((child) => {
      if (findParent(child) !== this) {
        return;
      }

      const name = child.name;
      myChildren[name] = myChildren[name] || [];
      if (myChildren[name].indexOf(child) === -1) {
        myChildren[name].push(child);
      }
    });

    this[symChildren] = myChildren;
  }

  createChild(childElement) {
    if (typeof childElement === 'string') {
      childElement = this.qs(childElement);
    }
    if (!this.root.contains(childElement)) {
      throw new Error('cannot find child element');
    }
    const child = this.constructor.get(childElement);
    if (child) {
      return Promise.resolve(child);
    }

    return this.constructor.create(childElement).then((newChild) => {
      const parent = findParent(newChild);
      parent[symChildren][newChild.name] =
        parent[symChildren][newChild.name] || [];
      parent[symChildren][newChild.name].push(newChild);

      return newChild;
    });
  }

  qs(sel) {
    return this.root.querySelector(sel);
  }

  qsa(sel) {
    return this.root.querySelectorAll(sel);
  }

  replaceRoot(root) {
    const parent = this.root.parentNode;
    parent.insertBefore(root, this.root);
    parent.removeChild(this.root);

    delete this.root[symComponent];
    this[symRoot] = root;
    root[symComponent] = this;

    return this.renderChildren()
      .then(this.bindDomEvents.bind(this));
  }

  // eslint-disable-next-line class-methods-use-this
  init() {}

  bindDomEvents() {
    if (this.events) {
      this.events.forEach((args) => {
        if (typeof args === 'string') {
          this.listen(...args.split(/\//));
        } else {
          this.listen(...args);
        }
      });
    }
  }

  listen(type, el, handler, useCapture) {
    type = typeof type === 'string' ? type.split(/ /) : type;
    handler = typeof handler === 'function'
      ? handler.bind(this)
      : this[handler].bind(this);

    type.forEach((eventName) => {
      if (el === '@root') {
        this.root.addEventListener(
          eventName,
          handler,
          !!useCapture,
        );
      } else if (typeof el === 'string') { // delegate
        delegate(this.root, el, eventName, handler, useCapture);
      } else {
        el.addEventListener(
          eventName,
          handler,
          !!useCapture,
        );
      }
    });

    return this;
  }

  get root() {
    return this[symRoot];
  }

  get children() {
    return this[symChildren];
  }

  static render(root = document) {
    return Promise.all(map.call(root.querySelectorAll('[data-vce]'), el =>
      this.create(el),
    ));
  }

  static create(el) {
    return Promise.resolve(this.loader(el.dataset.vce))
      .then(C => this.get(el) || new C(el));
  }

  static get(el) {
    return el[symComponent];
  }

  static loader(name) {
    return this.components[name];
  }
}

VComponent.components = {};
<commit_msg>add `qsChild` / `qsChildren` utility method<commit_after>import { delegate } from './util';
import EventEmitter from './emitter';

const symComponent = Symbol('component');
const symRoot = Symbol('root');
const symChildren = Symbol('children');
const symInitChildren = Symbol('init-children');
const map = Array.prototype.map;

function findParent(child) {
  let ancestor = child.root;
  while (ancestor = ancestor.parentNode) {
    if (ancestor[symComponent]) {
      return ancestor[symComponent];
    }
  }

  return null;
}

export class VComponent extends EventEmitter {
  constructor(root) {
    super();

    if (VComponent.get(root)) {
      throw new Error('cannot construct twice');
    }
    this[symRoot] = root;
    this[symRoot][symComponent] = this;
    this[symChildren] = {};
    this.name = this[symRoot].dataset.vceName
      || this.name
      || this.constructor.vceName
      || this[symRoot].dataset.vce;

    this.renderChildren()
      .then(this.bindDomEvents.bind(this))
      .then(this.init.bind(this));
  }

  removeChild(child) {
    const name = child.name;
    const idx = this[symChildren][name].indexOf(child);
    if (idx === -1) {
      throw new Error('child not found');
    }

    child.off();
    child.root.parentNode.removeChild(child.root);
    this[symChildren][name].splice(idx, 1);
  }

  renderChildren() {
    return VComponent.render(this.root).then(
      this[symInitChildren].bind(this),
    );
  }

  [symInitChildren](children) {
    const myChildren = {};
    children.forEach((child) => {
      if (findParent(child) !== this) {
        return;
      }

      const name = child.name;
      myChildren[name] = myChildren[name] || [];
      if (myChildren[name].indexOf(child) === -1) {
        myChildren[name].push(child);
      }
    });

    this[symChildren] = myChildren;
  }

  createChild(childElement) {
    if (typeof childElement === 'string') {
      childElement = this.qs(childElement);
    }
    if (!this.root.contains(childElement)) {
      throw new Error('cannot find child element');
    }
    const child = VComponent.get(childElement);
    if (child) {
      return Promise.resolve(child);
    }

    return VComponent.create(childElement).then((newChild) => {
      const parent = findParent(newChild);
      parent[symChildren][newChild.name] =
        parent[symChildren][newChild.name] || [];
      parent[symChildren][newChild.name].push(newChild);

      return newChild;
    });
  }

  qs(sel) {
    return this.root.querySelector(sel);
  }

  qsa(sel) {
    return this.root.querySelectorAll(sel);
  }

  qsChild(sel) {
    return VComponent.get(this.qs(sel));
  }

  qsChildren(sel) {
    const children = [];
    this.qsa(sel).forEach((el) => {
      const child = VComponent.get(el);
      if (child) {
        children.push(child);
      }
    });

    return children;
  }

  replaceRoot(root) {
    const parent = this.root.parentNode;
    parent.insertBefore(root, this.root);
    parent.removeChild(this.root);

    delete this.root[symComponent];
    this[symRoot] = root;
    root[symComponent] = this;

    return this.renderChildren()
      .then(this.bindDomEvents.bind(this));
  }

  // eslint-disable-next-line class-methods-use-this
  init() {}

  bindDomEvents() {
    if (this.events) {
      this.events.forEach((args) => {
        if (typeof args === 'string') {
          this.listen(...args.split(/\//));
        } else {
          this.listen(...args);
        }
      });
    }
  }

  listen(type, el, handler, useCapture) {
    type = typeof type === 'string' ? type.split(/ /) : type;
    handler = typeof handler === 'function'
      ? handler.bind(this)
      : this[handler].bind(this);

    type.forEach((eventName) => {
      if (el === '@root') {
        this.root.addEventListener(
          eventName,
          handler,
          !!useCapture,
        );
      } else if (typeof el === 'string') { // delegate
        delegate(this.root, el, eventName, handler, useCapture);
      } else {
        el.addEventListener(
          eventName,
          handler,
          !!useCapture,
        );
      }
    });

    return this;
  }

  get root() {
    return this[symRoot];
  }

  get children() {
    return this[symChildren];
  }

  static render(root = document) {
    return Promise.all(map.call(root.querySelectorAll('[data-vce]'), el =>
      this.create(el),
    ));
  }

  static create(el) {
    return Promise.resolve(this.loader(el.dataset.vce))
      .then(C => this.get(el) || new C(el));
  }

  static get(el) {
    return el[symComponent];
  }

  static loader(name) {
    return this.components[name];
  }
}

VComponent.components = {};
<|endoftext|>


<commit_before>/**
 * Dependencies
 */

var Path = require('path'),
    _ = require('lodash'),
    utils = require('./utils'),
    mocha = require('mocha'),
    EventEmitter = require('events').EventEmitter;

/**
 * Test Runner
 *
 * @param {Object} options
 * @api public
 */

module.exports = function(options) {

  if(!options.adapter) throw new Error('Must supply an adapter');
  if(!options.interfaces) throw new Error('Must supply a list of supported interfaces');
  if(!Array.isArray(options.interfaces)) throw new Error('Must supply a list of supported interfaces');

  this.adapter = options.adapter;
  this.config = options.config || {};
  this.interfaces = options.interfaces;

  // Attach config to adapter
  this.adapter.config = this.config;

  // Globalize Adapter
  global.Adapter = this.adapter;

  // Build an array of files to test
  var filter = '\\.(' + ['js'].join('|') + ')$';

  var files = [];

  this.interfaces.forEach(function(interface) {
    var interfacePath = Path.resolve(__dirname,'../interfaces/' + interface);
    files = files.concat(utils.fileLookup(interfacePath, filter, true));
  });

  // Build a Mocha Runner
  var test = new mocha();

  // Build a global event emitter to keep track of what models we need to clean up
  global.Events = new EventEmitter();

  // Add cleanup to files
  files.push(Path.resolve(__dirname, './cleanup'));

  // Allow Adapter to be a global without warning about a leak
  test.globals([Adapter, Events]);
  test.files = files;

  test.run(function(err) {
    process.exit(1);
  });
};

/**
 * File Loader
 *
 * @param {String} path
 * @param {Boolean} recursive
 * @return {Array}
 */

// function lookupFiles(path, recursive) {
//   var files = [];

//   if (!fs.existsSync(path)) path += '.js';

//   // Check if path is a file or directory
//   var stat = fs.statSync(path);
//   if (stat.isFile()) return path;

//   fs.readdirSync(path).forEach(function(file) {
//     file = Path.join(path, file);

//     // If file is directory recursivly call function again
//     var stat = fs.statSync(file);
//     if (stat.isDirectory()) {
//       if (recursive) files = files.concat(lookupFiles(file, recursive));
//       return;
//     }

//     var re = new RegExp('\\.(' + ['js'].join('|') + ')$');

//     if (!stat.isFile() || !re.test(file) || Path.basename(file)[0] == '.') return;
//     files.push(file);
//   });

//   return files;
// }
<commit_msg>exit test properly<commit_after>/**
 * Dependencies
 */

var Path = require('path'),
    _ = require('lodash'),
    utils = require('./utils'),
    mocha = require('mocha'),
    EventEmitter = require('events').EventEmitter;

/**
 * Test Runner
 *
 * @param {Object} options
 * @api public
 */

module.exports = function(options) {

  if(!options.adapter) throw new Error('Must supply an adapter');
  if(!options.interfaces) throw new Error('Must supply a list of supported interfaces');
  if(!Array.isArray(options.interfaces)) throw new Error('Must supply a list of supported interfaces');

  this.adapter = options.adapter;
  this.config = options.config || {};
  this.interfaces = options.interfaces;

  // Attach config to adapter
  this.adapter.config = this.config;

  // Globalize Adapter
  global.Adapter = this.adapter;

  // Build an array of files to test
  var filter = '\\.(' + ['js'].join('|') + ')$';

  var files = [];

  this.interfaces.forEach(function(interface) {
    var interfacePath = Path.resolve(__dirname,'../interfaces/' + interface);
    files = files.concat(utils.fileLookup(interfacePath, filter, true));
  });

  // Build a Mocha Runner
  var test = new mocha();

  // Build a global event emitter to keep track of what models we need to clean up
  global.Events = new EventEmitter();

  // Add cleanup to files
  files.push(Path.resolve(__dirname, './cleanup'));

  // Allow Adapter to be a global without warning about a leak
  test.globals([Adapter, Events]);
  test.files = files;

  test.run(function(err) {
    process.exit(0);
  });
};

/**
 * File Loader
 *
 * @param {String} path
 * @param {Boolean} recursive
 * @return {Array}
 */

// function lookupFiles(path, recursive) {
//   var files = [];

//   if (!fs.existsSync(path)) path += '.js';

//   // Check if path is a file or directory
//   var stat = fs.statSync(path);
//   if (stat.isFile()) return path;

//   fs.readdirSync(path).forEach(function(file) {
//     file = Path.join(path, file);

//     // If file is directory recursivly call function again
//     var stat = fs.statSync(file);
//     if (stat.isDirectory()) {
//       if (recursive) files = files.concat(lookupFiles(file, recursive));
//       return;
//     }

//     var re = new RegExp('\\.(' + ['js'].join('|') + ')$');

//     if (!stat.isFile() || !re.test(file) || Path.basename(file)[0] == '.') return;
//     files.push(file);
//   });

//   return files;
// }
<|endoftext|>


<commit_before>/* global require, module */

'use strict';

var url = require('url');
var path = require('path');

var url42 = require('url42');

function linksAbsolutizer(options) {
  if (! options) {
    options = {};
  }

  var rootPath = options.rootPath || '/';
  var embedByDefault = options.embed !== undefined ? !!options.embed : true;

  function formatLinkHref(baseUri, link) {
    var href = link.href;

    if (! href) {
      return;
    }

    var parsedBase = url42(baseUri);

    if (typeof href === 'string') {
      if (href[0] === '/') {
        parsedBase.setPath(href);
      }

      if (href[0] === '.') {
        parsedBase.joinPath(href);
      }

      if (href[0] === '+') {
        parsedBase.mergePath(href.path || href.substr(1));
      }

      if (~href.indexOf('?')) {
        parsedBase.setQuery(href);
      }

      if (href[0] === '&') {
        parsedBase.mergeQuery(href.path || ('?' + href.substr(1)));
      }
    } else {
      var path = href.path;

      if (href.setPath) {
        parsedBase.setPath(path);
      }

      if (href.joinPath) {
        parsedBase.joinPath(path);
      }

      if (href.mergePath) {
        parsedBase.mergePath(href.path);
      }

      if (href.setQuery) {
        parsedBase.setQuery(path);
      }

      if (href.mergeQuery) {
        parsedBase.mergeQuery(href.path);
      }
    }

    if (parsedBase.changed){
      link.href = url.format(parsedBase);
      return;
    }

    link.href = url.resolve(baseUri, href);
  }

  function absolutizeLinks(baseUri, item, addEmbed) {
    var links = item._links;

    if (! links) {
      links = {};
    }

    Object.keys(links).forEach(function (rel) {
      var link = links[rel];

      if (! Array.isArray(link)) {
        link = [ link ];
      }

      return link.forEach(formatLinkHref.bind(this, baseUri));
    });

    var embedded = item._embedded;

    if (! embedded) {
      return;
    }

    if (! addEmbed) {
      delete item._embedded;
      return;
    }

    Object.keys(embedded).forEach(function (rel) {
      var item = embedded[rel];

      if (! Array.isArray(item)) {
        item = [ item ];
      }

      item.forEach(function (item) {
        absolutizeLinks(baseUri, item, addEmbed);
      });
    });
  }

  return function linksAbsolutizerMiddleware(req, res, next) {
    var result = res.result;

    if (! result) {
      return next();
    }

    var links = result._links;

    if (typeof links !== 'object') {
      links = {};
      result._links = links;
    }

    var parsedUri = url.parse(req.uri, true);

    var baseUri = url.format(parsedUri);

    var nextLinkHref = links.next && links.next.href;

    // no need to have a 'next' link if it points to index
    if (nextLinkHref === rootPath) {
      delete links.next;
    }

    if (! links.self) {
      links.self = {
        href: parsedUri.path
      };
    }

    if (links.self.href !== rootPath) {
      links.index = {
        href: rootPath
      };
    }

    var addEmbed = 'embed' in parsedUri.query || embedByDefault;

    absolutizeLinks(baseUri, result, addEmbed);

    res.send(result);

    next();
  };
}

module.exports = linksAbsolutizer;
<commit_msg>removing res.send, letting the decision to the user to send or not<commit_after>/* global require, module */

'use strict';

var url = require('url');
var path = require('path');

var url42 = require('url42');

function linksAbsolutizer(options) {
  if (! options) {
    options = {};
  }

  var rootPath = options.rootPath || '/';
  var embedByDefault = options.embed !== undefined ? !!options.embed : true;

  function formatLinkHref(baseUri, link) {
    var href = link.href;

    if (! href) {
      return;
    }

    var parsedBase = url42(baseUri);

    if (typeof href === 'string') {
      if (href[0] === '/') {
        parsedBase.setPath(href);
      }

      if (href[0] === '.') {
        parsedBase.joinPath(href);
      }

      if (href[0] === '+') {
        parsedBase.mergePath(href.path || href.substr(1));
      }

      if (~href.indexOf('?')) {
        parsedBase.setQuery(href);
      }

      if (href[0] === '&') {
        parsedBase.mergeQuery(href.path || ('?' + href.substr(1)));
      }
    } else {
      var path = href.path;

      if (href.setPath) {
        parsedBase.setPath(path);
      }

      if (href.joinPath) {
        parsedBase.joinPath(path);
      }

      if (href.mergePath) {
        parsedBase.mergePath(href.path);
      }

      if (href.setQuery) {
        parsedBase.setQuery(path);
      }

      if (href.mergeQuery) {
        parsedBase.mergeQuery(href.path);
      }
    }

    if (parsedBase.changed){
      link.href = url.format(parsedBase);
      return;
    }

    link.href = url.resolve(baseUri, href);
  }

  function absolutizeLinks(baseUri, item, addEmbed) {
    var links = item._links;

    if (! links) {
      links = {};
    }

    Object.keys(links).forEach(function (rel) {
      var link = links[rel];

      if (! Array.isArray(link)) {
        link = [ link ];
      }

      return link.forEach(formatLinkHref.bind(this, baseUri));
    });

    var embedded = item._embedded;

    if (! embedded) {
      return;
    }

    if (! addEmbed) {
      delete item._embedded;
      return;
    }

    Object.keys(embedded).forEach(function (rel) {
      var item = embedded[rel];

      if (! Array.isArray(item)) {
        item = [ item ];
      }

      item.forEach(function (item) {
        absolutizeLinks(baseUri, item, addEmbed);
      });
    });
  }

  return function linksAbsolutizerMiddleware(req, res, next) {
    var result = res.result;

    if (! result) {
      return next();
    }

    var links = result._links;

    if (typeof links !== 'object') {
      links = {};
      result._links = links;
    }

    var parsedUri = url.parse(req.uri, true);

    var baseUri = url.format(parsedUri);

    var nextLinkHref = links.next && links.next.href;

    // no need to have a 'next' link if it points to index
    if (nextLinkHref === rootPath) {
      delete links.next;
    }

    if (! links.self) {
      links.self = {
        href: parsedUri.path
      };
    }

    if (links.self.href !== rootPath) {
      links.index = {
        href: rootPath
      };
    }

    var addEmbed = 'embed' in parsedUri.query || embedByDefault;

    absolutizeLinks(baseUri, result, addEmbed);

    next();
  };
}

module.exports = linksAbsolutizer;
<|endoftext|>


<commit_before>var Jvent = require('jvent'),
    inherits = require('inherits'),
    Timer = require('./timer'),
    Playlist = require('./playlist');

function windowWidth() {
    if (self.innerHeight) {
        return self.innerWidth;
    }

    if (document.documentElement && document.documentElement.clientHeight) {
        return document.documentElement.clientWidth;
    }

    if (document.body) {
        return document.body.clientWidth;
    }
}

function windowHeight() {
    if (self.innerHeight) {
        return self.innerHeight;
    }

    if (document.documentElement && document.documentElement.clientHeight) {
        return document.documentElement.clientHeight;
    }

    if (document.body) {
        return document.body.clientHeight;
    }
}

function setStyles(el, props) {
    var cssString = '';
    for (var p in props) {
        cssString += p + ':' + props[p] + ';';
    }
    el.style.cssText += ';' + cssString;
}

function findPoster(playlist) {
    var poster,
        item;

    for (var i in playlist) {
        item = playlist[i];

        if (item.constructor === Array) {
            poster = findPoster(item);
        } else {
            if (item.type.search(/^image/) > -1) {
                return item;
            }
        }

        if (poster) {
            return poster;
        }
    }
}

function createEl(name, props) {
    var el = document.createElement(name);
    for (var prop in props) {
        el[prop] = props[prop];
    }
    return el;
}

function DriveIn() {
    this._listeners = [];

    this.parentEl = null;
    this.mediaEl = null;
    this.mute = true;
    this.currMediaType = null;
    this.mediaAspect = 16 / 9;
    this.playlist = null;
    this.loop = true;
    this.slideshow = false;

    this.playlistLength = 0;
    this.currentItem = 0;
    this.slideshowItemDuration = 10;
    this._slideshowTimer = null;

    this.poster = null;

    this.loading = true;
}

inherits(DriveIn, Jvent);

DriveIn.prototype._updateSize = function(mediaEl, mediaType, mediaAspect) {

    var container = document.body,
        parentEl = this.parentEl,
        currMediaType = this.currMediaType;

    var winW = windowWidth(),
        winH = windowHeight(),
        containerW = container.offsetWidth < winW ? container.offsetWidth : winW,
        containerH = container.offsetHeight < winH ? container.offsetHeight : winH,
        containerAspect = containerW / containerH;

    if (container.nodeName === 'BODY') {
        setStyles(container, {
            height: 'auto'
        });

        if (winH > container.offsetHeight) {
            setStyles(container, {
                height: '100%'
            });
            setStyles(document.documentElement, {
                height: '100%'
            });
        }
    }

    if (containerAspect < mediaAspect) {
        // taller
        if (mediaType == 'video') {

            setStyles(parentEl, {
                width: Math.ceil(containerH * mediaAspect) + 'px',
                height: containerH + 1 + 'px'
            });

            setStyles(mediaEl, {
                width: Math.ceil(containerH * mediaAspect) + 'px',
                height: containerH + 'px'
            });

        } else {

            // is image
            setStyles(mediaEl, {
                width: 'auto',
                height: containerH + 'px'
                    // top: '0px',
                    // left: (-(containerH * mediaAspect - containerW) / 2) + 'px'
            });
        }

    } else {

        // wider
        if (mediaType == 'video') {

            setStyles(parentEl, {
                width: containerW + 'px',
                height: Math.ceil(containerW / mediaAspect) + 1 + 'px'
            });

            setStyles(mediaEl, {
                width: parentEl.offsetWidth + 'px',
                height: 'auto'
            });

        } else {

            // is image
            setStyles(mediaEl, {
                width: containerW + 'px',
                height: 'auto'
                    // top: (-(containerW / mediaAspect - containerH) / 2) + 'px',
                    // left: '0px'
            });
        }
    }
};

DriveIn.prototype._setVideoData = function() {
    var mediaEl = this.mediaEl;
    this.mediaAspect = mediaEl.videoWidth / mediaEl.videoHeight;
    this._updateSize(mediaEl, 'video', this.mediaAspect);
};

DriveIn.prototype._setImageData = function(data) {
    this.mediaAspect = data.naturalWidth / data.naturalHeight;
    this._updateSize(this.mediaEl, 'image', this.mediaAspect);
};

DriveIn.prototype._playVideoItem = function(item, itemNum) {
    var mediaEl = this.mediaEl,
        source,
        src,
        posterSrc,
        canPlayType;

    for (var i in item) {
        source = item[i];
        canPlayType = mediaEl.canPlayType(source.type);
        if (canPlayType === 'probably') {
            src = source.src;
        } else if (canPlayType && !src) {
            src = source.src;
        }

        if (source.type.search(/^image/) === 0 && !src) {
            posterSrc = source.src;
        }
    }

    if (src) {

        this.emit('media.loading');

        mediaEl.src = src;

        if (posterSrc) {
            mediaEl.poster = posterSrc;
        }

        if (this.playlistLength < 2) mediaEl.loop = true;
        if (this.mute) this.setVolume(0);

        this.currentItem = itemNum;

        mediaEl.load();

    } else if (posterSrc || this.poster) {

        // Fallback to a slideshow.
        this.slideshow = true;
        this.createMediaEl();
        this._playImageItem(item, itemNum);

    } else {

        this.emit('media.error', new Error('No playable source'));
    }
};

DriveIn.prototype._playImageItem = function(item, itemNum) {
    var source,
        src;

    for (var i in item) {
        source = item[i];
        if (source.type.search(/^image/) === 0 && !src) {
            src = source.src;
        }
    }

    if (!src && this.poster) {
        src = this.poster.src;
    }

    if (src) {

        this.emit('media.loading');

        this.mediaEl.src = src;
        this.currentItem = itemNum;

    } else {

        this.emit('media.error', new Error('No playable source'));
    }
};

DriveIn.prototype._playItem = function(item, itemNum) {
    if (this.currMediaType === 'video') {
        this._playVideoItem(item, itemNum);
    }

    if (this.currMediaType === 'image') {
        this._playImageItem(item, itemNum);
    }
};

DriveIn.prototype._loadPlaylist = function(playlist) {
    this.playlist = playlist;
    this.playlistLength = playlist.length;
    this._playItem(playlist[0], 0);
};

DriveIn.prototype._addListener = function(element, event, handler) {

    element.addEventListener(event, handler);

    this._listeners.push({
        element: element,
        event: event,
        handler: handler
    });
};

DriveIn.prototype._removeAllListeners = function() {
    var listeners = this._listeners,
        listen;

    for (var i in listeners) {
        listen = listeners[i];
        listen.element.removeEventListener(listen.event, listen.handler);
    }
};

DriveIn.prototype._attachVideoListeners = function() {
    var self = this,
        mediaEl = this.mediaEl;

    function onLoadedMetadata(data) {
        self._setVideoData(data);
        self.emit('media.metadata', data);
    }

    function onPlaying() {
        self.emit('media.playing', self.currentItem);
    }

    function onPause() {
        self.emit('media.pause');
    }

    function onProgress(event) {
        // Sort of buggy, with readyState and buffer being inconsistent...
        var percent = 0,
            ready = event.target.readyState,
            network = event.target.networkState,
            buffered = event.target.buffered,
            total = event.target.duration;

        if (network === 1 && ready === 0) {
            percent = 100;
        }

        if (ready > 0) {
            var end = buffered.end(0);
            percent = (end/total) * 100;
        }

        self.emit('media.progress', percent);
    }

    function onEnded() {
        if (!self._seeking) {
            self.emit('media.ended', self.currentItem);
            if (self.playlistLength > 1 && self.loop) {
                var itemNum = 0;
                if (self.currentItem + 1 < self.playlistLength) {
                    itemNum = self.currentItem + 1;
                }
                self.play(itemNum);
            }
        }
    }

    function onCanPlay() {
        self.emit('media.canplay');
        mediaEl.play();
        if (self._seeking) {
            self._seeking = false;
        }
    }

    this._addListener(mediaEl, 'loadedmetadata', onLoadedMetadata);
    this._addListener(mediaEl, 'playing', onPlaying);
    this._addListener(mediaEl, 'pause', onPause);
    this._addListener(mediaEl, 'ended', onEnded);
    this._addListener(mediaEl, 'canplay', onCanPlay);
    this._addListener(mediaEl, 'progress', onProgress, false);
};

DriveIn.prototype._attachImageListeners = function() {
    var self = this,
        mediaEl = this.mediaEl;

    function ended() {
        var event = new Event('ended');
        self.mediaEl.dispatchEvent(event);
    }

    function onPause() {
        self.emit('media.pause');
    }

    function onLoad() {
        self.emit('media.canplay');
        if (self.playlistLength > 1) {
            if (self._slideshowTimer) self._slideshowTimer.destroy();
            self._slideshowTimer = new Timer(ended, self.slideshowItemDuration * 1000);

            self._slideshowTimer.on('pause', onPause);
        }

        self._setImageData(this);
        self.emit('media.metadata', this);
        self.emit('media.playing', self.currentItem);
    }

    function onEnded() {
        self.emit('media.ended', self.currentItem);
        if (self.playlistLength > 1 && self.loop) {
            var itemNum = (self.currentItem + 1 < self.playlistLength) ? self.currentItem + 1 : 0;
            self.play(itemNum);
        }
    }

    this._addListener(mediaEl, 'load', onLoad);
    this._addListener(mediaEl, 'ended', onEnded);
};

DriveIn.prototype._attachListeners = function() {
    var self = this;

    function onResize() {
        window.requestAnimationFrame(function () {
            if (self.metadataLoaded) {
                self._updateSize(self.mediaEl, self.currMediaType, self.mediaAspect);
            }
        });
    }

    this._addListener(window, 'resize', onResize);

    if (this.currMediaType === 'video') {
        this._attachVideoListeners();
    } else {
        this._attachImageListeners();
    }

    this.on('media.canplay', function () {
        self.canplay = true;
    });

    this.on('media.metadata', function () {
        self.metadataLoaded = true;
    });

    this.on('media.loading', function () {
        self.canplay = false;
        self.metadataLoaded = false;
    });
};

DriveIn.prototype._setParent = function(el) {
    this.parentEl = el;

    setStyles(this.parentEl, {
        position: 'absolute',
        display: 'block',
        transform: 'translate3d(-50%,-50%,0)',
        '-webkit-transform': 'translate3d(-50%,-50%,0)',
        left: '50%',
        top: '50%'
    });

    return this.parentEl;
};

DriveIn.prototype.cleanup = function() {
    var el = this.parentEl;
    while (el.firstChild) {
        el.removeChild(el.firstChild);
    }
};

DriveIn.prototype.createMediaEl = function() {
    var mediaEl,
        mediaType;

    if (this.mediaEl) this.cleanup();

    if (this.slideshow) {
        mediaType = 'image';
        mediaEl = createEl('img');
    } else {
        mediaType = 'video';
        mediaEl = createEl('video', {
            height: 1,
            width: 1
            // preload: 'auto'
        });
    }

    this.mediaEl = mediaEl;
    this.currMediaType = mediaType;

    setStyles(this.mediaEl, {
        display: 'block'
    });

    this.parentEl.appendChild(mediaEl);
};

DriveIn.prototype.init = function(options) {
    options = options || {};

    if ('ontouchstart' in window || options.slideshow) {
        this.slideshow = true;
    }

    this._setParent(options.el);
    this.createMediaEl();
    this._attachListeners();
};

DriveIn.prototype.show = function(rawItem, options) {
    if (rawItem.constructor === Array) {
        return this.showPlaylist([rawItem], options);
    }

    if (rawItem.constructor === Object) {
        return this.showPlaylist([
            [rawItem]
        ], options);
    }

    return this.showPlaylist([Playlist.makePlaylistItem(rawItem)], options);
};

DriveIn.prototype.showPlaylist = function(rawPlaylist, options) {
    if (options.hasOwnProperty('mute')) {
        this.mute = options.mute;
    }
    if (options.hasOwnProperty('loop')) {
        this.loop = options.loop;
    }
    var playlist = Playlist.makePlaylist(rawPlaylist);

    if (options.poster) {
        this.poster = options.poster;
    } else {
        this.poster = findPoster(playlist);
    }

    this._loadPlaylist(playlist);
};

DriveIn.prototype.setVolume = function(level) {
    if (this.currMediaType === 'image') {
        return;
    }

    if (level === 0) {
        this.mute = true;
        this.mediaEl.muted = true;
        this.mediaEl.volume = 0;
    } else {
        this.mute = false;
        this.mediaEl.muted = false;
        this.mediaEl.volume = level;
    }
};

DriveIn.prototype.getMedia = function() {
    return this.mediaEl;
};

DriveIn.prototype.getPlaylist = function() {
    return this.playlist;
};

DriveIn.prototype.getItem = function(itemNum) {
    return this.playlist[itemNum];
};

DriveIn.prototype.play = function(itemNum) {
    if (typeof itemNum === 'number') {
        this._playItem(this.playlist[itemNum], itemNum);
    } else {
        if (this.currMediaType === 'video') {
            this.mediaEl.play();
        } else {
            if (this._slideshowTimer) {
                this._slideshowTimer.resume();
            }
        }
    }
};

DriveIn.prototype.pause = function() {
    if (this.currMediaType === 'video') {
        this.mediaEl.pause();
    } else {
        if (this._slideshowTimer) {
            this._slideshowTimer.pause();
        }
    }
};

DriveIn.prototype.close = function() {
    this._removeAllListeners();
    this.cleanup();
    if (this._slideshowTimer) {
        this._slideshowTimer.destroy();
        delete this._slideshowTimer;
    }
};

DriveIn.prototype.currentTime = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.currentTime;
    } else {
        return this._slideshowTimer.currentTime();
    }
};

DriveIn.prototype.currentTime = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.currentTime;
    } else {
        return this._slideshowTimer.currentTime();
    }
};

DriveIn.prototype.seekTo = function(time) {
    this._seeking = true;
    if (this.currMediaType === 'video') {
        this.mediaEl.currentTime = time;
    } else {
        // Not enabled for image slideshows
    }
};

DriveIn.prototype.duration = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.duration;
    } else {
        return this.slideshowItemDuration;
    }
};

module.exports = DriveIn;
<commit_msg>make better use of poster<commit_after>var Jvent = require('jvent'),
    inherits = require('inherits'),
    Timer = require('./timer'),
    Playlist = require('./playlist');

function windowWidth() {
    if (self.innerHeight) {
        return self.innerWidth;
    }

    if (document.documentElement && document.documentElement.clientHeight) {
        return document.documentElement.clientWidth;
    }

    if (document.body) {
        return document.body.clientWidth;
    }
}

function windowHeight() {
    if (self.innerHeight) {
        return self.innerHeight;
    }

    if (document.documentElement && document.documentElement.clientHeight) {
        return document.documentElement.clientHeight;
    }

    if (document.body) {
        return document.body.clientHeight;
    }
}

function setStyles(el, props) {
    var cssString = '';
    for (var p in props) {
        cssString += p + ':' + props[p] + ';';
    }
    el.style.cssText += ';' + cssString;
}

function findPoster(playlist) {
    var poster,
        item;

    for (var i in playlist) {
        item = playlist[i];

        if (item.constructor === Array) {
            poster = findPoster(item);
        } else {
            if (item.type.search(/^image/) > -1) {
                return item;
            }
        }

        if (poster) {
            return poster;
        }
    }
}

function createEl(name, props) {
    var el = document.createElement(name);
    for (var prop in props) {
        el[prop] = props[prop];
    }
    return el;
}

function DriveIn() {
    this._listeners = [];

    this.parentEl = null;
    this.mediaEl = null;
    this.placeholderEl = null;

    this.mute = true;
    this.currMediaType = null;
    this.mediaAspect = 16 / 9;
    this.playlist = null;
    this.loop = true;
    this.slideshow = false;

    this.playlistLength = 0;
    this.currentItem = 0;
    this.slideshowItemDuration = 10;
    this._slideshowTimer = null;

    this.poster = null;

    this.loading = true;
}

inherits(DriveIn, Jvent);

DriveIn.prototype._updateSize = function(mediaEl, mediaType, mediaAspect) {

    var pad = 1,
        container = document.body,
        parentEl = this.parentEl,
        currMediaType = this.currMediaType;

    var winW = windowWidth(),
        winH = windowHeight(),
        containerW = container.offsetWidth < winW ? container.offsetWidth : winW,
        containerH = container.offsetHeight < winH ? container.offsetHeight : winH,
        containerAspect = containerW / containerH;

    if (container.nodeName === 'BODY') {
        setStyles(container, {
            height: 'auto'
        });

        if (winH > container.offsetHeight) {
            setStyles(container, {
                height: '100%'
            });
            setStyles(document.documentElement, {
                height: '100%'
            });
        }
    }

    if (containerAspect < mediaAspect) {

        // taller

        setStyles(parentEl, {
            width: Math.ceil(containerH * mediaAspect) + 'px',
            height: containerH + pad + 'px'
        });

        if (mediaType === 'video') {

            setStyles(mediaEl, {
                width: Math.ceil(containerH * mediaAspect) + 'px',
                height: containerH + 'px'
            });

        } else {

            // is image
            setStyles(mediaEl, {
                width: 'auto',
                height: containerH + pad + 'px'
                    // top: '0px',
                    // left: (-(containerH * mediaAspect - containerW) / 2) + 'px'
            });
        }

    } else {

        // wider

        setStyles(parentEl, {
            width: containerW + 'px',
            height: Math.ceil(containerW / mediaAspect) + 1 + 'px'
        });

        if (mediaType === 'video') {

            setStyles(mediaEl, {
                width: parentEl.offsetWidth + 'px',
                height: 'auto'
            });

        } else {

            // is image
            setStyles(mediaEl, {
                width: containerW + 'px',
                height: 'auto'
                    // top: (-(containerW / mediaAspect - containerH) / 2) + 'px',
                    // left: '0px'
            });
        }
    }
};

DriveIn.prototype._setVideoData = function() {
    var mediaEl = this.mediaEl;
    this.mediaAspect = mediaEl.videoWidth / mediaEl.videoHeight;
    this._updateSize(mediaEl, 'video', this.mediaAspect);
};

DriveIn.prototype._setImageData = function(data) {
    this.mediaAspect = data.naturalWidth / data.naturalHeight;
    this._updateSize(this.mediaEl, 'image', this.mediaAspect);
};

DriveIn.prototype._playVideoItem = function(item, itemNum) {
    var mediaEl = this.mediaEl,
        source,
        src,
        posterSrc,
        canPlayType;

    for (var i in item) {
        source = item[i];
        canPlayType = mediaEl.canPlayType(source.type);
        if (canPlayType === 'probably') {
            src = source.src;
        } else if (canPlayType && !src) {
            src = source.src;
        }

        if (source.type.search(/^image/) === 0 && !posterSrc) {
            posterSrc = source.src;
        }
    }

    if (src) {

        this.emit('media.loading');

        mediaEl.src = src;

        if (posterSrc) {
            mediaEl.poster = posterSrc;
        }

        if (this.playlistLength < 2) mediaEl.loop = true;
        if (this.mute) this.setVolume(0);

        this.currentItem = itemNum;

        mediaEl.load();

    } else if (posterSrc || this.poster) {

        // Fallback to a slideshow.
        this.slideshow = true;
        this.createMediaEl();
        this._playImageItem(item, itemNum);

    } else {

        this.emit('media.error', new Error('No playable source'));
    }
};

DriveIn.prototype._playImageItem = function(item, itemNum) {
    var source,
        src;

    for (var i in item) {
        source = item[i];
        if (source.type.search(/^image/) === 0 && !src) {
            src = source.src;
        }
    }

    if (!src && this.poster) {
        src = this.poster.src;
    }

    if (src) {

        this.emit('media.loading');

        this.mediaEl.src = src;
        this.currentItem = itemNum;

    } else {

        this.emit('media.error', new Error('No playable source'));
    }
};

DriveIn.prototype._playItem = function(item, itemNum) {
    if (this.currMediaType === 'video') {
        this._playVideoItem(item, itemNum);
    }

    if (this.currMediaType === 'image') {
        this._playImageItem(item, itemNum);
    }
};

DriveIn.prototype._loadPlaylist = function(playlist) {
    this.playlist = playlist;
    this.playlistLength = playlist.length;
    this._playItem(playlist[0], 0);
};

DriveIn.prototype._addListener = function(element, event, handler) {

    element.addEventListener(event, handler);

    this._listeners.push({
        element: element,
        event: event,
        handler: handler
    });
};

DriveIn.prototype._removeAllListeners = function() {
    var listeners = this._listeners,
        listen;

    for (var i in listeners) {
        listen = listeners[i];
        listen.element.removeEventListener(listen.event, listen.handler);
    }
};

DriveIn.prototype._attachVideoListeners = function() {
    var self = this,
        mediaEl = this.mediaEl;

    function onLoadedMetadata(data) {
        self._setVideoData(data);
        self.emit('media.metadata', data);
    }

    function onPlaying() {
        self.emit('media.playing', self.currentItem);
    }

    function onPause() {
        self.emit('media.pause');
    }

    function onProgress(event) {
        // Sort of buggy, with readyState and buffer being inconsistent...
        var percent = 0,
            ready = event.target.readyState,
            network = event.target.networkState,
            buffered = event.target.buffered,
            total = event.target.duration;

        if (network === 1 && ready === 0) {
            percent = 100;
        }

        if (ready > 0) {
            var end = buffered.end(0);
            percent = (end/total) * 100;
        }

        self.emit('media.progress', percent);
    }

    function onEnded() {
        if (!self._seeking) {
            self.emit('media.ended', self.currentItem);
            if (self.playlistLength > 1 && self.loop) {
                var itemNum = 0;
                if (self.currentItem + 1 < self.playlistLength) {
                    itemNum = self.currentItem + 1;
                }
                self.play(itemNum);
            }
        }
    }

    function onCanPlay() {
        self.emit('media.canplay');
        mediaEl.play();
        if (self._seeking) {
            self._seeking = false;
        }
    }

    this._addListener(mediaEl, 'loadedmetadata', onLoadedMetadata);
    this._addListener(mediaEl, 'playing', onPlaying);
    this._addListener(mediaEl, 'pause', onPause);
    this._addListener(mediaEl, 'ended', onEnded);
    this._addListener(mediaEl, 'canplay', onCanPlay);
    this._addListener(mediaEl, 'progress', onProgress, false);
};

DriveIn.prototype._attachImageListeners = function() {
    var self = this,
        mediaEl = this.mediaEl;

    function ended() {
        var event = new Event('ended');
        self.mediaEl.dispatchEvent(event);
    }

    function onPause() {
        self.emit('media.pause');
    }

    function onLoad() {
        self.emit('media.canplay');
        if (self.playlistLength > 1) {
            if (self._slideshowTimer) self._slideshowTimer.destroy();
            self._slideshowTimer = new Timer(ended, self.slideshowItemDuration * 1000);

            self._slideshowTimer.on('pause', onPause);
        }

        self._setImageData(this);
        self.emit('media.metadata', this);
        self.emit('media.playing', self.currentItem);
    }

    function onEnded() {
        self.emit('media.ended', self.currentItem);
        if (self.playlistLength > 1 && self.loop) {
            var itemNum = (self.currentItem + 1 < self.playlistLength) ? self.currentItem + 1 : 0;
            self.play(itemNum);
        }
    }

    this._addListener(mediaEl, 'load', onLoad);
    this._addListener(mediaEl, 'ended', onEnded);
};

DriveIn.prototype._attachListeners = function() {
    var self = this;

    function onResize() {
        window.requestAnimationFrame(function () {
            if (self.metadataLoaded) {
                self._updateSize(self.mediaEl, self.currMediaType, self.mediaAspect);
            }
        });
    }

    this._addListener(window, 'resize', onResize);

    if (this.currMediaType === 'video') {
        this._attachVideoListeners();
    } else {
        this._attachImageListeners();
    }

    this.on('media.canplay', function () {
        self.canplay = true;
    });

    this.on('media.metadata', function () {
        self.metadataLoaded = true;
    });

    this.on('media.loading', function () {
        self.canplay = false;
        self.metadataLoaded = false;
    });
};

DriveIn.prototype._setParent = function(el) {
    this.parentEl = el;

    setStyles(this.parentEl, {
        position: 'absolute',
        display: 'block',
        transform: 'translate3d(-50%,-50%,0)',
        '-webkit-transform': 'translate3d(-50%,-50%,0)',
        left: '50%',
        top: '50%'
    });

    return this.parentEl;
};

DriveIn.prototype._cleanup = function() {
    var el = this.parentEl;
    while (el.firstChild) {
        el.removeChild(el.firstChild);
    }
};

DriveIn.prototype._createMediaEl = function() {
    var mediaEl,
        mediaType;

    if (this.mediaEl) this._cleanup();

    if (this.slideshow) {
        mediaType = 'image';
        mediaEl = createEl('img');
    } else {
        mediaType = 'video';
        mediaEl = createEl('video', {
            height: 1,
            width: 1,
            preload: 'none'
        });
    }

    this.mediaEl = mediaEl;
    this.currMediaType = mediaType;

    setStyles(this.mediaEl, {
        display: 'block'
    });

    this.parentEl.appendChild(mediaEl);
};

DriveIn.prototype.init = function(options) {
    options = options || {};

    if ('ontouchstart' in window || options.slideshow) {
        this.slideshow = true;
    }

    this._setParent(options.el);
    this._createMediaEl();
    this._attachListeners();
};

DriveIn.prototype.show = function(rawItem, options) {
    if (rawItem.constructor === Array) {
        return this.showPlaylist([rawItem], options);
    }

    if (rawItem.constructor === Object) {
        return this.showPlaylist([
            [rawItem]
        ], options);
    }

    return this.showPlaylist([Playlist.makePlaylistItem(rawItem)], options);
};

DriveIn.prototype.showPlaylist = function(rawPlaylist, options) {
    if (options.hasOwnProperty('mute')) {
        this.mute = options.mute;
    }
    if (options.hasOwnProperty('loop')) {
        this.loop = options.loop;
    }
    var playlist = Playlist.makePlaylist(rawPlaylist);

    if (options.poster) {
        this.poster = options.poster;
    } else {
        this.poster = findPoster(playlist);
    }

    this._loadPlaylist(playlist);
};

DriveIn.prototype.setVolume = function(level) {
    if (this.currMediaType === 'image') {
        return;
    }

    if (level === 0) {
        this.mute = true;
        this.mediaEl.muted = true;
        this.mediaEl.volume = 0;
    } else {
        this.mute = false;
        this.mediaEl.muted = false;
        this.mediaEl.volume = level;
    }
};

DriveIn.prototype.getMedia = function() {
    return this.mediaEl;
};

DriveIn.prototype.getPlaylist = function() {
    return this.playlist;
};

DriveIn.prototype.getItem = function(itemNum) {
    return this.playlist[itemNum];
};

DriveIn.prototype.play = function(itemNum) {
    if (typeof itemNum === 'number') {
        this._playItem(this.playlist[itemNum], itemNum);
    } else {
        if (this.currMediaType === 'video') {
            this.mediaEl.play();
        } else {
            if (this._slideshowTimer) {
                this._slideshowTimer.resume();
            }
        }
    }
};

DriveIn.prototype.pause = function() {
    if (this.currMediaType === 'video') {
        this.mediaEl.pause();
    } else {
        if (this._slideshowTimer) {
            this._slideshowTimer.pause();
        }
    }
};

DriveIn.prototype.close = function() {
    this._removeAllListeners();
    this._cleanup();
    if (this._slideshowTimer) {
        this._slideshowTimer.destroy();
        delete this._slideshowTimer;
    }
};

DriveIn.prototype.currentTime = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.currentTime;
    } else {
        return this._slideshowTimer.currentTime();
    }
};

DriveIn.prototype.currentTime = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.currentTime;
    } else {
        return this._slideshowTimer.currentTime();
    }
};

DriveIn.prototype.seekTo = function(time) {
    this._seeking = true;
    if (this.currMediaType === 'video') {
        this.mediaEl.currentTime = time;
    } else {
        // Not enabled for image slideshows
    }
};

DriveIn.prototype.duration = function() {
    if (this.currMediaType === 'video') {
        return this.mediaEl.duration;
    } else {
        return this.slideshowItemDuration;
    }
};

module.exports = DriveIn;
<|endoftext|>


<commit_before>var assert = require('assert');
var http = require('http');
var parse = require('parseurl');
var request = require('./request');
var startProxy = require('./startProxy');

var PORT_HEADER = 'x-koa-whistle-server-port';
var HTTPS_HEADER = 'x-whistle-https-request';
var resolveServer, rejectServer, config, PROXY_REQ_HEADER;
var defer = new Promise(function(resolve) {
  resolveServer = resolve;
});

exports.start = function (options) {
  if (config) {
    return defer;
  }
  assert(options, 'argument options is required.');
  assert(options.port > 0, ' Integer argument options.port > 0 is required.');
  assert(options.serverPort > 0, ' Integer argument options.serverPort > 0 is required.');
  assert(options.name && typeof options.name === 'string', 'String argument options.name is required.');
  config = options;
  PROXY_REQ_HEADER = 'x-koa-whistle-' + encodeURIComponent(options.name) + '_' + Date.now();
  var res = startProxy(options);
  res.then(resolveServer).catch(function(err) {
    throw err;
  });
  return defer;
};

exports.getPort = function() {
  assert(config, 'start the server first.');
  return config.port;
};

exports.getPortAsync = function() {
  return defer;
};

exports.intercept = function(req, res) {
  var headers = req.headers;
  if (headers[PROXY_REQ_HEADER]) {
    delete headers[PROXY_REQ_HEADER];
    delete headers[PORT_HEADER];
    delete headers[HTTPS_HEADER];
    return false;
  }

  headers[PORT_HEADER] = config.serverPort;
  headers[PROXY_REQ_HEADER] = 1;
  if (req.secure) {
    headers[HTTPS_HEADER] = 1;
  }
  return defer.then(function(port) {
    var options = parse(req);
    options.headers = req.headers;
    return new Promise(function(resolve, reject) {
      var _req = request(options, port, function(err, _res) {
        if (err) {
          return reject(err);
        }
        resolve(_res);
      });
      _req.on('error', reject);
      req.pipe(_req);
    });
  });
};

exports.createConnection =
  exports.createSocket = function(options) {
    var opts = {
      method: 'CONNECT',
      agent: false
    };
    if (typeof options === 'string') {
      opts.host = options;
      opts.port = arguments[1];
    }
    return new Promise(function(resolve, reject) {
      opts.headers = options.headers || {};
      opts.path = opts.headers.host
        = opts.host + (opts.port > 0 ? ':' + options.port : '');
      defer.then(function(port) {
        opts.host = '127.0.0.1';
        opts.port = port;
        var req = http.request(opts);
        req.once('connect', resolve);
        req.once('error', reject);
        req.end();
      });
    });
};

exports.export = function(obj) {
  ['start', 'getPort', 'createConnection', 'createSocket'].forEach(function(name) {
    obj[name] = exports[name];
  });
};
<commit_msg>feat: Add createConnection<commit_after>var assert = require('assert');
var http = require('http');
var parse = require('parseurl');
var request = require('./request');
var startProxy = require('./startProxy');

var PORT_HEADER = 'x-koa-whistle-server-port';
var HTTPS_HEADER = 'x-whistle-https-request';
var resolveServer, rejectServer, config, PROXY_REQ_HEADER;
var defer = new Promise(function(resolve) {
  resolveServer = resolve;
});

exports.start = function (options) {
  if (config) {
    return defer;
  }
  assert(options, 'argument options is required.');
  assert(options.port > 0, ' Integer argument options.port > 0 is required.');
  assert(options.serverPort > 0, ' Integer argument options.serverPort > 0 is required.');
  assert(options.name && typeof options.name === 'string', 'String argument options.name is required.');
  config = options;
  PROXY_REQ_HEADER = 'x-koa-whistle-' + encodeURIComponent(options.name) + '_' + Date.now();
  var res = startProxy(options);
  res.then(resolveServer).catch(function(err) {
    throw err;
  });
  return defer;
};

exports.getPortSync = function() {
  assert(config, 'start the server first.');
  return config.port;
};

exports.getPort = function() {
  return defer;
};

exports.intercept = function(req, res) {
  var headers = req.headers;
  if (headers[PROXY_REQ_HEADER]) {
    delete headers[PROXY_REQ_HEADER];
    delete headers[PORT_HEADER];
    delete headers[HTTPS_HEADER];
    return false;
  }

  headers[PORT_HEADER] = config.serverPort;
  headers[PROXY_REQ_HEADER] = 1;
  if (req.secure) {
    headers[HTTPS_HEADER] = 1;
  }
  return defer.then(function(port) {
    var options = parse(req);
    options.headers = req.headers;
    return new Promise(function(resolve, reject) {
      var _req = request(options, port, function(err, _res) {
        if (err) {
          return reject(err);
        }
        resolve(_res);
      });
      _req.on('error', reject);
      req.pipe(_req);
    });
  });
};

exports.createConnection =
  exports.createSocket = function(options) {
    var opts = {
      method: 'CONNECT',
      agent: false
    };
    if (typeof options === 'string') {
      opts.host = options;
      opts.port = arguments[1];
    }
    return new Promise(function(resolve, reject) {
      opts.headers = options.headers || {};
      opts.path = opts.headers.host
        = opts.host + (opts.port > 0 ? ':' + options.port : '');
      defer.then(function(port) {
        opts.host = '127.0.0.1';
        opts.port = port;
        var req = http.request(opts);
        req.once('connect', resolve);
        req.once('error', reject);
        req.end();
      });
    });
};

exports.export = function(obj) {
  ['start', 'getPort', 'createConnection', 'createSocket'].forEach(function(name) {
    obj[name] = exports[name];
  });
};
<|endoftext|>


<commit_before>var log = require('npmlog')
  , util = require('util')

log.addLevel('trace', 500, { fg: 'brightCyan' }, 'trac')

var defaults = {
  prefixStyle: log.prefixStyle
, headingStyle: log.headingStyle
, loglevel: log.level
, stream: process.stderr
}

module.exports = Logger

function Logger(component, opts) {
  if (!(this instanceof Logger))
    return new Logger(component, opts)

  if (!component) component = 'app'
  this.component = component
  this.log = log
  this.opts = util._extend(defaults, opts)
  this.log.level = this.opts.loglevel
  this.log.prefixStyle = this.opts.prefixStyle
  this.log.headingStyle = this.opts.headingStyle
  this.log.stream = this.opts.stream
}

Logger.createLogger = function(comp, opts) {
  return new Logger(comp, opts)
}

Logger.prototype.child = function(comp, opts) {
  return new Logger(comp, opts)
}

Logger.prototype.pause = function() {
  this.log.pause()
}

Logger.prototype.resume = function() {
  this.log.resume()
}

Logger.prototype.rmLevel = function(level) {
  delete this.log.levels[level]
  delete this.log.style[level]
  delete this.log[level]
  delete this.log.disp[level]
  delete this[level]
}

Logger.prototype.addLevel = function(level, n, style, disp) {
  this.log.addLevel(level, n, style, disp)
}

Logger.prototype.write = function(level, args) {
  args = [this.component].concat(args)
  this.log[level].apply(this.log, args)
}

;['trace', 'verbose', 'info', 'error', 'warn', 'http'].forEach(function(l) {
  Logger.prototype[l] = function() {
    var args = Array.prototype.slice.call(arguments)
    var level = args.shift()
    this.write(level, args)
  }
})
<commit_msg>Assign default levels in constructor<commit_after>var log = require('npmlog')
  , util = require('util')

log.addLevel('trace', 500, { fg: 'brightCyan' }, 'trac')

var defaults = {
  prefixStyle: log.prefixStyle
, headingStyle: log.headingStyle
, loglevel: log.level
, stream: process.stderr
}

var defaultLevels = [
  'silly'
, 'trace'
, 'verbose'
, 'info'
, 'http'
, 'warn'
, 'error'
, 'silent'
]

module.exports = Logger

function Logger(component, opts) {
  if (!(this instanceof Logger))
    return new Logger(component, opts)

  if (!component) component = 'app'
  this.component = component
  this.log = log
  this.opts = util._extend(defaults, opts)
  this.log.level = this.opts.loglevel
  this.log.prefixStyle = this.opts.prefixStyle
  this.log.headingStyle = this.opts.headingStyle
  this.log.stream = this.opts.stream
  defaultLevels.forEach(function(l) {
    this[l] = function() {
      var args = Array.prototype.slice.call(arguments)
      this.write(l, args)
    }.bind(this)
  }.bind(this))
}

Logger.createLogger = function(comp, opts) {
  return new Logger(comp, opts)
}

Logger.prototype.child = function(comp, opts) {
  return new Logger(comp, opts)
}

Logger.prototype.pause = function() {
  this.log.pause()
}

Logger.prototype.resume = function() {
  this.log.resume()
}

Logger.prototype.rmLevel = function(level) {
  delete this.log.levels[level]
  delete this.log.style[level]
  delete this.log[level]
  delete this.log.disp[level]
  delete this[level]
}

Logger.prototype.addLevel = function(level, n, style, disp) {
  this.log.addLevel(level, n, style, disp)
}

Logger.prototype.write = function(level, args) {
  args = [this.component].concat(args)
  this.log[level].apply(this.log, args)
}
<|endoftext|>


<commit_before>'use strict';

const util = require('./util');
const collect = require('./collect');
const sitespeed = require('./sitespeed');
const webpagetest = require('./webpagetest');
const Statistics = require('./statistics').Statistics;

function cleanupStatistics(pages, config, firstParty) {
  pages.forEach(page => {
    page.expireStats = page.expireStats.summarize();
    page.lastModifiedStats = page.lastModifiedStats.summarize();
    page.cookieStats = page.cookieStats.summarize();
    page.totalDomains = Object.keys(page.domains).length;
    if (!config.includeAssets) {
      page.assets = [];
    }
    if (!firstParty) {
      page.firstParty = {};
      page.thirdParty = {};
    } else {
      page.firstParty.cookieStats = page.firstParty.cookieStats.summarize();
      page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize();
    }
  });
}
/**
 * Convert a HAR object to a better page summary.
 * @module PageXray
 */

module.exports = {
  /**
   * Convert one HAR to a page. Use this when you are interested
   * of one specific run in a HAR.
   * @param {Object} har The HAR to process.
   * @param {Number} index The index of the HAR file of the run that will be converted.
   * @param {Object} config The config object.
   * @returns {Array} The converted page object.
   */
  convertIndex: (har, index, config) => {
    // TODO in the future only convert that specific run to save time
    const pages = module.exports.convert(har, config);
    return pages[index];
  },

  /**
   * Convert a HAR object to an array of pages.
   * @param {Object} har The HAR to process.
   * @param {Object} config The config object.
   * @returns {Array} The converted page objects.
   */

  convert: (har, config) => {
    config = config || {};

    const pages = [];
    let currentPage = {};
    const testedPages = {};
    let firstParty;

    if (
      config.firstParty ||
      (har.log.pages[0]._meta && har.log.pages[0]._meta.firstParty)
    ) {
      firstParty = config.firstParty || har.log.pages[0]._meta.firstParty;
    }

    function sortByTime(a, b) {
      // We can "fix" this later by checking entry.pageref
      // and for WebPagetest use entry._run
      return (
        new Date(a.startedDateTime).getTime() -
        new Date(b.startedDateTime).getTime()
      );
    }

    const entries = Array.from(har.log.entries);
    // Sometimes WebPageTest has request in run number 2
    // that has timings from run number 1
    if (har.log.creator.name !== 'WebPagetest') {
      entries.sort(sortByTime);
    }

    entries.forEach(entry => {
      if (!testedPages[entry.pageref]) {
        const requests = util.getDocumentRequests(entries, entry.pageref);

        const finalEntry = requests.pop();
        const redirectChain = requests.map(entry => entry.request.url);
        const httpVersion = finalEntry.response.httpVersion;
        const finalUrl = finalEntry.request.url;

        currentPage = {
          url: entry.request.url,
          meta: { browser: {}, startedDateTime: entry.startedDateTime },
          finalUrl,
          baseDomain: util.getHostname(finalUrl),
          documentRedirects: redirectChain.length,
          redirectChain,
          transferSize: 0,
          contentSize: 0,
          headerSize: 0,
          requests: 0,
          missingCompression: 0,
          fullyLoaded: util.getFullyLoaded(
            entry.pageref,
            entries,
            entry.startedDateTime
          ),
          httpType: util.getConnectionType(httpVersion),
          httpVersion: util.getHTTPVersion(httpVersion),
          contentTypes: collect.defaultContentTypes(),
          assets: [],
          responseCodes: {},
          firstParty: {
            cookieStats: new Statistics(),
            contentTypes: collect.defaultContentTypes()
          },
          thirdParty: {
            cookieStats: new Statistics(),
            contentTypes: collect.defaultContentTypes()
          },
          domains: {},
          expireStats: new Statistics(),
          lastModifiedStats: new Statistics(),
          cookieStats: new Statistics()
        };

        if (har.log.browser && har.log.browser.name) {
          currentPage.meta.browser.name = har.log.browser.name;
        }
        if (har.log.browser && har.log.browser.version) {
          currentPage.meta.browser.version = har.log.browser.version;
        }

        testedPages[entry.pageref] = currentPage;
        pages.push(currentPage);
      }
      const asset = collect.asset(entry);
      currentPage.expireStats.add(asset.expires);
      if (asset.timeSinceLastModified !== -1) {
        currentPage.lastModifiedStats.add(asset.timeSinceLastModified);
      }
      currentPage.cookieStats.add(asset.cookies);

      currentPage.assets.push(asset);
      collect.domainInfo(asset, currentPage.domains, config);
      collect.responseCode(asset, currentPage.responseCodes);
      collect.contentType(asset, currentPage.contentTypes);
      collect.missingCompression(asset, currentPage);

      currentPage.transferSize += entry.response.bodySize;
      currentPage.contentSize +=
        entry.response.content.size < 0
          ? entry.response.bodySize
          : entry.response.content.size;
      currentPage.headerSize += Math.max(entry.response.headersSize, 0);

      // add first/third party info
      if (firstParty) {
        // is it a third party asset?

        let stats = currentPage.thirdParty;

        if (asset.url.match(firstParty)) {
          stats = currentPage.firstParty;
        }

        stats.requests = stats.requests + 1 || 1;
        stats.transferSize =
          stats.transferSize + asset.transferSize || asset.transferSize;
        stats.contentSize =
          stats.contentSize + asset.contentSize || asset.contentSize;
        stats.headerSize =
          stats.headerSize + asset.headerSize || asset.headerSize;
        stats.cookieStats.add(asset.cookies);
        collect.contentType(asset, stats.contentTypes);
      }

      currentPage.requests += 1;
    });

    // cleanup the stats
    cleanupStatistics(pages, config, firstParty);

    // If we have that extra meta field in the HAR, we are pretty sure
    // it is generated using sitespeed.io/browsertime, so add those
    // extra metrics
    if (har.log.pages[0]._meta) {
      sitespeed.addMetrics(har, pages);
    } else if (har.log.creator.name === 'WebPagetest') {
      webpagetest.addMetrics(har, pages);
    }
    return pages;
  }
};
<commit_msg>Collect requests loaded after DOMContentLoaded and onLoad #17 (#62)<commit_after>'use strict';

const util = require('./util');
const collect = require('./collect');
const sitespeed = require('./sitespeed');
const webpagetest = require('./webpagetest');
const Statistics = require('./statistics').Statistics;

function cleanupStatistics(pages, config, firstParty) {
  pages.forEach(page => {
    page.expireStats = page.expireStats.summarize();
    page.lastModifiedStats = page.lastModifiedStats.summarize();
    page.cookieStats = page.cookieStats.summarize();
    page.totalDomains = Object.keys(page.domains).length;
    if (!config.includeAssets) {
      page.assets = [];
    }
    if (!firstParty) {
      page.firstParty = {};
      page.thirdParty = {};
    } else {
      page.firstParty.cookieStats = page.firstParty.cookieStats.summarize();
      page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize();
    }
  });
}
/**
 * Convert a HAR object to a better page summary.
 * @module PageXray
 */

module.exports = {
  /**
   * Convert one HAR to a page. Use this when you are interested
   * of one specific run in a HAR.
   * @param {Object} har The HAR to process.
   * @param {Number} index The index of the HAR file of the run that will be converted.
   * @param {Object} config The config object.
   * @returns {Array} The converted page object.
   */
  convertIndex: (har, index, config) => {
    // TODO in the future only convert that specific run to save time
    const pages = module.exports.convert(har, config);
    return pages[index];
  },

  /**
   * Convert a HAR object to an array of pages.
   * @param {Object} har The HAR to process.
   * @param {Object} config The config object.
   * @returns {Array} The converted page objects.
   */

  convert: (har, config) => {
    config = config || {};

    const pages = [];
    let currentPage = {};
    const testedPages = {};
    let firstParty;

    if (
      config.firstParty ||
      (har.log.pages[0]._meta && har.log.pages[0]._meta.firstParty)
    ) {
      firstParty = config.firstParty || har.log.pages[0]._meta.firstParty;
    }

    function sortByTime(a, b) {
      // We can "fix" this later by checking entry.pageref
      // and for WebPagetest use entry._run
      return (
        new Date(a.startedDateTime).getTime() -
        new Date(b.startedDateTime).getTime()
      );
    }

    const entries = Array.from(har.log.entries);
    // Sometimes WebPageTest has request in run number 2
    // that has timings from run number 1
    if (har.log.creator.name !== 'WebPagetest') {
      entries.sort(sortByTime);
    }

    // Summarize the timings for all pages
    const pageTimings = {};
    for (let page of har.log.pages) {
      const pageStartTime = new Date(page.startedDateTime).getTime();
      pageTimings[page.id] = {
        onLoad: pageStartTime + page.pageTimings.onLoad,
        onContentLoad: pageStartTime + page.pageTimings.onContentLoad
      };
    }
    entries.forEach(entry => {
      if (!testedPages[entry.pageref]) {
        const requests = util.getDocumentRequests(entries, entry.pageref);

        const finalEntry = requests.pop();
        const redirectChain = requests.map(entry => entry.request.url);
        const httpVersion = finalEntry.response.httpVersion;
        const finalUrl = finalEntry.request.url;

        currentPage = {
          url: entry.request.url,
          meta: { browser: {}, startedDateTime: entry.startedDateTime },
          finalUrl,
          baseDomain: util.getHostname(finalUrl),
          documentRedirects: redirectChain.length,
          redirectChain,
          transferSize: 0,
          contentSize: 0,
          headerSize: 0,
          requests: 0,
          missingCompression: 0,
          fullyLoaded: util.getFullyLoaded(
            entry.pageref,
            entries,
            entry.startedDateTime
          ),
          httpType: util.getConnectionType(httpVersion),
          httpVersion: util.getHTTPVersion(httpVersion),
          contentTypes: collect.defaultContentTypes(),
          assets: [],
          responseCodes: {},
          firstParty: {
            cookieStats: new Statistics(),
            contentTypes: collect.defaultContentTypes()
          },
          thirdParty: {
            cookieStats: new Statistics(),
            contentTypes: collect.defaultContentTypes()
          },
          domains: {},
          expireStats: new Statistics(),
          lastModifiedStats: new Statistics(),
          cookieStats: new Statistics(),
          afterOnContentLoad: {
            requests: 0,
            transferSize: 0,
            contentTypes: collect.defaultContentTypes()
          },
          afterOnLoad: {
            requests: 0,
            transferSize: 0,
            contentTypes: collect.defaultContentTypes()
          }
        };

        if (har.log.browser && har.log.browser.name) {
          currentPage.meta.browser.name = har.log.browser.name;
        }
        if (har.log.browser && har.log.browser.version) {
          currentPage.meta.browser.version = har.log.browser.version;
        }

        testedPages[entry.pageref] = currentPage;
        pages.push(currentPage);
      }
      const asset = collect.asset(entry);
      currentPage.expireStats.add(asset.expires);
      if (asset.timeSinceLastModified !== -1) {
        currentPage.lastModifiedStats.add(asset.timeSinceLastModified);
      }
      currentPage.cookieStats.add(asset.cookies);

      currentPage.assets.push(asset);
      collect.domainInfo(asset, currentPage.domains, config);
      collect.responseCode(asset, currentPage.responseCodes);
      collect.contentType(asset, currentPage.contentTypes);
      collect.missingCompression(asset, currentPage);

      currentPage.transferSize += entry.response.bodySize;
      currentPage.contentSize +=
        entry.response.content.size < 0
          ? entry.response.bodySize
          : entry.response.content.size;
      currentPage.headerSize += Math.max(entry.response.headersSize, 0);

      const entryStart = new Date(entry.startedDateTime).getTime();
      if (entryStart > pageTimings[entry.pageref].onLoad) {
        currentPage.afterOnLoad.requests += 1;
        currentPage.afterOnLoad.transferSize += entry.response.bodySize;
        collect.contentType(asset, currentPage.afterOnLoad.contentTypes);
      }
      if (entryStart > pageTimings[entry.pageref].onContentLoad) {
        currentPage.afterOnContentLoad.requests += 1;
        currentPage.afterOnContentLoad.transferSize += entry.response.bodySize;
        collect.contentType(asset, currentPage.afterOnContentLoad.contentTypes);
      }
      // add first/third party info
      if (firstParty) {
        // is it a third party asset?

        let stats = currentPage.thirdParty;

        if (asset.url.match(firstParty)) {
          stats = currentPage.firstParty;
        }

        stats.requests = stats.requests + 1 || 1;
        stats.transferSize =
          stats.transferSize + asset.transferSize || asset.transferSize;
        stats.contentSize =
          stats.contentSize + asset.contentSize || asset.contentSize;
        stats.headerSize =
          stats.headerSize + asset.headerSize || asset.headerSize;
        stats.cookieStats.add(asset.cookies);
        collect.contentType(asset, stats.contentTypes);
      }

      currentPage.requests += 1;
    });

    // cleanup the stats
    cleanupStatistics(pages, config, firstParty);

    // If we have that extra meta field in the HAR, we are pretty sure
    // it is generated using sitespeed.io/browsertime, so add those
    // extra metrics
    if (har.log.pages[0]._meta) {
      sitespeed.addMetrics(har, pages);
    } else if (har.log.creator.name === 'WebPagetest') {
      webpagetest.addMetrics(har, pages);
    }
    return pages;
  }
};
<|endoftext|>


<commit_before>/**
 * GitHubCalendar
 * Brings the contributions calendar from GitHub (provided username) into your page.
 *
 * @name GitHubCalendar
 * @function
 * @param {String|HTMLElement} container The calendar container (query selector or the element itself).
 * @param {String} username The GitHub username.
 * @param {Object} options An object containing the following fields:
 *
 *  - `summary_text` (String): The text that appears under the calendar (defaults to: `"Summary of
 *    pull requests, issues opened, and commits made by <username>"`).
 *  - `proxy` (Function): A function that receives as argument an url (string) and should return the proxied url.
 *    The default is using [@izuzak](https://github.com/izuzak)'s [`urlreq`](https://github.com/izuzak/urlreq).
 *
 * @return {Promise} A promise returned by the `fetch()` call.
 */
module.exports = function GitHubCalendar (container, username, options) {
    if (typeof container === "string") {
        container = document.querySelector(container);
    }

    options = options || {};
    options.summary_text = options.summary_text || `Summary of pull requests, issues opened, and commits made by <a href="https://github.com/${username}" target="blank">@${username}</a>`;

    // We need a proxy for CORS
    // Thanks, @izuzak (https://github.com/izuzak/urlreq)
    options.proxy = options.proxy || function (url) {
        return "https://urlreq.appspot.com/req?method=GET&url=" + url;
    };

    function fetchCalendar() {
      return fetch(options.proxy("https://github.com/" + username)).then(response => {
        return response.text()
      }).then(body => {
        var div = document.createElement("div");
        div.innerHTML = body;
        var cal = div.querySelector("#contributions-calendar");
        cal.querySelector(".left.text-muted").innerHTML = options.summary_text;
        container.innerHTML = cal.innerHTML;
        return container;
      }).then(calendar => {
        // 'include-fragment' with spinner img loads instead of the svg, fetchCalendar again
        if (calendar.querySelector('include-fragment')) {
          return fetchCalendar();
        }
      });
    }

    return fetchCalendar();
}
<commit_msg>fix code style<commit_after>/**
 * GitHubCalendar
 * Brings the contributions calendar from GitHub (provided username) into your page.
 *
 * @name GitHubCalendar
 * @function
 * @param {String|HTMLElement} container The calendar container (query selector or the element itself).
 * @param {String} username The GitHub username.
 * @param {Object} options An object containing the following fields:
 *
 *  - `summary_text` (String): The text that appears under the calendar (defaults to: `"Summary of
 *    pull requests, issues opened, and commits made by <username>"`).
 *  - `proxy` (Function): A function that receives as argument an url (string) and should return the proxied url.
 *    The default is using [@izuzak](https://github.com/izuzak)'s [`urlreq`](https://github.com/izuzak/urlreq).
 *
 * @return {Promise} A promise returned by the `fetch()` call.
 */
module.exports = function GitHubCalendar (container, username, options) {
    if (typeof container === "string") {
        container = document.querySelector(container);
    }

    options = options || {};
    options.summary_text = options.summary_text || `Summary of pull requests, issues opened, and commits made by <a href="https://github.com/${username}" target="blank">@${username}</a>`;

    // We need a proxy for CORS
    // Thanks, @izuzak (https://github.com/izuzak/urlreq)
    options.proxy = options.proxy || function (url) {
        return "https://urlreq.appspot.com/req?method=GET&url=" + url;
    };

    function fetchCalendar() {
        return fetch(options.proxy("https://github.com/" + username)).then(response => {
            return response.text()
        }).then(body => {
            var div = document.createElement("div");
            div.innerHTML = body;
            var cal = div.querySelector("#contributions-calendar");
            cal.querySelector(".left.text-muted").innerHTML = options.summary_text;
            container.innerHTML = cal.innerHTML;
            return container;
        }).then(calendar => {
            // If 'include-fragment' with spinner img loads instead of the svg, fetchCalendar again
            if (calendar.querySelector("include-fragment")) {
                return fetchCalendar();
            }
        });
    }

    return fetchCalendar();
}
<|endoftext|>


<commit_before>var _ = require('lodash'),
    inherits = require('inherits'),
    uuid = require('uuid'),
    UniversalVM = require('uvm'),
    PostmanEvent = require('postman-collection').Event,
    bootcode = require('./bootcode'),

    EXECUTION_TIMEOUT_ERROR_MESSAGE = 'sandbox: execution timeout',
    BRIDGE_DISCONNECTING_ERROR_MESSAGE = 'sandbox: execution interrupted, bridge disconnecting.',
    PostmanSandbox;

PostmanSandbox = function PostmanSandbox (options, callback) {
    options = _.assign({}, options); // ensure options is an object and is shallow cloned
    this.executing = {};
    this.debug = Boolean(options.debug);

    // set the dispatch timeout of UVM based on what is set in options unless original options sends the same
    _.isFinite(options.timeout) && (options.dispatchTimeout = this.executionTimeout = options.timeout);

    UniversalVM.call(this, options, function (err, context) {
        if (err) { return callback(err); }
        context.ping(function (err) {
            callback(err, context);
            context = null;
        });
    });
};

inherits(PostmanSandbox, UniversalVM);

_.assign(PostmanSandbox.prototype, {
    ping: function (callback) {
        var packet = uuid(),
            start = Date.now();

        this.once('pong', function (echo) {
            callback((echo !== packet ? new Error('sandbox: ping packet mismatch') : null), Date.now() - start, packet);
        });

        this.dispatch('ping', packet);
    },

    /**
     * @param {Event|String} target - can optionally be the code to execute
     * @param {Object} options
     * @param {String} options.id
     * @param {Boolean} options.debug
     * @param {Number} options.timeout
     * @param {Object} options.cursor
     * @param {Object} options.context
     * @param {Function} callback
     */
    execute: function (target, options, callback) {
        if (_.isFunction(options) && !callback) {
            callback = options;
            options = null;
        }

        !_.isObject(options) && (options = {});
        options.debug = this.debug; // @todo make this overrideable using options

        // if the target is simple code, we make a generic event out of it
        if (_.isString(target) || _.isArray(target)) {
            target = new PostmanEvent({
                script: target
            });
        }
        // if target is not a code and instead is not something that can be cast to an event, it is definitely an error
        else if (!_.isObject(target)) {
            return callback(new Error('sandbox: no target provided for execution'));
        }

        var id = _.isString(options.id) ? options.id : uuid(),
            executionEventName = 'execution.result.' + id,
            executionTimeout = _.get(options, 'timeout', this.executionTimeout),
            cursor = _.clone(_.get(options, 'cursor', {})); // clone the cursor as it travels through IPC for mutation

        // set the execution id in cursor
        cursor.execution = id;

        // set execution timeout and store the interrupt in a global object (so that we can clear during dispose)
        // force trigger of the `execution.${id}` event so that the normal error flow is taken
        this.executing[id] = _.isFinite(executionTimeout) ? (setTimeout(this.emit.bind(this, executionEventName,
            new Error(EXECUTION_TIMEOUT_ERROR_MESSAGE)), executionTimeout)) : null;

        // @todo decide how the results will return in a more managed fashion
        // listen to this once, so that subsequent calls are simply dropped. especially during timeout and other
        // errors
        this.once(executionEventName, function (err, result) {
            if (this.executing.hasOwnProperty(id)) { // clear any pending timeouts
                this.executing[id] && clearTimeout(this.executing[id]);
                delete this.executing[id];
            }

            this.emit('execution', err, id, result);
            callback(err, result);
        });

        // send the code to the sandbox to be intercepted and executed
        this.dispatch('execute', id, target, _.get(options, 'context', {}), {
            cursor: cursor,
            debug: _.get(options, 'debug'),
            timeout: executionTimeout,
            legacy: _.get(options, 'legacy')
        });
    },

    dispose: function () {
        var self = this;

        _.forEach(self.executing, function (irq, id) {
            irq && clearTimeout(irq);
            self.emit(`execution.result.${id}`, new Error(BRIDGE_DISCONNECTING_ERROR_MESSAGE));
        });

        self.disconnect();
        self = null;
    }
});

_.assign(PostmanSandbox, {
    create: function (options, callback) {
        return new PostmanSandbox(options, callback);
    }
});

module.exports = {
    /**
     * Creates a new instance of sandbox from the options that have been provided
     * @param {Object=} [options]
     * @param {Function} callback
     */
    createContext: function (options, callback) {
        if (_.isFunction(options) && !callback) {
            callback = options;
            options = {};
        }

        options = _.clone(options);
        bootcode(function (err, code) {
            if (err) { return callback(err); }
            if (!code) { return callback(new Error('sandbox: bootcode missing!')); }

            options.bootCode = code; // assign the code in options
            PostmanSandbox.create(options, callback);
        });
    }
};
<commit_msg>Ensured that debug mode can be locally overridden in .execute<commit_after>var _ = require('lodash'),
    inherits = require('inherits'),
    uuid = require('uuid'),
    UniversalVM = require('uvm'),
    PostmanEvent = require('postman-collection').Event,
    bootcode = require('./bootcode'),

    EXECUTION_TIMEOUT_ERROR_MESSAGE = 'sandbox: execution timeout',
    BRIDGE_DISCONNECTING_ERROR_MESSAGE = 'sandbox: execution interrupted, bridge disconnecting.',
    PostmanSandbox;

PostmanSandbox = function PostmanSandbox (options, callback) {
    options = _.assign({}, options); // ensure options is an object and is shallow cloned
    this.executing = {};
    this.debug = Boolean(options.debug);

    // set the dispatch timeout of UVM based on what is set in options unless original options sends the same
    _.isFinite(options.timeout) && (options.dispatchTimeout = this.executionTimeout = options.timeout);

    UniversalVM.call(this, options, function (err, context) {
        if (err) { return callback(err); }
        context.ping(function (err) {
            callback(err, context);
            context = null;
        });
    });
};

inherits(PostmanSandbox, UniversalVM);

_.assign(PostmanSandbox.prototype, {
    ping: function (callback) {
        var packet = uuid(),
            start = Date.now();

        this.once('pong', function (echo) {
            callback((echo !== packet ? new Error('sandbox: ping packet mismatch') : null), Date.now() - start, packet);
        });

        this.dispatch('ping', packet);
    },

    /**
     * @param {Event|String} target - can optionally be the code to execute
     * @param {Object} options
     * @param {String} options.id
     * @param {Boolean} options.debug
     * @param {Number} options.timeout
     * @param {Object} options.cursor
     * @param {Object} options.context
     * @param {Function} callback
     */
    execute: function (target, options, callback) {
        if (_.isFunction(options) && !callback) {
            callback = options;
            options = null;
        }

        !_.isObject(options) && (options = {});

        // if the target is simple code, we make a generic event out of it
        if (_.isString(target) || _.isArray(target)) {
            target = new PostmanEvent({
                script: target
            });
        }
        // if target is not a code and instead is not something that can be cast to an event, it is definitely an error
        else if (!_.isObject(target)) {
            return callback(new Error('sandbox: no target provided for execution'));
        }

        var id = _.isString(options.id) ? options.id : uuid(),
            executionEventName = 'execution.result.' + id,
            executionTimeout = _.get(options, 'timeout', this.executionTimeout),
            cursor = _.clone(_.get(options, 'cursor', {})), // clone the cursor as it travels through IPC for mutation
            debugMode = _.has(options, 'debug') ? options.debug : this.debug,
            waiting;

        // set the execution id in cursor
        cursor.execution = id;

        // set execution timeout and store the interrupt in a global object (so that we can clear during dispose)
        // force trigger of the `execution.${id}` event so that the normal error flow is taken
        this.executing[id] = _.isFinite(executionTimeout) ? (setTimeout(this.emit.bind(this, executionEventName,
            new Error(EXECUTION_TIMEOUT_ERROR_MESSAGE)), executionTimeout)) : null;

        // @todo decide how the results will return in a more managed fashion
        // listen to this once, so that subsequent calls are simply dropped. especially during timeout and other
        // errors
        this.once(executionEventName, function (err, result) {
            if (this.executing.hasOwnProperty(id)) { // clear any pending timeouts
                this.executing[id] && clearTimeout(this.executing[id]);
                delete this.executing[id];
            }

            this.emit('execution', err, id, result);
            callback(err, result);
        });

        // send the code to the sandbox to be intercepted and executed
        this.dispatch('execute', id, target, _.get(options, 'context', {}), {
            cursor: cursor,
            debug: debugMode,
            timeout: executionTimeout,
            legacy: _.get(options, 'legacy')
        });
    },

    dispose: function () {
        var self = this;

        _.forEach(self.executing, function (irq, id) {
            irq && clearTimeout(irq);
            self.emit(`execution.result.${id}`, new Error(BRIDGE_DISCONNECTING_ERROR_MESSAGE));
        });

        self.disconnect();
        self = null;
    }
});

_.assign(PostmanSandbox, {
    create: function (options, callback) {
        return new PostmanSandbox(options, callback);
    }
});

module.exports = {
    /**
     * Creates a new instance of sandbox from the options that have been provided
     * @param {Object=} [options]
     * @param {Function} callback
     */
    createContext: function (options, callback) {
        if (_.isFunction(options) && !callback) {
            callback = options;
            options = {};
        }

        options = _.clone(options);
        bootcode(function (err, code) {
            if (err) { return callback(err); }
            if (!code) { return callback(new Error('sandbox: bootcode missing!')); }

            options.bootCode = code; // assign the code in options
            PostmanSandbox.create(options, callback);
        });
    }
};
<|endoftext|>


<commit_before>/*
 * nikko
 * https://github.com/goliatone/nikko
 *
 * Copyright (c) 2015 goliatone
 * Licensed under the MIT license.
 */

'use strict';

var path = require('path');
var util = require('util');
var extend = require('gextend');
var EventEmitter = require('events').EventEmitter;

var DEFAULTS = {
    autoinitialize: true
};

function Nikko(config){
    EventEmitter.call(this);
    config = extend({}, DEFAULTS, config);
    if(config.autoinitialize) this.init(config);
}

util.inherits(Nikko, EventEmitter);

/*
 * Unit testing stuff and such.
 */
Nikko.packagePath = '../package.json';
Nikko.packageObject = require(Nikko.packagePath);

Nikko.prototype.init = function(config){
    if(this.initialized) return
    this.initialized = true;

    extend(this, DEFAULTS, config);

    this.basePath = path.resolve(path.join(__dirname, '..'));
    this.binPath = path.join(this.basePath, 'bin');
    this.pluginPath = path.join(this.basePath, 'plugins');
    this.modulePath = path.join(this.basePath, 'node_modules');

    this.buildConfig();
};

Nikko.prototype.buildConfig = function(){
    var Config = require('./utils/config');
    this.cloader = new Config();
    this.cloader.load('nikko', {});
    this.config = this.cloader.config;
    return this;
};

Nikko.prototype.getConfig = function(namespace){
    //TODO: Do this for realz, keypath etc. GConfig for node?
    if(namespace) return this.config[namespace];
    return this.config;
}

/**
 * Function that registers a plugin, usually
 * called from `install` command.
 * @param  {String} plugin Plugin id
 * @return {this}
 */
Nikko.prototype.registerPlugin = function(plugin){
    var pkgPath = path.join(this.basePath, 'package.json');
    var json = require(pkgPath);
    if(!json.plugins) json.plugins = [];

    var plugInfo = {};
    var pluginPkg = require(path.join(this.pluginPath, plugin));
    plugInfo.name = pluginPkg.name;
    plugInfo.description = [pluginPkg.name, pluginPkg.description];
    json.plugins.push(plugInfo);

    var fs = require('fs');
    fs.writeFileSync(pkgPath, JSON.stringify(json, null, 2));

    //After we have moved the plugin, we should cd to plugin dir and npm i

    this.emit('plugin.registered.' + plugin);
    return this;
};

Nikko.prototype.registerProject = function(name){
    console.log('CREATE REGISTRY');
};

///////////
// CONFIG
///////////
///A) When we create a project we check for .nikkorc
/// if its not there we make a new one
Nikko.prototype.createConfig = function(){

};

Nikko.extract = function(obj, props){
    var out = {};
    props.forEach(function(key){
        if(props[key] === undefined) return;
        out[key] = props[key];
    });
    return out;
};
///////////

/**
 * Executes a plugin. It will look for the
 * plugin in the directory containing installed
 * plugins.
 * Command id is the command name that we enter on
 * terminal, which then maps to a file inside `bin`
 * following the convention **nikko-**{command}.
 *
 *
 * @param  {String} command Command id
 * @param  {Object} config  Configuration obj
 * @return {Nikko}          Nikko instance.
 */
Nikko.prototype.execute = function(command, config){
    var module = path.join(this.pluginPath, 'nikko-' + command);

    //Extract the config for this command:
    //TODO: move to config manager.
    config = extend({}, this.config[command], config);

    try {
        var plugin = require(module);
        plugin(config, this);
    } catch(e){
        //we should check if we have it installed.
        this.logger.error('Error executing command '+ module + '\n\n' + e.message);
    }
};

/**
 * Logger facility. By default
 * exposes `console`
 * @type {Function}
 */
Nikko.prototype.logger = console;

/**
 * Execute plugin
 * @param  {String} command Command id
 * @param  {Object} config  Configuration obj
 * @return {Nikko}          Nikko instance.
 */
Nikko.execute = function(command, config){
    var nikko = new Nikko();
    nikko.execute(command, config);
    return nikko;
};

module.exports = Nikko;
<commit_msg>LIB: refactoring registerPlugin<commit_after>/*
 * nikko
 * https://github.com/goliatone/nikko
 *
 * Copyright (c) 2015 goliatone
 * Licensed under the MIT license.
 */

'use strict';

var path = require('path');
var util = require('util');
var extend = require('gextend');
var EventEmitter = require('events').EventEmitter;

var DEFAULTS = {
    autoinitialize: true
};

function Nikko(config){
    EventEmitter.call(this);
    config = extend({}, DEFAULTS, config);
    if(config.autoinitialize) this.init(config);
}

util.inherits(Nikko, EventEmitter);

/*
 * Unit testing stuff and such.
 */
Nikko.packagePath = '../package.json';
Nikko.packageObject = require(Nikko.packagePath);

Nikko.prototype.init = function(config){
    if(this.initialized) return
    this.initialized = true;

    extend(this, DEFAULTS, config);

    this.basePath = path.resolve(path.join(__dirname, '..'));
    this.binPath = path.join(this.basePath, 'bin');
    this.pluginPath = path.join(this.basePath, 'plugins');
    this.modulePath = path.join(this.basePath, 'node_modules');

    this.buildConfig();
};

Nikko.prototype.buildConfig = function(){
    var Config = require('./utils/config');
    this.cloader = new Config();
    this.cloader.load('nikko', {});
    this.config = this.cloader.config;
    return this;
};

Nikko.prototype.getConfig = function(namespace){
    //TODO: Do this for realz, keypath etc. GConfig for node?
    if(namespace) return this.config[namespace];
    return this.config;
}

/**
 * Function that registers a plugin, usually
 * called from `install` command.
 * @param  {String} plugin Plugin id
 * @return {this}
 */
Nikko.prototype.registerPlugin = function(plugin){
    var pkgPath = path.join(this.basePath, 'package.json');
    var json = require(pkgPath);
    if(!json.plugins) json.plugins = [];

    var plugInfo = {};
    var pluginPkg = require(path.join(this.pluginPath, plugin, 'package.json'));
    console.log('PACKAGE INFO', pluginPkg);

    plugInfo.name = pluginPkg.name;

    //TODO: we should add a custom entry on package: nikko-command
    // create [name]
    plugInfo.description = [pluginPkg.name.replace('nikko-', ''), pluginPkg.description];
    json.plugins.push(plugInfo);

    var fs = require('fs');
    fs.writeFileSync(pkgPath, JSON.stringify(json, null, 2));

    //After we have moved the plugin, we should cd to plugin dir and npm i

    this.emit('plugin.registered.' + plugin, {plugin:plugin});
    return this;
};

Nikko.prototype.registerProject = function(name){
    console.log('CREATE REGISTRY');
};

///////////
// CONFIG
///////////
///A) When we create a project we check for .nikkorc
/// if its not there we make a new one
Nikko.prototype.createConfig = function(){

};

Nikko.extract = function(obj, props){
    var out = {};
    props.forEach(function(key){
        if(props[key] === undefined) return;
        out[key] = props[key];
    });
    return out;
};
///////////

/**
 * Executes a plugin. It will look for the
 * plugin in the directory containing installed
 * plugins.
 * Command id is the command name that we enter on
 * terminal, which then maps to a file inside `bin`
 * following the convention **nikko-**{command}.
 *
 *
 * @param  {String} command Command id
 * @param  {Object} config  Configuration obj
 * @return {Nikko}          Nikko instance.
 */
Nikko.prototype.execute = function(command, config){
    var module = path.join(this.pluginPath, 'nikko-' + command);

    //Extract the config for this command:
    //TODO: move to config manager.
    config = extend({}, this.config[command], config);

    try {
        var plugin = require(module);
        plugin(config, this);
    } catch(e){
        //we should check if we have it installed.
        this.logger.error('Error executing command '+ module + '\n\n' + e.message);
    }
};

/**
 * Logger facility. By default
 * exposes `console`
 * @type {Function}
 */
Nikko.prototype.logger = console;

/**
 * Execute plugin
 * @param  {String} command Command id
 * @param  {Object} config  Configuration obj
 * @return {Nikko}          Nikko instance.
 */
Nikko.execute = function(command, config){
    var nikko = new Nikko();
    nikko.execute(command, config);
    return nikko;
};

module.exports = Nikko;
<|endoftext|>