From 83688e8476ceb4b625779e46d48b7f9d12f9a2e4 Mon Sep 17 00:00:00 2001 From: scott-wyatt Date: Wed, 8 May 2019 16:56:43 -0500 Subject: [PATCH] [feat] Sparks --- README.md | 12 +- archetype/config/realtime.ts | 5 +- lib/RealtimeSpool.ts | 52 +- lib/Spark.ts | 14 + lib/api/controllers/PrimisController.ts | 15 + lib/api/controllers/index.ts | 1 + lib/api/index.ts | 6 + lib/api/sparks/index.ts | 1 + lib/config/index.ts | 1 + lib/config/realtime.ts | 6 +- lib/config/routes.ts | 26 + lib/config/spool.ts | 16 +- lib/index.ts | 1 + lib/schemas/realtimeConfig.ts | 2 + package-lock.json | 653 +- package.json | 10 +- test/fixtures/api/index.js | 1 + test/fixtures/api/sparks/TestSpark.js | 10 + test/fixtures/api/sparks/index.js | 1 + test/fixtures/app.js | 27 +- test/fixtures/primus/primus.js | 7338 +++++++++++++++++++++++ test/integration/primus.test.js | 35 + test/unit/primus.test.js | 156 +- 23 files changed, 7851 insertions(+), 538 deletions(-) create mode 100644 lib/Spark.ts create mode 100644 lib/api/controllers/PrimisController.ts create mode 100644 lib/api/controllers/index.ts create mode 100644 lib/api/index.ts create mode 100644 lib/api/sparks/index.ts create mode 100644 lib/config/routes.ts create mode 100644 test/fixtures/api/index.js create mode 100644 test/fixtures/api/sparks/TestSpark.js create mode 100644 test/fixtures/api/sparks/index.js create mode 100644 test/fixtures/primus/primus.js create mode 100644 test/integration/primus.test.js diff --git a/README.md b/README.md index 413f0c5..e216058 100755 --- a/README.md +++ b/README.md @@ -36,19 +36,27 @@ Create the config file: `config/realtime.ts ` ```js export const realtime = { + prefix: null, // The prefix to use for the primus endpoint primus:{ options:{ //these options are passed directly to the Primus constructor: https://github.com/primus/primus#getting-started } - } + }, + plugins: { + // Plugins are a key and a library passed to primus.use + redis: require('primus-redis-rooms') + } } ``` ## Client +You can set the config path for the generated primus.js file by either setting + +`config.main.paths.www` (common defaults for webserver spools) or through `config.realtime.path` which should likely be to a static directory. You can include the primus client library as a script: ``` - + ``` ## License diff --git a/archetype/config/realtime.ts b/archetype/config/realtime.ts index 67bd3aa..ac18faf 100644 --- a/archetype/config/realtime.ts +++ b/archetype/config/realtime.ts @@ -1,5 +1,8 @@ export const realtime = { + path: null, + prefix: null, primus: { options: {} - } + }, + plugins: {} } diff --git a/lib/RealtimeSpool.ts b/lib/RealtimeSpool.ts index f6d4966..7d677ea 100755 --- a/lib/RealtimeSpool.ts +++ b/lib/RealtimeSpool.ts @@ -1,4 +1,5 @@ import { Spool } from '@fabrix/fabrix/dist/common' +import { Utils as RouterUtils } from '@fabrix/spool-router' import * as Primus from 'primus' const primusDefaults = { @@ -7,6 +8,7 @@ const primusDefaults = { import * as Validator from './validator' +import * as api from './api/index' import * as config from './config/index' import * as pkg from '../package.json' @@ -17,7 +19,7 @@ export class RealtimeSpool extends Spool { super(app, { config: config, pkg: pkg, - api: {} + api: api }) this.extensions = { @@ -61,19 +63,38 @@ export class RealtimeSpool extends Spool { } async initialize() { - const primusConfig = this.app.config.get('realtime.primus') + + const isExpress = this.app.config.get('web.server') === 'express' + const listener = isExpress ? 'webserver:http' : 'webserver:http:ready' + const pathname = this.app.config.get('realtime.prefix') ? `${this.app.config.get('realtime.prefix')}/primus` : 'primus' + + // The path for primus/primus.js + const path = this.app.config.get('realtime.path') || this.app.config.get('main.paths.www') || __dirname + + const primusConfig = Object.assign( + {}, + { pathname: pathname }, + this.app.config.get('realtime.primus') + ) + const plugins = this.app.config.get('realtime.plugins') || {} return new Promise((resolve, reject) => { - this.app.once('webserver:http:ready', (httpServer) => { + this.app.once(listener, (httpServer) => { + if (Array.isArray(httpServer)) { httpServer = httpServer[0] } try { - this._sockets = new Primus(httpServer, Object.assign(primusDefaults, primusConfig.options)) + this._sockets = Primus(httpServer, Object.assign( + {}, + primusConfig.options, + primusDefaults + )) } catch (err) { + this.app.log.error('Primus failed to create', err) reject(err) } @@ -86,7 +107,22 @@ export class RealtimeSpool extends Spool { reject(err) } - return resolve() + // Attach spark connection events + Object.keys(this.app.sparks || {}).forEach(k => { + this.sockets.on('connection', this.app.sparks[k].connection) + }) + + // Attach spark disconnection events + Object.keys(this.app.sparks || {}).forEach(k => { + this.sockets.on('disconnection', this.app.sparks[k].disconnection) + }) + + this._sockets.save(path + '/primus.js', function save(err) { + if (err) { + return reject(err) + } + return resolve() + }) }) }) } @@ -110,6 +146,12 @@ export class RealtimeSpool extends Spool { } return }) + .then( () => { + if (!this._sockets.Spark) { + throw new Error('Socket Spark does not exist') + } + return + }) .catch(err => { return Promise.reject(err) }) diff --git a/lib/Spark.ts b/lib/Spark.ts new file mode 100644 index 0000000..65104dc --- /dev/null +++ b/lib/Spark.ts @@ -0,0 +1,14 @@ +import {FabrixGeneric as Generic, FabrixModel} from '@fabrix/fabrix/dist/common' + +/** + * @module Spark + * @description Spark + */ +export class Spark extends Generic { + connection(spark) { + throw new Error('Connection should be overwritten by sub class!') + } + disconnection(spark) { + throw new Error('Disconnection should be overwritten by sub class!') + } +} diff --git a/lib/api/controllers/PrimisController.ts b/lib/api/controllers/PrimisController.ts new file mode 100644 index 0000000..5382a79 --- /dev/null +++ b/lib/api/controllers/PrimisController.ts @@ -0,0 +1,15 @@ +import { FabrixController as Controller } from '@fabrix/fabrix/dist/common' + +/** + * @module CartController + * @description Cart Controller. + */ +// TODO lock down certain requests by Owner(s) +export class PrimusController extends Controller { + spec(req, res) { + return res.send(this.app.sockets.spec) + } + library(req, res) { + return res.send(this.app.sockets.library()) + } +} diff --git a/lib/api/controllers/index.ts b/lib/api/controllers/index.ts new file mode 100644 index 0000000..1439cbf --- /dev/null +++ b/lib/api/controllers/index.ts @@ -0,0 +1 @@ +export { PrimusController } from './PrimisController' diff --git a/lib/api/index.ts b/lib/api/index.ts new file mode 100644 index 0000000..fbf1344 --- /dev/null +++ b/lib/api/index.ts @@ -0,0 +1,6 @@ +import * as controllers from './controllers' +import * as sparks from './sparks' +export { + // controllers + sparks +} diff --git a/lib/api/sparks/index.ts b/lib/api/sparks/index.ts new file mode 100644 index 0000000..336ce12 --- /dev/null +++ b/lib/api/sparks/index.ts @@ -0,0 +1 @@ +export {} diff --git a/lib/config/index.ts b/lib/config/index.ts index 5526e12..9c7867f 100755 --- a/lib/config/index.ts +++ b/lib/config/index.ts @@ -1,2 +1,3 @@ export { spool } from './spool' export { realtime } from './realtime' +export { routes } from './routes' diff --git a/lib/config/realtime.ts b/lib/config/realtime.ts index 67bd3aa..847e10c 100755 --- a/lib/config/realtime.ts +++ b/lib/config/realtime.ts @@ -1,5 +1,9 @@ export const realtime = { + path: null, + prefix: null, primus: { + transformer: 'engine.io', options: {} - } + }, + plugins: {} } diff --git a/lib/config/routes.ts b/lib/config/routes.ts new file mode 100644 index 0000000..7258b0e --- /dev/null +++ b/lib/config/routes.ts @@ -0,0 +1,26 @@ +export const routes = { + // '/primus/spec': { + // 'GET': { + // handler: 'PrimusController.spec', + // config: { + // prefix: 'realtime.prefix' + // } + // } + // }, + // '/primus/library': { + // 'GET': { + // handler: 'PrimusController.library', + // config: { + // prefix: 'realtime.prefix' + // } + // } + // }, + // '/primus/primus.js': { + // 'GET': { + // file: 'primus.js', + // config: { + // prefix: 'realtime.prefix' + // } + // } + // }, +} diff --git a/lib/config/spool.ts b/lib/config/spool.ts index 6f52828..994de3c 100755 --- a/lib/config/spool.ts +++ b/lib/config/spool.ts @@ -1,12 +1,12 @@ /** - * Trailpack Configuration + * Spool Configuration * - * @see {@link http://fabrixjs.io/doc/trailpack/config + * @see {@link https://fabrix.app/docs/spools/config */ export const spool = { /** - * API and config resources provided by this Trailpack. + * API and config resources provided by this Spool. */ provides: { resources: [], @@ -18,16 +18,16 @@ export const spool = { }, /** - * Configure the lifecycle of this pack; that is, how it boots up, and which - * order it loads relative to other trailpacks. + * Configure the lifecycle of this spool; that is, how it boots up, and which + * order it loads relative to other spools. */ lifecycle: { configure: { /** * List of events that must be fired before the configure lifecycle - * method is invoked on this Trailpack + * method is invoked on this Spool */ - listen: [ ], + listen: [ 'spool:router:configured'], /** * List of events emitted by the configure lifecycle method @@ -35,7 +35,7 @@ export const spool = { emit: [ ] }, initialize: { - listen: [ ], + listen: [ 'spool:router:initialized'], emit: [ ] } } diff --git a/lib/index.ts b/lib/index.ts index 487801e..886add9 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1 +1,2 @@ export { RealtimeSpool } from './RealtimeSpool' +export { Spark } from './Spark' diff --git a/lib/schemas/realtimeConfig.ts b/lib/schemas/realtimeConfig.ts index bdb1989..1ce897f 100644 --- a/lib/schemas/realtimeConfig.ts +++ b/lib/schemas/realtimeConfig.ts @@ -2,6 +2,8 @@ import * as joi from 'joi' export const realtimeConfig = joi.object().keys({ + path: joi.string().allow(null, ''), + prefix: joi.string().allow(null, ''), primus: joi.object(), plugins: joi.object() }) diff --git a/package-lock.json b/package-lock.json index 148b4e1..43f79e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@fabrix/spool-realtime", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -183,55 +183,23 @@ "dev": true }, "@fabrix/spool-express": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@fabrix/spool-express/-/spool-express-1.6.0.tgz", - "integrity": "sha512-WdqOLWgcKOsolB2Cf3QaeJH7Dq0WxvKbDqdZi5zhSmPRO0JirDivMhNPVVJnnTu0oyx/QhatktSOEmiDwv+ttQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@fabrix/spool-express/-/spool-express-1.6.3.tgz", + "integrity": "sha512-EV6m/dA4MzP4bkL30+e4HwtBbZP2S4fO/a5h52ryYKPDStwQfo5PHpLKD+ECV3oIHzaXiDw/TaUvYL4rk61M0Q==", + "dev": true, "requires": { "body-parser": "1.19.0", "compression": "1.7.4", "consolidate": "0.15.1", "cookie-parser": "1.4.4", "cors": "2.8.5", - "express-boom": "2.0.0", + "express-boom": "3.0.0", "express-session": "1.16.1", "helmet": "3.18.0", "joi": "14.3.1", "lodash": "4.17.11", "method-override": "3.0.0", "methods": "1.1.2" - }, - "dependencies": { - "hoek": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", - "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" - }, - "joi": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", - "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", - "requires": { - "hoek": "6.1.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } - } - }, - "@fabrix/spool-hapi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@fabrix/spool-hapi/-/spool-hapi-1.0.0.tgz", - "integrity": "sha512-69gDJt2Wb60w4NDoN7dPQFKrXd47dPQ79rf6tLYcsbA9yM7NwF061xUqENsBXN4ca1PIMnB4pREJThR8tR9eSg==", - "dev": true, - "requires": { - "hapi": "17.5.2", - "lodash": "4.17.10", - "vision": "5.3.3" } }, "@fabrix/spool-router": { @@ -288,16 +256,6 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, - "accept": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/accept/-/accept-3.0.2.tgz", - "integrity": "sha512-bghLXFkCOsC1Y2TZ51etWfKDs6q249SAoHTZVfzWWdlZxoij+mgkj9AmUJWQpDY48TfnrTDIe43Xem4zdMe7mQ==", - "dev": true, - "requires": { - "boom": "7.2.0", - "hoek": "5.0.3" - } - }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -322,15 +280,6 @@ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, - "ammo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ammo/-/ammo-3.0.1.tgz", - "integrity": "sha512-4UqoM8xQjwkQ78oiU4NbBK0UgYqeKMAKmwE4ec7Rz3rGU8ZEBFxzgF2sUYKOAlqIXExBDYLN6y1ShF5yQ4hwLQ==", - "dev": true, - "requires": { - "hoek": "5.0.3" - } - }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -436,10 +385,10 @@ "resolved": "https://registry.npmjs.org/asyncemit/-/asyncemit-3.0.1.tgz", "integrity": "sha1-zD4P4No5tTzBXls6qGFupqcr1Zk=" }, - "b64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/b64/-/b64-4.0.0.tgz", - "integrity": "sha512-EhmUQodKB0sdzPPrbIWbGqA5cQeTWxYrAgNeeT1rLZWtD3tbNTnphz8J4vkXI3cPgBNlXBjzEbzDzq0Nwi4f9A==", + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "babel-code-frame": { @@ -504,12 +453,6 @@ "callsite": "1.0.0" } }, - "big-time": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/big-time/-/big-time-2.0.1.tgz", - "integrity": "sha1-aMffjcMPl+lT8lpnp2rJcTwWyd4=", - "dev": true - }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -518,12 +461,14 @@ "bluebird": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "dev": true }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, "requires": { "bytes": "3.1.0", "content-type": "1.0.4", @@ -541,6 +486,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -549,6 +495,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": "2.1.2" } @@ -564,16 +511,6 @@ "hoek": "5.0.3" } }, - "bounce": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bounce/-/bounce-1.2.0.tgz", - "integrity": "sha512-8syCGe8B2/WC53118/F/tFy5aW00j+eaGPXmAUP7iBhxc+EBZZxS1vKelWyBCH6IqojgS2t1gF0glH30qAJKEw==", - "dev": true, - "requires": { - "boom": "7.2.0", - "hoek": "5.0.3" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -599,7 +536,8 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true }, "caching-transform": { "version": "3.0.2", @@ -637,43 +575,8 @@ "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, - "catbox": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/catbox/-/catbox-10.0.2.tgz", - "integrity": "sha512-cTQTQeKMhWHU0lX8CADE3g1koGJu+AlcWFzAjMX/8P+XbkScGYw3tJsQpe2Oh8q68vOQbOLacz9k+6V/F3Z9DA==", - "dev": true, - "requires": { - "boom": "7.2.0", - "bounce": "1.2.0", - "hoek": "5.0.3", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, - "catbox-memory": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/catbox-memory/-/catbox-memory-3.1.2.tgz", - "integrity": "sha512-lhWtutLVhsq3Mucxk2McxBPPibJ34WcHuWFz3xqub9u9Ve/IQYpZv3ijLhQXfQped9DXozURiaq9O3aZpP91eg==", - "dev": true, - "requires": { - "big-time": "2.0.1", - "boom": "7.2.0", - "hoek": "5.0.3" - } + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", + "dev": true }, "chalk": { "version": "2.4.1", @@ -787,6 +690,15 @@ "text-hex": "1.0.0" } }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", @@ -813,6 +725,7 @@ "version": "2.0.17", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, "requires": { "mime-db": "1.40.0" }, @@ -820,7 +733,8 @@ "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true } } }, @@ -828,6 +742,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, "requires": { "accepts": "1.3.5", "bytes": "3.0.0", @@ -841,12 +756,14 @@ "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -874,17 +791,9 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "requires": { - "bluebird": "3.5.4" - } - }, - "content": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/content/-/content-4.0.5.tgz", - "integrity": "sha512-wDP6CTWDpwCf791fNxlCCkZGRkrNzSEU/8ju9Hnr3Uc5mF/gFR5W+fcoGm6zUSlVPdSXYn5pCbySADKj7YM4Cg==", "dev": true, "requires": { - "boom": "7.2.0" + "bluebird": "3.5.4" } }, "content-disposition": { @@ -896,12 +805,14 @@ "content-security-policy-builder": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz", - "integrity": "sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w==" + "integrity": "sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w==", + "dev": true }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, "convert-source-map": { "version": "1.6.0", @@ -921,6 +832,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", + "dev": true, "requires": { "cookie": "0.3.1", "cookie-signature": "1.0.6" @@ -929,7 +841,14 @@ "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -941,6 +860,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, "requires": { "object-assign": "4.1.1", "vary": "1.1.2" @@ -980,19 +900,11 @@ "which": "1.3.1" } }, - "cryptiles": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-4.1.2.tgz", - "integrity": "sha512-U2ALcoAHvA1oO2xOreyHvtkQ+IELqDG2WVWRI1GH/XEmmfGIOalnM5MU5Dd2ITyWfr3m6kNqXiy8XuYyd4wKJw==", - "dev": true, - "requires": { - "boom": "7.2.0" - } - }, "dasherize": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", - "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=", + "dev": true }, "debug": { "version": "3.1.0", @@ -1032,6 +944,12 @@ "object-keys": "1.1.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1041,7 +959,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "destroy": { "version": "1.0.4", @@ -1074,22 +993,26 @@ "dns-prefetch-control": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", - "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" + "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=", + "dev": true }, "dont-sniff-mimetype": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", - "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" + "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=", + "dev": true }, "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=", + "dev": true }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true }, "emits": { "version": "3.0.0", @@ -1274,7 +1197,8 @@ "expect-ct": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", - "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" + "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==", + "dev": true }, "express": { "version": "4.16.4", @@ -1392,25 +1316,28 @@ } }, "express-boom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/express-boom/-/express-boom-2.0.0.tgz", - "integrity": "sha1-1AC5QOlhqKou2OP3fFlfoeQbZLM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/express-boom/-/express-boom-3.0.0.tgz", + "integrity": "sha512-/esN6Am8YE1rzRsi+vBpJkdr8O+GX+oBjRE/hEuBu6Y3uyS9y026XptRZduAMYS8KxyLzXM5Qh+RlnqLOR1pVQ==", + "dev": true, "requires": { - "boom": "3.2.2" + "boom": "7.3.0" }, "dependencies": { "boom": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/boom/-/boom-3.2.2.tgz", - "integrity": "sha1-DwzF0ErcUAO4x9cfQsynJx/vDng=", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz", + "integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==", + "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "6.1.3" } }, "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", + "dev": true } } }, @@ -1418,6 +1345,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.1.tgz", "integrity": "sha512-pWvUL8Tl5jUy1MLH7DhgUlpoKeVPUTe+y6WQD9YhcN0C5qAhsh4a8feVjiUXo3TFhIy191YGZ4tewW9edbl2xQ==", + "dev": true, "requires": { "cookie": "0.3.1", "cookie-signature": "1.0.6", @@ -1433,6 +1361,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -1440,10 +1369,17 @@ "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "extendible": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz", @@ -1452,7 +1388,8 @@ "feature-policy": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", - "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==", + "dev": true }, "finalhandler": { "version": "1.1.1", @@ -1537,6 +1474,23 @@ } } }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.7", + "mime-types": "2.1.18" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -1551,7 +1505,8 @@ "frameguard": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", - "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" + "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==", + "dev": true }, "fresh": { "version": "0.5.2", @@ -1672,44 +1627,6 @@ } } }, - "hapi": { - "version": "17.5.2", - "resolved": "https://registry.npmjs.org/hapi/-/hapi-17.5.2.tgz", - "integrity": "sha512-UxMKYzrjfXlcztJQPEB3os5rM3SKgSQVxoOym4KI3JdP4pxl5WUdZYF8it4Kga2OMTGwB+ZTy+DU9b/oDaQHRQ==", - "dev": true, - "requires": { - "accept": "3.0.2", - "ammo": "3.0.1", - "boom": "7.2.0", - "bounce": "1.2.0", - "call": "5.0.1", - "catbox": "10.0.2", - "catbox-memory": "3.1.2", - "heavy": "6.1.0", - "hoek": "5.0.3", - "joi": "13.7.0", - "mimos": "4.0.0", - "podium": "3.1.2", - "shot": "4.0.5", - "statehood": "6.0.6", - "subtext": "6.0.7", - "teamwork": "3.0.1", - "topo": "3.0.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1774,34 +1691,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "heavy": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/heavy/-/heavy-6.1.0.tgz", - "integrity": "sha512-TKS9DC9NOTGulHQI31Lx+bmeWmNOstbJbGMiN3pX6bF+Zc2GKSpbbym4oasNnB6yPGkqJ9TQXXYDGohqNSJRxA==", - "dev": true, - "requires": { - "boom": "7.2.0", - "hoek": "5.0.3", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, "helmet": { "version": "3.18.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.18.0.tgz", "integrity": "sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==", + "dev": true, "requires": { "depd": "2.0.0", "dns-prefetch-control": "0.1.0", @@ -1823,19 +1717,22 @@ "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true } } }, "helmet-crossdomain": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.3.0.tgz", - "integrity": "sha512-YiXhj0E35nC4Na5EPE4mTfoXMf9JTGpN4OtB4aLqShKuH9d2HNaJX5MQoglO6STVka0uMsHyG5lCut5Kzsy7Lg==" + "integrity": "sha512-YiXhj0E35nC4Na5EPE4mTfoXMf9JTGpN4OtB4aLqShKuH9d2HNaJX5MQoglO6STVka0uMsHyG5lCut5Kzsy7Lg==", + "dev": true }, "helmet-csp": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.7.1.tgz", "integrity": "sha512-sCHwywg4daQ2mY0YYwXSZRsgcCeerUwxMwNixGA7aMLkVmPTYBl7gJoZDHOZyXkqPrtuDT3s2B1A+RLI7WxSdQ==", + "dev": true, "requires": { "camelize": "1.0.0", "content-security-policy-builder": "2.0.0", @@ -1846,7 +1743,8 @@ "hide-powered-by": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", - "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" + "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=", + "dev": true }, "hoek": { "version": "5.0.3", @@ -1862,12 +1760,14 @@ "hpkp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", - "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=", + "dev": true }, "hsts": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "dev": true, "requires": { "depd": "2.0.0" }, @@ -1875,7 +1775,8 @@ "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true } } }, @@ -1883,6 +1784,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "1.1.2", "inherits": "2.0.3", @@ -1903,7 +1805,8 @@ "ienoopen": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", - "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" + "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==", + "dev": true }, "ignore-walk": { "version": "3.0.1", @@ -1938,7 +1841,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ini": { "version": "1.3.5", @@ -1958,17 +1862,6 @@ "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", "dev": true }, - "iron": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/iron/-/iron-5.0.4.tgz", - "integrity": "sha512-7iQ5/xFMIYaNt9g2oiNiWdhrOTdRUMFaWENUd0KghxwPUhrIH8DUY8FEyLNTTzf75jaII+jMexLdY/2HfV61RQ==", - "dev": true, - "requires": { - "boom": "7.2.0", - "cryptiles": "4.1.2", - "hoek": "5.0.3" - } - }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -2172,12 +2065,6 @@ "handlebars": "4.1.2" } }, - "items": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", - "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=", - "dev": true - }, "joi": { "version": "14.3.1", "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", @@ -2271,9 +2158,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.flattendeep": { @@ -2339,7 +2226,8 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, "mem": { "version": "4.3.0", @@ -2379,6 +2267,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "dev": true, "requires": { "debug": "3.1.0", "methods": "1.1.2", @@ -2389,7 +2278,8 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true }, "millisecond": { "version": "0.1.2", @@ -2421,16 +2311,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimos": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimos/-/mimos-4.0.0.tgz", - "integrity": "sha512-JvlvRLqGIlk+AYypWrbrDmhsM+6JVx/xBM5S3AMwTBz1trPCEoPN/swO2L4Wu653fL7oJdgk8DMQyG/Gq3JkZg==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "mime-db": "1.33.0" - } - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2617,20 +2497,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nigel": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/nigel/-/nigel-3.0.1.tgz", - "integrity": "sha512-kCVtUG9JyD//tsYrZY+/Y+2gUrANVSba8y23QkM5Znx0FOxlnl9Z4OVPBODmstKWTOvigfTO+Va1VPOu3eWSOQ==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "vise": "3.0.0" - } - }, "nocache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", - "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==", + "dev": true }, "node-environment-flags": { "version": "1.0.5", @@ -2671,7 +2542,8 @@ "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true }, "nopt": { "version": "4.0.1", @@ -2832,7 +2704,8 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-keys": { "version": "1.1.1", @@ -2866,6 +2739,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, "requires": { "ee-first": "1.1.1" } @@ -2873,7 +2747,8 @@ "on-headers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true }, "once": { "version": "1.4.0", @@ -3010,7 +2885,8 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, "path-exists": { "version": "3.0.0", @@ -3059,19 +2935,6 @@ } } }, - "pez": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pez/-/pez-4.0.2.tgz", - "integrity": "sha512-HuPxmGxHsEFPWhdkwBs2gIrHhFqktIxMtudISTFN95RQ85ZZAOl8Ki6u3nnN/X8OUaGlIGldk/l8p2IR4/i76w==", - "dev": true, - "requires": { - "b64": "4.0.0", - "boom": "7.2.0", - "content": "4.0.5", - "hoek": "5.0.3", - "nigel": "3.0.1" - } - }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -3090,30 +2953,8 @@ "platform": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" - }, - "podium": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/podium/-/podium-3.1.2.tgz", - "integrity": "sha512-18VrjJAduIdPv7d9zWsfmKxTj3cQTYC5Pv5gtKxcWujYBpGbV+mhNSPYhlHW5xeWoazYyKfB9FEsPT12r5rY1A==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true }, "predefine": { "version": "0.1.2", @@ -3151,6 +2992,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/primus-redis-rooms/-/primus-redis-rooms-0.3.2.tgz", "integrity": "sha512-IoTyW10q5b+G16E1TgK/fLfjDz+m7TDm0n9H/hLRX3EhH1qnZiKC+1s/cN4P0YGi+ARs0qZt+EGusZ6cooqKsA==", + "dev": true, "requires": { "node-uuid": "1.4.8", "redis": "2.8.0", @@ -3197,12 +3039,14 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "dev": true }, "range-parser": { "version": "1.2.0", @@ -3214,6 +3058,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -3225,6 +3070,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": "2.1.2" } @@ -3299,6 +3145,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "dev": true, "requires": { "double-ended-queue": "2.1.0-0", "redis-commands": "1.4.0", @@ -3308,17 +3155,20 @@ "redis-commands": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz", - "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==" + "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==", + "dev": true }, "redis-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", - "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", + "dev": true }, "redis-sentinel": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/redis-sentinel/-/redis-sentinel-0.3.3.tgz", "integrity": "sha1-K0ZGSqdpEgjH8s/foj0UiRCvlJI=", + "dev": true, "requires": { "redis": "0.12.1", "when": "3.7.8" @@ -3327,14 +3177,16 @@ "redis": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", - "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=" + "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=", + "dev": true } } }, "referrer-policy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", - "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==", + "dev": true }, "release-zalgo": { "version": "1.0.0", @@ -3384,12 +3236,14 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sax": { "version": "1.2.4", @@ -3488,7 +3342,8 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true }, "shebang-command": { "version": "1.2.0", @@ -3514,29 +3369,6 @@ "nanoid": "2.0.1" } }, - "shot": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/shot/-/shot-4.0.5.tgz", - "integrity": "sha1-x+dFXRHWD2ts08Q+FaO0McF+VWY=", - "dev": true, - "requires": { - "hoek": "5.0.3", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -3625,37 +3457,11 @@ "node-pre-gyp": "0.10.3" } }, - "statehood": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/statehood/-/statehood-6.0.6.tgz", - "integrity": "sha512-jR45n5ZMAkasw0xoE9j9TuLmJv4Sa3AkXe+6yIFT6a07kXYHgSbuD2OVGECdZGFxTmvNqLwL1iRIgvq6O6rq+A==", - "dev": true, - "requires": { - "boom": "7.2.0", - "bounce": "1.2.0", - "cryptiles": "4.1.2", - "hoek": "5.0.3", - "iron": "5.0.4", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "string-width": { "version": "1.0.2", @@ -3704,17 +3510,32 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "subtext": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/subtext/-/subtext-6.0.7.tgz", - "integrity": "sha512-IcJUvRjeR+NB437Iq+LORFNJW4L6Knqkj3oQrBrkdhIaS2VKJvx/9aYEq7vi+PEx5/OuehOL/40SkSZotLi/MA==", + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", "dev": true, "requires": { - "boom": "7.2.0", - "content": "4.0.5", - "hoek": "5.0.3", - "pez": "4.0.2", - "wreck": "14.0.2" + "component-emitter": "1.2.1", + "cookiejar": "2.1.2", + "debug": "3.1.0", + "extend": "3.0.2", + "form-data": "2.3.3", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.4.1", + "qs": "6.7.0", + "readable-stream": "2.3.6" + } + }, + "supertest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, + "requires": { + "methods": "1.1.2", + "superagent": "3.8.3" } }, "supports-color": { @@ -3741,12 +3562,6 @@ "yallist": "3.0.2" } }, - "teamwork": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/teamwork/-/teamwork-3.0.1.tgz", - "integrity": "sha512-hEkJIpDOfOYe9NYaLFk00zQbzZeKNCY8T2pRH3I13Y1mJwxaSQ6NEsjY5rCp+11ezCiZpWGoGFTbOuhg4qKevQ==", - "dev": true - }, "test-exclude": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", @@ -3789,7 +3604,8 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true }, "topo": { "version": "3.0.0", @@ -3853,6 +3669,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "2.1.24" @@ -3861,12 +3678,14 @@ "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, "requires": { "mime-db": "1.40.0" } @@ -3910,6 +3729,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dev": true, "requires": { "random-bytes": "1.0.0" } @@ -3922,7 +3742,8 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -3951,44 +3772,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "vise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vise/-/vise-3.0.0.tgz", - "integrity": "sha512-kBFZLmiL1Vm3rHXphkhvvAcsjgeQXRrOFCbJb0I50YZZP4HGRNH+xGzK3matIMcpbsfr3I02u9odj4oCD0TWgA==", - "dev": true, - "requires": { - "hoek": "5.0.3" - } - }, - "vision": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/vision/-/vision-5.3.3.tgz", - "integrity": "sha512-ijMMQYHqscXc9jyr2hoN3xNRNVzLb/vwiQ5MV5kQeJX+5r9hNSyWPUDZzA8PP3yJc3OWArtJDt2CioE3vWiuPQ==", - "dev": true, - "requires": { - "boom": "7.2.0", - "hoek": "5.0.3", - "items": "2.1.1", - "joi": "13.7.0" - }, - "dependencies": { - "joi": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", - "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", - "dev": true, - "requires": { - "hoek": "5.0.3", - "isemail": "3.1.2", - "topo": "3.0.0" - } - } - } - }, "when": { "version": "3.7.8", "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", + "dev": true }, "which": { "version": "1.3.1", @@ -4036,16 +3824,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "wreck": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/wreck/-/wreck-14.0.2.tgz", - "integrity": "sha512-QCm3omWNJUseqrSzwX2QZi1rBbmCfbFHJAXputLLyZ37VSiFnSYQB0ms/mPnSvrlIu7GVm89Y/gBNhSY26uVIQ==", - "dev": true, - "requires": { - "boom": "7.2.0", - "hoek": "5.0.3" - } - }, "write-file-atomic": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", @@ -4068,7 +3846,8 @@ "x-xss-protection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.1.0.tgz", - "integrity": "sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg==" + "integrity": "sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg==", + "dev": true }, "xmlhttprequest-ssl": { "version": "1.5.5", diff --git a/package.json b/package.json index 794b895..04651bd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fabrix/spool-realtime", - "version": "1.6.0", + "version": "1.6.1", "description": "Spool: Realtime, Synchronize the client and server via WebSockets using Primus", "homepage": "https://fabrix.app", "author": { @@ -48,27 +48,27 @@ "test" ], "dependencies": { - "@fabrix/spool-express": "^1.6.0", "engine.io": "^3.3.2", "engine.io-client": "^3.3.2", "joi": "^14.3.1", - "primus": "^7.3.2", - "primus-redis-rooms": "^0.3.2" + "primus": "^7.3.2" }, "devDependencies": { "@fabrix/fabrix": "^1.6.0", "@fabrix/lint": "^1.0.0-alpha.3", - "@fabrix/spool-hapi": "^1.0.0", + "@fabrix/spool-express": "^1.6.3", "@fabrix/spool-router": "^1.6.2", "@types/lodash": "^4.14.109", "@types/node": "~10.3.4", "assert-called": "^0.1.2-1", "express": "^4.16.4", + "primus-redis-rooms": "^0.3.2", "mocha": "^6.1.4", "nyc": "^14.1.0", "shortid": "^2.2.14", "smokesignals": "^3", "sqlite3": "^4.0.0", + "supertest": "^4.0.2", "tslib": "~1.9.0", "tslint": "~5.10.0", "tslint-microsoft-contrib": "~5.0.3", diff --git a/test/fixtures/api/index.js b/test/fixtures/api/index.js new file mode 100644 index 0000000..ae5b89e --- /dev/null +++ b/test/fixtures/api/index.js @@ -0,0 +1 @@ +exports.sparks = require('./sparks') diff --git a/test/fixtures/api/sparks/TestSpark.js b/test/fixtures/api/sparks/TestSpark.js new file mode 100644 index 0000000..e9a7d88 --- /dev/null +++ b/test/fixtures/api/sparks/TestSpark.js @@ -0,0 +1,10 @@ +const Spark = require('../../../../dist').Spark + +module.exports = class TestSpark extends Spark { + connection(spark) { + console.log('TEST conneciton', spark) + } + disconnection(spark) { + console.log('TEST disconnection', spark) + } +} diff --git a/test/fixtures/api/sparks/index.js b/test/fixtures/api/sparks/index.js new file mode 100644 index 0000000..e313839 --- /dev/null +++ b/test/fixtures/api/sparks/index.js @@ -0,0 +1 @@ +exports.TestSpark = require('./TestSpark') diff --git a/test/fixtures/app.js b/test/fixtures/app.js index 23d0138..98c2dab 100755 --- a/test/fixtures/app.js +++ b/test/fixtures/app.js @@ -8,14 +8,17 @@ module.exports = _.defaultsDeep({ pkg: { name: require('../../package').name + '-test' }, - api: {}, + api: require('./api'), config: { main: { spools: [ require('@fabrix/spool-router').RouterSpool, require('@fabrix/spool-express').ExpressSpool, require('../../dist').RealtimeSpool - ] + ], + paths: { + www: 'test/fixtures' + } }, web: { express: require('express'), @@ -32,20 +35,22 @@ module.exports = _.defaultsDeep({ '404', '500' ], - static: require('express').static('test/static') - } + static: require('express').static('test/fixtures') + }, + port: 3001 }, realtime: { + path: 'test/fixtures/primus', primus: { - redis: { - host: 'localhost', - port: 6379, - channel: 'primus' // Optional, defaults to `'primus`' - }, - transformer: 'websockets' + // redis: { + // host: 'localhost', + // port: 6379, + // channel: 'primus' // Optional, defaults to `'primus`' + // }, + // transformer: 'websockets' }, plugins: { - redis: PrimusRedisRooms + // redis: PrimusRedisRooms } } } diff --git a/test/fixtures/primus/primus.js b/test/fixtures/primus/primus.js new file mode 100644 index 0000000..d3aad61 --- /dev/null +++ b/test/fixtures/primus/primus.js @@ -0,0 +1,7338 @@ +(function UMDish(name, context, definition, plugins) { + context[name] = definition.call(context); + for (var i = 0; i < plugins.length; i++) { + plugins[i](context[name]) + } + if (typeof module !== "undefined" && module.exports) { + module.exports = context[name]; + } else if (typeof define === "function" && define.amd) { + define(function reference() { return context[name]; }); + } +})("Primus", this || {}, function wrapper() { + var define, module, exports + , Primus = (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 10000 || !(match = regex.exec(ms))) return 0; + + amount = parseFloat(match[1]); + + switch (match[2].toLowerCase()) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return amount * year; + + case 'weeks': + case 'week': + case 'wks': + case 'wk': + case 'w': + return amount * week; + + case 'days': + case 'day': + case 'd': + return amount * day; + + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return amount * hour; + + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return amount * minute; + + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return amount * second; + + default: + return amount; + } +}; + +},{}],6:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Wrap callbacks to prevent double execution. + * + * @param {Function} fn Function that should only be called once. + * @returns {Function} A wrapped callback which prevents execution. + * @api public + */ +module.exports = function one(fn) { + var called = 0 + , value; + + /** + * The function that prevents double execution. + * + * @api private + */ + function onetime() { + if (called) return value; + + called = 1; + value = fn.apply(this, arguments); + fn = null; + + return value; + } + + // + // To make debugging more easy we want to use the name of the supplied + // function. So when you look at the functions that are assigned to event + // listeners you don't see a load of `onetime` functions but actually the + // names of the functions that this module will call. + // + onetime.displayName = fn.displayName || fn.name || onetime.displayName || onetime.name; + return onetime; +}; + +},{}],7:[function(_dereq_,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],8:[function(_dereq_,module,exports){ +'use strict'; + +var has = Object.prototype.hasOwnProperty + , undef; + +/** + * Decode a URI encoded string. + * + * @param {String} input The URI encoded string. + * @returns {String} The decoded string. + * @api private + */ +function decode(input) { + return decodeURIComponent(input.replace(/\+/g, ' ')); +} + +/** + * Simple query string parser. + * + * @param {String} query The query string that needs to be parsed. + * @returns {Object} + * @api public + */ +function querystring(query) { + var parser = /([^=?&]+)=?([^&]*)/g + , result = {} + , part; + + while (part = parser.exec(query)) { + var key = decode(part[1]) + , value = decode(part[2]); + + // + // Prevent overriding of existing properties. This ensures that build-in + // methods like `toString` or __proto__ are not overriden by malicious + // querystrings. + // + if (key in result) continue; + result[key] = value; + } + + return result; +} + +/** + * Transform a query string to an object. + * + * @param {Object} obj Object that should be transformed. + * @param {String} prefix Optional prefix. + * @returns {String} + * @api public + */ +function querystringify(obj, prefix) { + prefix = prefix || ''; + + var pairs = [] + , value + , key; + + // + // Optionally prefix with a '?' if needed + // + if ('string' !== typeof prefix) prefix = '?'; + + for (key in obj) { + if (has.call(obj, key)) { + value = obj[key]; + + // + // Edge cases where we actually want to encode the value to an empty + // string instead of the stringified value. + // + if (!value && (value === null || value === undef || isNaN(value))) { + value = ''; + } + + pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(value)); + } + } + + return pairs.length ? prefix + pairs.join('&') : ''; +} + +// +// Expose the module. +// +exports.stringify = querystringify; +exports.parse = querystring; + +},{}],9:[function(_dereq_,module,exports){ +'use strict'; + +var EventEmitter = _dereq_('eventemitter3') + , millisecond = _dereq_('millisecond') + , destroy = _dereq_('demolish') + , Tick = _dereq_('tick-tock') + , one = _dereq_('one-time'); + +/** + * Returns sane defaults about a given value. + * + * @param {String} name Name of property we want. + * @param {Recovery} selfie Recovery instance that got created. + * @param {Object} opts User supplied options we want to check. + * @returns {Number} Some default value. + * @api private + */ +function defaults(name, selfie, opts) { + return millisecond( + name in opts ? opts[name] : (name in selfie ? selfie[name] : Recovery[name]) + ); +} + +/** + * Attempt to recover your connection with reconnection attempt. + * + * @constructor + * @param {Object} options Configuration + * @api public + */ +function Recovery(options) { + var recovery = this; + + if (!(recovery instanceof Recovery)) return new Recovery(options); + + options = options || {}; + + recovery.attempt = null; // Stores the current reconnect attempt. + recovery._fn = null; // Stores the callback. + + recovery['reconnect timeout'] = defaults('reconnect timeout', recovery, options); + recovery.retries = defaults('retries', recovery, options); + recovery.factor = defaults('factor', recovery, options); + recovery.max = defaults('max', recovery, options); + recovery.min = defaults('min', recovery, options); + recovery.timers = new Tick(recovery); +} + +Recovery.prototype = new EventEmitter(); +Recovery.prototype.constructor = Recovery; + +Recovery['reconnect timeout'] = '30 seconds'; // Maximum time to wait for an answer. +Recovery.max = Infinity; // Maximum delay. +Recovery.min = '500 ms'; // Minimum delay. +Recovery.retries = 10; // Maximum amount of retries. +Recovery.factor = 2; // Exponential back off factor. + +/** + * Start a new reconnect procedure. + * + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reconnect = function reconnect() { + var recovery = this; + + return recovery.backoff(function backedoff(err, opts) { + opts.duration = (+new Date()) - opts.start; + + if (err) return recovery.emit('reconnect failed', err, opts); + + recovery.emit('reconnected', opts); + }, recovery.attempt); +}; + +/** + * Exponential back off algorithm for retry operations. It uses a randomized + * retry so we don't DDOS our server when it goes down under pressure. + * + * @param {Function} fn Callback to be called after the timeout. + * @param {Object} opts Options for configuring the timeout. + * @returns {Recovery} + * @api private + */ +Recovery.prototype.backoff = function backoff(fn, opts) { + var recovery = this; + + opts = opts || recovery.attempt || {}; + + // + // Bailout when we already have a back off process running. We shouldn't call + // the callback then. + // + if (opts.backoff) return recovery; + + opts['reconnect timeout'] = defaults('reconnect timeout', recovery, opts); + opts.retries = defaults('retries', recovery, opts); + opts.factor = defaults('factor', recovery, opts); + opts.max = defaults('max', recovery, opts); + opts.min = defaults('min', recovery, opts); + + opts.start = +opts.start || +new Date(); + opts.duration = +opts.duration || 0; + opts.attempt = +opts.attempt || 0; + + // + // Bailout if we are about to make too much attempts. + // + if (opts.attempt === opts.retries) { + fn.call(recovery, new Error('Unable to recover'), opts); + return recovery; + } + + // + // Prevent duplicate back off attempts using the same options object and + // increment our attempt as we're about to have another go at this thing. + // + opts.backoff = true; + opts.attempt++; + + recovery.attempt = opts; + + // + // Calculate the timeout, but make it randomly so we don't retry connections + // at the same interval and defeat the purpose. This exponential back off is + // based on the work of: + // + // http://dthain.blogspot.nl/2009/02/exponential-backoff-in-distributed.html + // + opts.scheduled = opts.attempt !== 1 + ? Math.min(Math.round( + (Math.random() + 1) * opts.min * Math.pow(opts.factor, opts.attempt - 1) + ), opts.max) + : opts.min; + + recovery.timers.setTimeout('reconnect', function delay() { + opts.duration = (+new Date()) - opts.start; + opts.backoff = false; + recovery.timers.clear('reconnect, timeout'); + + // + // Create a `one` function which can only be called once. So we can use the + // same function for different types of invocations to create a much better + // and usable API. + // + var connect = recovery._fn = one(function connect(err) { + recovery.reset(); + + if (err) return recovery.backoff(fn, opts); + + fn.call(recovery, undefined, opts); + }); + + recovery.emit('reconnect', opts, connect); + recovery.timers.setTimeout('timeout', function timeout() { + var err = new Error('Failed to reconnect in a timely manner'); + opts.duration = (+new Date()) - opts.start; + + recovery.emit('reconnect timeout', err, opts); + connect(err); + }, opts['reconnect timeout']); + }, opts.scheduled); + + // + // Emit a `reconnecting` event with current reconnect options. This allows + // them to update the UI and provide their users with feedback. + // + recovery.emit('reconnect scheduled', opts); + + return recovery; +}; + +/** + * Check if the reconnection process is currently reconnecting. + * + * @returns {Boolean} + * @api public + */ +Recovery.prototype.reconnecting = function reconnecting() { + return !!this.attempt; +}; + +/** + * Tell our reconnection procedure that we're passed. + * + * @param {Error} err Reconnection failed. + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reconnected = function reconnected(err) { + if (this._fn) this._fn(err); + return this; +}; + +/** + * Reset the reconnection attempt so it can be re-used again. + * + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reset = function reset() { + this._fn = this.attempt = null; + this.timers.clear('reconnect, timeout'); + + return this; +}; + +/** + * Clean up the instance. + * + * @type {Function} + * @returns {Boolean} + * @api public + */ +Recovery.prototype.destroy = destroy('timers attempt _fn'); + +// +// Expose the module. +// +module.exports = Recovery; + +},{"demolish":1,"eventemitter3":10,"millisecond":5,"one-time":6,"tick-tock":12}],10:[function(_dereq_,module,exports){ +'use strict'; + +// +// We store our EE objects in a plain object whose properties are event names. +// If `Object.create(null)` is not supported we prefix the event names with a +// `~` to make sure that the built-in object properties are not overridden or +// used as an attack vector. +// We also assume that `Object.create(null)` is available when the event name +// is an ES6 Symbol. +// +var prefix = typeof Object.create !== 'function' ? '~' : false; + +/** + * Representation of a single EventEmitter function. + * + * @param {Function} fn Event handler to be called. + * @param {Mixed} context Context for function execution. + * @param {Boolean} once Only emit once + * @api private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Minimal EventEmitter interface that is molded against the Node.js + * EventEmitter interface. + * + * @constructor + * @api public + */ +function EventEmitter() { /* Nothing to set */ } + +/** + * Holds the assigned EventEmitters by name. + * + * @type {Object} + * @private + */ +EventEmitter.prototype._events = undefined; + +/** + * Return a list of assigned event listeners. + * + * @param {String} event The events that should be listed. + * @param {Boolean} exists We only need to know if there are listeners. + * @returns {Array|Boolean} + * @api public + */ +EventEmitter.prototype.listeners = function listeners(event, exists) { + var evt = prefix ? prefix + event : event + , available = this._events && this._events[evt]; + + if (exists) return !!available; + if (!available) return []; + if (available.fn) return [available.fn]; + + for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { + ee[i] = available[i].fn; + } + + return ee; +}; + +/** + * Emit an event to all registered event listeners. + * + * @param {String} event The name of the event. + * @returns {Boolean} Indication if we've emitted an event. + * @api public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events || !this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if ('function' === typeof listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Register a new EventListener for the given event. + * + * @param {String} event Name of the event. + * @param {Functon} fn Callback function. + * @param {Mixed} context The context of the function. + * @api public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + var listener = new EE(fn, context || this) + , evt = prefix ? prefix + event : event; + + if (!this._events) this._events = prefix ? {} : Object.create(null); + if (!this._events[evt]) this._events[evt] = listener; + else { + if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [ + this._events[evt], listener + ]; + } + + return this; +}; + +/** + * Add an EventListener that's only called once. + * + * @param {String} event Name of the event. + * @param {Function} fn Callback function. + * @param {Mixed} context The context of the function. + * @api public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + var listener = new EE(fn, context || this, true) + , evt = prefix ? prefix + event : event; + + if (!this._events) this._events = prefix ? {} : Object.create(null); + if (!this._events[evt]) this._events[evt] = listener; + else { + if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [ + this._events[evt], listener + ]; + } + + return this; +}; + +/** + * Remove event listeners. + * + * @param {String} event The event we want to remove. + * @param {Function} fn The listener that we need to find. + * @param {Mixed} context Only remove listeners matching this context. + * @param {Boolean} once Only remove once listeners. + * @api public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events || !this._events[evt]) return this; + + var listeners = this._events[evt] + , events = []; + + if (fn) { + if (listeners.fn) { + if ( + listeners.fn !== fn + || (once && !listeners.once) + || (context && listeners.context !== context) + ) { + events.push(listeners); + } + } else { + for (var i = 0, length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn + || (once && !listeners[i].once) + || (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) { + this._events[evt] = events.length === 1 ? events[0] : events; + } else { + delete this._events[evt]; + } + + return this; +}; + +/** + * Remove all listeners or only the listeners for the specified event. + * + * @param {String} event The event want to remove all listeners for. + * @api public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + if (!this._events) return this; + + if (event) delete this._events[prefix ? prefix + event : event]; + else this._events = prefix ? {} : Object.create(null); + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// This function doesn't apply anymore. +// +EventEmitter.prototype.setMaxListeners = function setMaxListeners() { + return this; +}; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Expose the module. +// +if ('undefined' !== typeof module) { + module.exports = EventEmitter; +} + +},{}],11:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Check if we're required to add a port number. + * + * @see https://url.spec.whatwg.org/#default-port + * @param {Number|String} port Port number we need to check + * @param {String} protocol Protocol we need to check against. + * @returns {Boolean} Is it a default port for the given protocol + * @api private + */ +module.exports = function required(port, protocol) { + protocol = protocol.split(':')[0]; + port = +port; + + if (!port) return false; + + switch (protocol) { + case 'http': + case 'ws': + return port !== 80; + + case 'https': + case 'wss': + return port !== 443; + + case 'ftp': + return port !== 21; + + case 'gopher': + return port !== 70; + + case 'file': + return false; + } + + return port !== 0; +}; + +},{}],12:[function(_dereq_,module,exports){ +(function (setImmediate,clearImmediate){ +'use strict'; + +var has = Object.prototype.hasOwnProperty + , ms = _dereq_('millisecond'); + +/** + * Timer instance. + * + * @constructor + * @param {Object} timer New timer instance. + * @param {Function} clear Clears the timer instance. + * @param {Function} duration Duration of the timer. + * @param {Function} fn The functions that need to be executed. + * @api private + */ +function Timer(timer, clear, duration, fn) { + this.start = +(new Date()); + this.duration = duration; + this.clear = clear; + this.timer = timer; + this.fns = [fn]; +} + +/** + * Calculate the time left for a given timer. + * + * @returns {Number} Time in milliseconds. + * @api public + */ +Timer.prototype.remaining = function remaining() { + return this.duration - this.taken(); +}; + +/** + * Calculate the amount of time it has taken since we've set the timer. + * + * @returns {Number} + * @api public + */ +Timer.prototype.taken = function taken() { + return +(new Date()) - this.start; +}; + +/** + * Custom wrappers for the various of clear{whatever} functions. We cannot + * invoke them directly as this will cause thrown errors in Google Chrome with + * an Illegal Invocation Error + * + * @see #2 + * @type {Function} + * @api private + */ +function unsetTimeout(id) { clearTimeout(id); } +function unsetInterval(id) { clearInterval(id); } +function unsetImmediate(id) { clearImmediate(id); } + +/** + * Simple timer management. + * + * @constructor + * @param {Mixed} context Context of the callbacks that we execute. + * @api public + */ +function Tick(context) { + if (!(this instanceof Tick)) return new Tick(context); + + this.timers = {}; + this.context = context || this; +} + +/** + * Return a function which will just iterate over all assigned callbacks and + * optionally clear the timers from memory if needed. + * + * @param {String} name Name of the timer we need to execute. + * @param {Boolean} clear Also clear from memory. + * @returns {Function} + * @api private + */ +Tick.prototype.tock = function ticktock(name, clear) { + var tock = this; + + return function tickedtock() { + if (!(name in tock.timers)) return; + + var timer = tock.timers[name] + , fns = timer.fns.slice() + , l = fns.length + , i = 0; + + if (clear) tock.clear(name); + else tock.start = +new Date(); + + for (; i < l; i++) { + fns[i].call(tock.context); + } + }; +}; + +/** + * Add a new timeout. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @param {Mixed} time Duration of the timer. + * @returns {Tick} + * @api public + */ +Tick.prototype.setTimeout = function timeout(name, fn, time) { + var tick = this + , tock; + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tock = ms(time); + tick.timers[name] = new Timer( + setTimeout(tick.tock(name, true), ms(time)), + unsetTimeout, + tock, + fn + ); + + return tick; +}; + +/** + * Add a new interval. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @param {Mixed} time Interval of the timer. + * @returns {Tick} + * @api public + */ +Tick.prototype.setInterval = function interval(name, fn, time) { + var tick = this + , tock; + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tock = ms(time); + tick.timers[name] = new Timer( + setInterval(tick.tock(name), ms(time)), + unsetInterval, + tock, + fn + ); + + return tick; +}; + +/** + * Add a new setImmediate. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @returns {Tick} + * @api public + */ +Tick.prototype.setImmediate = function immediate(name, fn) { + var tick = this; + + if ('function' !== typeof setImmediate) return tick.setTimeout(name, fn, 0); + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tick.timers[name] = new Timer( + setImmediate(tick.tock(name, true)), + unsetImmediate, + 0, + fn + ); + + return tick; +}; + +/** + * Check if we have a timer set. + * + * @param {String} name + * @returns {Boolean} + * @api public + */ +Tick.prototype.active = function active(name) { + return name in this.timers; +}; + +/** + * Properly clean up all timeout references. If no arguments are supplied we + * will attempt to clear every single timer that is present. + * + * @param {Arguments} ..args.. The names of the timeouts we need to clear + * @returns {Tick} + * @api public + */ +Tick.prototype.clear = function clear() { + var args = arguments.length ? arguments : [] + , tick = this + , timer, i, l; + + if (args.length === 1 && 'string' === typeof args[0]) { + args = args[0].split(/[, ]+/); + } + + if (!args.length) { + for (timer in tick.timers) { + if (has.call(tick.timers, timer)) args.push(timer); + } + } + + for (i = 0, l = args.length; i < l; i++) { + timer = tick.timers[args[i]]; + + if (!timer) continue; + timer.clear(timer.timer); + + timer.fns = timer.timer = timer.clear = null; + delete tick.timers[args[i]]; + } + + return tick; +}; + +/** + * Adjust a timeout or interval to a new duration. + * + * @returns {Tick} + * @api public + */ +Tick.prototype.adjust = function adjust(name, time) { + var interval + , tick = this + , tock = ms(time) + , timer = tick.timers[name]; + + if (!timer) return tick; + + interval = timer.clear === unsetInterval; + timer.clear(timer.timer); + timer.start = +(new Date()); + timer.duration = tock; + timer.timer = (interval ? setInterval : setTimeout)(tick.tock(name, !interval), tock); + + return tick; +}; + +/** + * We will no longer use this module, prepare your self for global cleanups. + * + * @returns {Boolean} + * @api public + */ +Tick.prototype.end = Tick.prototype.destroy = function end() { + if (!this.context) return false; + + this.clear(); + this.context = this.timers = null; + + return true; +}; + +// +// Expose the timer factory. +// +Tick.Timer = Timer; +module.exports = Tick; + +}).call(this,_dereq_("timers").setImmediate,_dereq_("timers").clearImmediate) +},{"millisecond":5,"timers":13}],13:[function(_dereq_,module,exports){ +(function (setImmediate,clearImmediate){ +var nextTick = _dereq_('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this,_dereq_("timers").setImmediate,_dereq_("timers").clearImmediate) +},{"process/browser.js":7,"timers":13}],14:[function(_dereq_,module,exports){ +(function (global){ +'use strict'; + +var required = _dereq_('requires-port') + , qs = _dereq_('querystringify') + , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\S\s]*)/i + , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//; + +/** + * These are the parse rules for the URL parser, it informs the parser + * about: + * + * 0. The char it Needs to parse, if it's a string it should be done using + * indexOf, RegExp using exec and NaN means set as current value. + * 1. The property we should set when parsing this value. + * 2. Indication if it's backwards or forward parsing, when set as number it's + * the value of extra chars that should be split off. + * 3. Inherit from location if non existing in the parser. + * 4. `toLowerCase` the resulting value. + */ +var rules = [ + ['#', 'hash'], // Extract from the back. + ['?', 'query'], // Extract from the back. + function sanitize(address) { // Sanitize what is left of the address + return address.replace('\\', '/'); + }, + ['/', 'pathname'], // Extract from the back. + ['@', 'auth', 1], // Extract from the front. + [NaN, 'host', undefined, 1, 1], // Set left over value. + [/:(\d+)$/, 'port', undefined, 1], // RegExp the back. + [NaN, 'hostname', undefined, 1, 1] // Set left over. +]; + +/** + * These properties should not be copied or inherited from. This is only needed + * for all non blob URL's as a blob URL does not include a hash, only the + * origin. + * + * @type {Object} + * @private + */ +var ignore = { hash: 1, query: 1 }; + +/** + * The location object differs when your code is loaded through a normal page, + * Worker or through a worker using a blob. And with the blobble begins the + * trouble as the location object will contain the URL of the blob, not the + * location of the page where our code is loaded in. The actual origin is + * encoded in the `pathname` so we can thankfully generate a good "default" + * location from it so we can generate proper relative URL's again. + * + * @param {Object|String} loc Optional default location object. + * @returns {Object} lolcation object. + * @public + */ +function lolcation(loc) { + var globalVar; + + if (typeof window !== 'undefined') globalVar = window; + else if (typeof global !== 'undefined') globalVar = global; + else if (typeof self !== 'undefined') globalVar = self; + else globalVar = {}; + + var location = globalVar.location || {}; + loc = loc || location; + + var finaldestination = {} + , type = typeof loc + , key; + + if ('blob:' === loc.protocol) { + finaldestination = new Url(unescape(loc.pathname), {}); + } else if ('string' === type) { + finaldestination = new Url(loc, {}); + for (key in ignore) delete finaldestination[key]; + } else if ('object' === type) { + for (key in loc) { + if (key in ignore) continue; + finaldestination[key] = loc[key]; + } + + if (finaldestination.slashes === undefined) { + finaldestination.slashes = slashes.test(loc.href); + } + } + + return finaldestination; +} + +/** + * @typedef ProtocolExtract + * @type Object + * @property {String} protocol Protocol matched in the URL, in lowercase. + * @property {Boolean} slashes `true` if protocol is followed by "//", else `false`. + * @property {String} rest Rest of the URL that is not part of the protocol. + */ + +/** + * Extract protocol information from a URL with/without double slash ("//"). + * + * @param {String} address URL we want to extract from. + * @return {ProtocolExtract} Extracted information. + * @private + */ +function extractProtocol(address) { + var match = protocolre.exec(address); + + return { + protocol: match[1] ? match[1].toLowerCase() : '', + slashes: !!match[2], + rest: match[3] + }; +} + +/** + * Resolve a relative URL pathname against a base URL pathname. + * + * @param {String} relative Pathname of the relative URL. + * @param {String} base Pathname of the base URL. + * @return {String} Resolved pathname. + * @private + */ +function resolve(relative, base) { + var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) + , i = path.length + , last = path[i - 1] + , unshift = false + , up = 0; + + while (i--) { + if (path[i] === '.') { + path.splice(i, 1); + } else if (path[i] === '..') { + path.splice(i, 1); + up++; + } else if (up) { + if (i === 0) unshift = true; + path.splice(i, 1); + up--; + } + } + + if (unshift) path.unshift(''); + if (last === '.' || last === '..') path.push(''); + + return path.join('/'); +} + +/** + * The actual URL instance. Instead of returning an object we've opted-in to + * create an actual constructor as it's much more memory efficient and + * faster and it pleases my OCD. + * + * It is worth noting that we should not use `URL` as class name to prevent + * clashes with the global URL instance that got introduced in browsers. + * + * @constructor + * @param {String} address URL we want to parse. + * @param {Object|String} [location] Location defaults for relative paths. + * @param {Boolean|Function} [parser] Parser for the query string. + * @private + */ +function Url(address, location, parser) { + if (!(this instanceof Url)) { + return new Url(address, location, parser); + } + + var relative, extracted, parse, instruction, index, key + , instructions = rules.slice() + , type = typeof location + , url = this + , i = 0; + + // + // The following if statements allows this module two have compatibility with + // 2 different API: + // + // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments + // where the boolean indicates that the query string should also be parsed. + // + // 2. The `URL` interface of the browser which accepts a URL, object as + // arguments. The supplied object will be used as default values / fall-back + // for relative paths. + // + if ('object' !== type && 'string' !== type) { + parser = location; + location = null; + } + + if (parser && 'function' !== typeof parser) parser = qs.parse; + + location = lolcation(location); + + // + // Extract protocol information before running the instructions. + // + extracted = extractProtocol(address || ''); + relative = !extracted.protocol && !extracted.slashes; + url.slashes = extracted.slashes || relative && location.slashes; + url.protocol = extracted.protocol || location.protocol || ''; + address = extracted.rest; + + // + // When the authority component is absent the URL starts with a path + // component. + // + if (!extracted.slashes) instructions[3] = [/(.*)/, 'pathname']; + + for (; i < instructions.length; i++) { + instruction = instructions[i]; + + if (typeof instruction === 'function') { + address = instruction(address); + continue; + } + + parse = instruction[0]; + key = instruction[1]; + + if (parse !== parse) { + url[key] = address; + } else if ('string' === typeof parse) { + if (~(index = address.indexOf(parse))) { + if ('number' === typeof instruction[2]) { + url[key] = address.slice(0, index); + address = address.slice(index + instruction[2]); + } else { + url[key] = address.slice(index); + address = address.slice(0, index); + } + } + } else if ((index = parse.exec(address))) { + url[key] = index[1]; + address = address.slice(0, index.index); + } + + url[key] = url[key] || ( + relative && instruction[3] ? location[key] || '' : '' + ); + + // + // Hostname, host and protocol should be lowercased so they can be used to + // create a proper `origin`. + // + if (instruction[4]) url[key] = url[key].toLowerCase(); + } + + // + // Also parse the supplied query string in to an object. If we're supplied + // with a custom parser as function use that instead of the default build-in + // parser. + // + if (parser) url.query = parser(url.query); + + // + // If the URL is relative, resolve the pathname against the base URL. + // + if ( + relative + && location.slashes + && url.pathname.charAt(0) !== '/' + && (url.pathname !== '' || location.pathname !== '') + ) { + url.pathname = resolve(url.pathname, location.pathname); + } + + // + // We should not add port numbers if they are already the default port number + // for a given protocol. As the host also contains the port number we're going + // override it with the hostname which contains no port number. + // + if (!required(url.port, url.protocol)) { + url.host = url.hostname; + url.port = ''; + } + + // + // Parse down the `auth` for the username and password. + // + url.username = url.password = ''; + if (url.auth) { + instruction = url.auth.split(':'); + url.username = instruction[0] || ''; + url.password = instruction[1] || ''; + } + + url.origin = url.protocol && url.host && url.protocol !== 'file:' + ? url.protocol +'//'+ url.host + : 'null'; + + // + // The href is just the compiled result. + // + url.href = url.toString(); +} + +/** + * This is convenience method for changing properties in the URL instance to + * insure that they all propagate correctly. + * + * @param {String} part Property we need to adjust. + * @param {Mixed} value The newly assigned value. + * @param {Boolean|Function} fn When setting the query, it will be the function + * used to parse the query. + * When setting the protocol, double slash will be + * removed from the final url if it is true. + * @returns {URL} URL instance for chaining. + * @public + */ +function set(part, value, fn) { + var url = this; + + switch (part) { + case 'query': + if ('string' === typeof value && value.length) { + value = (fn || qs.parse)(value); + } + + url[part] = value; + break; + + case 'port': + url[part] = value; + + if (!required(value, url.protocol)) { + url.host = url.hostname; + url[part] = ''; + } else if (value) { + url.host = url.hostname +':'+ value; + } + + break; + + case 'hostname': + url[part] = value; + + if (url.port) value += ':'+ url.port; + url.host = value; + break; + + case 'host': + url[part] = value; + + if (/:\d+$/.test(value)) { + value = value.split(':'); + url.port = value.pop(); + url.hostname = value.join(':'); + } else { + url.hostname = value; + url.port = ''; + } + + break; + + case 'protocol': + url.protocol = value.toLowerCase(); + url.slashes = !fn; + break; + + case 'pathname': + case 'hash': + if (value) { + var char = part === 'pathname' ? '/' : '#'; + url[part] = value.charAt(0) !== char ? char + value : value; + } else { + url[part] = value; + } + break; + + default: + url[part] = value; + } + + for (var i = 0; i < rules.length; i++) { + var ins = rules[i]; + + if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); + } + + url.origin = url.protocol && url.host && url.protocol !== 'file:' + ? url.protocol +'//'+ url.host + : 'null'; + + url.href = url.toString(); + + return url; +} + +/** + * Transform the properties back in to a valid and full URL string. + * + * @param {Function} stringify Optional query stringify function. + * @returns {String} Compiled version of the URL. + * @public + */ +function toString(stringify) { + if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; + + var query + , url = this + , protocol = url.protocol; + + if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; + + var result = protocol + (url.slashes ? '//' : ''); + + if (url.username) { + result += url.username; + if (url.password) result += ':'+ url.password; + result += '@'; + } + + result += url.host + url.pathname; + + query = 'object' === typeof url.query ? stringify(url.query) : url.query; + if (query) result += '?' !== query.charAt(0) ? '?'+ query : query; + + if (url.hash) result += url.hash; + + return result; +} + +Url.prototype = { set: set, toString: toString }; + +// +// Expose the URL parser and some additional properties that might be useful for +// others or testing. +// +Url.extractProtocol = extractProtocol; +Url.location = lolcation; +Url.qs = qs; + +module.exports = Url; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"querystringify":8,"requires-port":11}],15:[function(_dereq_,module,exports){ +'use strict'; + +var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('') + , length = 64 + , map = {} + , seed = 0 + , i = 0 + , prev; + +/** + * Return a string representing the specified number. + * + * @param {Number} num The number to convert. + * @returns {String} The string representation of the number. + * @api public + */ +function encode(num) { + var encoded = ''; + + do { + encoded = alphabet[num % length] + encoded; + num = Math.floor(num / length); + } while (num > 0); + + return encoded; +} + +/** + * Return the integer value specified by the given string. + * + * @param {String} str The string to convert. + * @returns {Number} The integer value represented by the string. + * @api public + */ +function decode(str) { + var decoded = 0; + + for (i = 0; i < str.length; i++) { + decoded = decoded * length + map[str.charAt(i)]; + } + + return decoded; +} + +/** + * Yeast: A tiny growing id generator. + * + * @returns {String} A unique id. + * @api public + */ +function yeast() { + var now = encode(+new Date()); + + if (now !== prev) return seed = 0, prev = now; + return now +'.'+ encode(seed++); +} + +// +// Map each character to its index. +// +for (; i < length; i++) map[alphabet[i]] = i; + +// +// Expose the `yeast`, `encode` and `decode` functions. +// +yeast.encode = encode; +yeast.decode = decode; +module.exports = yeast; + +},{}],16:[function(_dereq_,module,exports){ +/*globals require, define */ +'use strict'; + +var EventEmitter = _dereq_('eventemitter3') + , TickTock = _dereq_('tick-tock') + , Recovery = _dereq_('recovery') + , qs = _dereq_('querystringify') + , inherits = _dereq_('inherits') + , destroy = _dereq_('demolish') + , yeast = _dereq_('yeast') + , u2028 = /\u2028/g + , u2029 = /\u2029/g; + +/** + * Context assertion, ensure that some of our public Primus methods are called + * with the correct context to ensure that + * + * @param {Primus} self The context of the function. + * @param {String} method The method name. + * @api private + */ +function context(self, method) { + if (self instanceof Primus) return; + + var failure = new Error('Primus#'+ method + '\'s context should called with a Primus instance'); + + if ('function' !== typeof self.listeners || !self.listeners('error').length) { + throw failure; + } + + self.emit('error', failure); +} + +// +// Sets the default connection URL, it uses the default origin of the browser +// when supported but degrades for older browsers. In Node.js, we cannot guess +// where the user wants to connect to, so we just default to localhost. +// +var defaultUrl; + +try { + if (location.origin) { + defaultUrl = location.origin; + } else { + defaultUrl = location.protocol +'//'+ location.host; + } +} catch (e) { + defaultUrl = 'http://127.0.0.1'; +} + +/** + * Primus is a real-time library agnostic framework for establishing real-time + * connections with servers. + * + * Options: + * - reconnect, configuration for the reconnect process. + * - manual, don't automatically call `.open` to start the connection. + * - websockets, force the use of WebSockets, even when you should avoid them. + * - timeout, connect timeout, server didn't respond in a timely manner. + * - pingTimeout, The maximum amount of time to wait for the server to send a ping. + * - network, Use network events as leading method for network connection drops. + * - strategy, Reconnection strategies. + * - transport, Transport options. + * - url, uri, The URL to use connect with the server. + * + * @constructor + * @param {String} url The URL of your server. + * @param {Object} options The configuration. + * @api public + */ +function Primus(url, options) { + if (!(this instanceof Primus)) return new Primus(url, options); + + Primus.Stream.call(this); + + if ('function' !== typeof this.client) { + return this.critical(new Error( + 'The client library has not been compiled correctly, see '+ + 'https://github.com/primus/primus#client-library for more details' + )); + } + + if ('object' === typeof url) { + options = url; + url = options.url || options.uri || defaultUrl; + } else { + options = options || {}; + } + + if ('ping' in options || 'pong' in options) { + return this.critical(new Error( + 'The `ping` and `pong` options have been removed' + )); + } + + var primus = this; + + // The maximum number of messages that can be placed in queue. + options.queueSize = 'queueSize' in options ? options.queueSize : Infinity; + + // Connection timeout duration. + options.timeout = 'timeout' in options ? options.timeout : 10e3; + + // Stores the back off configuration. + options.reconnect = 'reconnect' in options ? options.reconnect : {}; + + // Heartbeat ping interval. + options.pingTimeout = 'pingTimeout' in options ? options.pingTimeout : 45000; + + // Reconnect strategies. + options.strategy = 'strategy' in options ? options.strategy : []; + + // Custom transport options. + options.transport = 'transport' in options ? options.transport : {}; + + primus.buffer = []; // Stores premature send data. + primus.writable = true; // Silly stream compatibility. + primus.readable = true; // Silly stream compatibility. + primus.url = primus.parse(url || defaultUrl); // Parse the URL to a readable format. + primus.readyState = Primus.CLOSED; // The readyState of the connection. + primus.options = options; // Reference to the supplied options. + primus.timers = new TickTock(this); // Contains all our timers. + primus.socket = null; // Reference to the internal connection. + primus.disconnect = false; // Did we receive a disconnect packet? + primus.transport = options.transport; // Transport options. + primus.transformers = { // Message transformers. + outgoing: [], + incoming: [] + }; + + // + // Create our reconnection instance. + // + primus.recovery = new Recovery(options.reconnect); + + // + // Parse the reconnection strategy. It can have the following strategies: + // + // - timeout: Reconnect when we have a network timeout. + // - disconnect: Reconnect when we have an unexpected disconnect. + // - online: Reconnect when we're back online. + // + if ('string' === typeof options.strategy) { + options.strategy = options.strategy.split(/\s?,\s?/g); + } + + if (false === options.strategy) { + // + // Strategies are disabled, but we still need an empty array to join it in + // to nothing. + // + options.strategy = []; + } else if (!options.strategy.length) { + options.strategy.push('disconnect', 'online'); + + // + // Timeout based reconnection should only be enabled conditionally. When + // authorization is enabled it could trigger. + // + if (!this.authorization) options.strategy.push('timeout'); + } + + options.strategy = options.strategy.join(',').toLowerCase(); + + // + // Force the use of WebSockets, even when we've detected some potential + // broken WebSocket implementation. + // + if ('websockets' in options) { + primus.AVOID_WEBSOCKETS = !options.websockets; + } + + // + // Force or disable the use of NETWORK events as leading client side + // disconnection detection. + // + if ('network' in options) { + primus.NETWORK_EVENTS = options.network; + } + + // + // Check if the user wants to manually initialise a connection. If they don't, + // we want to do it after a really small timeout so we give the users enough + // time to listen for `error` events etc. + // + if (!options.manual) primus.timers.setTimeout('open', function open() { + primus.timers.clear('open'); + primus.open(); + }, 0); + + primus.initialise(options); +} + +/** + * Simple require wrapper to make browserify, node and require.js play nice. + * + * @param {String} name The module to require. + * @returns {Object|Undefined} The module that we required. + * @api private + */ +Primus.requires = Primus.require = function requires(name) { + if ('function' !== typeof _dereq_) return undefined; + + return !('function' === typeof define && define.amd) + ? _dereq_(name) + : undefined; +}; + +// +// It's possible that we're running in Node.js or in a Node.js compatible +// environment. In this cases we try to inherit from the Stream base class. +// +try { + Primus.Stream = Primus.requires('stream'); +} catch (e) { } + +if (!Primus.Stream) Primus.Stream = EventEmitter; + +inherits(Primus, Primus.Stream); + +/** + * Primus readyStates, used internally to set the correct ready state. + * + * @type {Number} + * @private + */ +Primus.OPENING = 1; // We're opening the connection. +Primus.CLOSED = 2; // No active connection. +Primus.OPEN = 3; // The connection is open. + +/** + * Are we working with a potentially broken WebSockets implementation? This + * boolean can be used by transformers to remove `WebSockets` from their + * supported transports. + * + * @type {Boolean} + * @private + */ +Primus.prototype.AVOID_WEBSOCKETS = false; + +/** + * Some browsers support registering emitting `online` and `offline` events when + * the connection has been dropped on the client. We're going to detect it in + * a simple `try {} catch (e) {}` statement so we don't have to do complicated + * feature detection. + * + * @type {Boolean} + * @private + */ +Primus.prototype.NETWORK_EVENTS = false; +Primus.prototype.online = true; + +try { + if ( + Primus.prototype.NETWORK_EVENTS = 'onLine' in navigator + && (window.addEventListener || document.body.attachEvent) + ) { + if (!navigator.onLine) { + Primus.prototype.online = false; + } + } +} catch (e) { } + +/** + * The Ark contains all our plugins definitions. It's namespaced by + * name => plugin. + * + * @type {Object} + * @private + */ +Primus.prototype.ark = {}; + +/** + * Simple emit wrapper that returns a function that emits an event once it's + * called. This makes it easier for transports to emit specific events. + * + * @returns {Function} A function that will emit the event when called. + * @api public + */ +Primus.prototype.emits = _dereq_('emits'); + +/** + * Return the given plugin. + * + * @param {String} name The name of the plugin. + * @returns {Object|undefined} The plugin or undefined. + * @api public + */ +Primus.prototype.plugin = function plugin(name) { + context(this, 'plugin'); + + if (name) return this.ark[name]; + + var plugins = {}; + + for (name in this.ark) { + plugins[name] = this.ark[name]; + } + + return plugins; +}; + +/** + * Checks if the given event is an emitted event by Primus. + * + * @param {String} evt The event name. + * @returns {Boolean} Indication of the event is reserved for internal use. + * @api public + */ +Primus.prototype.reserved = function reserved(evt) { + return (/^(incoming|outgoing)::/).test(evt) + || evt in this.reserved.events; +}; + +/** + * The actual events that are used by the client. + * + * @type {Object} + * @public + */ +Primus.prototype.reserved.events = { + 'reconnect scheduled': 1, + 'reconnect timeout': 1, + 'readyStateChange': 1, + 'reconnect failed': 1, + 'reconnected': 1, + 'reconnect': 1, + 'offline': 1, + 'timeout': 1, + 'destroy': 1, + 'online': 1, + 'error': 1, + 'close': 1, + 'open': 1, + 'data': 1, + 'end': 1 +}; + +/** + * Initialise the Primus and setup all parsers and internal listeners. + * + * @param {Object} options The original options object. + * @returns {Primus} + * @api private + */ +Primus.prototype.initialise = function initialise(options) { + var primus = this; + + primus.recovery + .on('reconnected', primus.emits('reconnected')) + .on('reconnect failed', primus.emits('reconnect failed', function failed(next) { + primus.emit('end'); + next(); + })) + .on('reconnect timeout', primus.emits('reconnect timeout')) + .on('reconnect scheduled', primus.emits('reconnect scheduled')) + .on('reconnect', primus.emits('reconnect', function reconnect(next) { + primus.emit('outgoing::reconnect'); + next(); + })); + + primus.on('outgoing::open', function opening() { + var readyState = primus.readyState; + + primus.readyState = Primus.OPENING; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'opening'); + } + }); + + primus.on('incoming::open', function opened() { + var readyState = primus.readyState; + + if (primus.recovery.reconnecting()) { + primus.recovery.reconnected(); + } + + // + // The connection has been opened so we should set our state to + // (writ|read)able so our stream compatibility works as intended. + // + primus.writable = true; + primus.readable = true; + + // + // Make sure we are flagged as `online` as we've successfully opened the + // connection. + // + if (!primus.online) { + primus.online = true; + primus.emit('online'); + } + + primus.readyState = Primus.OPEN; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'open'); + } + + primus.heartbeat(); + + if (primus.buffer.length) { + var data = primus.buffer.slice() + , length = data.length + , i = 0; + + primus.buffer.length = 0; + + for (; i < length; i++) { + primus._write(data[i]); + } + } + + primus.emit('open'); + }); + + primus.on('incoming::ping', function ping(time) { + primus.online = true; + primus.heartbeat(); + primus.emit('outgoing::pong', time); + primus._write('primus::pong::'+ time); + }); + + primus.on('incoming::error', function error(e) { + var connect = primus.timers.active('connect') + , err = e; + + // + // When the error is not an Error instance we try to normalize it. + // + if ('string' === typeof e) { + err = new Error(e); + } else if (!(e instanceof Error) && 'object' === typeof e) { + // + // BrowserChannel and SockJS returns an object which contains some + // details of the error. In order to have a proper error we "copy" the + // details in an Error instance. + // + err = new Error(e.message || e.reason); + for (var key in e) { + if (Object.prototype.hasOwnProperty.call(e, key)) + err[key] = e[key]; + } + } + // + // We're still doing a reconnect attempt, it could be that we failed to + // connect because the server was down. Failing connect attempts should + // always emit an `error` event instead of a `open` event. + // + // + if (primus.recovery.reconnecting()) return primus.recovery.reconnected(err); + if (primus.listeners('error').length) primus.emit('error', err); + + // + // We received an error while connecting, this most likely the result of an + // unauthorized access to the server. + // + if (connect) { + if (~primus.options.strategy.indexOf('timeout')) { + primus.recovery.reconnect(); + } else { + primus.end(); + } + } + }); + + primus.on('incoming::data', function message(raw) { + primus.decoder(raw, function decoding(err, data) { + // + // Do a "safe" emit('error') when we fail to parse a message. We don't + // want to throw here as listening to errors should be optional. + // + if (err) return primus.listeners('error').length && primus.emit('error', err); + + // + // Handle all "primus::" prefixed protocol messages. + // + if (primus.protocol(data)) return; + primus.transforms(primus, primus, 'incoming', data, raw); + }); + }); + + primus.on('incoming::end', function end() { + var readyState = primus.readyState; + + // + // This `end` started with the receiving of a primus::server::close packet + // which indicated that the user/developer on the server closed the + // connection and it was not a result of a network disruption. So we should + // kill the connection without doing a reconnect. + // + if (primus.disconnect) { + primus.disconnect = false; + + return primus.end(); + } + + // + // Always set the readyState to closed, and if we're still connecting, close + // the connection so we're sure that everything after this if statement block + // is only executed because our readyState is set to `open`. + // + primus.readyState = Primus.CLOSED; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'end'); + } + + if (primus.timers.active('connect')) primus.end(); + if (readyState !== Primus.OPEN) { + return primus.recovery.reconnecting() + ? primus.recovery.reconnect() + : false; + } + + this.writable = false; + this.readable = false; + + // + // Clear all timers in case we're not going to reconnect. + // + this.timers.clear(); + + // + // Fire the `close` event as an indication of connection disruption. + // This is also fired by `primus#end` so it is emitted in all cases. + // + primus.emit('close'); + + // + // The disconnect was unintentional, probably because the server has + // shutdown, so if the reconnection is enabled start a reconnect procedure. + // + if (~primus.options.strategy.indexOf('disconnect')) { + return primus.recovery.reconnect(); + } + + primus.emit('outgoing::end'); + primus.emit('end'); + }); + + // + // Setup the real-time client. + // + primus.client(); + + // + // Process the potential plugins. + // + for (var plugin in primus.ark) { + primus.ark[plugin].call(primus, primus, options); + } + + // + // NOTE: The following code is only required if we're supporting network + // events as it requires access to browser globals. + // + if (!primus.NETWORK_EVENTS) return primus; + + /** + * Handler for offline notifications. + * + * @api private + */ + primus.offlineHandler = function offline() { + if (!primus.online) return; // Already or still offline, bailout. + + primus.online = false; + primus.emit('offline'); + primus.end(); + + // + // It is certainly possible that we're in a reconnection loop and that the + // user goes offline. In this case we want to kill the existing attempt so + // when the user goes online, it will attempt to reconnect freshly again. + // + primus.recovery.reset(); + }; + + /** + * Handler for online notifications. + * + * @api private + */ + primus.onlineHandler = function online() { + if (primus.online) return; // Already or still online, bailout. + + primus.online = true; + primus.emit('online'); + + if (~primus.options.strategy.indexOf('online')) { + primus.recovery.reconnect(); + } + }; + + if (window.addEventListener) { + window.addEventListener('offline', primus.offlineHandler, false); + window.addEventListener('online', primus.onlineHandler, false); + } else if (document.body.attachEvent){ + document.body.attachEvent('onoffline', primus.offlineHandler); + document.body.attachEvent('ononline', primus.onlineHandler); + } + + return primus; +}; + +/** + * Really dead simple protocol parser. We simply assume that every message that + * is prefixed with `primus::` could be used as some sort of protocol definition + * for Primus. + * + * @param {String} msg The data. + * @returns {Boolean} Is a protocol message. + * @api private + */ +Primus.prototype.protocol = function protocol(msg) { + if ( + 'string' !== typeof msg + || msg.indexOf('primus::') !== 0 + ) return false; + + var last = msg.indexOf(':', 8) + , value = msg.slice(last + 2); + + switch (msg.slice(8, last)) { + case 'ping': + this.emit('incoming::ping', +value); + break; + + case 'server': + // + // The server is closing the connection, forcefully disconnect so we don't + // reconnect again. + // + if ('close' === value) { + this.disconnect = true; + } + break; + + case 'id': + this.emit('incoming::id', value); + break; + + // + // Unknown protocol, somebody is probably sending `primus::` prefixed + // messages. + // + default: + return false; + } + + return true; +}; + +/** + * Execute the set of message transformers from Primus on the incoming or + * outgoing message. + * This function and it's content should be in sync with Spark#transforms in + * spark.js. + * + * @param {Primus} primus Reference to the Primus instance with message transformers. + * @param {Spark|Primus} connection Connection that receives or sends data. + * @param {String} type The type of message, 'incoming' or 'outgoing'. + * @param {Mixed} data The data to send or that has been received. + * @param {String} raw The raw encoded data. + * @returns {Primus} + * @api public + */ +Primus.prototype.transforms = function transforms(primus, connection, type, data, raw) { + var packet = { data: data } + , fns = primus.transformers[type]; + + // + // Iterate in series over the message transformers so we can allow optional + // asynchronous execution of message transformers which could for example + // retrieve additional data from the server, do extra decoding or even + // message validation. + // + (function transform(index, done) { + var transformer = fns[index++]; + + if (!transformer) return done(); + + if (1 === transformer.length) { + if (false === transformer.call(connection, packet)) { + // + // When false is returned by an incoming transformer it means that's + // being handled by the transformer and we should not emit the `data` + // event. + // + return; + } + + return transform(index, done); + } + + transformer.call(connection, packet, function finished(err, arg) { + if (err) return connection.emit('error', err); + if (false === arg) return; + + transform(index, done); + }); + }(0, function done() { + // + // We always emit 2 arguments for the data event, the first argument is the + // parsed data and the second argument is the raw string that we received. + // This allows you, for example, to do some validation on the parsed data + // and then save the raw string in your database without the stringify + // overhead. + // + if ('incoming' === type) return connection.emit('data', packet.data, raw); + + connection._write(packet.data); + })); + + return this; +}; + +/** + * Retrieve the current id from the server. + * + * @param {Function} fn Callback function. + * @returns {Primus} + * @api public + */ +Primus.prototype.id = function id(fn) { + if (this.socket && this.socket.id) return fn(this.socket.id); + + this._write('primus::id::'); + return this.once('incoming::id', fn); +}; + +/** + * Establish a connection with the server. When this function is called we + * assume that we don't have any open connections. If you do call it when you + * have a connection open, it could cause duplicate connections. + * + * @returns {Primus} + * @api public + */ +Primus.prototype.open = function open() { + context(this, 'open'); + + // + // Only start a `connection timeout` procedure if we're not reconnecting as + // that shouldn't count as an initial connection. This should be started + // before the connection is opened to capture failing connections and kill the + // timeout. + // + if (!this.recovery.reconnecting() && this.options.timeout) this.timeout(); + + this.emit('outgoing::open'); + return this; +}; + +/** + * Send a new message. + * + * @param {Mixed} data The data that needs to be written. + * @returns {Boolean} Always returns true as we don't support back pressure. + * @api public + */ +Primus.prototype.write = function write(data) { + context(this, 'write'); + this.transforms(this, this, 'outgoing', data); + + return true; +}; + +/** + * The actual message writer. + * + * @param {Mixed} data The message that needs to be written. + * @returns {Boolean} Successful write to the underlaying transport. + * @api private + */ +Primus.prototype._write = function write(data) { + var primus = this; + + // + // The connection is closed, normally this would already be done in the + // `spark.write` method, but as `_write` is used internally, we should also + // add the same check here to prevent potential crashes by writing to a dead + // socket. + // + if (Primus.OPEN !== primus.readyState) { + // + // If the buffer is at capacity, remove the first item. + // + if (this.buffer.length === this.options.queueSize) { + this.buffer.splice(0, 1); + } + + this.buffer.push(data); + return false; + } + + primus.encoder(data, function encoded(err, packet) { + // + // Do a "safe" emit('error') when we fail to parse a message. We don't + // want to throw here as listening to errors should be optional. + // + if (err) return primus.listeners('error').length && primus.emit('error', err); + + // + // Hack 1: \u2028 and \u2029 are allowed inside a JSON string, but JavaScript + // defines them as newline separators. Unescaped control characters are not + // allowed inside JSON strings, so this causes an error at parse time. We + // work around this issue by escaping these characters. This can cause + // errors with JSONP requests or if the string is just evaluated. + // + if ('string' === typeof packet) { + if (~packet.indexOf('\u2028')) packet = packet.replace(u2028, '\\u2028'); + if (~packet.indexOf('\u2029')) packet = packet.replace(u2029, '\\u2029'); + } + + primus.emit('outgoing::data', packet); + }); + + return true; +}; + +/** + * Set a timer that, upon expiration, closes the client. + * + * @returns {Primus} + * @api private + */ +Primus.prototype.heartbeat = function heartbeat() { + if (!this.options.pingTimeout) return this; + + this.timers.clear('heartbeat'); + this.timers.setTimeout('heartbeat', function expired() { + // + // The network events already captured the offline event. + // + if (!this.online) return; + + this.online = false; + this.emit('offline'); + this.emit('incoming::end'); + }, this.options.pingTimeout); + + return this; +}; + +/** + * Start a connection timeout. + * + * @returns {Primus} + * @api private + */ +Primus.prototype.timeout = function timeout() { + var primus = this; + + /** + * Remove all references to the timeout listener as we've received an event + * that can be used to determine state. + * + * @api private + */ + function remove() { + primus.removeListener('error', remove) + .removeListener('open', remove) + .removeListener('end', remove) + .timers.clear('connect'); + } + + primus.timers.setTimeout('connect', function expired() { + remove(); // Clean up old references. + + if (primus.readyState === Primus.OPEN || primus.recovery.reconnecting()) { + return; + } + + primus.emit('timeout'); + + // + // We failed to connect to the server. + // + if (~primus.options.strategy.indexOf('timeout')) { + primus.recovery.reconnect(); + } else { + primus.end(); + } + }, primus.options.timeout); + + return primus.on('error', remove) + .on('open', remove) + .on('end', remove); +}; + +/** + * Close the connection completely. + * + * @param {Mixed} data last packet of data. + * @returns {Primus} + * @api public + */ +Primus.prototype.end = function end(data) { + context(this, 'end'); + + if ( + this.readyState === Primus.CLOSED + && !this.timers.active('connect') + && !this.timers.active('open') + ) { + // + // If we are reconnecting stop the reconnection procedure. + // + if (this.recovery.reconnecting()) { + this.recovery.reset(); + this.emit('end'); + } + + return this; + } + + if (data !== undefined) this.write(data); + + this.writable = false; + this.readable = false; + + var readyState = this.readyState; + this.readyState = Primus.CLOSED; + + if (readyState !== this.readyState) { + this.emit('readyStateChange', 'end'); + } + + this.timers.clear(); + this.emit('outgoing::end'); + this.emit('close'); + this.emit('end'); + + return this; +}; + +/** + * Completely demolish the Primus instance and forcefully nuke all references. + * + * @returns {Boolean} + * @api public + */ +Primus.prototype.destroy = destroy('url timers options recovery socket transport transformers', { + before: 'end', + after: ['removeAllListeners', function detach() { + if (!this.NETWORK_EVENTS) return; + + if (window.addEventListener) { + window.removeEventListener('offline', this.offlineHandler); + window.removeEventListener('online', this.onlineHandler); + } else if (document.body.attachEvent){ + document.body.detachEvent('onoffline', this.offlineHandler); + document.body.detachEvent('ononline', this.onlineHandler); + } + }] +}); + +/** + * Create a shallow clone of a given object. + * + * @param {Object} obj The object that needs to be cloned. + * @returns {Object} Copy. + * @api private + */ +Primus.prototype.clone = function clone(obj) { + return this.merge({}, obj); +}; + +/** + * Merge different objects in to one target object. + * + * @param {Object} target The object where everything should be merged in. + * @returns {Object} Original target with all merged objects. + * @api private + */ +Primus.prototype.merge = function merge(target) { + for (var i = 1, key, obj; i < arguments.length; i++) { + obj = arguments[i]; + + for (key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) + target[key] = obj[key]; + } + } + + return target; +}; + +/** + * Parse the connection string. + * + * @type {Function} + * @param {String} url Connection URL. + * @returns {Object} Parsed connection. + * @api private + */ +Primus.prototype.parse = _dereq_('url-parse'); + +/** + * Parse a query string. + * + * @param {String} query The query string that needs to be parsed. + * @returns {Object} Parsed query string. + * @api private + */ +Primus.prototype.querystring = qs.parse; +/** + * Transform a query string object back into string equiv. + * + * @param {Object} obj The query string object. + * @returns {String} + * @api private + */ +Primus.prototype.querystringify = qs.stringify; + +/** + * Generates a connection URI. + * + * @param {String} protocol The protocol that should used to crate the URI. + * @returns {String|options} The URL. + * @api private + */ +Primus.prototype.uri = function uri(options) { + var url = this.url + , server = [] + , qsa = false; + + // + // Query strings are only allowed when we've received clearance for it. + // + if (options.query) qsa = true; + + options = options || {}; + options.protocol = 'protocol' in options + ? options.protocol + : 'http:'; + options.query = url.query && qsa + ? url.query.slice(1) + : false; + options.secure = 'secure' in options + ? options.secure + : url.protocol === 'https:' || url.protocol === 'wss:'; + options.auth = 'auth' in options + ? options.auth + : url.auth; + options.pathname = 'pathname' in options + ? options.pathname + : this.pathname; + options.port = 'port' in options + ? +options.port + : +url.port || (options.secure ? 443 : 80); + + // + // We need to make sure that we create a unique connection URL every time to + // prevent back forward cache from becoming an issue. We're doing this by + // forcing an cache busting query string in to the URL. + // + var querystring = this.querystring(options.query || ''); + querystring._primuscb = yeast(); + options.query = this.querystringify(querystring); + + // + // Allow transformation of the options before we construct a full URL from it. + // + this.emit('outgoing::url', options); + + // + // Automatically suffix the protocol so we can supply `ws:` and `http:` and + // it gets transformed correctly. + // + server.push(options.secure ? options.protocol.replace(':', 's:') : options.protocol, ''); + + server.push(options.auth ? options.auth +'@'+ url.host : url.host); + + // + // Pathnames are optional as some Transformers would just use the pathname + // directly. + // + if (options.pathname) server.push(options.pathname.slice(1)); + + // + // Optionally add a search query. + // + if (qsa) server[server.length - 1] += '?'+ options.query; + else delete options.query; + + if (options.object) return options; + return server.join('/'); +}; + +/** + * Register a new message transformer. This allows you to easily manipulate incoming + * and outgoing data which is particularity handy for plugins that want to send + * meta data together with the messages. + * + * @param {String} type Incoming or outgoing + * @param {Function} fn A new message transformer. + * @returns {Primus} + * @api public + */ +Primus.prototype.transform = function transform(type, fn) { + context(this, 'transform'); + + if (!(type in this.transformers)) { + return this.critical(new Error('Invalid transformer type')); + } + + this.transformers[type].push(fn); + return this; +}; + +/** + * A critical error has occurred, if we have an `error` listener, emit it there. + * If not, throw it, so we get a stack trace + proper error message. + * + * @param {Error} err The critical error. + * @returns {Primus} + * @api private + */ +Primus.prototype.critical = function critical(err) { + if (this.emit('error', err)) return this; + + throw err; +}; + +/** + * Syntax sugar, adopt a Socket.IO like API. + * + * @param {String} url The URL we want to connect to. + * @param {Object} options Connection options. + * @returns {Primus} + * @api public + */ +Primus.connect = function connect(url, options) { + return new Primus(url, options); +}; + +// +// Expose the EventEmitter so it can be re-used by wrapping libraries we're also +// exposing the Stream interface. +// +Primus.EventEmitter = EventEmitter; + +// +// These libraries are automatically inserted at the server-side using the +// Primus#library method. +// +Primus.prototype.client = function client() { + var onmessage = this.emits('incoming::data') + , onerror = this.emits('incoming::error') + , onopen = this.emits('incoming::open') + , onclose = this.emits('incoming::end') + , primus = this + , socket; + + // + // Select an available Engine.IO factory. + // + var factory = (function factory() { + if ('undefined' !== typeof eio) return eio; + + try { return Primus.requires('engine.io-client'); } + catch (e) {} + + return undefined; + })(); + + if (!factory) return primus.critical(new Error( + 'Missing required `engine.io-client` module. ' + + 'Please run `npm install --save engine.io-client`' + )); + + // + // Connect to the given URL. + // + primus.on('outgoing::open', function opening() { + primus.emit('outgoing::end'); + + primus.socket = socket = factory(primus.merge(primus.transport, + primus.url, + primus.uri({ protocol: 'http:', query: true, object: true }), { + // + // Never remember upgrades as switching from a WIFI to a 3G connection + // could still get your connection blocked as 3G connections are usually + // behind a reverse proxy so ISP's can optimize mobile traffic by + // caching requests. + // + rememberUpgrade: false, + + // + // Binary support in Engine.IO breaks a shit things. Turn it off for now. + // + forceBase64: true, + + // + // XDR has been the source of pain for most real-time users. It doesn't + // support the full CORS spec and is infested with bugs. It cannot connect + // cross-scheme, does not send ANY authorization information like Cookies, + // Basic Authorization headers etc. Force this off by default to ensure a + // stable connection. + // + enablesXDR: false, + + // + // Force timestamps on every single connection. Engine.IO only does this + // for polling by default, but WebSockets require an explicit `true` + // boolean. + // + timestampRequests: true, + path: this.pathname, + transports: !primus.AVOID_WEBSOCKETS + ? ['polling', 'websocket'] + : ['polling'] + })); + + // + // Nuke a growing memory leak as Engine.IO pushes instances in to an exposed + // `sockets` array. + // + if (factory.sockets && factory.sockets.length) { + factory.sockets.length = 0; + } + + // + // Setup the Event handlers. + // + socket.on('message', onmessage); + socket.on('error', onerror); + socket.on('close', onclose); + socket.on('open', onopen); + }); + + // + // We need to write a new message to the socket. + // + primus.on('outgoing::data', function write(message) { + if (socket) socket.send(message); + }); + + // + // Attempt to reconnect the socket. + // + primus.on('outgoing::reconnect', function reconnect() { + primus.emit('outgoing::open'); + }); + + // + // We need to close the socket. + // + primus.on('outgoing::end', function close() { + if (!socket) return; + + socket.removeListener('message', onmessage); + socket.removeListener('error', onerror); + socket.removeListener('close', onclose); + socket.removeListener('open', onopen); + socket.close(); + socket = null; + }); +}; +Primus.prototype.authorization = false; +Primus.prototype.pathname = "/primus"; +Primus.prototype.encoder = function encoder(data, fn) { + var err; + + try { data = JSON.stringify(data); } + catch (e) { err = e; } + + fn(err, data); +}; +Primus.prototype.decoder = function decoder(data, fn) { + var err; + + if ('string' !== typeof data) return fn(err, data); + + try { data = JSON.parse(data); } + catch (e) { err = e; } + + fn(err, data); +}; +Primus.prototype.version = "7.3.2"; + +if ( + 'undefined' !== typeof document + && 'undefined' !== typeof navigator +) { + // + // Hack 2: If you press ESC in FireFox it will close all active connections. + // Normally this makes sense, when your page is still loading. But versions + // before FireFox 22 will close all connections including WebSocket connections + // after page load. One way to prevent this is to do a `preventDefault()` and + // cancel the operation before it bubbles up to the browsers default handler. + // It needs to be added as `keydown` event, if it's added keyup it will not be + // able to prevent the connection from being closed. + // + if (document.addEventListener) { + document.addEventListener('keydown', function keydown(e) { + if (e.keyCode !== 27 || !e.preventDefault) return; + + e.preventDefault(); + }, false); + } + + // + // Hack 3: This is a Mac/Apple bug only, when you're behind a reverse proxy or + // have you network settings set to `automatic proxy discovery` the safari + // browser will crash when the WebSocket constructor is initialised. There is + // no way to detect the usage of these proxies available in JavaScript so we + // need to do some nasty browser sniffing. This only affects Safari versions + // lower then 5.1.4 + // + var ua = (navigator.userAgent || '').toLowerCase() + , parsed = ua.match(/.+(?:rv|it|ra|ie)[/: ](\d+)\.(\d+)(?:\.(\d+))?/) || [] + , version = +[parsed[1], parsed[2]].join('.'); + + if ( + !~ua.indexOf('chrome') + && ~ua.indexOf('safari') + && version < 534.54 + ) { + Primus.prototype.AVOID_WEBSOCKETS = true; + } +} + +// +// Expose the library. +// +module.exports = Primus; + +},{"demolish":1,"emits":2,"eventemitter3":3,"inherits":4,"querystringify":8,"recovery":9,"tick-tock":12,"url-parse":14,"yeast":15}]},{},[16])(16) +; + return Primus; +}, +[ +function (Primus) { +(function(f){var g;if(typeof window!=='undefined'){g=window}else if(typeof self!=='undefined'){g=self}g.eio=f()})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { + this.extraHeaders = opts.extraHeaders; + } + + if (opts.localAddress) { + this.localAddress = opts.localAddress; + } + } + + // set on handshake + this.id = null; + this.upgrades = null; + this.pingInterval = null; + this.pingTimeout = null; + + // set on heartbeat + this.pingIntervalTimer = null; + this.pingTimeoutTimer = null; + + this.open(); +} + +Socket.priorWebsocketSuccess = false; + +/** + * Mix in `Emitter`. + */ + +Emitter(Socket.prototype); + +/** + * Protocol version. + * + * @api public + */ + +Socket.protocol = parser.protocol; // this is an int + +/** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + +Socket.Socket = Socket; +Socket.Transport = _dereq_('./transport'); +Socket.transports = _dereq_('./transports/index'); +Socket.parser = _dereq_('engine.io-parser'); + +/** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + +Socket.prototype.createTransport = function (name) { + var query = clone(this.query); + + // append engine.io protocol identifier + query.EIO = parser.protocol; + + // transport name + query.transport = name; + + // per-transport options + var options = this.transportOptions[name] || {}; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + query: query, + socket: this, + agent: options.agent || this.agent, + hostname: options.hostname || this.hostname, + port: options.port || this.port, + secure: options.secure || this.secure, + path: options.path || this.path, + forceJSONP: options.forceJSONP || this.forceJSONP, + jsonp: options.jsonp || this.jsonp, + forceBase64: options.forceBase64 || this.forceBase64, + enablesXDR: options.enablesXDR || this.enablesXDR, + timestampRequests: options.timestampRequests || this.timestampRequests, + timestampParam: options.timestampParam || this.timestampParam, + policyPort: options.policyPort || this.policyPort, + pfx: options.pfx || this.pfx, + key: options.key || this.key, + passphrase: options.passphrase || this.passphrase, + cert: options.cert || this.cert, + ca: options.ca || this.ca, + ciphers: options.ciphers || this.ciphers, + rejectUnauthorized: options.rejectUnauthorized || this.rejectUnauthorized, + perMessageDeflate: options.perMessageDeflate || this.perMessageDeflate, + extraHeaders: options.extraHeaders || this.extraHeaders, + forceNode: options.forceNode || this.forceNode, + localAddress: options.localAddress || this.localAddress, + requestTimeout: options.requestTimeout || this.requestTimeout, + protocols: options.protocols || void (0), + isReactNative: this.isReactNative + }); + + return transport; +}; + +function clone (obj) { + var o = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + return o; +} + +/** + * Initializes transport to use and starts probe. + * + * @api private + */ +Socket.prototype.open = function () { + var transport; + if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) { + transport = 'websocket'; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit('error', 'No transports available'); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = 'opening'; + + // Retry with the next transport if the transport is disabled (jsonp: false) + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); +}; + +/** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + +Socket.prototype.setTransport = function (transport) { + var self = this; + + if (this.transport) { + this.transport.removeAllListeners(); + } + + // set up transport + this.transport = transport; + + // set up transport listeners + transport + .on('drain', function () { + self.onDrain(); + }) + .on('packet', function (packet) { + self.onPacket(packet); + }) + .on('error', function (e) { + self.onError(e); + }) + .on('close', function () { + self.onClose('transport close'); + }); +}; + +/** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + +Socket.prototype.probe = function (name) { + var transport = this.createTransport(name, { probe: 1 }); + var failed = false; + var self = this; + + Socket.priorWebsocketSuccess = false; + + function onTransportOpen () { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + if (failed) return; + + transport.send([{ type: 'ping', data: 'probe' }]); + transport.once('packet', function (msg) { + if (failed) return; + if ('pong' === msg.type && 'probe' === msg.data) { + self.upgrading = true; + self.emit('upgrading', transport); + if (!transport) return; + Socket.priorWebsocketSuccess = 'websocket' === transport.name; + + self.transport.pause(function () { + if (failed) return; + if ('closed' === self.readyState) return; + + cleanup(); + + self.setTransport(transport); + transport.send([{ type: 'upgrade' }]); + self.emit('upgrade', transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + var err = new Error('probe error'); + err.transport = transport.name; + self.emit('upgradeError', err); + } + }); + } + + function freezeTransport () { + if (failed) return; + + // Any callback called by transport should be ignored since now + failed = true; + + cleanup(); + + transport.close(); + transport = null; + } + + // Handle any error that happens while probing + function onerror (err) { + var error = new Error('probe error: ' + err); + error.transport = transport.name; + + freezeTransport(); + + self.emit('upgradeError', error); + } + + function onTransportClose () { + onerror('transport closed'); + } + + // When the socket is closed while we're probing + function onclose () { + onerror('socket closed'); + } + + // When the socket is upgraded while we're probing + function onupgrade (to) { + if (transport && to.name !== transport.name) { + freezeTransport(); + } + } + + // Remove all listeners on the transport and on self + function cleanup () { + transport.removeListener('open', onTransportOpen); + transport.removeListener('error', onerror); + transport.removeListener('close', onTransportClose); + self.removeListener('close', onclose); + self.removeListener('upgrading', onupgrade); + } + + transport.once('open', onTransportOpen); + transport.once('error', onerror); + transport.once('close', onTransportClose); + + this.once('close', onclose); + this.once('upgrading', onupgrade); + + transport.open(); +}; + +/** + * Called when connection is deemed open. + * + * @api public + */ + +Socket.prototype.onOpen = function () { + this.readyState = 'open'; + Socket.priorWebsocketSuccess = 'websocket' === this.transport.name; + this.emit('open'); + this.flush(); + + // we check for `readyState` in case an `open` + // listener already closed the socket + if ('open' === this.readyState && this.upgrade && this.transport.pause) { + for (var i = 0, l = this.upgrades.length; i < l; i++) { + this.probe(this.upgrades[i]); + } + } +}; + +/** + * Handles a packet. + * + * @api private + */ + +Socket.prototype.onPacket = function (packet) { + if ('opening' === this.readyState || 'open' === this.readyState || + 'closing' === this.readyState) { + + this.emit('packet', packet); + + // Socket is live - any packet counts + this.emit('heartbeat'); + + switch (packet.type) { + case 'open': + this.onHandshake(JSON.parse(packet.data)); + break; + + case 'pong': + this.setPing(); + this.emit('pong'); + break; + + case 'error': + var err = new Error('server error'); + err.code = packet.data; + this.onError(err); + break; + + case 'message': + this.emit('data', packet.data); + this.emit('message', packet.data); + break; + } + } else { + } +}; + +/** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + +Socket.prototype.onHandshake = function (data) { + this.emit('handshake', data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); + // In case open handler closes socket + if ('closed' === this.readyState) return; + this.setPing(); + + // Prolong liveness of socket on heartbeat + this.removeListener('heartbeat', this.onHeartbeat); + this.on('heartbeat', this.onHeartbeat); +}; + +/** + * Resets ping timeout. + * + * @api private + */ + +Socket.prototype.onHeartbeat = function (timeout) { + clearTimeout(this.pingTimeoutTimer); + var self = this; + self.pingTimeoutTimer = setTimeout(function () { + if ('closed' === self.readyState) return; + self.onClose('ping timeout'); + }, timeout || (self.pingInterval + self.pingTimeout)); +}; + +/** + * Pings server every `this.pingInterval` and expects response + * within `this.pingTimeout` or closes connection. + * + * @api private + */ + +Socket.prototype.setPing = function () { + var self = this; + clearTimeout(self.pingIntervalTimer); + self.pingIntervalTimer = setTimeout(function () { + self.ping(); + self.onHeartbeat(self.pingTimeout); + }, self.pingInterval); +}; + +/** +* Sends a ping packet. +* +* @api private +*/ + +Socket.prototype.ping = function () { + var self = this; + this.sendPacket('ping', function () { + self.emit('ping'); + }); +}; + +/** + * Called on `drain` event + * + * @api private + */ + +Socket.prototype.onDrain = function () { + this.writeBuffer.splice(0, this.prevBufferLen); + + // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit('drain'); + } else { + this.flush(); + } +}; + +/** + * Flush write buffers. + * + * @api private + */ + +Socket.prototype.flush = function () { + if ('closed' !== this.readyState && this.transport.writable && + !this.upgrading && this.writeBuffer.length) { + this.transport.send(this.writeBuffer); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = this.writeBuffer.length; + this.emit('flush'); + } +}; + +/** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + +Socket.prototype.write = +Socket.prototype.send = function (msg, options, fn) { + this.sendPacket('message', msg, options, fn); + return this; +}; + +/** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + +Socket.prototype.sendPacket = function (type, data, options, fn) { + if ('function' === typeof data) { + fn = data; + data = undefined; + } + + if ('function' === typeof options) { + fn = options; + options = null; + } + + if ('closing' === this.readyState || 'closed' === this.readyState) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + + var packet = { + type: type, + data: data, + options: options + }; + this.emit('packetCreate', packet); + this.writeBuffer.push(packet); + if (fn) this.once('flush', fn); + this.flush(); +}; + +/** + * Closes the connection. + * + * @api private + */ + +Socket.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.readyState = 'closing'; + + var self = this; + + if (this.writeBuffer.length) { + this.once('drain', function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close () { + self.onClose('forced close'); + self.transport.close(); + } + + function cleanupAndClose () { + self.removeListener('upgrade', cleanupAndClose); + self.removeListener('upgradeError', cleanupAndClose); + close(); + } + + function waitForUpgrade () { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once('upgrade', cleanupAndClose); + self.once('upgradeError', cleanupAndClose); + } + + return this; +}; + +/** + * Called upon transport error + * + * @api private + */ + +Socket.prototype.onError = function (err) { + Socket.priorWebsocketSuccess = false; + this.emit('error', err); + this.onClose('transport error', err); +}; + +/** + * Called upon transport close. + * + * @api private + */ + +Socket.prototype.onClose = function (reason, desc) { + if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) { + var self = this; + + // clear timers + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); + + // stop event from firing again for transport + this.transport.removeAllListeners('close'); + + // ensure transport won't stay open + this.transport.close(); + + // ignore further transport communication + this.transport.removeAllListeners(); + + // set ready state + this.readyState = 'closed'; + + // clear session id + this.id = null; + + // emit close event + this.emit('close', reason, desc); + + // clean buffers after, so users can still + // grab the buffers on `close` event + self.writeBuffer = []; + self.prevBufferLen = 0; + } +}; + +/** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + +Socket.prototype.filterUpgrades = function (upgrades) { + var filteredUpgrades = []; + for (var i = 0, j = upgrades.length; i < j; i++) { + if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; +}; + +},{"./transport":4,"./transports/index":5,"component-emitter":14,"engine.io-parser":16,"indexof":22,"parseqs":24,"parseuri":25}],4:[function(_dereq_,module,exports){ +/** + * Module dependencies. + */ + +var parser = _dereq_('engine.io-parser'); +var Emitter = _dereq_('component-emitter'); + +/** + * Module exports. + */ + +module.exports = Transport; + +/** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + +function Transport (opts) { + this.path = opts.path; + this.hostname = opts.hostname; + this.port = opts.port; + this.secure = opts.secure; + this.query = opts.query; + this.timestampParam = opts.timestampParam; + this.timestampRequests = opts.timestampRequests; + this.readyState = ''; + this.agent = opts.agent || false; + this.socket = opts.socket; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + this.forceNode = opts.forceNode; + + // results of ReactNative environment detection + this.isReactNative = opts.isReactNative; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + this.localAddress = opts.localAddress; +} + +/** + * Mix in `Emitter`. + */ + +Emitter(Transport.prototype); + +/** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + +Transport.prototype.onError = function (msg, desc) { + var err = new Error(msg); + err.type = 'TransportError'; + err.description = desc; + this.emit('error', err); + return this; +}; + +/** + * Opens the transport. + * + * @api public + */ + +Transport.prototype.open = function () { + if ('closed' === this.readyState || '' === this.readyState) { + this.readyState = 'opening'; + this.doOpen(); + } + + return this; +}; + +/** + * Closes the transport. + * + * @api private + */ + +Transport.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; +}; + +/** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + +Transport.prototype.send = function (packets) { + if ('open' === this.readyState) { + this.write(packets); + } else { + throw new Error('Transport not open'); + } +}; + +/** + * Called upon open + * + * @api private + */ + +Transport.prototype.onOpen = function () { + this.readyState = 'open'; + this.writable = true; + this.emit('open'); +}; + +/** + * Called with data. + * + * @param {String} data + * @api private + */ + +Transport.prototype.onData = function (data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); +}; + +/** + * Called with a decoded packet. + */ + +Transport.prototype.onPacket = function (packet) { + this.emit('packet', packet); +}; + +/** + * Called upon close. + * + * @api private + */ + +Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); +}; + +},{"component-emitter":14,"engine.io-parser":16}],5:[function(_dereq_,module,exports){ +/** + * Module dependencies + */ + +var XMLHttpRequest = _dereq_('xmlhttprequest-ssl'); +var XHR = _dereq_('./polling-xhr'); +var JSONP = _dereq_('./polling-jsonp'); +var websocket = _dereq_('./websocket'); + +/** + * Export transports. + */ + +exports.polling = polling; +exports.websocket = websocket; + +/** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + +function polling (opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (typeof location !== 'undefined') { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } +} + +},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,"xmlhttprequest-ssl":10}],6:[function(_dereq_,module,exports){ +(function (global){ +/** + * Module requirements. + */ + +var Polling = _dereq_('./polling'); +var inherit = _dereq_('component-inherit'); + +/** + * Module exports. + */ + +module.exports = JSONPPolling; + +/** + * Cached regular expressions. + */ + +var rNewline = /\n/g; +var rEscapedNewline = /\\n/g; + +/** + * Global JSONP callbacks. + */ + +var callbacks; + +/** + * Noop. + */ + +function empty () { } + +/** + * Until https://github.com/tc39/proposal-global is shipped. + */ +function glob () { + return typeof self !== 'undefined' ? self + : typeof window !== 'undefined' ? window + : typeof global !== 'undefined' ? global : {}; +} + +/** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + +function JSONPPolling (opts) { + Polling.call(this, opts); + + this.query = this.query || {}; + + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + var global = glob(); + callbacks = global.___eio = (global.___eio || []); + } + + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (typeof addEventListener === 'function') { + addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); + } +} + +/** + * Inherits from Polling. + */ + +inherit(JSONPPolling, Polling); + +/* + * JSONP only supports binary as base64 encoded strings + */ + +JSONPPolling.prototype.supportsBinary = false; + +/** + * Closes the socket. + * + * @api private + */ + +JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + Polling.prototype.doClose.call(this); +}; + +/** + * Starts a poll cycle. + * + * @api private + */ + +JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + script.onerror = function (e) { + self.onError('jsonp poll error', e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + this.script = script; + + var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } +}; + +/** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + +JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); + } + + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '