gplus-web-auth
Version:
Google Plus Login module for Node.js
1,965 lines (1,729 loc) • 74.3 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var superagent = require('superagent'),
_ = require('underscore');
var kvs = _(window.location.hash.slice(1).split('&')).map(function(str){ return str.split('=')})
function find(arr, key){
return _(arr).find(function(el){ return el[0] === key;})[1]
}
var state = JSON.parse(decodeURIComponent(find(kvs, 'state')));
var postURL = window.location.pathname;
superagent.post(postURL).send({
token: find(kvs, 'access_token')
}).end(function(err, res){
if(err || res.error) {
console.error(err , res);
alert('you\'re probably not supposed to be here. shoo.');
return;
}
window.location = state.nextUrl || '/admin';
})
},{"superagent":2,"underscore":5}],2:[function(require,module,exports){
/**
* Module dependencies.
*/
var Emitter = require('emitter');
var reduce = require('reduce');
/**
* Root reference for iframes.
*/
var root = 'undefined' == typeof window
? this
: window;
/**
* Noop.
*/
function noop(){};
/**
* Check if `obj` is a host object,
* we don't want to serialize these :)
*
* TODO: future proof, move to compoent land
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
function isHost(obj) {
var str = {}.toString.call(obj);
switch (str) {
case '[object File]':
case '[object Blob]':
case '[object FormData]':
return true;
default:
return false;
}
}
/**
* Determine XHR.
*/
function getXHR() {
if (root.XMLHttpRequest
&& ('file:' != root.location.protocol || !root.ActiveXObject)) {
return new XMLHttpRequest;
} else {
try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}
}
return false;
}
/**
* Removes leading and trailing whitespace, added to support IE.
*
* @param {String} s
* @return {String}
* @api private
*/
var trim = ''.trim
? function(s) { return s.trim(); }
: function(s) { return s.replace(/(^\s*|\s*$)/g, ''); };
/**
* Check if `obj` is an object.
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
function isObject(obj) {
return obj === Object(obj);
}
/**
* Serialize the given `obj`.
*
* @param {Object} obj
* @return {String}
* @api private
*/
function serialize(obj) {
if (!isObject(obj)) return obj;
var pairs = [];
for (var key in obj) {
if (null != obj[key]) {
pairs.push(encodeURIComponent(key)
+ '=' + encodeURIComponent(obj[key]));
}
}
return pairs.join('&');
}
/**
* Expose serialization method.
*/
request.serializeObject = serialize;
/**
* Parse the given x-www-form-urlencoded `str`.
*
* @param {String} str
* @return {Object}
* @api private
*/
function parseString(str) {
var obj = {};
var pairs = str.split('&');
var parts;
var pair;
for (var i = 0, len = pairs.length; i < len; ++i) {
pair = pairs[i];
parts = pair.split('=');
obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
return obj;
}
/**
* Expose parser.
*/
request.parseString = parseString;
/**
* Default MIME type map.
*
* superagent.types.xml = 'application/xml';
*
*/
request.types = {
html: 'text/html',
json: 'application/json',
xml: 'application/xml',
urlencoded: 'application/x-www-form-urlencoded',
'form': 'application/x-www-form-urlencoded',
'form-data': 'application/x-www-form-urlencoded'
};
/**
* Default serialization map.
*
* superagent.serialize['application/xml'] = function(obj){
* return 'generated xml here';
* };
*
*/
request.serialize = {
'application/x-www-form-urlencoded': serialize,
'application/json': JSON.stringify
};
/**
* Default parsers.
*
* superagent.parse['application/xml'] = function(str){
* return { object parsed from str };
* };
*
*/
request.parse = {
'application/x-www-form-urlencoded': parseString,
'application/json': JSON.parse
};
/**
* Parse the given header `str` into
* an object containing the mapped fields.
*
* @param {String} str
* @return {Object}
* @api private
*/
function parseHeader(str) {
var lines = str.split(/\r?\n/);
var fields = {};
var index;
var line;
var field;
var val;
lines.pop(); // trailing CRLF
for (var i = 0, len = lines.length; i < len; ++i) {
line = lines[i];
index = line.indexOf(':');
field = line.slice(0, index).toLowerCase();
val = trim(line.slice(index + 1));
fields[field] = val;
}
return fields;
}
/**
* Return the mime type for the given `str`.
*
* @param {String} str
* @return {String}
* @api private
*/
function type(str){
return str.split(/ *; */).shift();
};
/**
* Return header field parameters.
*
* @param {String} str
* @return {Object}
* @api private
*/
function params(str){
return reduce(str.split(/ *; */), function(obj, str){
var parts = str.split(/ *= */)
, key = parts.shift()
, val = parts.shift();
if (key && val) obj[key] = val;
return obj;
}, {});
};
/**
* Initialize a new `Response` with the given `xhr`.
*
* - set flags (.ok, .error, etc)
* - parse header
*
* Examples:
*
* Aliasing `superagent` as `request` is nice:
*
* request = superagent;
*
* We can use the promise-like API, or pass callbacks:
*
* request.get('/').end(function(res){});
* request.get('/', function(res){});
*
* Sending data can be chained:
*
* request
* .post('/user')
* .send({ name: 'tj' })
* .end(function(res){});
*
* Or passed to `.send()`:
*
* request
* .post('/user')
* .send({ name: 'tj' }, function(res){});
*
* Or passed to `.post()`:
*
* request
* .post('/user', { name: 'tj' })
* .end(function(res){});
*
* Or further reduced to a single call for simple cases:
*
* request
* .post('/user', { name: 'tj' }, function(res){});
*
* @param {XMLHTTPRequest} xhr
* @param {Object} options
* @api private
*/
function Response(req, options) {
options = options || {};
this.req = req;
this.xhr = this.req.xhr;
this.text = this.xhr.responseText;
this.setStatusProperties(this.xhr.status);
this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders());
// getAllResponseHeaders sometimes falsely returns "" for CORS requests, but
// getResponseHeader still works. so we get content-type even if getting
// other headers fails.
this.header['content-type'] = this.xhr.getResponseHeader('content-type');
this.setHeaderProperties(this.header);
this.body = this.req.method != 'HEAD'
? this.parseBody(this.text)
: null;
}
/**
* Get case-insensitive `field` value.
*
* @param {String} field
* @return {String}
* @api public
*/
Response.prototype.get = function(field){
return this.header[field.toLowerCase()];
};
/**
* Set header related properties:
*
* - `.type` the content type without params
*
* A response of "Content-Type: text/plain; charset=utf-8"
* will provide you with a `.type` of "text/plain".
*
* @param {Object} header
* @api private
*/
Response.prototype.setHeaderProperties = function(header){
// content-type
var ct = this.header['content-type'] || '';
this.type = type(ct);
// params
var obj = params(ct);
for (var key in obj) this[key] = obj[key];
};
/**
* Parse the given body `str`.
*
* Used for auto-parsing of bodies. Parsers
* are defined on the `superagent.parse` object.
*
* @param {String} str
* @return {Mixed}
* @api private
*/
Response.prototype.parseBody = function(str){
var parse = request.parse[this.type];
return parse && str && str.length
? parse(str)
: null;
};
/**
* Set flags such as `.ok` based on `status`.
*
* For example a 2xx response will give you a `.ok` of __true__
* whereas 5xx will be __false__ and `.error` will be __true__. The
* `.clientError` and `.serverError` are also available to be more
* specific, and `.statusType` is the class of error ranging from 1..5
* sometimes useful for mapping respond colors etc.
*
* "sugar" properties are also defined for common cases. Currently providing:
*
* - .noContent
* - .badRequest
* - .unauthorized
* - .notAcceptable
* - .notFound
*
* @param {Number} status
* @api private
*/
Response.prototype.setStatusProperties = function(status){
var type = status / 100 | 0;
// status / class
this.status = status;
this.statusType = type;
// basics
this.info = 1 == type;
this.ok = 2 == type;
this.clientError = 4 == type;
this.serverError = 5 == type;
this.error = (4 == type || 5 == type)
? this.toError()
: false;
// sugar
this.accepted = 202 == status;
this.noContent = 204 == status || 1223 == status;
this.badRequest = 400 == status;
this.unauthorized = 401 == status;
this.notAcceptable = 406 == status;
this.notFound = 404 == status;
this.forbidden = 403 == status;
};
/**
* Return an `Error` representative of this response.
*
* @return {Error}
* @api public
*/
Response.prototype.toError = function(){
var req = this.req;
var method = req.method;
var url = req.url;
var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')';
var err = new Error(msg);
err.status = this.status;
err.method = method;
err.url = url;
return err;
};
/**
* Expose `Response`.
*/
request.Response = Response;
/**
* Initialize a new `Request` with the given `method` and `url`.
*
* @param {String} method
* @param {String} url
* @api public
*/
function Request(method, url) {
var self = this;
Emitter.call(this);
this._query = this._query || [];
this.method = method;
this.url = url;
this.header = {};
this._header = {};
this.on('end', function(){
try {
var res = new Response(self);
if ('HEAD' == method) res.text = null;
self.callback(null, res);
} catch(e) {
var err = new Error('Parser is unable to parse the response');
err.parse = true;
err.original = e;
self.callback(err);
}
});
}
/**
* Mixin `Emitter`.
*/
Emitter(Request.prototype);
/**
* Allow for extension
*/
Request.prototype.use = function(fn) {
fn(this);
return this;
}
/**
* Set timeout to `ms`.
*
* @param {Number} ms
* @return {Request} for chaining
* @api public
*/
Request.prototype.timeout = function(ms){
this._timeout = ms;
return this;
};
/**
* Clear previous timeout.
*
* @return {Request} for chaining
* @api public
*/
Request.prototype.clearTimeout = function(){
this._timeout = 0;
clearTimeout(this._timer);
return this;
};
/**
* Abort the request, and clear potential timeout.
*
* @return {Request}
* @api public
*/
Request.prototype.abort = function(){
if (this.aborted) return;
this.aborted = true;
this.xhr.abort();
this.clearTimeout();
this.emit('abort');
return this;
};
/**
* Set header `field` to `val`, or multiple fields with one object.
*
* Examples:
*
* req.get('/')
* .set('Accept', 'application/json')
* .set('X-API-Key', 'foobar')
* .end(callback);
*
* req.get('/')
* .set({ Accept: 'application/json', 'X-API-Key': 'foobar' })
* .end(callback);
*
* @param {String|Object} field
* @param {String} val
* @return {Request} for chaining
* @api public
*/
Request.prototype.set = function(field, val){
if (isObject(field)) {
for (var key in field) {
this.set(key, field[key]);
}
return this;
}
this._header[field.toLowerCase()] = val;
this.header[field] = val;
return this;
};
/**
* Remove header `field`.
*
* Example:
*
* req.get('/')
* .unset('User-Agent')
* .end(callback);
*
* @param {String} field
* @return {Request} for chaining
* @api public
*/
Request.prototype.unset = function(field){
delete this._header[field.toLowerCase()];
delete this.header[field];
return this;
};
/**
* Get case-insensitive header `field` value.
*
* @param {String} field
* @return {String}
* @api private
*/
Request.prototype.getHeader = function(field){
return this._header[field.toLowerCase()];
};
/**
* Set Content-Type to `type`, mapping values from `request.types`.
*
* Examples:
*
* superagent.types.xml = 'application/xml';
*
* request.post('/')
* .type('xml')
* .send(xmlstring)
* .end(callback);
*
* request.post('/')
* .type('application/xml')
* .send(xmlstring)
* .end(callback);
*
* @param {String} type
* @return {Request} for chaining
* @api public
*/
Request.prototype.type = function(type){
this.set('Content-Type', request.types[type] || type);
return this;
};
/**
* Set Accept to `type`, mapping values from `request.types`.
*
* Examples:
*
* superagent.types.json = 'application/json';
*
* request.get('/agent')
* .accept('json')
* .end(callback);
*
* request.get('/agent')
* .accept('application/json')
* .end(callback);
*
* @param {String} accept
* @return {Request} for chaining
* @api public
*/
Request.prototype.accept = function(type){
this.set('Accept', request.types[type] || type);
return this;
};
/**
* Set Authorization field value with `user` and `pass`.
*
* @param {String} user
* @param {String} pass
* @return {Request} for chaining
* @api public
*/
Request.prototype.auth = function(user, pass){
var str = btoa(user + ':' + pass);
this.set('Authorization', 'Basic ' + str);
return this;
};
/**
* Add query-string `val`.
*
* Examples:
*
* request.get('/shoes')
* .query('size=10')
* .query({ color: 'blue' })
*
* @param {Object|String} val
* @return {Request} for chaining
* @api public
*/
Request.prototype.query = function(val){
if ('string' != typeof val) val = serialize(val);
if (val) this._query.push(val);
return this;
};
/**
* Write the field `name` and `val` for "multipart/form-data"
* request bodies.
*
* ``` js
* request.post('/upload')
* .field('foo', 'bar')
* .end(callback);
* ```
*
* @param {String} name
* @param {String|Blob|File} val
* @return {Request} for chaining
* @api public
*/
Request.prototype.field = function(name, val){
if (!this._formData) this._formData = new FormData();
this._formData.append(name, val);
return this;
};
/**
* Queue the given `file` as an attachment to the specified `field`,
* with optional `filename`.
*
* ``` js
* request.post('/upload')
* .attach(new Blob(['<a id="a"><b id="b">hey!</b></a>'], { type: "text/html"}))
* .end(callback);
* ```
*
* @param {String} field
* @param {Blob|File} file
* @param {String} filename
* @return {Request} for chaining
* @api public
*/
Request.prototype.attach = function(field, file, filename){
if (!this._formData) this._formData = new FormData();
this._formData.append(field, file, filename);
return this;
};
/**
* Send `data`, defaulting the `.type()` to "json" when
* an object is given.
*
* Examples:
*
* // querystring
* request.get('/search')
* .end(callback)
*
* // multiple data "writes"
* request.get('/search')
* .send({ search: 'query' })
* .send({ range: '1..5' })
* .send({ order: 'desc' })
* .end(callback)
*
* // manual json
* request.post('/user')
* .type('json')
* .send('{"name":"tj"})
* .end(callback)
*
* // auto json
* request.post('/user')
* .send({ name: 'tj' })
* .end(callback)
*
* // manual x-www-form-urlencoded
* request.post('/user')
* .type('form')
* .send('name=tj')
* .end(callback)
*
* // auto x-www-form-urlencoded
* request.post('/user')
* .type('form')
* .send({ name: 'tj' })
* .end(callback)
*
* // defaults to x-www-form-urlencoded
* request.post('/user')
* .send('name=tobi')
* .send('species=ferret')
* .end(callback)
*
* @param {String|Object} data
* @return {Request} for chaining
* @api public
*/
Request.prototype.send = function(data){
var obj = isObject(data);
var type = this.getHeader('Content-Type');
// merge
if (obj && isObject(this._data)) {
for (var key in data) {
this._data[key] = data[key];
}
} else if ('string' == typeof data) {
if (!type) this.type('form');
type = this.getHeader('Content-Type');
if ('application/x-www-form-urlencoded' == type) {
this._data = this._data
? this._data + '&' + data
: data;
} else {
this._data = (this._data || '') + data;
}
} else {
this._data = data;
}
if (!obj) return this;
if (!type) this.type('json');
return this;
};
/**
* Invoke the callback with `err` and `res`
* and handle arity check.
*
* @param {Error} err
* @param {Response} res
* @api private
*/
Request.prototype.callback = function(err, res){
var fn = this._callback;
if (2 == fn.length) return fn(err, res);
if (err) return this.emit('error', err);
fn(res);
};
/**
* Invoke callback with x-domain error.
*
* @api private
*/
Request.prototype.crossDomainError = function(){
var err = new Error('Origin is not allowed by Access-Control-Allow-Origin');
err.crossDomain = true;
this.callback(err);
};
/**
* Invoke callback with timeout error.
*
* @api private
*/
Request.prototype.timeoutError = function(){
var timeout = this._timeout;
var err = new Error('timeout of ' + timeout + 'ms exceeded');
err.timeout = timeout;
this.callback(err);
};
/**
* Enable transmission of cookies with x-domain requests.
*
* Note that for this to work the origin must not be
* using "Access-Control-Allow-Origin" with a wildcard,
* and also must set "Access-Control-Allow-Credentials"
* to "true".
*
* @api public
*/
Request.prototype.withCredentials = function(){
this._withCredentials = true;
return this;
};
/**
* Initiate request, invoking callback `fn(res)`
* with an instanceof `Response`.
*
* @param {Function} fn
* @return {Request} for chaining
* @api public
*/
Request.prototype.end = function(fn){
var self = this;
var xhr = this.xhr = getXHR();
var query = this._query.join('&');
var timeout = this._timeout;
var data = this._formData || this._data;
// store callback
this._callback = fn || noop;
// state change
xhr.onreadystatechange = function(){
if (4 != xhr.readyState) return;
if (0 == xhr.status) {
if (self.aborted) return self.timeoutError();
return self.crossDomainError();
}
self.emit('end');
};
// progress
if (xhr.upload) {
xhr.upload.onprogress = function(e){
e.percent = e.loaded / e.total * 100;
self.emit('progress', e);
};
}
// timeout
if (timeout && !this._timer) {
this._timer = setTimeout(function(){
self.abort();
}, timeout);
}
// querystring
if (query) {
query = request.serializeObject(query);
this.url += ~this.url.indexOf('?')
? '&' + query
: '?' + query;
}
// initiate request
xhr.open(this.method, this.url, true);
// CORS
if (this._withCredentials) xhr.withCredentials = true;
// body
if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !isHost(data)) {
// serialize stuff
var serialize = request.serialize[this.getHeader('Content-Type')];
if (serialize) data = serialize(data);
}
// set header fields
for (var field in this.header) {
if (null == this.header[field]) continue;
xhr.setRequestHeader(field, this.header[field]);
}
// send stuff
this.emit('request', this);
xhr.send(data);
return this;
};
/**
* Expose `Request`.
*/
request.Request = Request;
/**
* Issue a request:
*
* Examples:
*
* request('GET', '/users').end(callback)
* request('/users').end(callback)
* request('/users', callback)
*
* @param {String} method
* @param {String|Function} url or callback
* @return {Request}
* @api public
*/
function request(method, url) {
// callback
if ('function' == typeof url) {
return new Request('GET', method).end(url);
}
// url first
if (1 == arguments.length) {
return new Request('GET', method);
}
return new Request(method, url);
}
/**
* GET `url` with optional callback `fn(res)`.
*
* @param {String} url
* @param {Mixed|Function} data or fn
* @param {Function} fn
* @return {Request}
* @api public
*/
request.get = function(url, data, fn){
var req = request('GET', url);
if ('function' == typeof data) fn = data, data = null;
if (data) req.query(data);
if (fn) req.end(fn);
return req;
};
/**
* HEAD `url` with optional callback `fn(res)`.
*
* @param {String} url
* @param {Mixed|Function} data or fn
* @param {Function} fn
* @return {Request}
* @api public
*/
request.head = function(url, data, fn){
var req = request('HEAD', url);
if ('function' == typeof data) fn = data, data = null;
if (data) req.send(data);
if (fn) req.end(fn);
return req;
};
/**
* DELETE `url` with optional callback `fn(res)`.
*
* @param {String} url
* @param {Function} fn
* @return {Request}
* @api public
*/
request.del = function(url, fn){
var req = request('DELETE', url);
if (fn) req.end(fn);
return req;
};
/**
* PATCH `url` with optional `data` and callback `fn(res)`.
*
* @param {String} url
* @param {Mixed} data
* @param {Function} fn
* @return {Request}
* @api public
*/
request.patch = function(url, data, fn){
var req = request('PATCH', url);
if ('function' == typeof data) fn = data, data = null;
if (data) req.send(data);
if (fn) req.end(fn);
return req;
};
/**
* POST `url` with optional `data` and callback `fn(res)`.
*
* @param {String} url
* @param {Mixed} data
* @param {Function} fn
* @return {Request}
* @api public
*/
request.post = function(url, data, fn){
var req = request('POST', url);
if ('function' == typeof data) fn = data, data = null;
if (data) req.send(data);
if (fn) req.end(fn);
return req;
};
/**
* PUT `url` with optional `data` and callback `fn(res)`.
*
* @param {String} url
* @param {Mixed|Function} data or fn
* @param {Function} fn
* @return {Request}
* @api public
*/
request.put = function(url, data, fn){
var req = request('PUT', url);
if ('function' == typeof data) fn = data, data = null;
if (data) req.send(data);
if (fn) req.end(fn);
return req;
};
/**
* Expose `request`.
*/
module.exports = request;
},{"emitter":3,"reduce":4}],3:[function(require,module,exports){
/**
* Expose `Emitter`.
*/
module.exports = Emitter;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
},{}],4:[function(require,module,exports){
/**
* Reduce `arr` with `fn`.
*
* @param {Array} arr
* @param {Function} fn
* @param {Mixed} initial
*
* TODO: combatible error handling?
*/
module.exports = function(arr, fn, initial){
var idx = 0;
var len = arr.length;
var curr = arguments.length == 3
? initial
: arr[idx++];
while (idx < len) {
curr = fn.call(null, curr, arr[idx], ++idx, arr);
}
return curr;
};
},{}],5:[function(require,module,exports){
// Underscore.js 1.7.0
// http://underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var
push = ArrayProto.push,
slice = ArrayProto.slice,
concat = ArrayProto.concat,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// Current version.
_.VERSION = '1.7.0';
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
var createCallback = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
// A mostly-internal function to generate callbacks that can be applied
// to each element in a collection, returning the desired result — either
// identity, an arbitrary callback, a property matcher, or a property accessor.
_.iteratee = function(value, context, argCount) {
if (value == null) return _.identity;
if (_.isFunction(value)) return createCallback(value, context, argCount);
if (_.isObject(value)) return _.matches(value);
return _.property(value);
};
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles raw objects in addition to array-likes. Treats all
// sparse array-likes as if they were dense.
_.each = _.forEach = function(obj, iteratee, context) {
if (obj == null) return obj;
iteratee = createCallback(iteratee, context);
var i, length = obj.length;
if (length === +length) {
for (i = 0; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};
// Return the results of applying the iteratee to each element.
_.map = _.collect = function(obj, iteratee, context) {
if (obj == null) return [];
iteratee = _.iteratee(iteratee, context);
var keys = obj.length !== +obj.length && _.keys(obj),
length = (keys || obj).length,
results = Array(length),
currentKey;
for (var index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
};
var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
_.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
if (obj == null) obj = [];
iteratee = createCallback(iteratee, context, 4);
var keys = obj.length !== +obj.length && _.keys(obj),
length = (keys || obj).length,
index = 0, currentKey;
if (arguments.length < 3) {
if (!length) throw new TypeError(reduceError);
memo = obj[keys ? keys[index++] : index++];
}
for (; index < length; index++) {
currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
_.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
if (obj == null) obj = [];
iteratee = createCallback(iteratee, context, 4);
var keys = obj.length !== + obj.length && _.keys(obj),
index = (keys || obj).length,
currentKey;
if (arguments.length < 3) {
if (!index) throw new TypeError(reduceError);
memo = obj[keys ? keys[--index] : --index];
}
while (index--) {
currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, predicate, context) {
var result;
predicate = _.iteratee(predicate, context);
_.some(obj, function(value, index, list) {
if (predicate(value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Aliased as `select`.
_.filter = _.select = function(obj, predicate, context) {
var results = [];
if (obj == null) return results;
predicate = _.iteratee(predicate, context);
_.each(obj, function(value, index, list) {
if (predicate(value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
return _.filter(obj, _.negate(_.iteratee(predicate)), context);
};
// Determine whether all of the elements match a truth test.
// Aliased as `all`.
_.every = _.all = function(obj, predicate, context) {
if (obj == null) return true;
predicate = _.iteratee(predicate, context);
var keys = obj.length !== +obj.length && _.keys(obj),
length = (keys || obj).length,
index, currentKey;
for (index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
};
// Determine if at least one element in the object matches a truth test.
// Aliased as `any`.
_.some = _.any = function(obj, predicate, context) {
if (obj == null) return false;
predicate = _.iteratee(predicate, context);
var keys = obj.length !== +obj.length && _.keys(obj),
length = (keys || obj).length,
index, currentKey;
for (index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
if (predicate(obj[currentKey], currentKey, obj)) return true;
}
return false;
};
// Determine if the array or object contains a given value (using `===`).
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
if (obj == null) return false;
if (obj.length !== +obj.length) obj = _.values(obj);
return _.indexOf(obj, target) >= 0;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
return (isFunc ? method : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, _.property(key));
};
// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
_.where = function(obj, attrs) {
return _.filter(obj, _.matches(attrs));
};
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matches(attrs));
};
// Return the maximum element (or element-based computation).
_.max = function(obj, iteratee, context) {
var result = -Infinity, lastComputed = -Infinity,
value, computed;
if (iteratee == null && obj != null) {
obj = obj.length === +obj.length ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value > result) {
result = value;
}
}
} else {
iteratee = _.iteratee(iteratee, context);
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list);
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iteratee, context) {
var result = Infinity, lastComputed = Infinity,
value, computed;
if (iteratee == null && obj != null) {
obj = obj.length === +obj.length ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value < result) {
result = value;
}
}
} else {
iteratee = _.iteratee(iteratee, context);
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Shuffle a collection, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) {
var set = obj && obj.length === +obj.length ? obj : _.values(obj);
var length = set.length;
var shuffled = Array(length);
for (var index = 0, rand; index < length; index++) {
rand = _.random(0, index);
if (rand !== index) shuffled[index] = shuffled[rand];
shuffled[rand] = set[index];
}
return shuffled;
};
// Sample **n** random values from a collection.
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) {
if (n == null || guard) {
if (obj.length !== +obj.length) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
return _.shuffle(obj).slice(0, Math.max(0, n));
};
// Sort the object's values by a criterion produced by an iteratee.
_.sortBy = function(obj, iteratee, context) {
iteratee = _.iteratee(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iteratee(value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
// An internal function used for aggregate "group by" operations.
var group = function(behavior) {
return function(obj, iteratee, context) {
var result = {};
iteratee = _.iteratee(iteratee, context);
_.each(obj, function(value, index) {
var key = iteratee(value, index, obj);
behavior(result, value, key);
});
return result;
};
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = group(function(result, value, key) {
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
// Indexes the object's values by a criterion, similar to `groupBy`, but for
// when you know that your index values will be unique.
_.indexBy = group(function(result, value, key) {
result[key] = value;
});
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
_.countBy = group(function(result, value, key) {
if (_.has(result, key)) result[key]++; else result[key] = 1;
});
// Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iteratee, context) {
iteratee = _.iteratee(iteratee, context, 1);
var value = iteratee(obj);
var low = 0, high = array.length;
while (low < high) {
var mid = low + high >>> 1;
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
};
// Safely create a real, live array from anything iterable.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (obj.length === +obj.length) return _.map(obj, _.identity);
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
if (obj == null) return 0;
return obj.length === +obj.length ? obj.length : _.keys(obj).length;
};
// Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(obj, predicate, context) {
predicate = _.iteratee(predicate, context);
var pass = [], fail = [];
_.each(obj, function(value, key, obj) {
(predicate(value, key, obj) ? pass : fail).push(value);
});
return [pass, fail];
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0;
if (n == null || guard) return array[0];
if (n < 0) return [];
return slice.call(array, 0, n);
};
// Returns everything but the last entry of the array. Especially useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if (array == null) return void 0;
if (n == null || guard) return array[array.length - 1];
return slice.call(array, Math.max(array.length - n, 0));
};
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **n** will return
// the rest N values in the array. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, n == null || guard ? 1 : n);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, _.identity);
};
// Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, strict, output) {
if (shallow && _.every(input, _.isArray)) {
return concat.apply(output, input);
}
for (var i = 0, length = input.length; i < length; i++) {
var value = input[i];
if (!_.isArray(value) && !_.isArguments(value)) {
if (!strict) output.push(value);
} else if (shallow) {
push.apply(output, value);
} else {
flatten(value, shallow, strict, output);
}
}
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
return flatten(array, shallow, false, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
if (array == null) return [];
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}
if (iteratee != null) iteratee = _.iteratee(iteratee, context);
var result = [];
var seen = [];
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (isSorted) {
if (!i || seen !== value) result.push(value);
seen = value;
} else if (iteratee) {
var computed = iteratee(value, i, array);
if (_.indexOf(seen, computed) < 0) {
seen.push(computed);
result.push(value);
}
} else if (_.indexOf(result, value) < 0) {
result.push(value);
}
}
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(flatten(arguments, true, true, []));
};
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
if (array == null) return [];
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = array.length; i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = flatten(slice.call(arguments, 1), true, true, []);
return _.filter(array, function(value){
return !_.contains(rest, value);
});
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function(array) {
if (array == null) return [];
var length = _.max(arguments, 'length').length;
var results = Array(length);
for (var i = 0; i < length; i++) {
results[i] = _.pluck(arguments, i);
}
return results;
};
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
if (list == null) return {};
var result = {};
for (var i = 0, length = list.length; i < length; i++) {
if (values) {
result[list[i]] = values[i];
} else {
result[list[i][0]] = list[i][1];
}
}
return result;
};
// Return the position of the first occurrence of an item in an array,
// or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i = 0, length = array.length;
if (isSorted) {
if (typeof isSorted == 'number') {
i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
} else {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
for (; i < length; i++) if (array[i] === item) return i;
return -1;
};
_.lastIndexOf = function(array, item, from) {
if (array == null) return -1;
var idx = array.length;
if (typeof from == 'number') {
idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
}
while (--idx >= 0) if (array[idx] === item) return idx;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = step || 1;
var length = Math.max(Math.ceil((stop - start) / step), 0);
var range = Array(length);
for (var idx = 0; idx < length; idx++, start += step) {
range[idx] = start;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var Ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
args = slice.call(arguments, 2);
bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
Ctor.prototype = func.prototype;
var self = new Ctor;
Ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (_.isObject(result)) return result;
return self;
};
return bound;
};
// Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder, allowing any combination of arguments to be pre-filled.
_.partial = function(func) {
var boundArgs = slice.call(arguments, 1);
return function() {
var position = 0;
var args = boundArgs.slice();
for (var i = 0, length = args.length; i < length; i++) {
if (args[i] === _) args[i]