<commit_before>/**
 * Module dependencies
 */
var request = require('superagent')
  , util = require('util')
  , _ = require('underscore')
  , path = require('JSONPath').eval
  , accounting = require('accounting')
  , currency = require('currency-symbol-map')
  , parse = require('xml2js').parseString;

/**
 * API endpoint
 */
var endpoint = 'http://svcs.ebay.com/services/search/FindingService/v1';

var formatPrice = function (obj) {
  var amount = obj._
    , code = obj.$ && obj.$.currencyId
    , thousand, decimal;

  if (!amount || !currency) return null;

  if (~['DE'].indexOf(this.country)) {
    thousand = '.'; decimal = ',';
  } else {
    thousand = ','; decimal = '.';
  }

  return accounting.formatMoney(amount, currency(code), null, thousand, decimal);
};

var extractions = [
  { name: 'id', query: '$..itemId[0]' },
  { name: 'name', query: '$..title[0]' },
  { name: 'url', query: '$..viewItemURL[0]' },
  { name: 'offerPrice',
    query: '$..sellingStatus..convertedCurrentPrice[0]',
    transform: formatPrice
  },
  { name: 'listPrice',
    query: '$..discountPriceInfo..originalRetailPrice[0]',
    transform: formatPrice
  },
  {
    name: 'eek',
    query: '$..eekStatus[0]'
  }
];

module.exports = function (itemId) {
  return new Ebay(itemId);
};

var Ebay = function (itemId) {
  this._filters = {};

  if ('object' === typeof itemId) {
    if (itemId.keywords) this.keywords = itemId.keywords, this.mode = 'search';
    if (itemId.itemId) this.itemId = itemId, this.mode = 'lookup';
  } else {
    this.itemId = itemId;
  }
};

Ebay.prototype.id = function (id) {
  return this.id = id, this;
};

var countryId = [
  {countryCode: 'AT', globalId: 'EBAY-AT' },
  {countryCode: 'AU', globalId: 'EBAY-AU'},
  {countryCode: 'CH', globalId: 'EBAY-CH'},
  {countryCode: 'DE', globalId: 'EBAY-DE'},
  {countryCode: 'CA', globalId: 'EBAY-ENCA'},
  {countryCode: 'ES', globalId: 'EBAY-ES'},
  {countryCode: 'FR', globalId: 'EBAY-FR'},
  {countryCode: 'BE', globalId: 'EBAY-FRBE'},
  {countryCode: 'GB', globalId: 'EBAY-GB'},
  {countryCode: 'HK', globalId: 'EBAY-HK'},
  {countryCode: 'IE', globalId: 'EBAY-IE'},
  {countryCode: 'IN', globalId: 'EBAY-IN'},
  {countryCode: 'IT', globalId: 'EBAY-IT'},
  {countryCode: 'MY', globalId: 'EBAY-MY'},
  {countryCode: 'NL', globalId: 'EBAY-NL'},
  {countryCode: 'PH', globalId: 'EBAY-PH'},
  {countryCode: 'PL', globalId: 'EBAY-PL'},
  {countryCode: 'SG', globalId: 'EBAY-SG'},
  {countryCode: 'US', globalId: 'EBAY-US'}
];
var defaultCountry = 'EBAY-US';
Ebay.prototype.country = function (country) {
  // Lookup matching country
  var results = _.where(countryId, {countryCode: country});

  // Set endpoint
  this.globalId = results.length ? results[0].globalId : null;

  // Set country
  this.country = country;

  // Return this for chaining
  return this;
};

Ebay.prototype.location = function (country) {
  this._filters['LocatedIn'] = [country];

  return this;
};

Ebay.prototype.affiliate = function (id) {
  return this.trackingId = id, this;
};

var networks = [
  { id: 2, name: 'be free' },
  { id: 3, name: 'affilinet' },
  { id: 4, name: 'tradedoubler' },
  { id: 5, name: 'mediaplex' },
  { id: 6, name: 'doubleclick' },
  { id: 7, name: 'allyes' },
  { id: 8, name: 'bjmt' },
  { id: 9, name: 'ebay' }
];
var defaultNetwork = 9;
Ebay.prototype.network = function (network) {
  var results = _.where(networks, {name: network.toLowerCase()});

  // Set id
  this.networkId = results.length ? results[0].id : null;

  // Return this for chaining
  return this;
};

var listing = [
  { name: 'auction', id: 'Auction' },
  { name: 'buy it now', id: 'AuctionWithBIN' },
  { name: 'classified', id: 'Classified' },
  { name: 'fixed', id: 'FixedPrice' },
  { name: 'all', id: 'All' }
];

Ebay.prototype.type = function (type) {
  var results = _.where(listing, {name: type.toLowerCase()});

  // Choke on invalid listing type
  if (!results.length) throw new Error('Invalid listing type');

  if (!this._filters['ListingType']) {
    this._filters['ListingType'] = [];
  }

  // Add to filters
  this._filters['ListingType'].push(results[0].id);
  // Chain
  return this;
};

Ebay.prototype.condition = function (cond) {
  return this._filters['Condition'] = [capitalize(cond)], this;
};

Ebay.prototype.price = function (range) {
  // Convert to array if necessary
  range = _.isString(range) ? range.split('..') : range;

  // Add limits to filters
  if (range[0]) this._filters['MinPrice'] = [range[0]];
  if (range[1]) this._filters['MaxPrice'] = [range[1]];

  // Chain
  return this;
};

Ebay.prototype.category = function (id) {
  return this._categoryid = id, this;
};

Ebay.prototype.addExtractionRule = function (rule) {
  extractions.push(rule);
  return this;
};

Ebay.prototype.done = function (cb) {
  var r = request.get(endpoint)
    , that = this;

  // Add easy fields
  r
    .query({'OPERATION-NAME': 'findItemsAdvanced'})
    .query({'SERVICE-VERSION': '1.0.0'})
    .query({'SECURITY-APPNAME': this.id})
    .query({'GLOBAL-ID': this.globalId || defaultCountry})
    .query({'RESPONSE-DATA-FORMAT': 'XML'})
    .query({'REST-PAYLOAD': ''}) // Why, ebay? Why?
    .query({'paginationInput.entriesPerPage': 1})
    .query({keywords: this.keywords})
    .query({'affiliate.trackingId': this.trackingId})
    .query({'affiliate.networkId': this.networkId || defaultNetwork})
    .query({'categoryId': this._categoryid});

  // Add filters
  _
    .chain(this._filters)
    .pairs()
    .map(map)
    .flatten()
    .each(r.query, r);

  function map (x, i) {
    var nameTemplate = 'itemFilter(%d).name=%s'
      , valueTemplate = 'itemFilter(%d).value(%d)=%s';

    // Holy shit what is that
    return [
      util.format(nameTemplate, i, x[0]),
      _.map(x[1], function (y, j) {
        return util.format(valueTemplate, i, j, y);
      })
    ];
  }

  // Run it
  r.end(function (err, res) {
    if (err) return cb(err);

    return parse(res.text, function (err, p) {
      // Parsing errors
      if (err) return cb(err);

      // API errors
      if (err = parseErr(p)) return cb(err);

      // Return result
      return cb(null, parseResults.call(that, p, extractions));
    });
  });
};

var parseErr = function (obj) {
  var result = path(obj, '$..error..message[0]');
  return result.length ? new Error(result[0]) : null;
};

var first = function (obj, query) {
  var result = path(obj, query);
  return result.length ? result[0] : null;
};

var parseResults = function (obj, extractions) {
  var that = this;

  var res = _
    .chain(extractions)
    .map(function (x) {
      var key = x.name
        , val = first(obj, x.query);

      // Transform value if we have a transform available
      if (x.transform && val !== null) val = x.transform.call(that, val);

      return [key, val];
    })
    .filter(function (x) {
      return x[1] !== null;
    })
    .object()
    .value();

  return _.keys(res).length ? res : null;
};

var capitalize = function (str) {
  return str.slice(0,1).toUpperCase() + str.slice(1)
};
<commit_msg>feat: additional extraction fields<commit_after>/**
 * Module dependencies
 */
var request = require('superagent')
  , util = require('util')
  , _ = require('underscore')
  , path = require('JSONPath').eval
  , accounting = require('accounting')
  , currency = require('currency-symbol-map')
  , parse = require('xml2js').parseString;

/**
 * API endpoint
 */
var endpoint = 'http://svcs.ebay.com/services/search/FindingService/v1';

var formatPrice = function (obj) {
  var amount = obj._
    , code = obj.$ && obj.$.currencyId
    , thousand, decimal;

  if (!amount || !currency) return null;

  if (~['DE'].indexOf(this.country)) {
    thousand = '.'; decimal = ',';
  } else {
    thousand = ','; decimal = '.';
  }

  return accounting.formatMoney(amount, currency(code), null, thousand, decimal);
};

var extractions = [
  { name: 'id', query: '$..itemId[0]' },
  { name: 'name', query: '$..title[0]' },
  { name: 'url', query: '$..viewItemURL[0]' },
  { name: 'offerPrice',
    query: '$..sellingStatus..convertedCurrentPrice[0]',
    transform: formatPrice
  },
  { name: 'listPrice',
    query: '$..discountPriceInfo..originalRetailPrice[0]',
    transform: formatPrice
  },
  {
    name: 'eek',
    query: '$..eekStatus[0]'
  },
  {
    name: 'buyItNowAvailable',
    query: '$..listingInfo..buyItNowAvailable[0]'
  },
  {
    name: 'buyItNowPrice',
    query: '$..listingInfo..convertedBuyItNowPrice[0]',
    transform: formatPrice
  },
  {
    name: 'endTime',
    query: '$..listingInfo..endTime[0]'
  }
];

module.exports = function (itemId) {
  return new Ebay(itemId);
};

var Ebay = function (itemId) {
  this._filters = {};

  if ('object' === typeof itemId) {
    if (itemId.keywords) this.keywords = itemId.keywords, this.mode = 'search';
    if (itemId.itemId) this.itemId = itemId, this.mode = 'lookup';
  } else {
    this.itemId = itemId;
  }
};

Ebay.prototype.id = function (id) {
  return this.id = id, this;
};

var countryId = [
  {countryCode: 'AT', globalId: 'EBAY-AT' },
  {countryCode: 'AU', globalId: 'EBAY-AU'},
  {countryCode: 'CH', globalId: 'EBAY-CH'},
  {countryCode: 'DE', globalId: 'EBAY-DE'},
  {countryCode: 'CA', globalId: 'EBAY-ENCA'},
  {countryCode: 'ES', globalId: 'EBAY-ES'},
  {countryCode: 'FR', globalId: 'EBAY-FR'},
  {countryCode: 'BE', globalId: 'EBAY-FRBE'},
  {countryCode: 'GB', globalId: 'EBAY-GB'},
  {countryCode: 'HK', globalId: 'EBAY-HK'},
  {countryCode: 'IE', globalId: 'EBAY-IE'},
  {countryCode: 'IN', globalId: 'EBAY-IN'},
  {countryCode: 'IT', globalId: 'EBAY-IT'},
  {countryCode: 'MY', globalId: 'EBAY-MY'},
  {countryCode: 'NL', globalId: 'EBAY-NL'},
  {countryCode: 'PH', globalId: 'EBAY-PH'},
  {countryCode: 'PL', globalId: 'EBAY-PL'},
  {countryCode: 'SG', globalId: 'EBAY-SG'},
  {countryCode: 'US', globalId: 'EBAY-US'}
];
var defaultCountry = 'EBAY-US';
Ebay.prototype.country = function (country) {
  // Lookup matching country
  var results = _.where(countryId, {countryCode: country});

  // Set endpoint
  this.globalId = results.length ? results[0].globalId : null;

  // Set country
  this.country = country;

  // Return this for chaining
  return this;
};

Ebay.prototype.location = function (country) {
  this._filters['LocatedIn'] = [country];

  return this;
};

Ebay.prototype.affiliate = function (id) {
  return this.trackingId = id, this;
};

var networks = [
  { id: 2, name: 'be free' },
  { id: 3, name: 'affilinet' },
  { id: 4, name: 'tradedoubler' },
  { id: 5, name: 'mediaplex' },
  { id: 6, name: 'doubleclick' },
  { id: 7, name: 'allyes' },
  { id: 8, name: 'bjmt' },
  { id: 9, name: 'ebay' }
];
var defaultNetwork = 9;
Ebay.prototype.network = function (network) {
  var results = _.where(networks, {name: network.toLowerCase()});

  // Set id
  this.networkId = results.length ? results[0].id : null;

  // Return this for chaining
  return this;
};

var listing = [
  { name: 'auction', id: 'Auction' },
  { name: 'buy it now', id: 'AuctionWithBIN' },
  { name: 'classified', id: 'Classified' },
  { name: 'fixed', id: 'FixedPrice' },
  { name: 'all', id: 'All' }
];

Ebay.prototype.type = function (type) {
  var results = _.where(listing, {name: type.toLowerCase()});

  // Choke on invalid listing type
  if (!results.length) throw new Error('Invalid listing type');

  if (!this._filters['ListingType']) {
    this._filters['ListingType'] = [];
  }

  // Add to filters
  this._filters['ListingType'].push(results[0].id);
  // Chain
  return this;
};

Ebay.prototype.condition = function (cond) {
  return this._filters['Condition'] = [capitalize(cond)], this;
};

Ebay.prototype.price = function (range) {
  // Convert to array if necessary
  range = _.isString(range) ? range.split('..') : range;

  // Add limits to filters
  if (range[0]) this._filters['MinPrice'] = [range[0]];
  if (range[1]) this._filters['MaxPrice'] = [range[1]];

  // Chain
  return this;
};

Ebay.prototype.category = function (id) {
  return this._categoryid = id, this;
};

Ebay.prototype.addExtractionRule = function (rule) {
  extractions.push(rule);
  return this;
};

Ebay.prototype.done = function (cb) {
  var r = request.get(endpoint)
    , that = this;

  // Add easy fields
  r
    .query({'OPERATION-NAME': 'findItemsAdvanced'})
    .query({'SERVICE-VERSION': '1.0.0'})
    .query({'SECURITY-APPNAME': this.id})
    .query({'GLOBAL-ID': this.globalId || defaultCountry})
    .query({'RESPONSE-DATA-FORMAT': 'XML'})
    .query({'REST-PAYLOAD': ''}) // Why, ebay? Why?
    .query({'paginationInput.entriesPerPage': 1})
    .query({keywords: this.keywords})
    .query({'affiliate.trackingId': this.trackingId})
    .query({'affiliate.networkId': this.networkId || defaultNetwork})
    .query({'categoryId': this._categoryid});

  // Add filters
  _
    .chain(this._filters)
    .pairs()
    .map(map)
    .flatten()
    .each(r.query, r);

  function map (x, i) {
    var nameTemplate = 'itemFilter(%d).name=%s'
      , valueTemplate = 'itemFilter(%d).value(%d)=%s';

    // Holy shit what is that
    return [
      util.format(nameTemplate, i, x[0]),
      _.map(x[1], function (y, j) {
        return util.format(valueTemplate, i, j, y);
      })
    ];
  }

  // Run it
  r.end(function (err, res) {
    if (err) return cb(err);

    return parse(res.text, function (err, p) {
      // Parsing errors
      if (err) return cb(err);

      // API errors
      if (err = parseErr(p)) return cb(err);

      // Return result
      return cb(null, parseResults.call(that, p, extractions));
    });
  });
};

var parseErr = function (obj) {
  var result = path(obj, '$..error..message[0]');
  return result.length ? new Error(result[0]) : null;
};

var first = function (obj, query) {
  var result = path(obj, query);
  return result.length ? result[0] : null;
};

var parseResults = function (obj, extractions) {
  var that = this;

  var res = _
    .chain(extractions)
    .map(function (x) {
      var key = x.name
        , val = first(obj, x.query);

      // Transform value if we have a transform available
      if (x.transform && val !== null) val = x.transform.call(that, val);

      return [key, val];
    })
    .filter(function (x) {
      return x[1] !== null;
    })
    .object()
    .value();

  return _.keys(res).length ? res : null;
};

var capitalize = function (str) {
  return str.slice(0,1).toUpperCase() + str.slice(1)
};
<|endoftext|>


<commit_before>/**
 * @fileOverview DOM events for ns.View
 */

/**
 * Хеш событий для удобного биндинга touch/desktop
 * @type {Object}
 */
ns.V.EVENTS = {
    'click': 'click',
    'dblclick': 'dblclick',
    'mousedown': 'mousedown',
    'mousemove': 'mousemove',
    'mouseup': 'mouseup'
};

/**
 * Список DOM-событий.
 * @type {Array}
 * @constant
 */
ns.V.DOM_EVENTS = [
    'blur',
    'change',
    'click',
    'dblclick',
    'focus',
    'focusin',
    'focusout',
    'keydown',
    'keypress',
    'keyup',
    'mousedown',
    'mouseenter',
    'mouseleave',
    'mousemove',
    'mouseout',
    'mouseover',
    'mouseup',
    'resize',
    'scroll',
    'submit'
];

/**
 * Список внутренних Noscript-событий.
 * @type {Array}
 * @constant
 */
ns.V.NS_EVENTS = [
    'ns-async',
    'ns-init',
    'ns-htmlinit',
    'ns-show',
    'ns-repaint',
    'ns-hide',
    'ns-htmldestroy'
];

//TODO: Pointer events support (MSIE 10)

// @see http://sitr.us/2011/07/28/how-mobile-safari-emulates-mouse-events.html
// @see http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
if (ns.IS_TOUCH) {
    ns.V.DOM_EVENTS.push('tap', 'touchstart', 'touchmove', 'touchend');
    ns.V.EVENTS = {
        'click': 'tap',
        'dblclick': 'doubletap',
        'mousedown': 'touchstart',
        'mousemove': 'touchmove',
        'mouseup': 'touchend'
    };

}
<commit_msg>Drag'n'Drop events in list of DOM events<commit_after>/**
 * @fileOverview DOM events for ns.View
 */

/**
 * Хеш событий для удобного биндинга touch/desktop
 * @type {Object}
 */
ns.V.EVENTS = {
    'click': 'click',
    'dblclick': 'dblclick',
    'mousedown': 'mousedown',
    'mousemove': 'mousemove',
    'mouseup': 'mouseup'
};

/**
 * Список DOM-событий.
 * @type {Array}
 * @constant
 */
ns.V.DOM_EVENTS = [
    'blur',
    'change',
    'click',
    'dblclick',
    'dragstart',
    'dragenter',
    'dragover',
    'dragleave',
    'drag',
    'drop',
    'dragend',
    'focus',
    'focusin',
    'focusout',
    'keydown',
    'keypress',
    'keyup',
    'mousedown',
    'mouseenter',
    'mouseleave',
    'mousemove',
    'mouseout',
    'mouseover',
    'mouseup',
    'resize',
    'scroll',
    'submit'
];

/**
 * Список внутренних Noscript-событий.
 * @type {Array}
 * @constant
 */
ns.V.NS_EVENTS = [
    'ns-async',
    'ns-init',
    'ns-htmlinit',
    'ns-show',
    'ns-repaint',
    'ns-hide',
    'ns-htmldestroy'
];

//TODO: Pointer events support (MSIE 10)

// @see http://sitr.us/2011/07/28/how-mobile-safari-emulates-mouse-events.html
// @see http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
if (ns.IS_TOUCH) {
    ns.V.DOM_EVENTS.push('tap', 'touchstart', 'touchmove', 'touchend');
    ns.V.EVENTS = {
        'click': 'tap',
        'dblclick': 'doubletap',
        'mousedown': 'touchstart',
        'mousemove': 'touchmove',
        'mouseup': 'touchend'
    };

}
<|endoftext|>


<commit_before>/** Middleware server
*
*  @copyright  Copyright (C) 2015 by Yieme
*  @module     middle-server
 */
