UNPKG

gplus-web-auth

Version:

Google Plus Login module for Node.js

1,965 lines (1,729 loc) 74.3 kB
(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]