<commit_before>var Stream = require('stream')

module.exports = function (inherits, EventEmitter) {
	var MAX_COUNT = Math.pow(2, 31) // largest smi value
	var clock = Date.now()
	setInterval(function () { clock = Date.now() }, 10)
	function noop() { return false }

	//
	// http: either require('http') or require('https')
	// ip: host ip
	// port: host port
	// options: {
	//   ping: ping path (no ping checks)
	//   pingTimeout: in ms (2000)
	//   maxSockets: max concurrent open sockets (20)
	//   timeout: default request timeout in ms (60000)
	//   resolution: how often timeouts are checked in ms (1000)
	// }
	function Endpoint(http, ip, port, options) {
		options = options || {}

		this.http = http
		this.ip = ip
		this.port = port
		this.healthy = false
		this.name = this.ip + ':' + this.port
		this.address = this.ip
		this.pingPath = options.ping
		this.pingTimeout = options.pingTimeout || 2000

		this.agent = new http.Agent()
		this.agent.maxSockets = options.maxSockets || 20

		this.requests = {}
		this.requestCount = 0
		this.requestsLastCheck = 0
		this.requestRate = 0
		this.pending = 0
		this.successes = 0
		this.failures = 0

		this.timeout = options.timeout || (60 * 1000)
		this.resolution = options.resolution || 1000
		this.timeoutInterval = setInterval(this.checkTimeouts.bind(this), this.resolution)

		this.ping()
	}
	inherits(Endpoint, EventEmitter)

	Endpoint.prototype.checkTimeouts = function () {
		var keys = Object.keys(this.requests)
		for (var i = 0; i < keys.length; i++) {
			var r = this.requests[keys[i]]
			var expireTime = clock - r.options.timeout
			if (r.lastTouched <= expireTime) {
				this.emit("timeout", r)
				r.abort()
			}
		}
		if (!this.healthy) {
			this.ping()
		}
		this.requestRate = this.requestCount - this.requestsLastCheck
		this.requestsLastCheck = this.requestCount
	}

	Endpoint.prototype.resetCounters = function () {
		this.requestsLastCheck = this.requestRate - this.pending
		this.requestCount = this.pending
		this.successes = 0
		this.failures = 0
	}

	Endpoint.prototype.setPending = function () {
		this.pending = this.requestCount - (this.successes + this.failures)
		if (this.requestCount === MAX_COUNT) {
			this.resetCounters()
		}
	}

	Endpoint.prototype.complete = function (error, request, response, body) {
		this.deleteRequest(request.id)
		this.setPending()
		request.callback(error, response, body)
		request.callback = null
	}

	Endpoint.prototype.succeeded = function (request, response, body) {
		this.successes++
		this.complete(null, request, response, body)
	}

	Endpoint.prototype.failed = function (error, request) {
		this.failures++
		this.complete(error, request)
	}

	Endpoint.prototype.busyness = function () {
		return this.pending
	}

	// options: {
	//   agent:
	//   path:
	//   method:
	//   retryFilter:
	//   timeout: request timeout in ms (this.timeout)
	//   encoding: response body encoding (utf8)
	//   data: string, buffer, or stream
	//   stream: stream instead of buffer response body (default based on callback)
	// }
	// callback: function (error, response, body) {}
	// callback: function (error, response) {}
	Endpoint.prototype.request = function (options, callback) {
		options.host = this.ip
		options.port = this.port
		options.retryFilter = options.retryFilter || noop
		options.timeout = options.timeout || this.timeout
		options.headers = options.headers || {}
		if (options.agent !== false) {
			options.agent = this.agent
		}
		if (options.encoding !== null) {
			options.encoding = options.encoding || 'utf8'
		}

		var req = this.http.request(options)
		req.node = this
		req.options = options
		req.id = this.requestCount++
		req.stream = (options.stream === undefined) ? callback.length === 2 : options.stream
		req.lastTouched = clock
		req.callback = callback || noop
		req.on('response', gotResponse)
		req.on('error', gotError)

		var data = options.data
		if (data instanceof Stream) {
			data.pipe(req)
		}
		else {
			if (data) {
				req.setHeader("Content-Length"
					, Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)
				)
			}
			req.end(data)
		}

		this.setPending()
		this.requests[req.id] = req
	}

	Endpoint.prototype.setHealthy = function (newState) {
		if (this.healthy !== newState) {
			this.healthy = newState
			if (!this.healthy) {
				this.ping() // ping may set this back to healthy
				if (!this.healthy) {
					this.emit('health', this)
				}
			}
			else {
				this.emit('health', this)
			}
		}
	}

	Endpoint.prototype.deleteRequest = function (id) {
		delete this.requests[id]
	}

	function gotPingResponse(error, response, body) {
		this.node.setHealthy(!error && response.statusCode === 200)
	}

	Endpoint.prototype.ping = function () {
		if (this.pingPath) {
			this.request(
				{ path: this.pingPath
				, method: 'GET'
				, timeout: this.pingTimeout
				}
				, gotPingResponse)
		}
		else {
			this.setHealthy(true)
		}
	}

	// this = request
	function gotResponse(response) {
		if (this.stream) {
			return this.node.succeeded(this, response)
		}
		response.bodyChunks = []
		response.bodyLength = 0
		response.request = this
		response.on('data', gotData)
		response.on('end', gotEnd)
		response.on('aborted', gotAborted)
	}

	// this = request
	function gotError(error) {
		this.node.failed(
			{ reason: error.message
			, attempt: this
			, message: this.node.ip + ':' + this.node.port + ' error: ' + error.message
			}
			, this)
		this.node.setHealthy(false)
	}

	// this = response
	function gotData(chunk) {
		this.request.lastTouched = clock
		this.bodyChunks.push(chunk)
		this.bodyLength += chunk.length
	}

	// this = response
	function gotEnd() {
		var req = this.request
		var opt = req.options
		var node = req.node

		if (req.callback === null) { return }
		node.setHealthy(true)

		var buffer = new Buffer(this.bodyLength)
		var offset = 0
		for (var i = 0; i < this.bodyChunks.length; i++) {
			var chunk = this.bodyChunks[i]
			chunk.copy(buffer, offset, 0, chunk.length)
			offset += chunk.length
		}

		var body = (opt.encoding !== null) ? buffer.toString(opt.encoding) : buffer

		var delay = opt.retryFilter(opt, this, body)
		if (delay !== false) { // delay may be 0
			return node.failed(
			{ delay: delay
			, reason: 'filter'
			, attempt: req
			, message: node.ip + ':' + node.port + ' error: rejected by filter'
			}
			, req)
		}
		node.succeeded(req, this, body)
	}

	// this = response
	function gotAborted() {
		this.request.node.failed(
			{ reason: 'aborted'
			, attempt: this.request
			, message: this.request.node.ip + ':' + this.request.node.port + ' error: connection aborted'
			}
			, this.request)
	}

	var overloaded = new Endpoint({Agent: Object})
	clearInterval(overloaded.timeoutInterval)
	overloaded.healthy = false
	overloaded.request = function (options, callback) {
		return callback(
			{ reason: 'full'
			, delay: true
			, attempt: { options: options }
			, message: 'too many pending requests'
			}
		)
	}

	Endpoint.overloaded = function () {
		return overloaded
	}

	var unhealthy = new Endpoint({Agent: Object})
	clearInterval(unhealthy.timeoutInterval)
	unhealthy.healthy = false
	unhealthy.request = function (options, callback) {
		return callback(
			{ reason: 'unhealthy'
			, delay: true
			, attempt: { options: options }
			, message: 'no nodes'
			}
		)
	}

	Endpoint.unhealthy = function () {
		return unhealthy
	}

	return Endpoint
}
<commit_msg>lazily start timeout intervals<commit_after>var Stream = require('stream')

module.exports = function (inherits, EventEmitter) {
	var MAX_COUNT = Math.pow(2, 31) // largest smi value
	var clock = Date.now()
	var clockInterval = null
	function noop() { return false }

	//
	// http: either require('http') or require('https')
	// ip: host ip
	// port: host port
	// options: {
	//   ping: ping path (no ping checks)
	//   pingTimeout: in ms (2000)
	//   maxSockets: max concurrent open sockets (20)
	//   timeout: default request timeout in ms (60000)
	//   resolution: how often timeouts are checked in ms (1000)
	// }
	function Endpoint(http, ip, port, options) {
		options = options || {}

		this.http = http
		this.ip = ip
		this.port = port
		this.healthy = false
		this.name = this.ip + ':' + this.port
		this.address = this.ip
		this.pingPath = options.ping
		this.pingTimeout = options.pingTimeout || 2000

		this.agent = new http.Agent()
		this.agent.maxSockets = options.maxSockets || 20

		this.requests = {}
		this.requestCount = 0
		this.requestsLastCheck = 0
		this.requestRate = 0
		this.pending = 0
		this.successes = 0
		this.failures = 0

		this.timeout = options.timeout || (60 * 1000)
		this.resolution = options.resolution || 1000

		this.timeoutInterval = (this.resolution > 0) ?
			setInterval(this.checkTimeouts.bind(this), this.resolution) :
			null

		if (!clockInterval) {
			clockInterval = setInterval(function () { clock = Date.now() }, 10)
		}

		this.ping()
	}
	inherits(Endpoint, EventEmitter)

	Endpoint.prototype.checkTimeouts = function () {
		var keys = Object.keys(this.requests)
		for (var i = 0; i < keys.length; i++) {
			var r = this.requests[keys[i]]
			var expireTime = clock - r.options.timeout
			if (r.lastTouched <= expireTime) {
				this.emit("timeout", r)
				r.abort()
			}
		}
		if (!this.healthy) {
			this.ping()
		}
		this.requestRate = this.requestCount - this.requestsLastCheck
		this.requestsLastCheck = this.requestCount
	}

	Endpoint.prototype.resetCounters = function () {
		this.requestsLastCheck = this.requestRate - this.pending
		this.requestCount = this.pending
		this.successes = 0
		this.failures = 0
	}

	Endpoint.prototype.setPending = function () {
		this.pending = this.requestCount - (this.successes + this.failures)
		if (this.requestCount === MAX_COUNT) {
			this.resetCounters()
		}
	}

	Endpoint.prototype.complete = function (error, request, response, body) {
		this.deleteRequest(request.id)
		this.setPending()
		request.callback(error, response, body)
		request.callback = null
	}

	Endpoint.prototype.succeeded = function (request, response, body) {
		this.successes++
		this.complete(null, request, response, body)
	}

	Endpoint.prototype.failed = function (error, request) {
		this.failures++
		this.complete(error, request)
	}

	Endpoint.prototype.busyness = function () {
		return this.pending
	}

	// options: {
	//   agent:
	//   path:
	//   method:
	//   retryFilter:
	//   timeout: request timeout in ms (this.timeout)
	//   encoding: response body encoding (utf8)
	//   data: string, buffer, or stream
	//   stream: stream instead of buffer response body (default based on callback)
	// }
	// callback: function (error, response, body) {}
	// callback: function (error, response) {}
	Endpoint.prototype.request = function (options, callback) {
		options.host = this.ip
		options.port = this.port
		options.retryFilter = options.retryFilter || noop
		options.timeout = options.timeout || this.timeout
		options.headers = options.headers || {}
		if (options.agent !== false) {
			options.agent = this.agent
		}
		if (options.encoding !== null) {
			options.encoding = options.encoding || 'utf8'
		}

		var req = this.http.request(options)
		req.node = this
		req.options = options
		req.id = this.requestCount++
		req.stream = (options.stream === undefined) ? callback.length === 2 : options.stream
		req.lastTouched = clock
		req.callback = callback || noop
		req.on('response', gotResponse)
		req.on('error', gotError)

		var data = options.data
		if (data instanceof Stream) {
			data.pipe(req)
		}
		else {
			if (data) {
				req.setHeader("Content-Length"
					, Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)
				)
			}
			req.end(data)
		}

		this.setPending()
		this.requests[req.id] = req
	}

	Endpoint.prototype.setHealthy = function (newState) {
		if (this.healthy !== newState) {
			this.healthy = newState
			if (!this.healthy) {
				this.ping() // ping may set this back to healthy
				if (!this.healthy) {
					this.emit('health', this)
				}
			}
			else {
				this.emit('health', this)
			}
		}
	}

	Endpoint.prototype.deleteRequest = function (id) {
		delete this.requests[id]
	}

	function gotPingResponse(error, response, body) {
		this.node.setHealthy(!error && response.statusCode === 200)
	}

	Endpoint.prototype.ping = function () {
		if (this.pingPath) {
			this.request(
				{ path: this.pingPath
				, method: 'GET'
				, timeout: this.pingTimeout
				}
				, gotPingResponse)
		}
		else {
			this.setHealthy(true)
		}
	}

	// this = request
	function gotResponse(response) {
		if (this.stream) {
			return this.node.succeeded(this, response)
		}
		response.bodyChunks = []
		response.bodyLength = 0
		response.request = this
		response.on('data', gotData)
		response.on('end', gotEnd)
		response.on('aborted', gotAborted)
	}

	// this = request
	function gotError(error) {
		this.node.failed(
			{ reason: error.message
			, attempt: this
			, message: this.node.ip + ':' + this.node.port + ' error: ' + error.message
			}
			, this)
		this.node.setHealthy(false)
	}

	// this = response
	function gotData(chunk) {
		this.request.lastTouched = clock
		this.bodyChunks.push(chunk)
		this.bodyLength += chunk.length
	}

	// this = response
	function gotEnd() {
		var req = this.request
		var opt = req.options
		var node = req.node

		if (req.callback === null) { return }
		node.setHealthy(true)

		var buffer = new Buffer(this.bodyLength)
		var offset = 0
		for (var i = 0; i < this.bodyChunks.length; i++) {
			var chunk = this.bodyChunks[i]
			chunk.copy(buffer, offset, 0, chunk.length)
			offset += chunk.length
		}

		var body = (opt.encoding !== null) ? buffer.toString(opt.encoding) : buffer

		var delay = opt.retryFilter(opt, this, body)
		if (delay !== false) { // delay may be 0
			return node.failed(
			{ delay: delay
			, reason: 'filter'
			, attempt: req
			, message: node.ip + ':' + node.port + ' error: rejected by filter'
			}
			, req)
		}
		node.succeeded(req, this, body)
	}

	// this = response
	function gotAborted() {
		this.request.node.failed(
			{ reason: 'aborted'
			, attempt: this.request
			, message: this.request.node.ip + ':' + this.request.node.port + ' error: connection aborted'
			}
			, this.request)
	}

	var overloaded = new Endpoint({Agent: Object})
	clearInterval(overloaded.timeoutInterval)
	overloaded.healthy = false
	overloaded.request = function (options, callback) {
		return callback(
			{ reason: 'full'
			, delay: true
			, attempt: { options: options }
			, message: 'too many pending requests'
			}
		)
	}

	Endpoint.overloaded = function () {
		return overloaded
	}

	var unhealthy = new Endpoint({Agent: Object})
	clearInterval(unhealthy.timeoutInterval)
	unhealthy.healthy = false
	unhealthy.request = function (options, callback) {
		return callback(
			{ reason: 'unhealthy'
			, delay: true
			, attempt: { options: options }
			, message: 'no nodes'
			}
		)
	}

	Endpoint.unhealthy = function () {
		return unhealthy
	}

	return Endpoint
}
<|endoftext|>


<commit_before>/**
 * Responsible for sequentially executing requests
 */

var async = require('async')
  , queue
  ;

executor = async.queue(function (task, cb) {
  var callback
    , lastArg = task.arguments[task.arguments.length - 1]
    ;

  // Always tell the queue task is complete. Execute callback if any was given.
  if (typeof lastArg === 'function') {
    callback = function () {
      lastArg(arguments);
      cb();
    };

    task.arguments[task.arguments.length - 1] = callback;
  } else {
    callback = function () {
      cb();
    };

    task.arguments.push(callback);
  }

  task.fn.apply(task.this, task.arguments);
}, 1);

function test1 (msg, cb) {
  var callback = cb || function () {};

  console.log("ooooo TEST1");

  setTimeout(function () {
    console.log("Hello " + msg);
    callback();
  }, 1500);
}

function test2 (msg, cb) {
  var callback = cb || function () {};

  console.log("ooooo TEST2");

  setTimeout(function () {
    console.log("Ola " + msg);
    callback();
  }, 500);
}

function bloup () { console.log("FINISHED"); }

executor.push({ this: null, fn: test1, arguments: [ 'world' ] });
executor.push({ this: null, fn: test2, arguments: [ 'world', bloup ] });

/*
 *
queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

Arguments

worker(task, callback) - An asynchronous function for processing a queued task, which must call its callback(err) argument when finished, with an optional error as an argument.
concurrency - An integer for determining how many worker functions should be run in parallel.
Queue objects

The queue object returned by this function has the following properties and methods:

length() - a function returning the number of items waiting to be processed.
concurrency - an integer for determining how many worker functions should be run in parallel. This property can be changed after a queue is created to alter the concurrency on-the-fly.
push(task, [callback]) - add a new task to the queue, the callback is called once the worker has finished processing the task. instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
unshift(task, [callback]) - add a new task to the front of the queue.
saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
empty - a callback that is called when the last item from the queue is given to a worker
drain - a callback that is called when the last item from the queue has returned from the worker
Example

// create a queue object with concurrency 2

var q = async.queue(function (task, callback) {
    console.log('hello ' + task.name);
    callback();
}, 2);

// assign a callback
q.drain = function() {
    console.log('all items have been processed');
}

// add some items to the queue

q.push({name: 'foo'}, function (err) {
    console.log('finished processing foo');
});
q.push({name: 'bar'}, function (err) {
    console.log('finished processing bar');
});

// add some items to the queue (batch-wise)

q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
    console.log('finished processing bar');
});

// add some items to the front of the queue

q.unshift({name: 'bar'}, function (err) {
    console.log('finished processing bar');
});
 */

// Interface
module.exports = executor;
<commit_msg>Executor works with arguments<commit_after>/**
 * Responsible for sequentially executing requests
 */

var async = require('async')
  , queue
  ;

executor = async.queue(function (task, cb) {
  var callback
    , lastArg = task.arguments[task.arguments.length - 1]
    ;

  // Always tell the queue task is complete. Execute callback if any was given.
  if (typeof lastArg === 'function') {
    callback = function () {
      lastArg.apply(null, arguments);
      cb();
    };

    task.arguments[task.arguments.length - 1] = callback;
  } else {
    callback = function () {
      cb();
    };

    task.arguments.push(callback);
  }

  task.fn.apply(task.this, task.arguments);
}, 1);

function test1 (msg, cb) {
  var callback = cb || function () {};

  console.log("ooooo TEST1");

  setTimeout(function () {
    console.log("Hello " + msg);
    callback();
  }, 1500);
}

function test2 (msg, cb) {
  var callback = cb || function () {};

  console.log("ooooo TEST2");

  setTimeout(function () {
    console.log("Ola " + msg);
    callback('YEAH');
  }, 500);
}

function bloup () { console.log("FINISHED"); console.log(arguments); }

executor.push({ this: null, fn: test1, arguments: [ 'world' ] });
executor.push({ this: null, fn: test2, arguments: [ 'world', bloup ] });

/*
 *
queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

Arguments

worker(task, callback) - An asynchronous function for processing a queued task, which must call its callback(err) argument when finished, with an optional error as an argument.
concurrency - An integer for determining how many worker functions should be run in parallel.
Queue objects

The queue object returned by this function has the following properties and methods:

length() - a function returning the number of items waiting to be processed.
concurrency - an integer for determining how many worker functions should be run in parallel. This property can be changed after a queue is created to alter the concurrency on-the-fly.
push(task, [callback]) - add a new task to the queue, the callback is called once the worker has finished processing the task. instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
unshift(task, [callback]) - add a new task to the front of the queue.
saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
empty - a callback that is called when the last item from the queue is given to a worker
drain - a callback that is called when the last item from the queue has returned from the worker
Example

// create a queue object with concurrency 2

var q = async.queue(function (task, callback) {
    console.log('hello ' + task.name);
    callback();
}, 2);

// assign a callback
q.drain = function() {
    console.log('all items have been processed');
}

// add some items to the queue

q.push({name: 'foo'}, function (err) {
    console.log('finished processing foo');
});
q.push({name: 'bar'}, function (err) {
    console.log('finished processing bar');
});

// add some items to the queue (batch-wise)

q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
    console.log('finished processing bar');
});

// add some items to the front of the queue

q.unshift({name: 'bar'}, function (err) {
    console.log('finished processing bar');
});
 */