'use strict';
var fs = require('fs')
var express     = require('express')
var app         = express()
var port        = process.env.port || 3000
var path        = require('path')
var pkgPath     = path.normalize(process.cwd() + '/package.json')
var pack        = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
var logger      = {
  info:  function(msg) { console.log('info:', msg) },
  debug: function(msg) { console.log('debug:', msg) },
  warn:  function(msg) { console.warn('warn:', msg) },
  error: function(msg) { console.error('error:', msg) },
  log:   console.log,
}

function middleServer(options) {
  if ('function' === typeof options) options = { middleware: options }
  options                    = options || {}
  if (options.logger) logger = options.logger
  if (options.port)   port   = options.port

  function consoleLog(req, res, next) {
    req.app.locals._log.info(req.url)
    next()
  }

  function ignoreFavicon(req, res, next) {
    if (req.url == '/favicon.ico') return res.status(404) // ignore favicon
    next()
  }

  options.pre        = options.pre        || [ consoleLog, ignoreFavicon ]
  options.middleware = options.middleware || require('readme-middleware')
  options.post       = options.post       || []

  for (var i=0, len = options.pre.length; i < len; i++) {
    app.use(options.pre[i])
  }

  app.use(options.middleware)
  for (i=0, len = options.post.length; i < len; i++) {
    app.use(options.post[i])
  }
  var appName = pack.name + '@' + pack.version

  app.locals._log = logger
  app.listen(port)
  logger.info(appName + ' listening on ' + port)
  return app
}

function readmeMiddleware() {
  return require('readme-middleware')
}

module.exports               = middleServer
module.exports.log           = consoleLog
module.exports.ignoreFavicon = ignoreFavicon
module.exports.readme        = readmeMiddleware
<commit_msg>expand exports<commit_after>/** Middleware server
*
*  @copyright  Copyright (C) 2015 by Yieme
*  @module     middle-server
 */
'use strict';
var fs = require('fs')
var express     = require('express')
var app         = express()
var port        = process.env.port || 3000
var path        = require('path')
var pkgPath     = path.normalize(process.cwd() + '/package.json')
var pack        = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
var logger      = {
  info:  function(msg) { console.log('info:', msg) },
  debug: function(msg) { console.log('debug:', msg) },
  warn:  function(msg) { console.warn('warn:', msg) },
  error: function(msg) { console.error('error:', msg) },
  log:   console.log,
}

function consoleLog(req, res, next) {
  req.app.locals._log.info(req.url)
  next()
}

function ignoreFavicon(req, res, next) {
  if (req.url == '/favicon.ico') return res.status(404) // ignore favicon
  next()
}

function middleServer(options) {
  if ('function' === typeof options) options = { middleware: options }
  options                    = options || {}
  if (options.logger) logger = options.logger
  if (options.port)   port   = options.port

  options.pre        = options.pre        || [ consoleLog, ignoreFavicon ]
  options.middleware = options.middleware || require('readme-middleware')
  options.post       = options.post       || []

  for (var i=0, len = options.pre.length; i < len; i++) {
    app.use(options.pre[i])
  }

  app.use(options.middleware)
  for (i=0, len = options.post.length; i < len; i++) {
    app.use(options.post[i])
  }
  var appName = pack.name + '@' + pack.version

  app.locals._log = logger
  app.listen(port)
  logger.info(appName + ' listening on ' + port)
  return app
}

function readmeMiddleware() {
  return require('readme-middleware')
}

module.exports               = middleServer
module.exports.log           = consoleLog
module.exports.ignoreFavicon = ignoreFavicon
module.exports.readme        = readmeMiddleware
<|endoftext|>


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

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');