// Interface
module.exports = executor;
<|endoftext|>


<commit_before>/**
 * dat-gui JavaScript Controller Library
 * http://code.google.com/p/dat-gui
 *
 * Copyright 2011 Data Arts Team, Google Creative Lab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */

define([
    'dat/controllers/Controller',
    'dat/dom/dom',
    'dat/utils/common'
], function(Controller, dom, common) {

  /**
   * @class Provides a GUI interface to fire a specified method, a property of an object.
   *
   * @extends dat.controllers.Controller
   *
   * @param {Object} object The object to be manipulated
   * @param {string} property The name of the property to be manipulated
   *
   * @member dat.controllers
   */
  var FunctionController = function(object, property, text) {

    FunctionController.superclass.call(this, object, property);

    var _this = this;

    this.__button = document.createElement('div');
    this.__button.innerHTML = text === undefined ? 'Fire' : text;
    dom.bind(this.__button, 'click', function(e) {
      e.preventDefault();
      _this.fire();
      return false;
    });

    dom.addClass(this.__button, 'button');

    this.domElement.appendChild(this.__button);

  };

  FunctionController.superclass = Controller;

  common.extend(

      FunctionController.prototype,
      Controller.prototype,
      {

        fire: function() {
          if (this.__onChange) {
            this.__onChange.call(this);
          }
          if (this.__onFinishChange) {
            this.__onFinishChange.call(this, this.getValue());
          }
          this.getValue().call(this.object);
        }
      }

  );

  return FunctionController;

});<commit_msg>Make onFinishChange after getValue<commit_after>/**
 * dat-gui JavaScript Controller Library
 * http://code.google.com/p/dat-gui
 *
 * Copyright 2011 Data Arts Team, Google Creative Lab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */

define([
    'dat/controllers/Controller',
    'dat/dom/dom',
    'dat/utils/common'
], function(Controller, dom, common) {

  /**
   * @class Provides a GUI interface to fire a specified method, a property of an object.
   *
   * @extends dat.controllers.Controller
   *
   * @param {Object} object The object to be manipulated
   * @param {string} property The name of the property to be manipulated
   *
   * @member dat.controllers
   */
  var FunctionController = function(object, property, text) {

    FunctionController.superclass.call(this, object, property);

    var _this = this;

    this.__button = document.createElement('div');
    this.__button.innerHTML = text === undefined ? 'Fire' : text;
    dom.bind(this.__button, 'click', function(e) {
      e.preventDefault();
      _this.fire();
      return false;
    });

    dom.addClass(this.__button, 'button');

    this.domElement.appendChild(this.__button);

  };

  FunctionController.superclass = Controller;

  common.extend(

      FunctionController.prototype,
      Controller.prototype,
      {

        fire: function() {
          if (this.__onChange) {
            this.__onChange.call(this);
          }
          this.getValue().call(this.object);
          if (this.__onFinishChange) {
            this.__onFinishChange.call(this, this.getValue());
          }
        }
      }

  );

  return FunctionController;

});
<|endoftext|>


<commit_before>var https = require('https');
var url = require('url');
var crypto = require('crypto');

var PROTOCOL = 'https://';
var USER_AGENT = 'facebook-nodejs (+https://github.com/vanng822/facebook-nodejs)';

var DOMAIN_MAP = {
	/*api : 'api.facebook.com',
	apiVideo : 'api-video.facebook.com',
	apiRead : 'api-read.facebook.com',*/
	graph : 'graph.facebook.com',
	graphVideo : 'graph-video.facebook.com',
	www : 'www.facebook.com',
};

var DROP_QUERY_PARAMS = ['code', 'state', 'signed_request'];
var SUPPORT_KEYS = ['state', 'code', 'access_token', 'user_id', 'expires'];

var appId;
var appSecret;
var scope = '';
var redirectUri;

module.exports.auth = function(config) {
	if(!config || !config.appId || !config.appSecret || !config.redirectUri) {
		throw new Error('appId, appSecret or redirectUri is missing!');
	}
	appId = config.appId;
	appSecret = config.appSecret;
	redirectUri = config.redirectUri;

	if(config.scope) {
		scope = config.scope;
	}

	return authenticate;
}

var redirectLoginForm = module.exports.redirectLoginForm = function(req, res) {
	var state = establishCSRFTokenState(req);
	var currentUrl = getCurrentUrl(req);
	var query = '/dialog/oauth?client_id=' + appId + '&redirect_uri=' + currentUrl + '&scope=' + scope + '&state=' + state;
	var url = PROTOCOL + DOMAIN_MAP.www + query;
	redirect(res, url);
}

function authenticate(req, res, next) {
	var code, state, signedRequest, accessToken, expires;

	parseQuery(req);
	signedRequest = getSignedRequest(req);
	if(signedRequest && signedRequest.oauth_token) {
		setPersistentData(req, 'access_token', signedRequest.oauth_token);
		setPersistentData(req, 'expires', signedRequest.expires);
		setPersistentData(req, 'user_id', signedRequest.user_id);
		req.facebook = new Facebook(signedRequest.oauth_token);
		next();
		return;
	}
	accessToken = getPersistentData(req, 'access_token');
	expires = getPersistentData(req, 'expires', Math.floor(Date.now() / 1000));
	if(accessToken && validateExpires(req, expires)) {
		req.facebook = new Facebook(accessToken);
		next();
		return;
	}
	code = req.query.code;
	state = req.query.state;

	if(code && state && state === getPersistentData(req, 'state')) {
		getAccessTokenFromCode(req, res, code, function(err, accessToken, expires) {
			if(accessToken) {
				//setPersistentData(req, 'code', code);
				setPersistentData(req, 'access_token', accessToken);
				setPersistentData(req, 'expires', expires);
				req.facebook = new Facebook(accessToken);
			}
			next();
		});
		return;
	}

	redirect(res, redirectUri);
}

var Facebook = function(accessToken) {
	this._accessToken
	if(accessToken) {
		this._setAccessToken(accessToken);
	}
}

Facebook.prototype = {
	_setAccessToken : function(accessToken) {
		this._accessToken = accessToken;
	},
	_getAccessToken : function() {
		return this._accessToken;
	},
	_graph : function(options, callback) {
		https.get(options, function(res) {
			var data = '';
			res.on('data', function(chunk) {
				data += chunk;
			});
			res.on('end', function() {
				try {
					if(res.statusCode == 200) {
						callback(null, JSON.parse(data));
					} else {
						/* just dump what we get */
						callback(new Error(data), null);
					}
				} catch(e) {/* json parse may throw error */
					callback(e, null);
				}
			});
		});
	},
	graph : function(path, callback) {
		if(path.indexOf('?') != -1) {
			if(path[path.length - 1] != '?' && path[path.length - 1] != '&') {
				path += '&';
			}
		} else {
			path += '?';
		}
		var options = {
			host : DOMAIN_MAP.graph,
			path : path + 'access_token=' + this._getAccessToken(),
			headers : {
				'User-Agent' : USER_AGENT
			}
		};
		this._graph(options, callback);
	},
	fql : function(query, callback) {
		var encodedQuery;
		if( query instanceof Object) {
			encodedQuery = encodeURIComponent(JSON.stringify(query));
		} else {
			encodedQuery = encodeURIComponent(query);
		}
		var options = {
			host : DOMAIN_MAP.graph,
			path : '/fql?q=' + encodedQuery + '&format=json&access_token=' + this._getAccessToken(),
			headers : {
				'User-Agent' : USER_AGENT
			}
		};
		this._graph(options, callback);
	}
	/* TODO: add some convenient methods for graph api */
}

/* TODO: use case or remove this */
function getUserAccessToken(req, res, callback) {
	var signedRequest = getSignedRequest(req);
	var accessToken, code;
	if(signedRequest) {
		if(signedRequest.hasOwnProperty('oauth_token')) {
			accessToken = signedRequest.oauth_token;
			setPersistentData(req, 'access_token', accessToken);
			callback(err, accessToken);
			return;
		}

		if(signedRequest.hasOwnProperty('code')) {
			code = signedRequest.code;
			getAccessTokenFromCode(req, res, code, function(err, accessToken, expires) {
				if(accessToken) {
					setPersistentData(req, 'code', code);
					setPersistentData(req, 'access_token', accessToken);
					setPersistentData(req, 'expires', expires);
				}
				callback(err, accessToken);
			});
			return;
		}
	}

	callback(null, getPersistentData(req, 'access_token'));

}

function getAccessTokenFromCode(req, res, code, callback) {
	var query = '/oauth/access_token?client_id=' + appId + '&redirect_uri=' + getCurrentUrl(req) + '&client_secret=' + appSecret + '&code=' + code;
	var options = {
		host : DOMAIN_MAP.graph,
		path : query,
		headers : {
			'User-Agent' : USER_AGENT
		}
	};

	https.get(options, function(r) {
		var data = "";
		r.on('data', function(chunk) {
			data += chunk;
		});
		r.on('end', function() {
			if(r.statusCode == 200) {
				try {
					parsedUrl = url.parse('http://fakedomain.test?' + data, true);
					/* expires is relative so adding from now */
					callback(null, parsedUrl.access_token, math.floor(Date.now() / 1000) + parsedUrl.expires);
				} catch(e) {
					callback(e, null, null);
				}
			} else {
				callback(new Error('Can not get access_token. Data: ' + data), null, null);
			}
		});
	});
}

/* TODO: need to handle current url somehow to avoid data lost */
function getCurrentUrl(req) {
	return encodeURIComponent(redirectUri);
}

function redirect(res, url) {
	res.end('<script type="text/javascript">top.location.href="' + url + '"</script>');
}

function parseSignedRequest(signedRequest) {
	var sig, data, expectedSig;
	payload = signedRequest.split('.');
	sig = payload[0];
	try {
		data = JSON.parse(new Buffer(payload[1], 'base64').toString());
	} catch(e) {
		return null;
	}
	if(data.algorithm.toUpperCase() !== 'HMAC-SHA256') {
		console.log('Unknown signed_request hash algorithm: ' + data.algorithm + '. Expected HMAC-SHA256');
		return null;
	}
	expectedSig = crypto.createHmac('sha256', appSecret);
	expectedSig.update(payload[1]);
	expectedSig = expectedSig.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

	if(sig !== expectedSig) {
		console.log('Bad signed_request encoding. Expected: ' + expectedSig + ' but got: ' + sig);
		return null;
	}

	return data;
}

function getSignedRequest(req) {
	var signedRequest;
	if(!req.hasOwnProperty('signedRequest')) {
		if(req.body !== undefined && req.body.hasOwnProperty('signed_request')) {
			signedRequest = parseSignedRequest(req.body.signed_request);
		} else if(req.cookies && req.cookies.hasOwnProperty(getSignedRequestCookieName())) {
			signedRequest = parseSignedRequest(req.cookies[getSignedRequestCookieName()]);
		}
		/* check expires */
		if(signedRequest && !validateExpires(req, signedRequest.expires)) {
			signedRequest = null;
		}
		/* keep it for this request only */
		req.signedRequest = signedRequest;
	}
	return req.signedRequest;
}

/* if expired clear all persistent data */
function validateExpires(req, expires) {
	if(!expires) {
		return false;
	}
	if(Date.now() - (expires * 1000) > 0) {
		clearAllPersistentData(req);
		return false;
	}
	return true;
}

function establishCSRFTokenState(req) {
	var shasum = crypto.createHash('md5'), state;
	shasum.update((Math.random() * (new Date().getTime())).toString());
	state = shasum.digest('hex');
	setPersistentData(req, 'state', state);
	return state;
}

function setPersistentData(req, key, value) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	req.session[key] = value;
}

function getPersistentData(req, key, defaultValue) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	defaultValue = (defaultValue == undefined) ? null : defaultValue;

	return req.session.hasOwnProperty(key) ? req.session[key] : defaultValue;
}

function clearPersistentData(req, key) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	delete req.session[key];
}

function clearAllPersistentData(req) {
	SUPPORT_KEYS.forEach(function(key) {
		delete req.session[key];
	});
}

function parseQuery(req) {
	if(req.query === undefined) {
		req.query = url.parse(req.url, true).query || {};
	}
}

function getSignedRequestCookieName() {
	return 'fbsr_' + appId;
}
<commit_msg>Default expires to now may cause unexpected behavour<commit_after>var https = require('https');
var url = require('url');
var crypto = require('crypto');

var PROTOCOL = 'https://';
var USER_AGENT = 'facebook-nodejs (+https://github.com/vanng822/facebook-nodejs)';

var DOMAIN_MAP = {
	/*api : 'api.facebook.com',
	apiVideo : 'api-video.facebook.com',
	apiRead : 'api-read.facebook.com',*/
	graph : 'graph.facebook.com',
	graphVideo : 'graph-video.facebook.com',
	www : 'www.facebook.com',
};

var DROP_QUERY_PARAMS = ['code', 'state', 'signed_request'];
var SUPPORT_KEYS = ['state', 'code', 'access_token', 'user_id', 'expires'];

var appId;
var appSecret;
var scope = '';
var redirectUri;

module.exports.auth = function(config) {
	if(!config || !config.appId || !config.appSecret || !config.redirectUri) {
		throw new Error('appId, appSecret or redirectUri is missing!');
	}
	appId = config.appId;
	appSecret = config.appSecret;
	redirectUri = config.redirectUri;

	if(config.scope) {
		scope = config.scope;
	}

	return authenticate;
}

var redirectLoginForm = module.exports.redirectLoginForm = function(req, res) {
	var state = establishCSRFTokenState(req);
	var currentUrl = getCurrentUrl(req);
	var query = '/dialog/oauth?client_id=' + appId + '&redirect_uri=' + currentUrl + '&scope=' + scope + '&state=' + state;
	var url = PROTOCOL + DOMAIN_MAP.www + query;
	redirect(res, url);
}

function authenticate(req, res, next) {
	var code, state, signedRequest, accessToken, expires;

	parseQuery(req);
	signedRequest = getSignedRequest(req);
	if(signedRequest && signedRequest.oauth_token) {
		setPersistentData(req, 'access_token', signedRequest.oauth_token);
		setPersistentData(req, 'expires', signedRequest.expires);
		setPersistentData(req, 'user_id', signedRequest.user_id);
		req.facebook = new Facebook(signedRequest.oauth_token);
		next();
		return;
	}
	accessToken = getPersistentData(req, 'access_token');
	expires = getPersistentData(req, 'expires');
	if(accessToken && expires && validateExpires(req, expires)) {
		req.facebook = new Facebook(accessToken);
		next();
		return;
	}
	code = req.query.code;
	state = req.query.state;

	if(code && state && state === getPersistentData(req, 'state')) {
		getAccessTokenFromCode(req, res, code, function(err, accessToken, expires) {
			if(accessToken) {
				//setPersistentData(req, 'code', code);
				setPersistentData(req, 'access_token', accessToken);
				setPersistentData(req, 'expires', expires);
				req.facebook = new Facebook(accessToken);
			}
			next();
		});
		return;
	}

	redirect(res, redirectUri);
}

var Facebook = function(accessToken) {
	this._accessToken
	if(accessToken) {
		this._setAccessToken(accessToken);
	}
}

Facebook.prototype = {
	_setAccessToken : function(accessToken) {
		this._accessToken = accessToken;
	},
	_getAccessToken : function() {
		return this._accessToken;
	},
	_graph : function(options, callback) {
		https.get(options, function(res) {
			var data = '';
			res.on('data', function(chunk) {
				data += chunk;
			});
			res.on('end', function() {
				try {
					if(res.statusCode == 200) {
						callback(null, JSON.parse(data));
					} else {
						/* just dump what we get */
						callback(new Error(data), null);
					}
				} catch(e) {/* json parse may throw error */
					callback(e, null);
				}
			});
		});
	},
	graph : function(path, callback) {
		if(path.indexOf('?') != -1) {
			if(path[path.length - 1] != '?' && path[path.length - 1] != '&') {
				path += '&';
			}
		} else {
			path += '?';
		}
		var options = {
			host : DOMAIN_MAP.graph,
			path : path + 'access_token=' + this._getAccessToken(),
			headers : {
				'User-Agent' : USER_AGENT
			}
		};
		this._graph(options, callback);
	},
	fql : function(query, callback) {
		var encodedQuery;
		if( query instanceof Object) {
			encodedQuery = encodeURIComponent(JSON.stringify(query));
		} else {
			encodedQuery = encodeURIComponent(query);
		}
		var options = {
			host : DOMAIN_MAP.graph,
			path : '/fql?q=' + encodedQuery + '&format=json&access_token=' + this._getAccessToken(),
			headers : {
				'User-Agent' : USER_AGENT
			}
		};
		this._graph(options, callback);
	}
	/* TODO: add some convenient methods for graph api */
}

/* TODO: use case or remove this */
function getUserAccessToken(req, res, callback) {
	var signedRequest = getSignedRequest(req);
	var accessToken, code;
	if(signedRequest) {
		if(signedRequest.hasOwnProperty('oauth_token')) {
			accessToken = signedRequest.oauth_token;
			setPersistentData(req, 'access_token', accessToken);
			callback(err, accessToken);
			return;
		}

		if(signedRequest.hasOwnProperty('code')) {
			code = signedRequest.code;
			getAccessTokenFromCode(req, res, code, function(err, accessToken, expires) {
				if(accessToken) {
					setPersistentData(req, 'code', code);
					setPersistentData(req, 'access_token', accessToken);
					setPersistentData(req, 'expires', expires);
				}
				callback(err, accessToken);
			});
			return;
		}
	}

	callback(null, getPersistentData(req, 'access_token'));

}

function getAccessTokenFromCode(req, res, code, callback) {
	var query = '/oauth/access_token?client_id=' + appId + '&redirect_uri=' + getCurrentUrl(req) + '&client_secret=' + appSecret + '&code=' + code;
	var options = {
		host : DOMAIN_MAP.graph,
		path : query,
		headers : {
			'User-Agent' : USER_AGENT
		}
	};

	https.get(options, function(r) {
		var data = "";
		r.on('data', function(chunk) {
			data += chunk;
		});
		r.on('end', function() {
			if(r.statusCode == 200) {
				try {
					parsedUrl = url.parse('http://fakedomain.test?' + data, true);
					/* expires is relative so adding from now */
					callback(null, parsedUrl.access_token, math.floor(Date.now() / 1000) + parsedUrl.expires);
				} catch(e) {
					callback(e, null, null);
				}
			} else {
				callback(new Error('Can not get access_token. Data: ' + data), null, null);
			}
		});
	});
}

/* TODO: need to handle current url somehow to avoid data lost */
function getCurrentUrl(req) {
	return encodeURIComponent(redirectUri);
}

function redirect(res, url) {
	res.end('<script type="text/javascript">top.location.href="' + url + '"</script>');
}

function parseSignedRequest(signedRequest) {
	var sig, data, expectedSig;
	payload = signedRequest.split('.');
	sig = payload[0];
	try {
		data = JSON.parse(new Buffer(payload[1], 'base64').toString());
	} catch(e) {
		return null;
	}
	if(data.algorithm.toUpperCase() !== 'HMAC-SHA256') {
		console.log('Unknown signed_request hash algorithm: ' + data.algorithm + '. Expected HMAC-SHA256');
		return null;
	}
	expectedSig = crypto.createHmac('sha256', appSecret);
	expectedSig.update(payload[1]);
	expectedSig = expectedSig.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

	if(sig !== expectedSig) {
		console.log('Bad signed_request encoding. Expected: ' + expectedSig + ' but got: ' + sig);
		return null;
	}

	return data;
}