// initialise the list of valid "write" methods
var WRITE_METHODS = ['write', 'send'];
var CLOSE_METHODS = ['close', 'end'];

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '4.0.3'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  // get the autoreply setting
  var autoreply = (opts || {}).autoreply;
  var connect = (opts || {}).connect || require('./connect');

  // initialise the metadata
  var localMeta = {};

  // create the signaller
  var signaller = mbus('', (opts || {}).logger);

  // initialise the id
  var id = signaller.id = (opts || {}).id || uuid();

  // initialise the attributes
  var attributes = signaller.attributes = {
    browser: detect.browser,
    browserVersion: detect.browserVersion,
    id: id,
    agent: 'signaller@' + metadata.version
  };

  // create the peers map
  var peers = signaller.peers = getable({});

  // initialise the ready state to disconnected
  var readyState = 0;

  var write;
  var close;
  var processor;
  var announceTimer = 0;

  function announceOnReconnect() {
    signaller.announce();
  }

  function bindBrowserEvents() {
    messenger.addEventListener('message', function(evt) {
      processor(evt.data);
    });

    messenger.addEventListener('open', handleMessengerOpen);
    messenger.addEventListener('close', handleMessengerClose);
  }

  function bindEvents() {
    // handle message data events
    messenger.on(opts.dataEvent, processor);
    messenger.on(opts.openEvent, handleMessengerOpen);
    messenger.on(opts.closeEvent, handleMessengerClose);
    messenger.on(opts.errorEvent, handleMessengerClose);
  }

  function connectToHost(url) {
    connect(url, opts, function(err, socket) {
      if (err) {
        return signaller('error', err);
      }

      // create the actual messenger from a primus connection
      signaller._messenger = messenger = socket.connect(url);

      // if the socket has a ready state, check for connected

      // now init
      init();
    });
  }

  function createDataLine(args) {
    return args.map(prepareArg).join('|');
  }

  function createMetadata() {
    return extend({}, localMeta, { id: signaller.id });
  }

  function extractProp(name) {
    return messenger[name];
  }

  function handleMessengerClose(err) {
    if (err instanceof Error) {
      console.log('socket closed with error: ', err);
    }

    readyState = RS_DISCONNECTED;
    signaller('disconnected');
  }

  function handleMessengerOpen() {
    readyState = RS_CONNECTED;
    signaller('open');
    signaller('connected');
  }

  // attempt to detect whether the underlying messenger is closing
  // this can be tough as we deal with both native (or simulated native)
  // sockets or an abstraction layer such as primus
  function isClosing() {
    var isAbstraction = messenger &&
        // a primus socket has a socket attribute
        typeof messenger.socket != 'undefined';

    return isAbstraction ? false : (
      messenger &&
      typeof messenger.readyState != 'undefined' &&
      messenger.readyState >= 2
    );
  }

  function isF(target) {
    return typeof target == 'function';
  }

  function init() {
    var initEvents = ['init'];

    // extract the write and close function references
    write = [opts.writeMethod].concat(WRITE_METHODS).map(extractProp).filter(isF)[0];
    close = [opts.closeMethod].concat(CLOSE_METHODS).map(extractProp).filter(isF)[0];

    // create the processor
    signaller.process = processor = require('./processor')(signaller, opts);

    // if the messenger doesn't provide a valid write method, then complain
    if (typeof write != 'function') {
      throw new Error('provided messenger does not implement a "' +
        opts.writeMethod + '" write method');
    }

    // handle core browser messenging apis
    if (typeof messenger.addEventListener == 'function') {
      bindBrowserEvents();
    }
    else if (typeof messenger.on == 'function') {
      bindEvents();
    }

    // determine if we are connected or not
    readyState = messenger.connected ? RS_CONNECTED : RS_DISCONNECTED;
    if (readyState !== RS_CONNECTED) {
      signaller.once('connected', function() {
        // always announce on reconnect
        signaller.on('connected', announceOnReconnect);
      });
    }
    else {
      initEvents = initEvents.concat(['open', 'connected']);
    }

    // emit the initialized event
    setTimeout(function() {
      initEvents.forEach(function(name) {
        signaller(name);
      });
    }, 0);
  }

  function prepareArg(arg) {
    if (typeof arg == 'object' && (! (arg instanceof String))) {
      return JSON.stringify(arg);
    }
    else if (typeof arg == 'function') {
      return null;
    }

    return arg;
  }

  /**
    ### signaller#send(message, data*)

    Use the send function to send a message to other peers in the current
    signalling scope (if announced in a room this will be a room, otherwise
    broadcast to all peers connected to the signalling server).

  **/
  var send = signaller.send = function() {
    // iterate over the arguments and stringify as required
    // var metadata = { id: signaller.id };
    var args = [].slice.call(arguments);
    var dataline;

    // inject the metadata
    args.splice(1, 0, createMetadata());
    dataline = createDataLine(args);

    // perform an isclosing check
    if (isClosing()) {
      return;
    }

    // if we are not initialized, then wait until we are
    if (readyState !== RS_CONNECTED) {
      return signaller.once('connected', function() {
        write.call(messenger, dataline);
      });
    }

    // send the data over the messenger
    return write.call(messenger, dataline);
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data, sender) {

    function sendAnnounce() {
      (sender || send)('/announce', attributes);
      signaller('local:announce', attributes);
    }

    clearTimeout(announceTimer);

    // update internal attributes
    extend(attributes, data, { id: signaller.id });

    // if we are already connected, then ensure we announce on
    // reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', announceOnReconnect);
      signaller.on('connected', announceOnReconnect);
    }

    // send the attributes over the network
    return announceTimer = setTimeout(function() {
      if (readyState !== RS_CONNECTED) {
        return signaller.once('connected', sendAnnounce);
      }

      sendAnnounce();
    }, (opts || {}).announceDelay || 10);
  };

  /**
    ### isMaster(targetId)

    A simple function that indicates whether the local signaller is the master
    for it's relationship with peer signaller indicated by `targetId`.  Roles
    are determined at the point at which signalling peers discover each other,
    and are simply worked out by whichever peer has the lowest signaller id
    when lexigraphically sorted.

    For example, if we have two signaller peers that have discovered each
    others with the following ids:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6`

    They would be assigned roles:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6` (master)

  **/
  signaller.isMaster = function(targetId) {
    var peer = peers.get(targetId);

    return peer && peer.roleIdx !== 0;
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    send('/leave', { id: id });

    // stop announcing on reconnect
    signaller.removeListener('connected', announceOnReconnect);

    // call the close method
    if (typeof close == 'function') {
      close.call(messenger);
    }

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  /**
    ### metadata(data?)

    Get (pass no data) or set the metadata that is passed through with each
    request sent by the signaller.

    __NOTE:__ Regardless of what is passed to this function, metadata
    generated by the signaller will **always** include the id of the signaller
    and this cannot be modified.
  **/
  signaller.metadata = function(data) {
    if (arguments.length === 0) {
      return extend({}, localMeta);
    }

    localMeta = extend({}, data);
  };

  /**
    ### to(targetId)

    Use the `to` function to send a message to the specified target peer.
    A large parge of negotiating a WebRTC peer connection involves direct
    communication between two parties which must be done by the signalling
    server.  The `to` function provides a simple way to provide a logical
    communication channel between the two parties:

    ```js
    var send = signaller.to('e95fa05b-9062-45c6-bfa2-5055bf6625f4').send;

    // create an offer on a local peer connection
    pc.createOffer(
      function(desc) {
        // set the local description using the offer sdp
        // if this occurs successfully send this to our peer
        pc.setLocalDescription(
          desc,
          function() {
            send('/sdp', desc);
          },
          handleFail
        );
      },
      handleFail
    );
    ```

  **/
  signaller.to = function(targetId) {
    // create a sender that will prepend messages with /to|targetId|
    var sender = function() {
      // get the peer (yes when send is called to make sure it hasn't left)
      var peer = signaller.peers.get(targetId);
      var args;

      if (! peer) {
        throw new Error('Unknown peer: ' + targetId);
      }

      // if the peer is inactive, then abort
      if (peer.inactive) {
        return;
      }

      args = [
        '/to',
        targetId
      ].concat([].slice.call(arguments));

      // inject metadata
      args.splice(3, 0, createMetadata());

      setTimeout(function() {
        if (readyState === RS_CONNECTED) {
          write.call(messenger, createDataLine(args));
        }
      }, 0);
    };

    return {
      announce: function(data) {
        return signaller.announce(data, sender);
      },

      send: sender,
    };
  };

  // initialise opts defaults
  opts = defaults({}, opts, require('./defaults'));

  // set the autoreply flag
  signaller.autoreply = autoreply === undefined || autoreply;

  // if the messenger is a string, then we are going to attach to a
  // ws endpoint and automatically set up primus
  if (typeof messenger == 'string' || (messenger instanceof String)) {
    connectToHost(messenger);
  }
  // otherwise, initialise the connection
  else {
    init();
  }

  // connect an instance of the messenger to the signaller
  signaller._messenger = messenger;

  // expose the process as a process function
  signaller.process = processor;

  return signaller;
};
<commit_msg>First pass of tweaking functionality to be use pure messengers<commit_after>/* jshint node: true */
'use strict';

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '4.0.3'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  // get the autoreply setting
  var autoreply = (opts || {}).autoreply;

  // initialise the metadata
  var localMeta = {};

  // create the signaller
  var signaller = mbus('', (opts || {}).logger);

  // initialise the id
  var id = signaller.id = (opts || {}).id || uuid();

  // initialise the attributes
  var attributes = signaller.attributes = {
    browser: detect.browser,
    browserVersion: detect.browserVersion,
    id: id,
    agent: 'signaller@' + metadata.version
  };

  // create the peers map
  var peers = signaller.peers = getable({});

  // create the outbound message queue
  var queue = require('pull-pushable')();

  var processor;
  var announceTimer = 0;
  var readyState = RS_DISCONNECTED;

  function announceOnReconnect() {
    signaller.announce();
  }

  function connect() {
    // initiate the messenger
    readyState = RS_CONNECTING;
    messenger(function(err, source, sink) {
      if (err) {
        return signaller('error', err);
      }

      // flag as connected
      readyState = RS_CONNECTED;

      // pass messages to the processor
      pull(
        source,
        // monitor disconnection
        pull.through(function(null, function() {
          console.log('ended');

          // trigger the disconnected event
          signaller('disconnected');
        })),
        pull.drain(function(data) {
          // process the message
          console.log('need to process the message: ', data);
        })
      )

      // pass the queue to the sink
      pull(queue, sink);

      // trigger the connected event
      signaller('connected');
    });
  }

  function createDataLine(args) {
    return args.map(prepareArg).join('|');
  }

  function createMetadata() {
    return extend({}, localMeta, { id: signaller.id });
  }

  function extractProp(name) {
    return messenger[name];
  }

  function prepareArg(arg) {
    if (typeof arg == 'object' && (! (arg instanceof String))) {
      return JSON.stringify(arg);
    }
    else if (typeof arg == 'function') {
      return null;
    }

    return arg;
  }

  /**
    ### signaller#send(message, data*)

    Use the send function to send a message to other peers in the current
    signalling scope (if announced in a room this will be a room, otherwise
    broadcast to all peers connected to the signalling server).

  **/
  var send = signaller.send = function() {
    // iterate over the arguments and stringify as required
    // var metadata = { id: signaller.id };
    var args = [].slice.call(arguments);

    // inject the metadata
    args.splice(1, 0, createMetadata());

    // queue the dataline for sending
    queue.push(createDataLine(args));
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data, sender) {

    function sendAnnounce() {
      (sender || send)('/announce', attributes);
      signaller('local:announce', attributes);
    }

    clearTimeout(announceTimer);

    // update internal attributes
    extend(attributes, data, { id: signaller.id });

    // if we are already connected, then ensure we announce on reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', announceOnReconnect);
      signaller.on('connected', announceOnReconnect);
    }

    // send the attributes over the network
    return announceTimer = setTimeout(function() {
      if (readyState !== RS_CONNECTED) {
        return signaller.once('connected', sendAnnounce);
      }

      sendAnnounce();
    }, (opts || {}).announceDelay || 10);
  };

  /**
    ### isMaster(targetId)

    A simple function that indicates whether the local signaller is the master
    for it's relationship with peer signaller indicated by `targetId`.  Roles
    are determined at the point at which signalling peers discover each other,
    and are simply worked out by whichever peer has the lowest signaller id
    when lexigraphically sorted.

    For example, if we have two signaller peers that have discovered each
    others with the following ids:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6`

    They would be assigned roles:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6` (master)

  **/
  signaller.isMaster = function(targetId) {
    var peer = peers.get(targetId);

    return peer && peer.roleIdx !== 0;
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    send('/leave', { id: id });

    // stop announcing on reconnect
    signaller.removeListener('connected', announceOnReconnect);

    // end our current queue
    queue.end();

    // create a new queue to buffer new messages
    queue = pushable();

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  /**
    ### metadata(data?)

    Get (pass no data) or set the metadata that is passed through with each
    request sent by the signaller.

    __NOTE:__ Regardless of what is passed to this function, metadata
    generated by the signaller will **always** include the id of the signaller
    and this cannot be modified.
  **/
  signaller.metadata = function(data) {
    if (arguments.length === 0) {
      return extend({}, localMeta);
    }

    localMeta = extend({}, data);
  };

  /**
    ### to(targetId)

    Use the `to` function to send a message to the specified target peer.
    A large parge of negotiating a WebRTC peer connection involves direct
    communication between two parties which must be done by the signalling
    server.  The `to` function provides a simple way to provide a logical
    communication channel between the two parties:

    ```js
    var send = signaller.to('e95fa05b-9062-45c6-bfa2-5055bf6625f4').send;

    // create an offer on a local peer connection
    pc.createOffer(
      function(desc) {
        // set the local description using the offer sdp
        // if this occurs successfully send this to our peer
        pc.setLocalDescription(
          desc,
          function() {
            send('/sdp', desc);
          },
          handleFail
        );
      },
      handleFail
    );
    ```

  **/
  signaller.to = function(targetId) {
    // create a sender that will prepend messages with /to|targetId|
    var sender = function() {
      // get the peer (yes when send is called to make sure it hasn't left)
      var peer = signaller.peers.get(targetId);
      var args;

      if (! peer) {
        throw new Error('Unknown peer: ' + targetId);
      }

      // if the peer is inactive, then abort
      if (peer.inactive) {
        return;
      }

      args = [
        '/to',
        targetId
      ].concat([].slice.call(arguments));

      // inject metadata
      args.splice(3, 0, createMetadata());
      queue.push(createDataLine(args));
    };

    return {
      announce: function(data) {
        return signaller.announce(data, sender);
      },

      send: sender,
    };
  };

  // initialise opts defaults
  opts = defaults({}, opts, require('./defaults'));

  // set the autoreply flag
  signaller.autoreply = autoreply === undefined || autoreply;

  // create the processor
  signaller.process = processor = require('./processor')(signaller, opts);

  connect();
  return signaller;
};
<|endoftext|>


<commit_before>// interface XmlNode {
//     name: string; // element name (empty for text nodes)
//     type: string; // node type (element or text), see NodeType constants
//     value: string; // value of a text node
//     parent: XmlNode; // reference to parent node (null in root node)
//     attributes: {[name: string]: string}; // map of attributes name => value
//     children: XmlNode[];  // array of children nodes
// }

const flatMap = (arr, fn) => [].concat(...arr.map(fn));

const xmlQuery = (node) => {

    const nodes = Array.isArray(node) ? node : [node];
    const length = nodes.length;

    const get = (i) => nodes[i];

    const findInNode = (node, sel) => {
        const res = (node.name === sel) ? [node] : [];
        return res.concat(flatMap(node.children, (node) => findInNode(node, sel)));
    };

    const find = (sel) =>
        xmlQuery(flatMap(nodes, (node) => findInNode(node, sel)));

    const children = () =>
        xmlQuery(flatMap(nodes, (node) => node.children));

    const attr = (name) => {
        if (!length) {
            return;
        }
        const attrs = nodes[0].attributes
        return name ? attrs[name] : attrs;
    };

    const eq = (n) => xmlQuery(nodes[n]);
    const first = () => eq(0);
    const last = () => eq(length - 1);
    const map = (fn) => nodes.map(fn);
    const size = () => length;

    // @todo
    // .prop()
    // .each()

    return {
        attr,
        children,
        eq,
        find,
        first,
        get,
        length,
        map,
        size,
    };
};