function getSignedRequest(req) {
	var signedRequest;
	if(!req.hasOwnProperty('signedRequest')) {
		if(req.body !== undefined && req.body.hasOwnProperty('signed_request')) {
			signedRequest = parseSignedRequest(req.body.signed_request);
		} else if(req.cookies && req.cookies.hasOwnProperty(getSignedRequestCookieName())) {
			signedRequest = parseSignedRequest(req.cookies[getSignedRequestCookieName()]);
		}
		/* check expires */
		if(signedRequest && !validateExpires(req, signedRequest.expires)) {
			signedRequest = null;
		}
		/* keep it for this request only */
		req.signedRequest = signedRequest;
	}
	return req.signedRequest;
}

/* if expired clear all persistent data */
function validateExpires(req, expires) {
	if(!expires) {
		return false;
	}
	if(Date.now() - (expires * 1000) > 0) {
		clearAllPersistentData(req);
		return false;
	}
	return true;
}

function establishCSRFTokenState(req) {
	var shasum = crypto.createHash('md5'), state;
	shasum.update((Math.random() * (new Date().getTime())).toString());
	state = shasum.digest('hex');
	setPersistentData(req, 'state', state);
	return state;
}

function setPersistentData(req, key, value) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	req.session[key] = value;
}

function getPersistentData(req, key, defaultValue) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	defaultValue = (defaultValue == undefined) ? null : defaultValue;

	return req.session.hasOwnProperty(key) ? req.session[key] : defaultValue;
}

function clearPersistentData(req, key) {
	if(SUPPORT_KEYS.indexOf(key) == -1) {
		throw new Error('Does not support key: ' + key);
	}
	delete req.session[key];
}

function clearAllPersistentData(req) {
	SUPPORT_KEYS.forEach(function(key) {
		delete req.session[key];
	});
}

function parseQuery(req) {
	if(req.query === undefined) {
		req.query = url.parse(req.url, true).query || {};
	}
}

function getSignedRequestCookieName() {
	return 'fbsr_' + appId;
}
<|endoftext|>


<commit_before>var moment = require('moment');
var _ = require('underscore');

window.moment = moment;

// Preserve the ascendant order!
var MOMENT_AGGREGATIONS = {
  second: 's',
  minute: 'm',
  hour: 'h',
  day: 'd',
  week: 'w',
  month: 'M',
  quarter: 'Q',
  year: 'y'
};

function formatUTCTimestamp (timestamp) {
  return moment.unix(timestamp).utc().format('DD-MM-YYYY HH:mm:ss');
}

var helper = {};

function trimBuckets (buckets, filledBuckets, totalBuckets) {
  var index = null;
  var keepGoing = true;
  for (var i = filledBuckets.length - 1; i >= 0 && keepGoing && (_.isFinite(totalBuckets) ? i >= totalBuckets : true); i--) {
    if (filledBuckets[i]) {
      keepGoing = false;
    } else {
      index = i;
    }
  }

  return index !== null
    ? buckets.slice(0, index)
    : buckets;
}

helper.isShorterThan = function (limit, aggregation) {
  var keys = _.keys(MOMENT_AGGREGATIONS);
  var limitIndex = _.indexOf(keys, limit);
  var aggregationIndex = _.indexOf(keys, aggregation);
  return limitIndex > -1 && aggregationIndex > -1 && aggregationIndex < limitIndex;
};

helper.fillTimestampBuckets = function (buckets, start, aggregation, numberOfBins, from, totalBuckets) {
  var startDate = moment.unix(start).utc();
  var filledBuckets = []; // To catch empty buckets
  var definedBucket = false;

  for (var i = 0; i < numberOfBins; i++) {
    definedBucket = buckets[i] !== undefined;
    filledBuckets.push(definedBucket);

    buckets[i] = _.extend({
      bin: i,
      start: startDate.clone().add(i, MOMENT_AGGREGATIONS[aggregation]).unix(),
      end: startDate.clone().add(i + 1, MOMENT_AGGREGATIONS[aggregation]).unix() - 1,
      next: startDate.clone().add(i + 1, MOMENT_AGGREGATIONS[aggregation]).unix(),
      freq: 0
    }, buckets[i]);
    delete buckets[i].timestamp;
  }

  return from === 'totals'
    ? buckets
    : trimBuckets(buckets, filledBuckets, totalBuckets);
};

helper.fillNumericBuckets = function (buckets, start, width, numberOfBins) {
  for (var i = 0; i < numberOfBins; i++) {
    buckets[i] = _.extend({
      bin: i,
      start: start + (i * width),
      end: start + ((i + 1) * width),
      freq: 0
    }, buckets[i]);
  }
};

helper.hasChangedSomeOf = function (list, changed) {
  return _.some(_.keys(changed), function (key) {
    return _.contains(list, key);
  });
};

helper.calculateLimits = function (bins) {
  var start = Infinity;
  var end = -Infinity;

  _.each(bins, function (bin) {
    if (_.isFinite(bin.min)) {
      start = Math.min(bin.min, start);
    }
    if (_.isFinite(bin.max)) {
      end = Math.max(bin.max, end);
    }
  });

  return {
    start: start !== Infinity ? start : null,
    end: end !== -Infinity ? end : null
  };
};

helper.calculateDateRanges = function (min, max) {
  var startDate = moment.unix(min).utc();
  var endDate = moment.unix(max).utc();
  var ranges = {};

  _.each(_.keys(MOMENT_AGGREGATIONS), function (agg) {
    var startClone = startDate.clone();
    var endClone = endDate.clone();
    var start = startClone.startOf(agg).unix();
    var end = endClone.startOf(agg).add(1, MOMENT_AGGREGATIONS[agg]).unix() - 1;

    ranges[agg] = {
      start: start,
      end: end
    };
  });

  return ranges;
};

helper.formatUTCTimestamp = function (timestamp) {
  return formatUTCTimestamp(timestamp);
};

module.exports = helper;
<commit_msg>Remove global variable<commit_after>var moment = require('moment');
var _ = require('underscore');

// Preserve the ascendant order!
var MOMENT_AGGREGATIONS = {
  second: 's',
  minute: 'm',
  hour: 'h',
  day: 'd',
  week: 'w',
  month: 'M',
  quarter: 'Q',
  year: 'y'
};

function formatUTCTimestamp (timestamp) {
  return moment.unix(timestamp).utc().format('DD-MM-YYYY HH:mm:ss');
}

var helper = {};

function trimBuckets (buckets, filledBuckets, totalBuckets) {
  var index = null;
  var keepGoing = true;
  for (var i = filledBuckets.length - 1; i >= 0 && keepGoing && (_.isFinite(totalBuckets) ? i >= totalBuckets : true); i--) {
    if (filledBuckets[i]) {
      keepGoing = false;
    } else {
      index = i;
    }
  }

  return index !== null
    ? buckets.slice(0, index)
    : buckets;
}

helper.isShorterThan = function (limit, aggregation) {
  var keys = _.keys(MOMENT_AGGREGATIONS);
  var limitIndex = _.indexOf(keys, limit);
  var aggregationIndex = _.indexOf(keys, aggregation);
  return limitIndex > -1 && aggregationIndex > -1 && aggregationIndex < limitIndex;
};

helper.fillTimestampBuckets = function (buckets, start, aggregation, numberOfBins, from, totalBuckets) {
  var startDate = moment.unix(start).utc();
  var filledBuckets = []; // To catch empty buckets
  var definedBucket = false;

  for (var i = 0; i < numberOfBins; i++) {
    definedBucket = buckets[i] !== undefined;
    filledBuckets.push(definedBucket);

    buckets[i] = _.extend({
      bin: i,
      start: startDate.clone().add(i, MOMENT_AGGREGATIONS[aggregation]).unix(),
      end: startDate.clone().add(i + 1, MOMENT_AGGREGATIONS[aggregation]).unix() - 1,
      next: startDate.clone().add(i + 1, MOMENT_AGGREGATIONS[aggregation]).unix(),
      freq: 0
    }, buckets[i]);
    delete buckets[i].timestamp;
  }

  return from === 'totals'
    ? buckets
    : trimBuckets(buckets, filledBuckets, totalBuckets);
};

helper.fillNumericBuckets = function (buckets, start, width, numberOfBins) {
  for (var i = 0; i < numberOfBins; i++) {
    buckets[i] = _.extend({
      bin: i,
      start: start + (i * width),
      end: start + ((i + 1) * width),
      freq: 0
    }, buckets[i]);
  }
};

helper.hasChangedSomeOf = function (list, changed) {
  return _.some(_.keys(changed), function (key) {
    return _.contains(list, key);
  });
};

helper.calculateLimits = function (bins) {
  var start = Infinity;
  var end = -Infinity;

  _.each(bins, function (bin) {
    if (_.isFinite(bin.min)) {
      start = Math.min(bin.min, start);
    }
    if (_.isFinite(bin.max)) {
      end = Math.max(bin.max, end);
    }
  });

  return {
    start: start !== Infinity ? start : null,
    end: end !== -Infinity ? end : null
  };
};

helper.calculateDateRanges = function (min, max) {
  var startDate = moment.unix(min).utc();
  var endDate = moment.unix(max).utc();
  var ranges = {};

  _.each(_.keys(MOMENT_AGGREGATIONS), function (agg) {
    var startClone = startDate.clone();
    var endClone = endDate.clone();
    var start = startClone.startOf(agg).unix();
    var end = endClone.startOf(agg).add(1, MOMENT_AGGREGATIONS[agg]).unix() - 1;

    ranges[agg] = {
      start: start,
      end: end
    };
  });

  return ranges;
};

helper.formatUTCTimestamp = function (timestamp) {
  return formatUTCTimestamp(timestamp);
};

module.exports = helper;
<|endoftext|>


<commit_before>var db = require('./gitGraphAst');
var _ = require('lodash');
var gitGraphParser = require('./parser/gitGraph');
var d3 = require('../../d3');
var Logger = require('../../logger');

var log = new Logger.Log();
var allCommitsDict = {};
var branchNum;
var config = {
    nodeSpacing: 75,
    nodeFillColor: 'yellow',
    nodeStrokeWidth: 2,
    nodeStrokeColor: 'grey',
    lineStrokeWidth: 4,
    branchOffset: 50,
    lineColor: 'grey',
    leftMargin: 50,
    branchColors: ['#442f74', '#983351', '#609732', '#AA9A39'],
    nodeRadius: 15,
    nodeLabel: {
        width: 75,
        height: 100,
        x: -25,
        y: 15
    }
}
var apiConfig = {};
exports.setConf = function(c) {
    apiConfig = c;
}

function svgCreateDefs(svg) {
    svg
        .append('defs')
        .append('g')
        .attr('id', 'def-commit')
        .append('circle')
        .attr('r', config.nodeRadius)
        .attr('cx', 0)
        .attr('cy', 0);
    svg.select('#def-commit')
        .append('foreignObject')
        .attr('width', config.nodeLabel.width)
        .attr('height', config.nodeLabel.height)
        .attr('x', config.nodeLabel.x)
        .attr('y', config.nodeLabel.y)
        .attr('class', 'node-label')
        .attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
        .append('xhtml:p')
        .html('');
}

function svgDrawLine(svg, points, colorIdx, interpolate) {
    interpolate = interpolate || 'basis';
    var color = config.branchColors[colorIdx % config.branchColors.length];
    var lineGen = d3.svg.line()
        .x(function(d) {
            return Math.round(d.x)
        })
        .y(function(d) {
            return Math.round(d.y)
        })
        .interpolate(interpolate);

    svg
        .append('svg:path')
        .attr('d', lineGen(points))
        .style('stroke', color)
        .style('stroke-width', config.lineStrokeWidth)
        .style('fill', 'none');
}
// Pass in the element and its pre-transform coords
function getElementCoords(element, coords) {
    coords = coords || element.node().getBBox();
    var ctm = element.node().getCTM(),
        xn = ctm.e + coords.x * ctm.a,
        yn = ctm.f + coords.y * ctm.d;
    //log.debug(ctm, coords);
    return {
        left: xn,
        top: yn,
        width: coords.width,
        height: coords.height
    };
}

function svgDrawLineForCommits(svg, fromId, toId, direction, color) {
    log.debug('svgDrawLineForCommits: ', fromId, toId);
    var fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle'));
    var toBbox = getElementCoords(svg.select('#node-' + toId + ' circle'));
    //log.debug('svgDrawLineForCommits: ', fromBbox, toBbox);
    switch (direction) {
        case 'LR':
            // (toBbox)
            //  +--------
            //          + (fromBbox)
            if (fromBbox.left - toBbox.left > config.nodeSpacing) {
                var lineStart = { x: fromBbox.left - config.nodeSpacing, y: toBbox.top  + toBbox.height/2};
                var lineEnd ={ x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height/2 };
                svgDrawLine(svg, [lineStart , lineEnd], color, 'linear')
                svgDrawLine(svg, [
                    {x: fromBbox.left, y: fromBbox.top + fromBbox.height/2},
                    {x: fromBbox.left - config.nodeSpacing/2, y: fromBbox.top + fromBbox.height/2},
                    {x: fromBbox.left - config.nodeSpacing/2, y: lineStart.y},
                lineStart], color);
            } else {
                svgDrawLine(svg, [{
                    'x': fromBbox.left,
                    'y': fromBbox.top + fromBbox.height / 2
                }, {
                    'x': fromBbox.left - config.nodeSpacing/2,
                    'y': fromBbox.top + fromBbox.height / 2
                }, {
                    'x': fromBbox.left - config.nodeSpacing/2,
                    'y': toBbox.top + toBbox.height / 2
                }, {
                    'x': toBbox.left + toBbox.width,
                    'y': toBbox.top + toBbox.height / 2
                }], color);
            }
            break;
        case 'BT':
            //      +           (fromBbox)
            //      |
            //      |
            //              +   (toBbox)
            if (toBbox.top - fromBbox.top > config.nodeSpacing) {
                lineStart = { x: toBbox.left + toBbox.width/2, y: fromBbox.top  + fromBbox.height + config.nodeSpacing};
                lineEnd ={ x: toBbox.left + toBbox.width/2, y: toBbox.top };
                svgDrawLine(svg, [lineStart , lineEnd], color, 'linear')
                svgDrawLine(svg, [
                    {x: fromBbox.left + fromBbox.width/2, y: fromBbox.top + fromBbox.height},
                    {x: fromBbox.left + fromBbox.width/2, y: fromBbox.top + fromBbox.height + config.nodeSpacing/2},
                    {x: toBbox.left + toBbox.width/2, y: lineStart.y - config.nodeSpacing/2},
                lineStart], color);
            } else {
                svgDrawLine(svg, [{
                    'x': fromBbox.left + fromBbox.width/2,
                    'y': fromBbox.top + fromBbox.height
                }, {
                    'x': fromBbox.left + fromBbox.width/2,
                    'y': fromBbox.top + config.nodeSpacing/2
                }, {
                    'x': toBbox.left + toBbox.width/2,
                    'y': toBbox.top - config.nodeSpacing/2
                }, {
                    'x': toBbox.left + toBbox.width/2,
                    'y': toBbox.top
                }], color);
            }
            break;
    }
}

function cloneNode(svg, selector) {
    return svg.select(selector).node().cloneNode(true);
}

function renderCommitHistory(svg, commitid, branches, direction) {
    var commit;
    var numCommits = Object.keys(allCommitsDict).length;
    if (_.isString(commitid)) {
        do {
            commit = allCommitsDict[commitid];
            log.debug('in renderCommitHistory', commit.id, commit.seq);
            if (svg.select('#node-' + commitid).size() > 0)  {
                return;
            }
            svg
                .append(function() {
                    return cloneNode(svg, '#def-commit');
                })
                .attr('class', 'commit')
                .attr('id', function() {
                    return 'node-' + commit.id;
                })
                .attr('transform', function() {
                    switch (direction) {
                        case 'LR':
                            return 'translate(' + (commit.seq * config.nodeSpacing + config.leftMargin) + ', '
                                + (branchNum * config.branchOffset) + ')';
                        case 'BT':
                            return 'translate(' + (branchNum * config.branchOffset + config.leftMargin) + ', '
                                + ((numCommits - commit.seq) * config.nodeSpacing) + ')';
                    }
                })
                .attr('fill', config.nodeFillColor)
                .attr('stroke', config.nodeStrokeColor)
                .attr('stroke-width', config.nodeStrokeWidth);

            var branch = _.find(branches, ['commit', commit]);
            if (branch) {
                log.debug('found branch ', branch.name);
                svg.select('#node-' + commit.id + ' p')
                    .append('xhtml:span')
                    .attr('class', 'branch-label')
                    .text(branch.name + ', ');
            }
            svg.select('#node-' + commit.id + ' p')
                .append('xhtml:span')
                .attr('class', 'commit-id')
                .text(commit.id);
            if (commit.message !== '' && direction === 'BT') {
                svg.select('#node-' + commit.id + ' p')
                    .append('xhtml:span')
                    .attr('class', 'commit-msg')
                    .text( ', ' + commit.message);
            }
            commitid = commit.parent
        } while (commitid && allCommitsDict[commitid]);
    }

    if (_.isArray(commitid)) {
        log.debug('found merge commmit', commitid);
        renderCommitHistory(svg, commitid[0], branches, direction);
        branchNum++;
        renderCommitHistory(svg, commitid[1], branches, direction);
    }
}

function renderLines(svg, commit, direction, branchColor) {
    branchColor = branchColor || 0;
    while (commit.seq > 0 && !commit.lineDrawn) {
        if (_.isString(commit.parent)) {
            svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor);
            commit.lineDrawn = true;
            commit = allCommitsDict[commit.parent];
        } else if (_.isArray(commit.parent)) {
            svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor)
            svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
            renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1);
            commit.lineDrawn = true;
            commit = allCommitsDict[commit.parent[0]];
        }
    }
}

exports.draw = function(txt, id, ver) {
    try {
        var parser;
        parser = gitGraphParser.parser;
        parser.yy = db;

        log.debug('in gitgraph renderer', txt, id, ver);
        // Parse the graph definition
        parser.parse(txt + '\n');

        config = _.extend(config, apiConfig, db.getOptions());
        log.debug('effective options', config);
        var direction = db.getDirection();
        allCommitsDict = db.getCommits();
        var branches = db.getBranchesAsObjArray();
        if (direction === 'BT') {
            config.nodeLabel.x =  branches.length * config.branchOffset;
            config.nodeLabel.width =  '100%';
            config.nodeLabel.y = -1 * 2* config.nodeRadius;
        }
        var svg = d3.select('#' + id);
        svgCreateDefs(svg);
        branchNum = 1;
        _.each(branches, function(v) {
            renderCommitHistory(svg, v.commit.id, branches, direction);
            renderLines(svg, v.commit, direction);
            branchNum++;
        });
        svg.attr('height', function() {
            if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing;
            return branches.length * config.branchOffset;
        });
        svg.attr('width', function() {
            return Object.keys(allCommitsDict).length * config.nodeSpacing;
        });
    } catch (e) {
        log.error('Error while rendering gitgraph');
        log.error(e.message);
    }
};
<commit_msg>fix branching lines<commit_after>var db = require('./gitGraphAst');
var _ = require('lodash');
var gitGraphParser = require('./parser/gitGraph');
var d3 = require('../../d3');
var Logger = require('../../logger');

var log = new Logger.Log();
var allCommitsDict = {};
var branchNum;
var config = {
    nodeSpacing: 75,
    nodeFillColor: 'yellow',
    nodeStrokeWidth: 2,
    nodeStrokeColor: 'grey',
    lineStrokeWidth: 4,
    branchOffset: 50,
    lineColor: 'grey',
    leftMargin: 50,
    branchColors: ['#442f74', '#983351', '#609732', '#AA9A39'],
    nodeRadius: 15,
    nodeLabel: {
        width: 75,
        height: 100,
        x: -25,
        y: 15
    }
}
var apiConfig = {};
exports.setConf = function(c) {
    apiConfig = c;
}