module.exports = xmlQuery;<commit_msg>rename param<commit_after>// interface XmlNode {
//     name: string; // element name (empty for text nodes)
//     type: string; // node type (element or text), see NodeType constants
//     value: string; // value of a text node
//     parent: XmlNode; // reference to parent node (null in root node)
//     attributes: {[name: string]: string}; // map of attributes name => value
//     children: XmlNode[];  // array of children nodes
// }

const flatMap = (arr, fn) => [].concat(...arr.map(fn));

const xmlQuery = (ast) => {

    const nodes = Array.isArray(ast) ? ast : [ast];
    const length = nodes.length;

    const get = (i) => nodes[i];

    const findInNode = (node, sel) => {
        const res = (node.name === sel) ? [node] : [];
        return res.concat(flatMap(node.children, (node) => findInNode(node, sel)));
    };

    const find = (sel) =>
        xmlQuery(flatMap(nodes, (node) => findInNode(node, sel)));

    const children = () =>
        xmlQuery(flatMap(nodes, (node) => node.children));

    const attr = (name) => {
        if (!length) {
            return;
        }
        const attrs = nodes[0].attributes
        return name ? attrs[name] : attrs;
    };

    const eq = (n) => xmlQuery(nodes[n]);
    const first = () => eq(0);
    const last = () => eq(length - 1);
    const map = (fn) => nodes.map(fn);
    const size = () => length;

    // @todo
    // .prop()
    // .each()

    return {
        attr,
        children,
        eq,
        find,
        first,
        get,
        length,
        map,
        size,
    };
};

module.exports = xmlQuery;<|endoftext|>


<commit_before>module.exports = {
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended"
  ],
  "plugins": [
    "react"
  ],
  "parserOptions": {
    "ecmaVersion": 6
  },
  "rules": {
    // disallow await inside of loops
    "no-await-in-loop": "error",
    // disallow unnecessary parentheses
    "no-extra-parens": "error",
    "space-before-function-paren": ["error", {
        "anonymous": "never",  // is for anonymous function expressions(e.g. function () {}).
        "named": "never", // is for named function expressions (e.g. function foo () {}).
        "asyncArrow": "ignore" // is for async arrow function expressions (e.g. async () => {}).
    }]
  }
}<commit_msg>feat: add no-x rules<commit_after>module.exports = {
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended"
  ],
  "plugins": [
    "react"
  ],
  "parserOptions": {
    "ecmaVersion": 6
  },
  "rules": {
    // disallow await inside of loops
    "no-await-in-loop": "error",
    // disallow unnecessary parentheses
    "no-extra-parens": "error",
    // disallow calling some Object.prototype methods directly on objects
    "no-prototype-builtins": "error",
    // disallow template literal placeholder syntax in regular strings
    "no-template-curly-in-string": "error",
    // disallow negating the left operand of relational operators
    "no-unsafe-negation": "error",
    // enforce valid JSDoc comments
    "valid-jsdoc": "error",
    "space-before-function-paren": ["error", {
        "anonymous": "never",  // is for anonymous function expressions(e.g. function () {}).
        "named": "never", // is for named function expressions (e.g. function foo () {}).
        "asyncArrow": "ignore" // is for async arrow function expressions (e.g. async () => {}).
    }]
  }
}<|endoftext|>


<commit_before>'use strict';
var childCompiler = require('./lib/compiler.js');
var assert = require('assert');
var _ = require('lodash');
var fs = require('fs');
var path = require('path');

function FaviconsWebpackPlugin (options) {
  if (typeof options === 'string') {
    options = {logo: options};
  }
  assert(typeof options === 'object', 'FaviconsWebpackPlugin options are required');
  assert(options.logo, 'An input file is required');
  this.options = _.extend({
    prefix: 'icons-[hash]/',
    emitStats: false,
    statsFilename: 'iconstats-[hash].json',
    persistentCache: true,
    inject: true,
    background: '#fff'
  }, options);
  this.options.icons = _.extend({
    android: true,
    appleIcon: true,
    appleStartup: true,
    coast: false,
    favicons: true,
    firefox: true,
    opengraph: false,
    twitter: false,
    yandex: false,
    windows: false
  }, this.options.icons);
}

FaviconsWebpackPlugin.prototype.apply = function (compiler) {
  var self = this;
  if (!self.options.title) {
    self.options.title = guessAppName(compiler.context);
  }

  // Generate the favicons (webpack 4 compliant + back compat)
  var compilationResult;
  (compiler.hooks
    ? compiler.hooks.make.tapAsync.bind(compiler.hooks.make, 'FaviconsWebpackPluginMake')
    : compiler.plugin.bind(compiler, 'make')
  )((compilation, callback) => {
    childCompiler.compileTemplate(self.options, compiler.context, compilation)
      .then(function (result) {
        compilationResult = result;
        callback();
      })
      .catch(callback);
  });

  // Hook into the html-webpack-plugin processing
  // and add the html
  if (self.options.inject) {
    var addFaviconsToHtml = function (htmlPluginData, callback) {
      if (htmlPluginData.plugin.options.favicons !== false) {
        htmlPluginData.html = htmlPluginData.html.replace(
          /(<\/head>)/i, compilationResult.stats.html.join('') + '$&');
      }
      callback(null, htmlPluginData);
    };

    // webpack 4
    if (compiler.hooks) {
      compiler.hooks.compilation.tap('FaviconsWebpackPlugin', function (cmpp) {
        cmpp.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync('favicons-webpack-plugin', addFaviconsToHtml);
      });
    } else {
      compiler.plugin('compilation', function (compilation) {
        compilation.plugin('html-webpack-plugin-before-html-processing', addFaviconsToHtml);
      });
    }
  }

  // Remove the stats from the output if they are not required (webpack 4 compliant + back compat)
  if (!self.options.emitStats) {
    (compiler.hooks
      ? compiler.hooks.emit.tapAsync.bind(compiler.hooks.emit, 'FaviconsWebpackPluginEmit')
      : compiler.plugin.bind(compiler, 'emit')
    )((compilation, callback) => {
      delete compilation.assets[compilationResult.outputName];
      callback();
    });
  }
};

/**
 * Tries to guess the name from the package.json
 */
function guessAppName (compilerWorkingDirectory) {
  var packageJson = path.resolve(compilerWorkingDirectory, 'package.json');
  if (!fs.existsSync(packageJson)) {
    packageJson = path.resolve(compilerWorkingDirectory, '../package.json');
    if (!fs.existsSync(packageJson)) {
      return 'Webpack App';
    }
  }
  return JSON.parse(fs.readFileSync(packageJson)).name;
}

module.exports = FaviconsWebpackPlugin;
<commit_msg>inject html to end of head<commit_after>'use strict';
var childCompiler = require('./lib/compiler.js');
var assert = require('assert');
var _ = require('lodash');
var fs = require('fs');
var path = require('path');

function FaviconsWebpackPlugin (options) {
  if (typeof options === 'string') {
    options = {logo: options};
  }
  assert(typeof options === 'object', 'FaviconsWebpackPlugin options are required');
  assert(options.logo, 'An input file is required');
  this.options = _.extend({
    prefix: 'icons-[hash]/',
    emitStats: false,
    statsFilename: 'iconstats-[hash].json',
    persistentCache: true,
    inject: true,
    background: '#fff'
  }, options);
  this.options.icons = _.extend({
    android: true,
    appleIcon: true,
    appleStartup: true,
    coast: false,
    favicons: true,
    firefox: true,
    opengraph: false,
    twitter: false,
    yandex: false,
    windows: false
  }, this.options.icons);
}