function svgCreateDefs(svg) {
    svg
        .append('defs')
        .append('g')
        .attr('id', 'def-commit')
        .append('circle')
        .attr('r', config.nodeRadius)
        .attr('cx', 0)
        .attr('cy', 0);
    svg.select('#def-commit')
        .append('foreignObject')
        .attr('width', config.nodeLabel.width)
        .attr('height', config.nodeLabel.height)
        .attr('x', config.nodeLabel.x)
        .attr('y', config.nodeLabel.y)
        .attr('class', 'node-label')
        .attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
        .append('xhtml:p')
        .html('');
}

function svgDrawLine(svg, points, colorIdx, interpolate) {
    interpolate = interpolate || 'basis';
    var color = config.branchColors[colorIdx % config.branchColors.length];
    var lineGen = d3.svg.line()
        .x(function(d) {
            return Math.round(d.x)
        })
        .y(function(d) {
            return Math.round(d.y)
        })
        .interpolate(interpolate);

    svg
        .append('svg:path')
        .attr('d', lineGen(points))
        .style('stroke', color)
        .style('stroke-width', config.lineStrokeWidth)
        .style('fill', 'none');
}
// Pass in the element and its pre-transform coords
function getElementCoords(element, coords) {
    coords = coords || element.node().getBBox();
    var ctm = element.node().getCTM(),
        xn = ctm.e + coords.x * ctm.a,
        yn = ctm.f + coords.y * ctm.d;
    //log.debug(ctm, coords);
    return {
        left: xn,
        top: yn,
        width: coords.width,
        height: coords.height
    };
}

function svgDrawLineForCommits(svg, fromId, toId, direction, color) {
    log.debug('svgDrawLineForCommits: ', fromId, toId);
    var fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle'));
    var toBbox = getElementCoords(svg.select('#node-' + toId + ' circle'));
    //log.debug('svgDrawLineForCommits: ', fromBbox, toBbox);
    switch (direction) {
        case 'LR':
            // (toBbox)
            //  +--------
            //          + (fromBbox)
            if (fromBbox.left - toBbox.left > config.nodeSpacing) {
                var lineStart = { x: fromBbox.left - config.nodeSpacing, y: toBbox.top  + toBbox.height/2};
                var lineEnd ={ x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height/2 };
                svgDrawLine(svg, [lineStart , lineEnd], color, 'linear')
                svgDrawLine(svg, [
                    {x: fromBbox.left, y: fromBbox.top + fromBbox.height/2},
                    {x: fromBbox.left - config.nodeSpacing/2, y: fromBbox.top + fromBbox.height/2},
                    {x: fromBbox.left - config.nodeSpacing/2, y: lineStart.y},
                lineStart], color);
            } else {
                svgDrawLine(svg, [{
                    'x': fromBbox.left,
                    'y': fromBbox.top + fromBbox.height / 2
                }, {
                    'x': fromBbox.left - config.nodeSpacing/2,
                    'y': fromBbox.top + fromBbox.height / 2
                }, {
                    'x': fromBbox.left - config.nodeSpacing/2,
                    'y': toBbox.top + toBbox.height / 2
                }, {
                    'x': toBbox.left + toBbox.width,
                    'y': toBbox.top + toBbox.height / 2
                }], color);
            }
            break;
        case 'BT':
            //      +           (fromBbox)
            //      |
            //      |
            //              +   (toBbox)
            if (toBbox.top - fromBbox.top > config.nodeSpacing) {
                lineStart = { x: toBbox.left + toBbox.width/2, y: fromBbox.top  + fromBbox.height + config.nodeSpacing};
                lineEnd ={ x: toBbox.left + toBbox.width/2, y: toBbox.top };
                svgDrawLine(svg, [lineStart , lineEnd], color, 'linear')
                svgDrawLine(svg, [
                    {x: fromBbox.left + fromBbox.width/2, y: fromBbox.top + fromBbox.height},
                    {x: fromBbox.left + fromBbox.width/2, y: fromBbox.top + fromBbox.height + config.nodeSpacing/2},
                    {x: toBbox.left + toBbox.width/2, y: lineStart.y - config.nodeSpacing/2},
                lineStart], color);
            } else {
                svgDrawLine(svg, [{
                    'x': fromBbox.left + fromBbox.width/2,
                    'y': fromBbox.top + fromBbox.height
                }, {
                    'x': fromBbox.left + fromBbox.width/2,
                    'y': fromBbox.top + config.nodeSpacing/2
                }, {
                    'x': toBbox.left + toBbox.width/2,
                    'y': toBbox.top - config.nodeSpacing/2
                }, {
                    'x': toBbox.left + toBbox.width/2,
                    'y': toBbox.top
                }], color);
            }
            break;
    }
}

function cloneNode(svg, selector) {
    return svg.select(selector).node().cloneNode(true);
}

function renderCommitHistory(svg, commitid, branches, direction) {
    var commit;
    var numCommits = Object.keys(allCommitsDict).length;
    if (_.isString(commitid)) {
        do {
            commit = allCommitsDict[commitid];
            log.debug('in renderCommitHistory', commit.id, commit.seq);
            if (svg.select('#node-' + commitid).size() > 0)  {
                return;
            }
            svg
                .append(function() {
                    return cloneNode(svg, '#def-commit');
                })
                .attr('class', 'commit')
                .attr('id', function() {
                    return 'node-' + commit.id;
                })
                .attr('transform', function() {
                    switch (direction) {
                        case 'LR':
                            return 'translate(' + (commit.seq * config.nodeSpacing + config.leftMargin) + ', '
                                + (branchNum * config.branchOffset) + ')';
                        case 'BT':
                            return 'translate(' + (branchNum * config.branchOffset + config.leftMargin) + ', '
                                + ((numCommits - commit.seq) * config.nodeSpacing) + ')';
                    }
                })
                .attr('fill', config.nodeFillColor)
                .attr('stroke', config.nodeStrokeColor)
                .attr('stroke-width', config.nodeStrokeWidth);

            var branch = _.find(branches, ['commit', commit]);
            if (branch) {
                log.debug('found branch ', branch.name);
                svg.select('#node-' + commit.id + ' p')
                    .append('xhtml:span')
                    .attr('class', 'branch-label')
                    .text(branch.name + ', ');
            }
            svg.select('#node-' + commit.id + ' p')
                .append('xhtml:span')
                .attr('class', 'commit-id')
                .text(commit.id);
            if (commit.message !== '' && direction === 'BT') {
                svg.select('#node-' + commit.id + ' p')
                    .append('xhtml:span')
                    .attr('class', 'commit-msg')
                    .text( ', ' + commit.message);
            }
            commitid = commit.parent
        } while (commitid && allCommitsDict[commitid]);
    }

    if (_.isArray(commitid)) {
        log.debug('found merge commmit', commitid);
        renderCommitHistory(svg, commitid[0], branches, direction);
        branchNum++;
        renderCommitHistory(svg, commitid[1], branches, direction);
        branchNum--;
    }
}

function renderLines(svg, commit, direction, branchColor) {
    branchColor = branchColor || 0;
    while (commit.seq > 0 && !commit.lineDrawn) {
        if (_.isString(commit.parent)) {
            svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor);
            commit.lineDrawn = true;
            commit = allCommitsDict[commit.parent];
        } else if (_.isArray(commit.parent)) {
            svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor)
            svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
            renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1);
            commit.lineDrawn = true;
            commit = allCommitsDict[commit.parent[0]];
        }
    }
}

exports.draw = function(txt, id, ver) {
    try {
        var parser;
        parser = gitGraphParser.parser;
        parser.yy = db;

        log.debug('in gitgraph renderer', txt, id, ver);
        // Parse the graph definition
        parser.parse(txt + '\n');

        config = _.extend(config, apiConfig, db.getOptions());
        log.debug('effective options', config);
        var direction = db.getDirection();
        allCommitsDict = db.getCommits();
        var branches = db.getBranchesAsObjArray();
        if (direction === 'BT') {
            config.nodeLabel.x =  branches.length * config.branchOffset;
            config.nodeLabel.width =  '100%';
            config.nodeLabel.y = -1 * 2* config.nodeRadius;
        }
        var svg = d3.select('#' + id);
        svgCreateDefs(svg);
        branchNum = 1;
        _.each(branches, function(v) {
            renderCommitHistory(svg, v.commit.id, branches, direction);
            renderLines(svg, v.commit, direction);
            branchNum++;
        });
        svg.attr('height', function() {
            if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing;
            return (branches.length + 1) * config.branchOffset;
        });
        //svg.attr('width', function() {
            //if (direction === 'LR') return Object.keys(allCommitsDict).length * config.nodeSpacing + config.leftMargin;
            //return (branches.length + 1) * config.branchOffset;
        //});
    } catch (e) {
        log.error('Error while rendering gitgraph');
        log.error(e.message);
    }
};
<|endoftext|>


<commit_before>/**
 * usage:
 *
 * var features = require('./features');
 *
 * if (features('alpha', req)) {
 *   // do alpha features
 * }
 *
 * // as an express route handler - if it fails the feature test then
 * // the user will see a 404 (in this case, you can catch /cool-feature, and
 * // express will call the second handler)
 * app.get('/cool-feature', features.route('alpha'), function (req, res) {
 *   res.render('cool-feature');
 * });
 *
 * // as a hbs template (defined in ./hbs.js)
 * {{#feature user "alpha"}}You are part of the cool gang{{/feature}}
 */
'use strict';

var Features = require('feature-gateway'),
    undefsafe = require('undefsafe'),
    options = require('./config');

var teamjsbin = ['rem', 'allouis', 'yandle', 'electric_g'];
var alphausers = teamjsbin.concat(['sil', 'slexaxton', 'reybango', 'phuu', 'agcolom', 'glennjones', 'rossbruniges', 'andrewnez', 'chrismahon', 'brianleroux', 'jed', 'iancrowther', 'jakearchibald']);

/* Begin: user types */
function alpha(req) {
  var name = undefsafe(req, 'session.user.name');
  if (name) {
    return alphausers.indexOf(name) !== -1;
  }
  return false;
}

function beta(req) {
  return undefsafe(req, 'session.user.beta');
}

function team(req) {
  var name = undefsafe(req, 'session.user.name');
  if (name) {
    return teamjsbin.indexOf(name) !== -1;
  }
  return false;
}

function pro(req) {
  return flags.alpha(req) || undefsafe(req, 'session.user.pro');
}

/* End: user types */

function ipAsNum(req) {
  // takes the last part of an IP (n.n.n.last-part) and returns as number
  return (req.headers['x-real-ip'] || req.ip || '0.0').split('.').slice(-1) * 1;
}

function percentage(n, req) {
  var ip = ipAsNum(req);
  return (ip / 256) <= (n / 100);
}

var flags = {
  /* Begin: actual features */

  github: function () {
    return options.github && options.github.id;
  },

  // private bins
  private: function (req) {
    return alpha(req); // pro
  },

  // whether user can delete bins
  delete: true, // live 25 Feb 2014

  // allows for sandbox play in a bin without actually saving
  sandbox: function (req) {
    return alpha(req); // pro
  },

  // info/hover card with details of bin and streaming info
  infocard: function (req) {
    return alpha(req);
  },

  // seperate account management pages
  accountPages: function (req) {
    return alpha(req) || percentage(10, req);
  },

  // use SSL for sign in
  sslLogin: true,

  // using memcache for sessions
  serverSession: true,

  // code-analysis engine for javascript panel
  tern: function (req) {
    return alpha(req);
  },

  stripe: function (req) {
    return team(req);
  },

  vanity: function (req) {
    return alpha(req); // return pro(req);
  },

  dropbox: function (req) {
    return alpha(req);
  },

  assets: function (req) {
    return alpha(req);
  },

  revisionless: function (req) {
    return team(req);
  },

  fileMenuTest: function (req) {
    return team(req) || percentage(50, req);
<<<<<<< HEAD
  },

  upgrade: function (req) {
    return team(req); // return !pro(req);
  },

  revisionless: function (req) {
    return team(req);
  },

=======
  }
>>>>>>> Use percentage of visitor for feature flagging
};

var features = module.exports = new Features(flags);

features.log = function () {
  // console.log.apply(console, [].slice.apply(arguments));
};
<commit_msg>Fixed dodgy merge<commit_after>/**
 * usage:
 *
 * var features = require('./features');
 *
 * if (features('alpha', req)) {
 *   // do alpha features
 * }
 *
 * // as an express route handler - if it fails the feature test then
 * // the user will see a 404 (in this case, you can catch /cool-feature, and
 * // express will call the second handler)
 * app.get('/cool-feature', features.route('alpha'), function (req, res) {
 *   res.render('cool-feature');
 * });
 *
 * // as a hbs template (defined in ./hbs.js)
 * {{#feature user "alpha"}}You are part of the cool gang{{/feature}}
 */
'use strict';

var Features = require('feature-gateway'),
    undefsafe = require('undefsafe'),
    options = require('./config');

var teamjsbin = ['rem', 'allouis', 'yandle', 'electric_g'];
var alphausers = teamjsbin.concat(['sil', 'slexaxton', 'reybango', 'phuu', 'agcolom', 'glennjones', 'rossbruniges', 'andrewnez', 'chrismahon', 'brianleroux', 'jed', 'iancrowther', 'jakearchibald']);

/* Begin: user types */
function alpha(req) {
  var name = undefsafe(req, 'session.user.name');
  if (name) {
    return alphausers.indexOf(name) !== -1;
  }
  return false;
}

function beta(req) {
  return undefsafe(req, 'session.user.beta');
}

function team(req) {
  var name = undefsafe(req, 'session.user.name');
  if (name) {
    return teamjsbin.indexOf(name) !== -1;
  }
  return false;
}

function pro(req) {
  return flags.alpha(req) || undefsafe(req, 'session.user.pro');
}

/* End: user types */

function ipAsNum(req) {
  // takes the last part of an IP (n.n.n.last-part) and returns as number
  return (req.headers['x-real-ip'] || req.ip || '0.0').split('.').slice(-1) * 1;
}

function percentage(n, req) {
  var ip = ipAsNum(req);
  return (ip / 256) <= (n / 100);
}

var flags = {
  /* Begin: actual features */

  github: function () {
    return options.github && options.github.id;
  },

  // private bins
  private: function (req) {
    return alpha(req); // pro
  },

  // whether user can delete bins
  delete: true, // live 25 Feb 2014

  // allows for sandbox play in a bin without actually saving
  sandbox: function (req) {
    return alpha(req); // pro
  },

  // info/hover card with details of bin and streaming info
  infocard: function (req) {
    return alpha(req);
  },

  // seperate account management pages
  accountPages: function (req) {
    return alpha(req) || percentage(10, req);
  },

  // use SSL for sign in
  sslLogin: true,

  // using memcache for sessions
  serverSession: true,

  // code-analysis engine for javascript panel
  tern: function (req) {
    return alpha(req);
  },

  stripe: function (req) {
    return team(req);
  },

  vanity: function (req) {
    return alpha(req); // return pro(req);
  },

  dropbox: function (req) {
    return alpha(req);
  },

  assets: function (req) {
    return alpha(req);
  },

  revisionless: function (req) {
    return team(req);
  },

  fileMenuTest: function (req) {
    return team(req) || percentage(50, req);
  },

  upgrade: function (req) {
    return team(req); // return !pro(req);
  },
};

var features = module.exports = new Features(flags);

features.log = function () {
  // console.log.apply(console, [].slice.apply(arguments));
};
<|endoftext|>


<commit_before>exports.createPlatformModule = function(platform) {
	if (!platform.sendMessage) {
		platform.sendMessage = function() {
			throw 'What kind of shit platform is this that doesn\'t even support sending messages?';
		};
	}

	if (!platform.sendUrl) {
		platform.sendUrl = function(url, thread) {
			platform.sendMessage(url, thread); // fallback to sending a message
		};
	}

	if (!platform.sendImage) {
		platform.sendImage = function(type, image, description, thread) {
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendMessage(description, thread);
					platform.sendUrl(image, thread);
					break;
				case 'file': // fallback to sending a file
					platform.sendFile(image, thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage(description, thread);
					platform.sendMessage('I also have something to send you but cant seem to do so...', thread);
					break;
			}
		};
	}

	if (!platform.sendFile) {
		platform.sendFile = function(type, file, description, thread) {
			platform.sendMessage(description, thread);
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendUrl(file, thread);
					break;
				case 'file': // fallback to sending a message
					platform.sendMessage('I have a file to send you but cant seem to do so...', thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage('I have something to send you but cant seem to do so...', thread);
					break;
			}
		}
	}

	if (!platform.sendTyping) {
		platform.sendTyping = function(thread) {
			//TODO fix me
			platform.sendMessage('Working on it...', thread); // fallback to sending a message
		}
	}

	if (!platform.setTitle) {
		platform.setTitle = function(title, thread) { // fallback to sending a message
			platform.sendMessage('If I could set the title of this chat I would set it to "' + title + '"', thread);
		}
	}

	if (!platform.commandPrefix) {
		if (platform.config && platform.config.commandPrefix) {
			platform.commandPrefix = platform.config.commandPrefix;
		}
		else {
			platform.commandPrefix = '/';
		}
	}

	if (!platform.sendPrivateMessage) {
		platform.sendPrivateMessage = function(message, thread) {
			platform.sendMessage(message, thread);
		}
	}

	return platform;
};

exports.createEvent = function(thread, senderId, senderName, message) {
	return {
		thread_id: thread,
		sender_id: senderId,
		sender_name: senderName,
		body: message
	};
};
<commit_msg>Moved patch to shim.js<commit_after>exports.createPlatformModule = function(platform) {
	if (!platform.sendMessage) {
		platform.sendMessage = function() {
			throw 'What kind of shit platform is this that doesn\'t even support sending messages?';
		};
	}

	if (!platform.sendUrl) {
		platform.sendUrl = function(url, thread) {
			platform.sendMessage(url, thread); // fallback to sending a message
		};
	}

	if (!platform.sendImage) {
		platform.sendImage = function(type, image, description, thread) {
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendMessage(description, thread);
					platform.sendUrl(image, thread);
					break;
				case 'file': // fallback to sending a file
					platform.sendFile(image, thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage(description, thread);
					platform.sendMessage('I also have something to send you but cant seem to do so...', thread);
					break;
			}
		};
	}

	if (!platform.sendFile) {
		platform.sendFile = function(type, file, description, thread) {
			platform.sendMessage(description, thread);
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendUrl(file, thread);
					break;
				case 'file': // fallback to sending a message
					platform.sendMessage('I have a file to send you but cant seem to do so...', thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage('I have something to send you but cant seem to do so...', thread);
					break;
			}
		}
	}

	if (!platform.sendTyping) {
		platform.sendTyping = function(thread) {
			//TODO fix me
			platform.sendMessage('Working on it...', thread); // fallback to sending a message
		}
	}

	if (!platform.setTitle) {
		platform.setTitle = function(title, thread) { // fallback to sending a message
			platform.sendMessage('If I could set the title of this chat I would set it to "' + title + '"', thread);
		}
	}

	if (!platform.commandPrefix) {
		if (platform.config && platform.config.commandPrefix) {
			platform.commandPrefix = platform.config.commandPrefix;
		}
		else {
			platform.commandPrefix = '/';
		}
	}

	if (!platform.sendPrivateMessage) {
		platform.sendPrivateMessage = function(message, thread) {
			platform.sendMessage(message, thread);
		}
	}

	return platform;
};

exports.createEvent = function(thread, senderId, senderName, message) {
	return {
		thread_id: thread,
		sender_id: senderId,
		sender_name: senderName + "", // Accept sender_name  = null as a literal
		body: message
	};
};
<|endoftext|>