FaviconsWebpackPlugin.prototype.apply = function (compiler) {
  var self = this;
  if (!self.options.title) {
    self.options.title = guessAppName(compiler.context);
  }

  // Generate the favicons (webpack 4 compliant + back compat)
  var compilationResult;
  (compiler.hooks
    ? compiler.hooks.make.tapAsync.bind(compiler.hooks.make, 'FaviconsWebpackPluginMake')
    : compiler.plugin.bind(compiler, 'make')
  )((compilation, callback) => {
    childCompiler.compileTemplate(self.options, compiler.context, compilation)
      .then(function (result) {
        compilationResult = result;
        callback();
      })
      .catch(callback);
  });

  // Hook into the html-webpack-plugin processing
  // and add the html
  if (self.options.inject) {
    compiler.plugin('compilation', function (compilation) {
      compilation.plugin('html-webpack-plugin-before-html-processing', function (htmlPluginData, callback) {
        if (htmlPluginData.plugin.options.favicons !== false) {
          htmlPluginData.html = htmlPluginData.html.replace(
            /(<\/head>)/i, compilationResult.stats.html.join('') + '$&');
          callback(null, htmlPluginData);
        }
      });
    } else {
      compiler.plugin('compilation', function (compilation) {
        compilation.plugin('html-webpack-plugin-before-html-processing', addFaviconsToHtml);
      });
    }
  }

  // Remove the stats from the output if they are not required (webpack 4 compliant + back compat)
  if (!self.options.emitStats) {
    (compiler.hooks
      ? compiler.hooks.emit.tapAsync.bind(compiler.hooks.emit, 'FaviconsWebpackPluginEmit')
      : compiler.plugin.bind(compiler, 'emit')
    )((compilation, callback) => {
      delete compilation.assets[compilationResult.outputName];
      callback();
    });
  }
};

/**
 * Tries to guess the name from the package.json
 */
function guessAppName (compilerWorkingDirectory) {
  var packageJson = path.resolve(compilerWorkingDirectory, 'package.json');
  if (!fs.existsSync(packageJson)) {
    packageJson = path.resolve(compilerWorkingDirectory, '../package.json');
    if (!fs.existsSync(packageJson)) {
      return 'Webpack App';
    }
  }
  return JSON.parse(fs.readFileSync(packageJson)).name;
}

module.exports = FaviconsWebpackPlugin;
<|endoftext|>


<commit_before>/**
 * @module ol/source/Raster
 */
import {getUid} from '../util.js';
import ImageCanvas from '../ImageCanvas.js';
import TileQueue from '../TileQueue.js';
import {createCanvasContext2D} from '../dom.js';
import {listen} from '../events.js';
import Event from '../events/Event.js';
import EventType from '../events/EventType.js';
import {Processor} from 'pixelworks/lib/index';
import {equals, getCenter, getHeight, getWidth} from '../extent.js';
import ImageLayer from '../layer/Image.js';
import TileLayer from '../layer/Tile.js';
import {assign} from '../obj.js';
import {create as createTransform} from '../transform.js';
import ImageSource from './Image.js';
import TileSource from './Tile.js';
import SourceState from './State.js';
import Source from './Source.js';

/**
 * A function that takes an array of input data, performs some operation, and
 * returns an array of output data.
 * For `pixel` type operations, the function will be called with an array of
 * pixels, where each pixel is an array of four numbers (`[r, g, b, a]`) in the
 * range of 0 - 255. It should return a single pixel array.
 * For `'image'` type operations, functions will be called with an array of
 * {@link ImageData https://developer.mozilla.org/en-US/docs/Web/API/ImageData}
 * and should return a single {@link ImageData
 * https://developer.mozilla.org/en-US/docs/Web/API/ImageData}.  The operations
 * are called with a second "data" argument, which can be used for storage.  The
 * data object is accessible from raster events, where it can be initialized in
 * "beforeoperations" and accessed again in "afteroperations".
 *
 * @typedef {function((Array<Array<number>>|Array<ImageData>), Object):
 *     (Array<number>|ImageData)} Operation
 */

/**
 * @enum {string}
 */
const RasterEventType = {
  /**
   * Triggered before operations are run.
   * @event ol/source/Raster~RasterSourceEvent#beforeoperations
   * @api
   */
  BEFOREOPERATIONS: 'beforeoperations',

  /**
   * Triggered after operations are run.
   * @event ol/source/Raster~RasterSourceEvent#afteroperations
   * @api
   */
  AFTEROPERATIONS: 'afteroperations'
};

/**
 * Raster operation type. Supported values are `'pixel'` and `'image'`.
 * @enum {string}
 */
const RasterOperationType = {
  PIXEL: 'pixel',
  IMAGE: 'image'
};

/**
 * @classdesc
 * Events emitted by {@link module:ol/source/Raster} instances are instances of this
 * type.
 */
class RasterSourceEvent extends Event {
  /**
   * @param {string} type Type.
   * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
   * @param {Object} data An object made available to operations.
   */
  constructor(type, frameState, data) {
    super(type);

    /**
     * The raster extent.
     * @type {import("../extent.js").Extent}
     * @api
     */
    this.extent = frameState.extent;

    /**
     * The pixel resolution (map units per pixel).
     * @type {number}
     * @api
     */
    this.resolution = frameState.viewState.resolution / frameState.pixelRatio;

    /**
     * An object made available to all operations.  This can be used by operations
     * as a storage object (e.g. for calculating statistics).
     * @type {Object}
     * @api
     */
    this.data = data;

  }

}

/**
 * @typedef {Object} Options
 * @property {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources Input
 * sources or layers.  For vector data, use an VectorImage layer.
 * @property {Operation} [operation] Raster operation.
 * The operation will be called with data from input sources
 * and the output will be assigned to the raster source.
 * @property {Object} [lib] Functions that will be made available to operations run in a worker.
 * @property {number} [threads] By default, operations will be run in a single worker thread.
 * To avoid using workers altogether, set `threads: 0`.  For pixel operations, operations can
 * be run in multiple worker threads.  Note that there is additional overhead in
 * transferring data to multiple workers, and that depending on the user's
 * system, it may not be possible to parallelize the work.
 * @property {RasterOperationType} [operationType='pixel'] Operation type.
 * Supported values are `'pixel'` and `'image'`.  By default,
 * `'pixel'` operations are assumed, and operations will be called with an
 * array of pixels from input sources.  If set to `'image'`, operations will
 * be called with an array of ImageData objects from input sources.
 */

/**
 * @classdesc
 * A source that transforms data from any number of input sources using an
 * {@link module:ol/source/Raster~Operation} function to transform input pixel values into
 * output pixel values.
 *
 * @fires ol/source/Raster~RasterSourceEvent
 * @api
 */
class RasterSource extends ImageSource {
  /**
   * @param {Options} options Options.
   */
  constructor(options) {
    super({
      projection: null
    });

    /**
     * @private
     * @type {*}
     */
    this.worker_ = null;

    /**
     * @private
     * @type {RasterOperationType}
     */
    this.operationType_ = options.operationType !== undefined ?
      options.operationType : RasterOperationType.PIXEL;

    /**
     * @private
     * @type {number}
     */
    this.threads_ = options.threads !== undefined ? options.threads : 1;

    /**
     * @private
     * @type {Array<import("../renderer/canvas/Layer.js").default>}
     */
    this.renderers_ = createRenderers(options.sources);

    for (let r = 0, rr = this.renderers_.length; r < rr; ++r) {
      listen(this.renderers_[r], EventType.CHANGE,
        this.changed, this);
    }

    /**
     * @private
     * @type {import("../TileQueue.js").default}
     */
    this.tileQueue_ = new TileQueue(
      function() {
        return 1;
      },
      this.changed.bind(this));

    const layerStatesArray = getLayerStatesArray(this.renderers_);

    /**
     * @type {Object<string, import("../layer/Layer.js").State>}
     */
    const layerStates = {};
    for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
      layerStates[getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
    }

    /**
     * The most recently requested frame state.
     * @type {import("../PluggableMap.js").FrameState}
     * @private
     */
    this.requestedFrameState_;

    /**
     * The most recently rendered image canvas.
     * @type {import("../ImageCanvas.js").default}
     * @private
     */
    this.renderedImageCanvas_ = null;

    /**
     * The most recently rendered revision.
     * @type {number}
     */
    this.renderedRevision_;

    /**
     * @private
     * @type {import("../PluggableMap.js").FrameState}
     */
    this.frameState_ = {
      animate: false,
      coordinateToPixelTransform: createTransform(),
      extent: null,
      focus: null,
      index: 0,
      layerStates: layerStates,
      layerStatesArray: layerStatesArray,
      pixelRatio: 1,
      pixelToCoordinateTransform: createTransform(),
      postRenderFunctions: [],
      size: [0, 0],
      skippedFeatureUids: {},
      tileQueue: this.tileQueue_,
      time: Date.now(),
      usedTiles: {},
      viewState: /** @type {import("../View.js").State} */ ({
        rotation: 0
      }),
      viewHints: [],
      wantedTiles: {}
    };

    if (options.operation !== undefined) {
      this.setOperation(options.operation, options.lib);
    }

  }

  /**
   * Set the operation.
   * @param {Operation} operation New operation.
   * @param {Object=} opt_lib Functions that will be available to operations run
   *     in a worker.
   * @api
   */
  setOperation(operation, opt_lib) {
    this.worker_ = new Processor({
      operation: operation,
      imageOps: this.operationType_ === RasterOperationType.IMAGE,
      queue: 1,
      lib: opt_lib,
      threads: this.threads_
    });
    this.changed();
  }

  /**
   * Update the stored frame state.
   * @param {import("../extent.js").Extent} extent The view extent (in map units).
   * @param {number} resolution The view resolution.
   * @param {import("../proj/Projection.js").default} projection The view projection.
   * @return {import("../PluggableMap.js").FrameState} The updated frame state.
   * @private
   */
  updateFrameState_(extent, resolution, projection) {

    const frameState = /** @type {import("../PluggableMap.js").FrameState} */ (assign({}, this.frameState_));

    frameState.viewState = /** @type {import("../View.js").State} */ (assign({}, frameState.viewState));

    const center = getCenter(extent);

    frameState.extent = extent.slice();
    frameState.focus = center;
    frameState.size[0] = Math.round(getWidth(extent) / resolution);
    frameState.size[1] = Math.round(getHeight(extent) / resolution);
    frameState.time = Date.now();
    frameState.animate = false;

    const viewState = frameState.viewState;
    viewState.center = center;
    viewState.projection = projection;
    viewState.resolution = resolution;
    return frameState;
  }

  /**
   * Determine if all sources are ready.
   * @return {boolean} All sources are ready.
   * @private
   */
  allSourcesReady_() {
    let ready = true;
    let source;
    for (let i = 0, ii = this.renderers_.length; i < ii; ++i) {
      source = this.renderers_[i].getLayer().getSource();
      if (source.getState() !== SourceState.READY) {
        ready = false;
        break;
      }
    }
    return ready;
  }

  /**
   * @inheritDoc
   */
  getImage(extent, resolution, pixelRatio, projection) {
    if (!this.allSourcesReady_()) {
      return null;
    }

    const frameState = this.updateFrameState_(extent, resolution, projection);
    this.requestedFrameState_ = frameState;

    // check if we can't reuse the existing ol/ImageCanvas
    if (this.renderedImageCanvas_) {
      const renderedResolution = this.renderedImageCanvas_.getResolution();
      const renderedExtent = this.renderedImageCanvas_.getExtent();
      if (resolution !== renderedResolution || !equals(extent, renderedExtent)) {
        this.renderedImageCanvas_ = null;
      }
    }

    if (!this.renderedImageCanvas_ || this.getRevision() !== this.renderedRevision_) {
      this.processSources_();
    }

    frameState.tileQueue.loadMoreTiles(16, 16);

    if (frameState.animate) {
      requestAnimationFrame(this.changed.bind(this));
    }

    return this.renderedImageCanvas_;
  }

  /**
   * Start processing source data.
   * @private
   */
  processSources_() {
    const frameState = this.requestedFrameState_;
    const len = this.renderers_.length;
    const imageDatas = new Array(len);
    for (let i = 0; i < len; ++i) {
      const imageData = getImageData(
        this.renderers_[i], frameState, frameState.layerStatesArray[i]);
      if (imageData) {
        imageDatas[i] = imageData;
      } else {
        return;
      }
    }

    const data = {};
    this.dispatchEvent(new RasterSourceEvent(RasterEventType.BEFOREOPERATIONS, frameState, data));
    this.worker_.process(imageDatas, data, this.onWorkerComplete_.bind(this, frameState));
  }

  /**
   * Called when pixel processing is complete.
   * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
   * @param {Error} err Any error during processing.
   * @param {ImageData} output The output image data.
   * @param {Object} data The user data.
   * @private
   */
  onWorkerComplete_(frameState, err, output, data) {
    if (err || !output) {
      return;
    }

    // do nothing if extent or resolution changed
    const extent = frameState.extent;
    const resolution = frameState.viewState.resolution;
    if (resolution !== this.requestedFrameState_.viewState.resolution ||
        !equals(extent, this.requestedFrameState_.extent)) {
      return;
    }

    let context;
    if (this.renderedImageCanvas_) {
      context = this.renderedImageCanvas_.getImage().getContext('2d');
    } else {
      const width = Math.round(getWidth(extent) / resolution);
      const height = Math.round(getHeight(extent) / resolution);
      context = createCanvasContext2D(width, height);
      this.renderedImageCanvas_ = new ImageCanvas(extent, resolution, 1, context.canvas);
    }
    context.putImageData(output, 0, 0);

    this.changed();
    this.renderedRevision_ = this.getRevision();

    this.dispatchEvent(new RasterSourceEvent(RasterEventType.AFTEROPERATIONS, frameState, data));
  }

  /**
   * @override
   */
  getImageInternal() {
    return null; // not implemented
  }
}

/**
 * A reusable canvas context.
 * @type {CanvasRenderingContext2D}
 * @private
 */
let sharedContext = null;

/**
 * Get image data from a renderer.
 * @param {import("../renderer/canvas/Layer.js").default} renderer Layer renderer.
 * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
 * @param {import("../layer/Layer.js").State} layerState The layer state.
 * @return {ImageData} The image data.
 */
function getImageData(renderer, frameState, layerState) {
  if (!renderer.prepareFrame(frameState, layerState)) {
    return null;
  }
  const width = frameState.size[0];
  const height = frameState.size[1];
  const element = renderer.renderFrame(frameState, layerState);
  if (!(element instanceof HTMLCanvasElement)) {
    throw new Error('Unsupported rendered element: ' + element);
  }
  if (element.width === width && element.height === height) {
    const context = element.getContext('2d');
    return context.getImageData(0, 0, width, height);
  }

  if (!sharedContext) {
    sharedContext = createCanvasContext2D(width, height);
  } else {
    const canvas = sharedContext.canvas;
    if (canvas.width !== width || canvas.height !== height) {
      sharedContext = createCanvasContext2D(width, height);
    } else {
      sharedContext.clearRect(0, 0, width, height);
    }
  }
  sharedContext.drawImage(element, 0, 0, width, height);
  return sharedContext.getImageData(0, 0, width, height);
}

/**
 * Get a list of layer states from a list of renderers.
 * @param {Array<import("../renderer/canvas/Layer.js").default>} renderers Layer renderers.
 * @return {Array<import("../layer/Layer.js").State>} The layer states.
 */
function getLayerStatesArray(renderers) {
  return renderers.map(function(renderer) {
    return renderer.getLayer().getLayerState();
  });
}

/**
 * Create renderers for all sources.
 * @param {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources The sources.
 * @return {Array<import("../renderer/canvas/Layer.js").default>} Array of layer renderers.
 */
function createRenderers(sources) {
  const len = sources.length;
  const renderers = new Array(len);
  for (let i = 0; i < len; ++i) {
    renderers[i] = createRenderer(sources[i]);
  }
  return renderers;
}

/**
 * Create a renderer for the provided source.
 * @param {import("./Source.js").default|import("../layer/Layer.js").default} layerOrSource The layer or source.
 * @return {import("../renderer/canvas/Layer.js").default} The renderer.
 */
function createRenderer(layerOrSource) {
  // @type {import("../layer/Layer.js").default}
  let layer;
  if (layerOrSource instanceof Source) {
    if (layerOrSource instanceof TileSource) {
      layer = new TileLayer({source: layerOrSource});
    } else if (layerOrSource instanceof ImageSource) {
      layer = new ImageLayer({source: layerOrSource});
    }
  } else {
    layer = layerOrSource;
  }
  return layer ? /** @type {import("../renderer/canvas/Layer.js").default} */ (layer.createRenderer()) : null;
}

export default RasterSource;
<commit_msg>Raster source listens for layer change<commit_after>/**
 * @module ol/source/Raster
 */
import {getUid} from '../util.js';
import ImageCanvas from '../ImageCanvas.js';
import TileQueue from '../TileQueue.js';
import {createCanvasContext2D} from '../dom.js';
import {listen} from '../events.js';
import Event from '../events/Event.js';
import EventType from '../events/EventType.js';
import {Processor} from 'pixelworks/lib/index';
import {equals, getCenter, getHeight, getWidth} from '../extent.js';
import ImageLayer from '../layer/Image.js';
import TileLayer from '../layer/Tile.js';
import {assign} from '../obj.js';
import {create as createTransform} from '../transform.js';
import ImageSource from './Image.js';
import TileSource from './Tile.js';
import SourceState from './State.js';
import Source from './Source.js';

/**
 * A function that takes an array of input data, performs some operation, and
 * returns an array of output data.
 * For `pixel` type operations, the function will be called with an array of
 * pixels, where each pixel is an array of four numbers (`[r, g, b, a]`) in the
 * range of 0 - 255. It should return a single pixel array.
 * For `'image'` type operations, functions will be called with an array of
 * {@link ImageData https://developer.mozilla.org/en-US/docs/Web/API/ImageData}
 * and should return a single {@link ImageData
 * https://developer.mozilla.org/en-US/docs/Web/API/ImageData}.  The operations
 * are called with a second "data" argument, which can be used for storage.  The
 * data object is accessible from raster events, where it can be initialized in
 * "beforeoperations" and accessed again in "afteroperations".
 *
 * @typedef {function((Array<Array<number>>|Array<ImageData>), Object):
 *     (Array<number>|ImageData)} Operation
 */

/**
 * @enum {string}
 */
const RasterEventType = {
  /**
   * Triggered before operations are run.
   * @event ol/source/Raster~RasterSourceEvent#beforeoperations
   * @api
   */
  BEFOREOPERATIONS: 'beforeoperations',

  /**
   * Triggered after operations are run.
   * @event ol/source/Raster~RasterSourceEvent#afteroperations
   * @api
   */
  AFTEROPERATIONS: 'afteroperations'
};

/**
 * Raster operation type. Supported values are `'pixel'` and `'image'`.
 * @enum {string}
 */
const RasterOperationType = {
  PIXEL: 'pixel',
  IMAGE: 'image'
};

/**
 * @classdesc
 * Events emitted by {@link module:ol/source/Raster} instances are instances of this
 * type.
 */
class RasterSourceEvent extends Event {
  /**
   * @param {string} type Type.
   * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
   * @param {Object} data An object made available to operations.
   */
  constructor(type, frameState, data) {
    super(type);

    /**
     * The raster extent.
     * @type {import("../extent.js").Extent}
     * @api
     */
    this.extent = frameState.extent;

    /**
     * The pixel resolution (map units per pixel).
     * @type {number}
     * @api
     */
    this.resolution = frameState.viewState.resolution / frameState.pixelRatio;

    /**
     * An object made available to all operations.  This can be used by operations
     * as a storage object (e.g. for calculating statistics).
     * @type {Object}
     * @api
     */
    this.data = data;

  }

}

/**
 * @typedef {Object} Options
 * @property {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources Input
 * sources or layers.  For vector data, use an VectorImage layer.
 * @property {Operation} [operation] Raster operation.
 * The operation will be called with data from input sources
 * and the output will be assigned to the raster source.
 * @property {Object} [lib] Functions that will be made available to operations run in a worker.
 * @property {number} [threads] By default, operations will be run in a single worker thread.
 * To avoid using workers altogether, set `threads: 0`.  For pixel operations, operations can
 * be run in multiple worker threads.  Note that there is additional overhead in
 * transferring data to multiple workers, and that depending on the user's
 * system, it may not be possible to parallelize the work.
 * @property {RasterOperationType} [operationType='pixel'] Operation type.
 * Supported values are `'pixel'` and `'image'`.  By default,
 * `'pixel'` operations are assumed, and operations will be called with an
 * array of pixels from input sources.  If set to `'image'`, operations will
 * be called with an array of ImageData objects from input sources.
 */

/**
 * @classdesc
 * A source that transforms data from any number of input sources using an
 * {@link module:ol/source/Raster~Operation} function to transform input pixel values into
 * output pixel values.
 *
 * @fires ol/source/Raster~RasterSourceEvent
 * @api
 */
class RasterSource extends ImageSource {
  /**
   * @param {Options} options Options.
   */
  constructor(options) {
    super({
      projection: null
    });

    /**
     * @private
     * @type {*}
     */
    this.worker_ = null;

    /**
     * @private
     * @type {RasterOperationType}
     */
    this.operationType_ = options.operationType !== undefined ?
      options.operationType : RasterOperationType.PIXEL;

    /**
     * @private
     * @type {number}
     */
    this.threads_ = options.threads !== undefined ? options.threads : 1;

    /**
     * @private
     * @type {Array<import("../layer/Layer.js").default>}
     */
    this.layers_ = createLayers(options.sources);

    for (let i = 0, ii = this.layers_.length; i < ii; ++i) {
      listen(this.layers_[i], EventType.CHANGE, this.changed, this);
    }

    /**
     * @private
     * @type {import("../TileQueue.js").default}
     */
    this.tileQueue_ = new TileQueue(function() {
      return 1;
    }, this.changed.bind(this));

    const layerStatesArray = getLayerStatesArray(this.layers_);

    /**
     * @type {Object<string, import("../layer/Layer.js").State>}
     */
    const layerStates = {};
    for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
      layerStates[getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
    }

    /**
     * The most recently requested frame state.
     * @type {import("../PluggableMap.js").FrameState}
     * @private
     */
    this.requestedFrameState_;

    /**
     * The most recently rendered image canvas.
     * @type {import("../ImageCanvas.js").default}
     * @private
     */
    this.renderedImageCanvas_ = null;

    /**
     * The most recently rendered revision.
     * @type {number}
     */
    this.renderedRevision_;

    /**
     * @private
     * @type {import("../PluggableMap.js").FrameState}
     */
    this.frameState_ = {
      animate: false,
      coordinateToPixelTransform: createTransform(),
      extent: null,
      focus: null,
      index: 0,
      layerStates: layerStates,
      layerStatesArray: layerStatesArray,
      pixelRatio: 1,
      pixelToCoordinateTransform: createTransform(),
      postRenderFunctions: [],
      size: [0, 0],
      skippedFeatureUids: {},
      tileQueue: this.tileQueue_,
      time: Date.now(),
      usedTiles: {},
      viewState: /** @type {import("../View.js").State} */ ({
        rotation: 0
      }),
      viewHints: [],
      wantedTiles: {}
    };

    if (options.operation !== undefined) {
      this.setOperation(options.operation, options.lib);
    }

  }

  /**
   * Set the operation.
   * @param {Operation} operation New operation.
   * @param {Object=} opt_lib Functions that will be available to operations run
   *     in a worker.
   * @api
   */
  setOperation(operation, opt_lib) {
    this.worker_ = new Processor({
      operation: operation,
      imageOps: this.operationType_ === RasterOperationType.IMAGE,
      queue: 1,
      lib: opt_lib,
      threads: this.threads_
    });
    this.changed();
  }

  /**
   * Update the stored frame state.
   * @param {import("../extent.js").Extent} extent The view extent (in map units).
   * @param {number} resolution The view resolution.
   * @param {import("../proj/Projection.js").default} projection The view projection.
   * @return {import("../PluggableMap.js").FrameState} The updated frame state.
   * @private
   */
  updateFrameState_(extent, resolution, projection) {

    const frameState = /** @type {import("../PluggableMap.js").FrameState} */ (assign({}, this.frameState_));

    frameState.viewState = /** @type {import("../View.js").State} */ (assign({}, frameState.viewState));

    const center = getCenter(extent);

    frameState.extent = extent.slice();
    frameState.focus = center;
    frameState.size[0] = Math.round(getWidth(extent) / resolution);
    frameState.size[1] = Math.round(getHeight(extent) / resolution);
    frameState.time = Date.now();
    frameState.animate = false;

    const viewState = frameState.viewState;
    viewState.center = center;
    viewState.projection = projection;
    viewState.resolution = resolution;
    return frameState;
  }

  /**
   * Determine if all sources are ready.
   * @return {boolean} All sources are ready.
   * @private
   */
  allSourcesReady_() {
    let ready = true;
    let source;
    for (let i = 0, ii = this.layers_.length; i < ii; ++i) {
      source = this.layers_[i].getSource();
      if (source.getState() !== SourceState.READY) {
        ready = false;
        break;
      }
    }
    return ready;
  }

  /**
   * @inheritDoc
   */
  getImage(extent, resolution, pixelRatio, projection) {
    if (!this.allSourcesReady_()) {
      return null;
    }

    const frameState = this.updateFrameState_(extent, resolution, projection);
    this.requestedFrameState_ = frameState;

    // check if we can't reuse the existing ol/ImageCanvas
    if (this.renderedImageCanvas_) {
      const renderedResolution = this.renderedImageCanvas_.getResolution();
      const renderedExtent = this.renderedImageCanvas_.getExtent();
      if (resolution !== renderedResolution || !equals(extent, renderedExtent)) {
        this.renderedImageCanvas_ = null;
      }
    }

    if (!this.renderedImageCanvas_ || this.getRevision() !== this.renderedRevision_) {
      this.processSources_();
    }

    frameState.tileQueue.loadMoreTiles(16, 16);

    if (frameState.animate) {
      requestAnimationFrame(this.changed.bind(this));
    }

    return this.renderedImageCanvas_;
  }

  /**
   * Start processing source data.
   * @private
   */
  processSources_() {
    const frameState = this.requestedFrameState_;
    const len = this.layers_.length;
    const imageDatas = new Array(len);
    for (let i = 0; i < len; ++i) {
      const imageData = getImageData(this.layers_[i], frameState, frameState.layerStatesArray[i]);
      if (imageData) {
        imageDatas[i] = imageData;
      } else {
        return;
      }
    }

    const data = {};
    this.dispatchEvent(new RasterSourceEvent(RasterEventType.BEFOREOPERATIONS, frameState, data));
    this.worker_.process(imageDatas, data, this.onWorkerComplete_.bind(this, frameState));
  }

  /**
   * Called when pixel processing is complete.
   * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
   * @param {Error} err Any error during processing.
   * @param {ImageData} output The output image data.
   * @param {Object} data The user data.
   * @private
   */
  onWorkerComplete_(frameState, err, output, data) {
    if (err || !output) {
      return;
    }

    // do nothing if extent or resolution changed
    const extent = frameState.extent;
    const resolution = frameState.viewState.resolution;
    if (resolution !== this.requestedFrameState_.viewState.resolution ||
        !equals(extent, this.requestedFrameState_.extent)) {
      return;
    }

    let context;
    if (this.renderedImageCanvas_) {
      context = this.renderedImageCanvas_.getImage().getContext('2d');
    } else {
      const width = Math.round(getWidth(extent) / resolution);
      const height = Math.round(getHeight(extent) / resolution);
      context = createCanvasContext2D(width, height);
      this.renderedImageCanvas_ = new ImageCanvas(extent, resolution, 1, context.canvas);
    }
    context.putImageData(output, 0, 0);

    this.changed();
    this.renderedRevision_ = this.getRevision();

    this.dispatchEvent(new RasterSourceEvent(RasterEventType.AFTEROPERATIONS, frameState, data));
  }

  /**
   * @override
   */
  getImageInternal() {
    return null; // not implemented
  }
}

/**
 * A reusable canvas context.
 * @type {CanvasRenderingContext2D}
 * @private
 */
let sharedContext = null;

/**
 * Get image data from a layer.
 * @param {import("../layer/Layer.js").default} layer Layer to render.
 * @param {import("../PluggableMap.js").FrameState} frameState The frame state.
 * @param {import("../layer/Layer.js").State} layerState The layer state.
 * @return {ImageData} The image data.
 */
function getImageData(layer, frameState, layerState) {
  const renderer = layer.getRenderer();
  if (!renderer) {
    throw new Error('Unsupported layer type: ' + layer);
  }

  if (!renderer.prepareFrame(frameState, layerState)) {
    return null;
  }
  const width = frameState.size[0];
  const height = frameState.size[1];
  const element = renderer.renderFrame(frameState, layerState);
  if (!(element instanceof HTMLCanvasElement)) {
    throw new Error('Unsupported rendered element: ' + element);
  }
  if (element.width === width && element.height === height) {
    const context = element.getContext('2d');
    return context.getImageData(0, 0, width, height);
  }

  if (!sharedContext) {
    sharedContext = createCanvasContext2D(width, height);
  } else {
    const canvas = sharedContext.canvas;
    if (canvas.width !== width || canvas.height !== height) {
      sharedContext = createCanvasContext2D(width, height);
    } else {
      sharedContext.clearRect(0, 0, width, height);
    }
  }
  sharedContext.drawImage(element, 0, 0, width, height);
  return sharedContext.getImageData(0, 0, width, height);
}

/**
 * Get a list of layer states from a list of layers.
 * @param {Array<import("../layer/Layer.js").default>} layers Layers.
 * @return {Array<import("../layer/Layer.js").State>} The layer states.
 */
function getLayerStatesArray(layers) {
  return layers.map(function(layer) {
    return layer.getLayerState();
  });
}

/**
 * Create layers for all sources.
 * @param {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources The sources.
 * @return {Array<import("../layer/Layer.js").default>} Array of layers.
 */
function createLayers(sources) {
  const len = sources.length;
  const layers = new Array(len);
  for (let i = 0; i < len; ++i) {
    layers[i] = createLayer(sources[i]);
  }
  return layers;
}

/**
 * Create a layer for the provided source.
 * @param {import("./Source.js").default|import("../layer/Layer.js").default} layerOrSource The layer or source.
 * @return {import("../layer/Layer.js").default} The layer.
 */
function createLayer(layerOrSource) {
  // @type {import("../layer/Layer.js").default}
  let layer;
  if (layerOrSource instanceof Source) {
    if (layerOrSource instanceof TileSource) {
      layer = new TileLayer({source: layerOrSource});
    } else if (layerOrSource instanceof ImageSource) {
      layer = new ImageLayer({source: layerOrSource});
    }
  } else {
    layer = layerOrSource;
  }
  return layer;
}

export default RasterSource;
<|endoftext|>


<commit_before>var root       = __dirname + '/../../';
var fixtures   = require(root + 'tests/helpers/fixtures');
var _          = require('underscore');
var jsdom      = require('jsdom');
var xpath      = require(root + 'lib/krake/helpers').xpath.xpath;

describe("xpath#xpath", function() {
  it("Should return a list of objects", function() {
    var html  = fixtures.kittens;
    var query = "//a[contains(@class,'photo-click')]";
    jsdom.env(html, [], function(err, window) {
      var results = xpath(query, window.document);
      expect(results.constructor).toEqual(Array);
      _.map(results, function(result) {
        expect(result.constructor).toEqual(Object);
      });
    });
  });
});
<commit_msg>make xpath spec asynchronous<commit_after>var root       = __dirname + '/../../';
var fixtures   = require(root + 'tests/helpers/fixtures');
var _          = require('underscore');
var jsdom      = require('jsdom');
var xpath      = require(root + 'lib/krake/helpers').xpath.xpath;

describe("xpath#xpath", function() {
  it("Should return a list of objects", function(done) {
    var html  = fixtures.kittens;
    var query = "//a[contains(@class,'photo-click')]";
    jsdom.env(html, [], function(err, window) {
      var results = xpath(query, window.document);
      expect(results.constructor).toEqual(Array);
      _.map(results, function(result) {
        expect(result.constructor).toEqual(Object);
      });
      done();
    });
  });
});
<|endoftext|>