From efa61be5e1c2a1524c93db5ba8514eee2605f686 Mon Sep 17 00:00:00 2001 From: werner mendizabal Date: Mon, 25 Jun 2018 00:13:27 -0500 Subject: [PATCH 1/3] RFE: remove pusher.com dependency --- .env.example | 4 + .env.local.example | 21 + README.md | 34 + app.json | 12 + coturn/Dockerfile | 10 + coturn/turnserver.conf | 6 + docker-compose.yml | 59 + index.js | 6 +- lib/coturn-ice-server-provider.js | 27 + lib/server.js | 56 +- lib/socketcluster-pub-sub-gateway.js | 31 + lib/twilio-ice-server-provider.js | 15 + package-lock.json | 65 + package.json | 1 + socketcluster/.dockerignore | 1 + socketcluster/Dockerfile | 15 + socketcluster/README.md | 5 + socketcluster/broker.js | 29 + socketcluster/dockerwait.js | 23 + socketcluster/package-lock.json | 1477 +++++++ socketcluster/package.json | 36 + socketcluster/public/favicon.ico | Bin 0 -> 52690 bytes socketcluster/public/img/logo.png | Bin 0 -> 19587 bytes socketcluster/public/index.html | 99 + socketcluster/public/socketcluster.js | 5685 +++++++++++++++++++++++++ socketcluster/server.js | 106 + socketcluster/worker.js | 43 + 27 files changed, 7854 insertions(+), 12 deletions(-) create mode 100644 .env.local.example create mode 100644 coturn/Dockerfile create mode 100644 coturn/turnserver.conf create mode 100644 docker-compose.yml create mode 100644 lib/coturn-ice-server-provider.js create mode 100644 lib/socketcluster-pub-sub-gateway.js create mode 100644 lib/twilio-ice-server-provider.js create mode 100644 socketcluster/.dockerignore create mode 100644 socketcluster/Dockerfile create mode 100644 socketcluster/README.md create mode 100644 socketcluster/broker.js create mode 100644 socketcluster/dockerwait.js create mode 100644 socketcluster/package-lock.json create mode 100644 socketcluster/package.json create mode 100644 socketcluster/public/favicon.ico create mode 100644 socketcluster/public/img/logo.png create mode 100644 socketcluster/public/index.html create mode 100644 socketcluster/public/socketcluster.js create mode 100644 socketcluster/server.js create mode 100644 socketcluster/worker.js diff --git a/.env.example b/.env.example index 558ee17..5d58f89 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,7 @@ GITHUB_CLIENT_ID=redacted GITHUB_CLIENT_SECRET=redacted GITHUB_OAUTH_TOKEN=redacted HASH_SECRET=redacted +COTURN_USERNAME=redacted +COTURN_PASSWORD=redacted +ACTIVE_PUB_SUB_GATEWAY=pusher +ACTIVE_ICE_SERVER_PROVIDER=twilio diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..7be2be1 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,21 @@ +DATABASE_URL=postgres://teletype:password@localhost:5432/teletype-server-dev +TEST_DATABASE_URL=postgres://teletype:password@localhost:5433/teletype-server-test +PORT=3000 +PUSHER_APP_ID=348824 +PUSHER_KEY=redacted +PUSHER_SECRET=redacted +PUSHER_CLUSTER=mt1 +TWILIO_ACCOUNT=redacted +TWILIO_AUTH_TOKEN=redacted +NEW_RELIC_ENABLED=false +NEW_RELIC_APP_NAME=atom-teletype-development +NEW_RELIC_LICENSE_KEY=redacted +GITHUB_API_URL=https://api.github.com +GITHUB_CLIENT_ID=redacted +GITHUB_CLIENT_SECRET=redacted +GITHUB_OAUTH_TOKEN=redacted +HASH_SECRET=redacted +COTURN_USERNAME=teletype +COTURN_PASSWORD=password +ACTIVE_PUB_SUB_GATEWAY=socketcluster +ACTIVE_ICE_SERVER_PROVIDER=coturn diff --git a/README.md b/README.md index af19eb6..408f42a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,40 @@ To run teletype-server locally, you'll first need to have: npm test ``` +### Running locally using docker + +This allows to deploy teletype-server to a full private solution. +Coturn is used instead of Twilio and SocketCluster instead of Pusher. + +1. Clone and bootstrap + + ``` + git clone https://github.com/atom/teletype-server.git + cd teletype-server + cp .env.local.example .env + docker-compose build + docker-compose up -d + createdb teletype-server-dev + createdb teletype-server-test + npm install + npm run migrate up + ``` + +2. Copy the client ID and client secret for your OAuth app on github.com, and set those values in your `.env` file + +3. Start the server + + ``` + ./script/server + ``` + +4. Run the tests + + ``` + npm test + ``` + + ## Deploying Atom core team members can use [this guide](./docs/deployment.md) to test pull requests and deploy changes to production. diff --git a/app.json b/app.json index 0f2f769..8bc505c 100644 --- a/app.json +++ b/app.json @@ -40,6 +40,18 @@ }, "TWILIO_AUTH_TOKEN": { "required": true + }, + "COTURN_USERNAME": { + "required": true + }, + "COTURN_PASSWORD": { + "required": true + }, + "ACTIVE_PUB_SUB_GATEWAY": { + "required": true + }, + "ACTIVE_ICE_SERVER_PROVIDER": { + "required": true } }, "formation": {}, diff --git a/coturn/Dockerfile b/coturn/Dockerfile new file mode 100644 index 0000000..9bd373d --- /dev/null +++ b/coturn/Dockerfile @@ -0,0 +1,10 @@ +FROM ubuntu:18.04 + +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y coturn + +ADD turnserver.conf /etc/turnserver.conf + +EXPOSE 3478 + +CMD exec /bin/bash -c "trap : TERM INT; /usr/bin/turnserver -c /etc/turnserver.conf -v" diff --git a/coturn/turnserver.conf b/coturn/turnserver.conf new file mode 100644 index 0000000..6b1c8e5 --- /dev/null +++ b/coturn/turnserver.conf @@ -0,0 +1,6 @@ +no-stun +verbose +fingerprint +lt-cred-mech +user=teletype:password +realm=teletype diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bcc7a67 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +version: '3' + +services: + database: + image: postgres + ports: + - 5432:5432 + volumes: + - ./database:/var/lib/postgresql/data + restart: always + environment: + POSTGRES_USER: teletype + POSTGRES_PASSWORD: password + POSTGRES_DB: teletype-server-dev + networks: + teletype: + + test-database: + image: postgres + ports: + - 5433:5432 + volumes: + - ./test-database:/var/lib/postgresql/data + restart: always + environment: + POSTGRES_USER: teletype + POSTGRES_PASSWORD: password + POSTGRES_DB: teletype-server-test + networks: + teletype: + + coturn: + image: coturn + build: + context: coturn + dockerfile: Dockerfile + ports: + - 3478:3478 + restart: always + networks: + teletype: + aliases: + - coturn + + socketcluster: + image: socketcluster + build: + context: socketcluster + dockerfile: Dockerfile + ports: + - 8000:8000 + restart: always + networks: + teletype: + aliases: + - socketcluster + +networks: + teletype: diff --git a/index.js b/index.js index 85ef3bc..1a55e4a 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,11 @@ async function startServer (id) { githubOauthToken: process.env.GITHUB_OAUTH_TOKEN, boomtownSecret: process.env.BOOMTOWN_SECRET, hashSecret: process.env.HASH_SECRET, - port: process.env.PORT || 3000 + port: process.env.PORT || 3000, + coturnUsername: process.env.COTURN_USERNAME, + coturnPassword: process.env.COTURN_PASSWORD, + activePubSubGateway: process.env.ACTIVE_PUB_SUB_GATEWAY, + activeIceServerProvider: process.env.ACTIVE_ICE_SERVER_PROVIDER }) await server.start() console.log(`Worker ${id} (pid: ${process.pid}): listening on port ${server.port}`) diff --git a/lib/coturn-ice-server-provider.js b/lib/coturn-ice-server-provider.js new file mode 100644 index 0000000..8ab48e2 --- /dev/null +++ b/lib/coturn-ice-server-provider.js @@ -0,0 +1,27 @@ +module.exports = +class CoturnIceServerProvider { + constructor ({coturnUsername, coturnPassword}) { + this.coturnUsername = coturnUsername + this.coturnPassword = coturnPassword + } + + async fetchICEServers () { + return {ttl: 86400, servers: [ + { + 'urls': 'stun:stun.l.google.com:19302' + }, + { + 'urls': 'turn:localhost:3478?transport=udp', + 'username': this.coturnUsername, + 'credential': this.coturnPassword + }, + { + 'urls': 'turn:localhost:3478?transport=tcp', + 'username': this.coturnUsername, + 'credential': this.coturnPassword + } + ] + } + + } +} diff --git a/lib/server.js b/lib/server.js index 672c32a..06d644d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3,7 +3,10 @@ const pgp = require('pg-promise')() const request = require('request-promise-native') const IdentityProvider = require('./identity-provider') const ModelLayer = require('./model-layer') -const PubSubGateway = require('./pusher-pub-sub-gateway') +const PusherPubSubGateway = require('./pusher-pub-sub-gateway') +const SocketClusterPubSubGateway = require('./socketcluster-pub-sub-gateway') +const TwilioIceServerProvider = require('./twilio-ice-server-provider') +const CoturnIceServerProvider = require('./coturn-ice-server-provider') module.exports = class Server { @@ -21,7 +24,11 @@ class Server { this.githubOauthToken = options.githubOauthToken this.boomtownSecret = options.boomtownSecret this.hashSecret = options.hashSecret - this.port = options.port + this.port = options.port, + this.coturnUsername = options.coturnUsername, + this.coturnPassword = options.coturnPassword, + this.activePubSubGateway = options.activePubSubGateway, + this.activeIceServerProvider = options.activeIceServerProvider } async start () { @@ -33,14 +40,42 @@ class Server { clientSecret: this.githubClientSecret, oauthToken: this.githubOauthToken }) - const pubSubGateway = new PubSubGateway({ - appId: this.pusherAppId, - key: this.pusherKey, - secret: this.pusherSecret, - cluster: this.pusherCluster - }) - const twilioICEServerURL = `https://${this.twilioAccount}:${this.twilioAuthToken}@api.twilio.com/2010-04-01/Accounts/${this.twilioAccount}/Tokens.json` + var pubSubGateway + + switch(this.activePubSubGateway) { + case 'socketcluster': + pubSubGateway = new SocketClusterPubSubGateway({}) + break; + + case 'pusher': + default: + pubSubGateway = new PusherPubSubGateway({ + appId: this.pusherAppId, + key: this.pusherKey, + secret: this.pusherSecret, + cluster: this.pusherCluster + }) + break; + } + + var iceServerProvider + switch(this.activeIceServerProvider) { + case 'coturn': + iceServerProvider = new CoturnIceServerProvider({ + coturnUsername: this.coturnUsername, + coturnPassword: this.coturnPassword + }) + break; + + case 'twilio': + default: + iceServerProvider = new TwilioIceServerProvider({ + twilioAccount: this.twilioAccount, + twilioAuthToken: this.twilioAuthToken + }) + break; + } const controllerLayer = buildControllerLayer({ modelLayer, @@ -48,8 +83,7 @@ class Server { identityProvider, boomtownSecret: this.boomtownSecret, fetchICEServers: async () => { - const response = JSON.parse(await request.post(twilioICEServerURL)) - return {ttl: parseInt(response.ttl), servers: response.ice_servers} + iceServerProvider.fetchICEServers() } }) diff --git a/lib/socketcluster-pub-sub-gateway.js b/lib/socketcluster-pub-sub-gateway.js new file mode 100644 index 0000000..ae23fd8 --- /dev/null +++ b/lib/socketcluster-pub-sub-gateway.js @@ -0,0 +1,31 @@ +const socketCluster = require('socketcluster-client'); + +module.exports = +class SocketClusterPubSubGateway { + constructor ({appId, key, secret, cluster}) { + var options = { + port: 8000, + autoConnect: true, + autoReconnect: true + }; + + this.socket = socketCluster.create(options); + this.socket.on('error', function (error) { + }); + } + + broadcast (channelName, eventName, data) { + channelName = channelName.replace(/\//g, '.') + var message = { + channel: `${channelName}.${eventName}`, + data: data + } + this.socket.emit('publish', message); + } + + async isOperational () { + return new Promise((resolve) => { + resolve(this.socket.state == this.socket.OPEN) + }) + } +} diff --git a/lib/twilio-ice-server-provider.js b/lib/twilio-ice-server-provider.js new file mode 100644 index 0000000..a3a51dd --- /dev/null +++ b/lib/twilio-ice-server-provider.js @@ -0,0 +1,15 @@ +module.exports = +class TwilioIceServerProvider { + constructor ({twilioAccount, twilioAuthToken}) { + this.twilioAccount = twilioAccount + this.twilioAuthToken = twilioAuthToken + } + + async fetchICEServers () { + const twilioICEServerURL = `https://${this.twilioAccount}:${this.twilioAuthToken}@api.twilio.com/2010-04-01/Accounts/${this.twilioAccount}/Tokens.json` + + const response = JSON.parse(await request.post(twilioICEServerURL)) + return {ttl: parseInt(response.ttl), servers: response.ice_servers} + + } +} diff --git a/package-lock.json b/package-lock.json index b636339..0d763ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -249,6 +249,11 @@ } } }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -272,6 +277,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -895,6 +905,11 @@ "invert-kv": "1.0.0" } }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=" + }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -1621,6 +1636,24 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "requires": { + "component-emitter": "1.2.1" + } + }, + "sc-errors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + }, + "sc-formatter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", + "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", @@ -1708,6 +1741,30 @@ "hoek": "2.16.3" } }, + "socketcluster-client": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.0.tgz", + "integrity": "sha512-Fdgm6j0TgdXIkVrMI2oBzjW6WL22HmGaX6Uky5I/DmhfJGAu9hoG45VnBxUf+jE+ciZHx5NHorSp00A1YMLvCw==", + "requires": { + "base-64": "0.1.0", + "clone": "2.1.1", + "component-emitter": "1.2.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "^1.2.0", + "sc-errors": "^1.4.0", + "sc-formatter": "^3.0.1", + "uuid": "3.2.1", + "ws": "5.1.1" + }, + "dependencies": { + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + } + } + }, "spex": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/spex/-/spex-2.0.2.tgz", @@ -1955,6 +2012,14 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "requires": { + "async-limiter": "~1.0.0" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 9a254fc..4584e3f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "request": "^2.81.0", "request-promise-native": "^1.0.4", "server-destroy": "^1.0.1", + "socketcluster-client": "^13.0.0", "temp": "^0.8.3", "throng": "^4.0.0", "uuid": "^3.0.1" diff --git a/socketcluster/.dockerignore b/socketcluster/.dockerignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/socketcluster/.dockerignore @@ -0,0 +1 @@ +node_modules/ diff --git a/socketcluster/Dockerfile b/socketcluster/Dockerfile new file mode 100644 index 0000000..afedc80 --- /dev/null +++ b/socketcluster/Dockerfile @@ -0,0 +1,15 @@ +FROM node:8-slim + +LABEL maintainer="Jonathan Gros-Dubois" +LABEL version="13.1.7" +LABEL description="Docker file for SocketCluster with support for clustering." + +RUN mkdir -p /usr/src/ +WORKDIR /usr/src/ +COPY . /usr/src/ + +RUN npm install . + +EXPOSE 8000 + +CMD ["npm", "run", "start:docker"] diff --git a/socketcluster/README.md b/socketcluster/README.md new file mode 100644 index 0000000..f4a3856 --- /dev/null +++ b/socketcluster/README.md @@ -0,0 +1,5 @@ +SocketCluster Sample App +====== + +This is a sample SocketCluster app. + diff --git a/socketcluster/broker.js b/socketcluster/broker.js new file mode 100644 index 0000000..4297c34 --- /dev/null +++ b/socketcluster/broker.js @@ -0,0 +1,29 @@ +var SCBroker = require('socketcluster/scbroker'); +var scClusterBrokerClient = require('scc-broker-client'); + +class Broker extends SCBroker { + run() { + console.log(' >> Broker PID:', process.pid); + + // This is defined in server.js (taken from environment variable SC_CLUSTER_STATE_SERVER_HOST). + // If this property is defined, the broker will try to attach itself to the SC cluster for + // automatic horizontal scalability. + // This is mostly intended for the Kubernetes deployment of SocketCluster - In this case, + // The clustering/sharding all happens automatically. + + if (this.options.clusterStateServerHost) { + scClusterBrokerClient.attach(this, { + stateServerHost: this.options.clusterStateServerHost, + stateServerPort: this.options.clusterStateServerPort, + mappingEngine: this.options.clusterMappingEngine, + clientPoolSize: this.options.clusterClientPoolSize, + authKey: this.options.clusterAuthKey, + stateServerConnectTimeout: this.options.clusterStateServerConnectTimeout, + stateServerAckTimeout: this.options.clusterStateServerAckTimeout, + stateServerReconnectRandomness: this.options.clusterStateServerReconnectRandomness + }); + } + } +} + +new Broker(); diff --git a/socketcluster/dockerwait.js b/socketcluster/dockerwait.js new file mode 100644 index 0000000..8e13de3 --- /dev/null +++ b/socketcluster/dockerwait.js @@ -0,0 +1,23 @@ +/* + This script waits for the master controller script to become available. + With orchestrators like Kubernetes, the master controller file may be fed in through + a volume container at runtime and so it is necessary to wait for it before launch. +*/ + +var fsUtil = require('socketcluster/fsutil'); +var waitForFile = fsUtil.waitForFile; + +var SOCKETCLUSTER_MASTER_DEFAULT_CONTROLLER = './server.js'; +var masterControllerPath = process.env.SOCKETCLUSTER_MASTER_CONTROLLER || SOCKETCLUSTER_MASTER_DEFAULT_CONTROLLER; +var bootCheckInterval = Number(process.env.SOCKETCLUSTER_BOOT_CHECK_INTERVAL) || 200; +var bootTimeout = Number(process.env.SOCKETCLUSTER_CONTROLLER_BOOT_TIMEOUT) || 10000; +var bootStartTime = Date.now(); + +var errorMessage = `Failed to locate the master controller file at path ${masterControllerPath} ` + +`before SOCKETCLUSTER_CONTROLLER_BOOT_TIMEOUT`; + +waitForFile(masterControllerPath, bootCheckInterval, bootStartTime, bootTimeout, errorMessage) +.catch((err) => { + console.error('> Boot error: ' + err.message); + process.exit(1); +}); diff --git a/socketcluster/package-lock.json b/socketcluster/package-lock.json new file mode 100644 index 0000000..c31bcf0 --- /dev/null +++ b/socketcluster/package-lock.json @@ -0,0 +1,1477 @@ +{ + "name": "socketcluster-sample", + "version": "1.0.0", + "lockfileVersion": 1, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=" + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==" + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", + "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=" + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=" + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==" + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "chokidar": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz", + "integrity": "sha1-L0RHq16W5Q+z14n9kNTHLg5McMI=" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=" + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==" + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "connect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.0.1.tgz", + "integrity": "sha1-Aw4s3wNysSiSpoV8Rlopi7lE71A=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "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==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.2.tgz", + "integrity": "sha1-OElZHBDM5khHbDx8Li40FttZY8Q=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=" + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=" + }, + "expirymanager": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/expirymanager/-/expirymanager-0.9.3.tgz", + "integrity": "sha1-5fazugDY12z2MxHCtx19/JvePk8=" + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==" + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==" + }, + "finalhandler": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.0.2.tgz", + "integrity": "sha1-BgPYde6H1WeiZmkoFcyK1E/M7to=" + }, + "fleximap": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/fleximap/-/fleximap-0.9.10.tgz", + "integrity": "sha1-GqUP9qj+oAN8w3jjjdrMCRAlrBA=" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "optional": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "optional": true + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "optional": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "optional": true + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==" + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=" + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=" + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=" + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==" + }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=" + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=" + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=" + }, + "jsonwebtoken": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==" + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=" + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==" + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + }, + "minimist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.0.tgz", + "integrity": "sha1-zfIl6ImPhAolje1E/JF3Z3Cv3JM=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "morgan": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.7.0.tgz", + "integrity": "sha1-6xDKjlDRq+D409rVwCAdBS2YHGI=", + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "optional": true + }, + "ncom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ncom/-/ncom-1.0.1.tgz", + "integrity": "sha512-g7/hfG/yYNoi4GMwiyW1F17KFcDhgQ7YLOe+889sfaS+D4G9qcpLvtQu7FY5diDa6K+mLNU7tdhy5KD5t68ByQ==" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=" + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=" + }, + "parseurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.0.1.tgz", + "integrity": "sha1-Llfc5u/dN8NRhwEDCUTCK/OIt7Q=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "randomatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==" + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==" + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=" + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=" + }, + "rxjs": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sc-auth": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sc-auth/-/sc-auth-5.0.1.tgz", + "integrity": "sha512-UgTdvJZS17wkLfNNwj8/uObfACUiGPc5Jl2VmTPpwipasj1kO1Sr64VVhCBUz38j+f/9rBCKlzxCTwljxgX95Q==" + }, + "sc-broker": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/sc-broker/-/sc-broker-5.1.3.tgz", + "integrity": "sha512-5mWOGPrh+HokIPVRPESnbvHNGbsgmhiu8LVQIxkA/gNkLUGRJIYudXLekWufqOtbmheCI91z3VAacmDsB/LzYQ==", + "dependencies": { + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + } + } + }, + "sc-broker-cluster": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/sc-broker-cluster/-/sc-broker-cluster-6.1.5.tgz", + "integrity": "sha512-9plfm1TSBWO+9oSvUXCFl9wT1fJjNwl5H22alnTIloXzxDYVqvN0kaPn3WWJOP4tZUdeXKWKQlENAYE+5F9L7A==", + "dependencies": { + "async": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.0.0.tgz", + "integrity": "sha1-0JAK04WvE4BFQKEJxCFm4657K50=" + } + } + }, + "sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==" + }, + "sc-errors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.0.tgz", + "integrity": "sha512-h+jRWx/xRJmkPFDd0IltoTl/QJ6hAr5Y+3ZVeBQRLuWZKe+dHdf2uVwFp2OYqlLQ7GHht4y9eXG2zOf2Ik6PTw==" + }, + "sc-formatter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", + "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==" + }, + "sc-framework-health-check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sc-framework-health-check/-/sc-framework-health-check-2.0.0.tgz", + "integrity": "sha512-AXX6ieqT/PEbTQJAnmAWPGb7o2zoKtry1pToALk4VxLknSbitAh3ho4be8yNN9/FsIR8KJkZza7fJ2xx/NkUrw==" + }, + "sc-hasher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sc-hasher/-/sc-hasher-1.0.0.tgz", + "integrity": "sha1-uyKuH1opW4R8cK/0UVU2IklQ/xE=" + }, + "sc-hot-reboot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sc-hot-reboot/-/sc-hot-reboot-1.0.0.tgz", + "integrity": "sha1-TcxGLuaOc7fP6TH506fT1vjWNss=" + }, + "sc-simple-broker": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sc-simple-broker/-/sc-simple-broker-2.1.2.tgz", + "integrity": "sha512-8hbr47jLhrMecShZi6lunEeUPySkuLHlpg6G7g5jbBJQRrBiFiTuQdwk7KpMwAjLBh1qfaoku9Z+yWieOd5oLA==" + }, + "sc-uws": { + "version": "10.148.0", + "resolved": "https://registry.npmjs.org/sc-uws/-/sc-uws-10.148.0.tgz", + "integrity": "sha1-OFZXma6WHlTM2kWUeihWyrSMj20=" + }, + "scc-broker-client": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/scc-broker-client/-/scc-broker-client-5.0.2.tgz", + "integrity": "sha512-6CQOkMQYfopMk1G7jGCdPBiXktbEOCVoFRI7atn7/kWlIk+B5xifEcj2aCdX3AM+RiI6GtQhhN3T/SXrF+y5rA==", + "dependencies": { + "socketcluster-client": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-11.2.2.tgz", + "integrity": "sha512-Jo6+yDxBn7BDqj4LazZjL+eK55yfwTl7Grzf2v/SO3pq3JEqdZimXDC+Pi8uN58BXqYj+dg9W9PFGU6pWZS4Vw==" + } + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dependencies": { + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + } + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "skeleton-rendezvous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/skeleton-rendezvous/-/skeleton-rendezvous-1.1.0.tgz", + "integrity": "sha512-PGS4TXJmnSBHqf4cQS3ee4Ohbs2IScvlVlLKLjEgOp/W/bD5tf7pLFEiTUh4HFEKPVafpeGTBjeLFjlQfDOamg==" + }, + "socketcluster": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/socketcluster/-/socketcluster-13.1.8.tgz", + "integrity": "sha512-EwKqh+o8rNfYeK8NIRc8MfLsAppCTZDgM2ODrQG8iMtKzmqyEruNW036ZpSmTYf52kpaG5Kqjn2ezAnjuVc5Zw==", + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "socketcluster-client": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.0.tgz", + "integrity": "sha512-Fdgm6j0TgdXIkVrMI2oBzjW6WL22HmGaX6Uky5I/DmhfJGAu9hoG45VnBxUf+jE+ciZHx5NHorSp00A1YMLvCw==" + }, + "socketcluster-server": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-13.1.2.tgz", + "integrity": "sha512-ynLjVFYpFCgIbWJH4AvemVKKyGBUljmGOMo36cC+Ucj9i0XfH4mPjlyo5Kaie5kWu+uH6RDChO4n8ir4m1QmUw==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=" + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==" + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==" + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==" + } + } +} diff --git a/socketcluster/package.json b/socketcluster/package.json new file mode 100644 index 0000000..f8640e5 --- /dev/null +++ b/socketcluster/package.json @@ -0,0 +1,36 @@ +{ + "name": "socketcluster-sample", + "description": "A sample SocketCluster app", + "version": "1.0.0", + "contributors": [ + { + "name": "Jonathan Gros-Dubois", + "email": "grosjona@yahoo.com.au" + } + ], + "dependencies": { + "connect": "3.0.1", + "express": "4.16.3", + "minimist": "1.1.0", + "morgan": "1.7.0", + "sc-errors": "^1.4.0", + "sc-framework-health-check": "^2.0.0", + "sc-hot-reboot": "^1.0.0", + "scc-broker-client": "^5.0.2", + "serve-static": "1.13.2", + "socketcluster": "^13.1.8", + "socketcluster-client": "^13.0.0" + }, + "keywords": [ + "websocket", + "server", + "realtime", + "cluster", + "scalable" + ], + "readmeFilename": "README.md", + "scripts": { + "start": "node server.js", + "start:docker": "node dockerwait.js && node ${SOCKETCLUSTER_MASTER_CONTROLLER:-server.js}" + } +} diff --git a/socketcluster/public/favicon.ico b/socketcluster/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e286d11643f0972d044b2b5d50a1cb0cbabad205 GIT binary patch literal 52690 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%0DhR0L2#Jvbr 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 literal 0 HcmV?d00001 diff --git a/socketcluster/public/img/logo.png b/socketcluster/public/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8989a3ab818aec60a693dc9a3c91904469c2c5 GIT binary patch literal 19587 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 literal 0 HcmV?d00001 diff --git a/socketcluster/public/index.html b/socketcluster/public/index.html new file mode 100644 index 0000000..283ec61 --- /dev/null +++ b/socketcluster/public/index.html @@ -0,0 +1,99 @@ + + + + SocketCluster + + + + + + +
+
+
+ SocketCluster +
+
+ Design is not just what it looks like and feels like. Design is how it works. — Steve Jobs +
+ +

+ +

+
+
+ + + diff --git a/socketcluster/public/socketcluster.js b/socketcluster/public/socketcluster.js new file mode 100644 index 0000000..14e1d09 --- /dev/null +++ b/socketcluster/public/socketcluster.js @@ -0,0 +1,5685 @@ +/** + * SocketCluster JavaScript client v13.0.0 + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.socketCluster = 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 maxTimeout) { + throw new InvalidArgumentsError('The ' + propertyName + + ' value provided exceeded the maximum amount allowed'); + } + }; + + verifyDuration('connectTimeout'); + verifyDuration('ackTimeout'); + + this._localEvents = { + 'connect': 1, + 'connectAbort': 1, + 'close': 1, + 'disconnect': 1, + 'message': 1, + 'error': 1, + 'raw': 1, + 'kickOut': 1, + 'subscribe': 1, + 'unsubscribe': 1, + 'subscribeStateChange': 1, + 'authStateChange': 1, + 'authenticate': 1, + 'deauthenticate': 1, + 'removeAuthToken': 1, + 'subscribeRequest': 1 + }; + + this.connectAttempts = 0; + + this._emitBuffer = new LinkedList(); + this.channels = {}; + + this.options = opts; + + this._cid = 1; + + this.options.callIdGenerator = function () { + return self._cid++; + }; + + if (this.options.autoReconnect) { + if (this.options.autoReconnectOptions == null) { + this.options.autoReconnectOptions = {}; + } + + // Add properties to the this.options.autoReconnectOptions object. + // We assign the reference to a reconnectOptions variable to avoid repetition. + var reconnectOptions = this.options.autoReconnectOptions; + if (reconnectOptions.initialDelay == null) { + reconnectOptions.initialDelay = 10000; + } + if (reconnectOptions.randomness == null) { + reconnectOptions.randomness = 10000; + } + if (reconnectOptions.multiplier == null) { + reconnectOptions.multiplier = 1.5; + } + if (reconnectOptions.maxDelay == null) { + reconnectOptions.maxDelay = 60000; + } + } + + if (this.options.subscriptionRetryOptions == null) { + this.options.subscriptionRetryOptions = {}; + } + + if (this.options.authEngine) { + this.auth = this.options.authEngine; + } else { + this.auth = new AuthEngine(); + } + + if (this.options.codecEngine) { + this.codec = this.options.codecEngine; + } else { + // Default codec engine + this.codec = formatter; + } + + this.options.path = this.options.path.replace(/\/$/, '') + '/'; + + this.options.query = opts.query || {}; + if (typeof this.options.query == 'string') { + this.options.query = querystring.parse(this.options.query); + } + + this._channelEmitter = new Emitter(); + + this._unloadHandler = function () { + self.disconnect(); + }; + + if (isBrowser && this.disconnectOnUnload && global.addEventListener) { + global.addEventListener('beforeunload', this._unloadHandler, false); + } + this._clientMap[this.clientId] = this; + + if (this.options.autoConnect) { + this.connect(); + } +}; + +SCClientSocket.prototype = Object.create(Emitter.prototype); + +SCClientSocket.CONNECTING = SCClientSocket.prototype.CONNECTING = SCTransport.prototype.CONNECTING; +SCClientSocket.OPEN = SCClientSocket.prototype.OPEN = SCTransport.prototype.OPEN; +SCClientSocket.CLOSED = SCClientSocket.prototype.CLOSED = SCTransport.prototype.CLOSED; + +SCClientSocket.AUTHENTICATED = SCClientSocket.prototype.AUTHENTICATED = 'authenticated'; +SCClientSocket.UNAUTHENTICATED = SCClientSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; + +SCClientSocket.PENDING = SCClientSocket.prototype.PENDING = 'pending'; + +SCClientSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; +SCClientSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; + +SCClientSocket.prototype._privateEventHandlerMap = { + '#publish': function (data) { + var undecoratedChannelName = this._undecorateChannelName(data.channel); + var isSubscribed = this.isSubscribed(undecoratedChannelName, true); + + if (isSubscribed) { + this._channelEmitter.emit(undecoratedChannelName, data.data); + } + }, + '#kickOut': function (data) { + var undecoratedChannelName = this._undecorateChannelName(data.channel); + var channel = this.channels[undecoratedChannelName]; + if (channel) { + Emitter.prototype.emit.call(this, 'kickOut', data.message, undecoratedChannelName); + channel.emit('kickOut', data.message, undecoratedChannelName); + this._triggerChannelUnsubscribe(channel); + } + }, + '#setAuthToken': function (data, response) { + var self = this; + + if (data) { + var triggerAuthenticate = function (err) { + if (err) { + // This is a non-fatal error, we don't want to close the connection + // because of this but we do want to notify the server and throw an error + // on the client. + response.error(err); + self._onSCError(err); + } else { + self._changeToAuthenticatedState(data.token); + response.end(); + } + }; + + this.auth.saveToken(this.authTokenName, data.token, {}, triggerAuthenticate); + } else { + response.error(new InvalidMessageError('No token data provided by #setAuthToken event')); + } + }, + '#removeAuthToken': function (data, response) { + var self = this; + + this.auth.removeToken(this.authTokenName, function (err, oldToken) { + if (err) { + // Non-fatal error - Do not close the connection + response.error(err); + self._onSCError(err); + } else { + Emitter.prototype.emit.call(self, 'removeAuthToken', oldToken); + self._changeToUnauthenticatedStateAndClearTokens(); + response.end(); + } + }); + }, + '#disconnect': function (data) { + this.transport.close(data.code, data.data); + } +}; + +SCClientSocket.prototype.getState = function () { + return this.state; +}; + +SCClientSocket.prototype.getBytesReceived = function () { + return this.transport.getBytesReceived(); +}; + +SCClientSocket.prototype.deauthenticate = function (callback) { + var self = this; + + this.auth.removeToken(this.authTokenName, function (err, oldToken) { + if (err) { + // Non-fatal error - Do not close the connection + self._onSCError(err); + } else { + Emitter.prototype.emit.call(self, 'removeAuthToken', oldToken); + if (self.state != self.CLOSED) { + self.emit('#removeAuthToken'); + } + self._changeToUnauthenticatedStateAndClearTokens(); + } + callback && callback(err); + }); +}; + +SCClientSocket.prototype.connect = SCClientSocket.prototype.open = function () { + var self = this; + + if (!this.active) { + var error = new InvalidActionError('Cannot connect a destroyed client'); + this._onSCError(error); + return; + } + + if (this.state == this.CLOSED) { + this.pendingReconnect = false; + this.pendingReconnectTimeout = null; + clearTimeout(this._reconnectTimeoutRef); + + this.state = this.CONNECTING; + Emitter.prototype.emit.call(this, 'connecting'); + + if (this.transport) { + this.transport.off(); + } + + this.transport = new SCTransport(this.auth, this.codec, this.options); + + this.transport.on('open', function (status) { + self.state = self.OPEN; + self._onSCOpen(status); + }); + + this.transport.on('error', function (err) { + self._onSCError(err); + }); + + this.transport.on('close', function (code, data) { + self.state = self.CLOSED; + self._onSCClose(code, data); + }); + + this.transport.on('openAbort', function (code, data) { + self.state = self.CLOSED; + self._onSCClose(code, data, true); + }); + + this.transport.on('event', function (event, data, res) { + self._onSCEvent(event, data, res); + }); + } +}; + +SCClientSocket.prototype.reconnect = function (code, data) { + this.disconnect(code, data); + this.connect(); +}; + +SCClientSocket.prototype.disconnect = function (code, data) { + code = code || 1000; + + if (typeof code != 'number') { + throw new InvalidArgumentsError('If specified, the code argument must be a number'); + } + + if (this.state == this.OPEN || this.state == this.CONNECTING) { + this.transport.close(code, data); + } else { + this.pendingReconnect = false; + this.pendingReconnectTimeout = null; + clearTimeout(this._reconnectTimeoutRef); + } +}; + +SCClientSocket.prototype.destroy = function (code, data) { + if (isBrowser && global.removeEventListener) { + global.removeEventListener('beforeunload', this._unloadHandler, false); + } + this.active = false; + this.disconnect(code, data); + delete this._clientMap[this.clientId]; +}; + +SCClientSocket.prototype._changeToUnauthenticatedStateAndClearTokens = function () { + if (this.authState != this.UNAUTHENTICATED) { + var oldState = this.authState; + var oldSignedToken = this.signedAuthToken; + this.authState = this.UNAUTHENTICATED; + this.signedAuthToken = null; + this.authToken = null; + + var stateChangeData = { + oldState: oldState, + newState: this.authState + }; + Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); + Emitter.prototype.emit.call(this, 'deauthenticate', oldSignedToken); + } +}; + +SCClientSocket.prototype._changeToAuthenticatedState = function (signedAuthToken) { + this.signedAuthToken = signedAuthToken; + this.authToken = this._extractAuthTokenData(signedAuthToken); + + if (this.authState != this.AUTHENTICATED) { + var oldState = this.authState; + this.authState = this.AUTHENTICATED; + var stateChangeData = { + oldState: oldState, + newState: this.authState, + signedAuthToken: signedAuthToken, + authToken: this.authToken + }; + if (!this.preparingPendingSubscriptions) { + this.processPendingSubscriptions(); + } + + Emitter.prototype.emit.call(this, 'authStateChange', stateChangeData); + } + Emitter.prototype.emit.call(this, 'authenticate', signedAuthToken); +}; + +SCClientSocket.prototype.decodeBase64 = function (encodedString) { + var decodedString; + if (typeof Buffer == 'undefined') { + if (global.atob) { + decodedString = global.atob(encodedString); + } else { + decodedString = base64.decode(encodedString); + } + } else { + var buffer = new Buffer(encodedString, 'base64'); + decodedString = buffer.toString('utf8'); + } + return decodedString; +}; + +SCClientSocket.prototype.encodeBase64 = function (decodedString) { + var encodedString; + if (typeof Buffer == 'undefined') { + if (global.btoa) { + encodedString = global.btoa(decodedString); + } else { + encodedString = base64.encode(decodedString); + } + } else { + var buffer = new Buffer(decodedString, 'utf8'); + encodedString = buffer.toString('base64'); + } + return encodedString; +}; + +SCClientSocket.prototype._extractAuthTokenData = function (signedAuthToken) { + var tokenParts = (signedAuthToken || '').split('.'); + var encodedTokenData = tokenParts[1]; + if (encodedTokenData != null) { + var tokenData = encodedTokenData; + try { + tokenData = this.decodeBase64(tokenData); + return JSON.parse(tokenData); + } catch (e) { + return tokenData; + } + } + return null; +}; + +SCClientSocket.prototype.getAuthToken = function () { + return this.authToken; +}; + +SCClientSocket.prototype.getSignedAuthToken = function () { + return this.signedAuthToken; +}; + +// Perform client-initiated authentication by providing an encrypted token string. +SCClientSocket.prototype.authenticate = function (signedAuthToken, callback) { + var self = this; + + this.emit('#authenticate', signedAuthToken, function (err, authStatus) { + if (authStatus && authStatus.isAuthenticated != null) { + // If authStatus is correctly formatted (has an isAuthenticated property), + // then we will rehydrate the authError. + if (authStatus.authError) { + authStatus.authError = scErrors.hydrateError(authStatus.authError); + } + } else { + // Some errors like BadConnectionError and TimeoutError will not pass a valid + // authStatus object to the current function, so we need to create it ourselves. + authStatus = { + isAuthenticated: self.authState, + authError: null + }; + } + if (err) { + if (err.name != 'BadConnectionError' && err.name != 'TimeoutError') { + // In case of a bad/closed connection or a timeout, we maintain the last + // known auth state since those errors don't mean that the token is invalid. + + self._changeToUnauthenticatedStateAndClearTokens(); + } + callback && callback(err, authStatus); + } else { + self.auth.saveToken(self.authTokenName, signedAuthToken, {}, function (err) { + if (err) { + self._onSCError(err); + } + if (authStatus.isAuthenticated) { + self._changeToAuthenticatedState(signedAuthToken); + } else { + self._changeToUnauthenticatedStateAndClearTokens(); + } + callback && callback(err, authStatus); + }); + } + }); +}; + +SCClientSocket.prototype._tryReconnect = function (initialDelay) { + var self = this; + + var exponent = this.connectAttempts++; + var reconnectOptions = this.options.autoReconnectOptions; + var timeout; + + if (initialDelay == null || exponent > 0) { + var initialTimeout = Math.round(reconnectOptions.initialDelay + (reconnectOptions.randomness || 0) * Math.random()); + + timeout = Math.round(initialTimeout * Math.pow(reconnectOptions.multiplier, exponent)); + } else { + timeout = initialDelay; + } + + if (timeout > reconnectOptions.maxDelay) { + timeout = reconnectOptions.maxDelay; + } + + clearTimeout(this._reconnectTimeoutRef); + + this.pendingReconnect = true; + this.pendingReconnectTimeout = timeout; + this._reconnectTimeoutRef = setTimeout(function () { + self.connect(); + }, timeout); +}; + +SCClientSocket.prototype._onSCOpen = function (status) { + var self = this; + + this.preparingPendingSubscriptions = true; + + if (status) { + this.id = status.id; + this.pingTimeout = status.pingTimeout; + this.transport.pingTimeout = this.pingTimeout; + if (status.isAuthenticated) { + this._changeToAuthenticatedState(status.authToken); + } else { + this._changeToUnauthenticatedStateAndClearTokens(); + } + } else { + // This can happen if auth.loadToken (in sctransport.js) fails with + // an error - This means that the signedAuthToken cannot be loaded by + // the auth engine and therefore, we need to unauthenticate the client. + this._changeToUnauthenticatedStateAndClearTokens(); + } + + this.connectAttempts = 0; + + if (this.options.autoSubscribeOnConnect) { + this.processPendingSubscriptions(); + } + + // If the user invokes the callback while in autoSubscribeOnConnect mode, it + // won't break anything. + Emitter.prototype.emit.call(this, 'connect', status, function () { + self.processPendingSubscriptions(); + }); + + if (this.state == this.OPEN) { + this._flushEmitBuffer(); + } +}; + +SCClientSocket.prototype._onSCError = function (err) { + var self = this; + + // Throw error in different stack frame so that error handling + // cannot interfere with a reconnect action. + setTimeout(function () { + if (self.listeners('error').length < 1) { + throw err; + } else { + Emitter.prototype.emit.call(self, 'error', err); + } + }, 0); +}; + +SCClientSocket.prototype._suspendSubscriptions = function () { + var channel, newState; + for (var channelName in this.channels) { + if (this.channels.hasOwnProperty(channelName)) { + channel = this.channels[channelName]; + if (channel.state == channel.SUBSCRIBED || + channel.state == channel.PENDING) { + + newState = channel.PENDING; + } else { + newState = channel.UNSUBSCRIBED; + } + + this._triggerChannelUnsubscribe(channel, newState); + } + } +}; + +SCClientSocket.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { + var currentNode = this._emitBuffer.head; + var nextNode; + + while (currentNode) { + nextNode = currentNode.next; + var eventObject = currentNode.data; + clearTimeout(eventObject.timeout); + delete eventObject.timeout; + currentNode.detach(); + currentNode = nextNode; + + var callback = eventObject.callback; + if (callback) { + delete eventObject.callback; + var errorMessage = "Event '" + eventObject.event + + "' was aborted due to a bad connection"; + var error = new BadConnectionError(errorMessage, failureType); + callback.call(eventObject, error, eventObject); + } + // Cleanup any pending response callback in the transport layer too. + if (eventObject.cid) { + this.transport.cancelPendingResponse(eventObject.cid); + } + } +}; + +SCClientSocket.prototype._onSCClose = function (code, data, openAbort) { + var self = this; + + this.id = null; + + if (this.transport) { + this.transport.off(); + } + this.pendingReconnect = false; + this.pendingReconnectTimeout = null; + clearTimeout(this._reconnectTimeoutRef); + + this._suspendSubscriptions(); + this._abortAllPendingEventsDueToBadConnection(openAbort ? 'connectAbort' : 'disconnect'); + + // Try to reconnect + // on server ping timeout (4000) + // or on client pong timeout (4001) + // or on close without status (1005) + // or on handshake failure (4003) + // or on handshake rejection (4008) + // or on socket hung up (1006) + if (this.options.autoReconnect) { + if (code == 4000 || code == 4001 || code == 1005) { + // If there is a ping or pong timeout or socket closes without + // status, don't wait before trying to reconnect - These could happen + // if the client wakes up after a period of inactivity and in this case we + // want to re-establish the connection as soon as possible. + this._tryReconnect(0); + + // Codes 4500 and above will be treated as permanent disconnects. + // Socket will not try to auto-reconnect. + } else if (code != 1000 && code < 4500) { + this._tryReconnect(); + } + } + + if (openAbort) { + Emitter.prototype.emit.call(self, 'connectAbort', code, data); + } else { + Emitter.prototype.emit.call(self, 'disconnect', code, data); + } + Emitter.prototype.emit.call(self, 'close', code, data); + + if (!SCClientSocket.ignoreStatuses[code]) { + var closeMessage; + if (data) { + closeMessage = 'Socket connection closed with status code ' + code + ' and reason: ' + data; + } else { + closeMessage = 'Socket connection closed with status code ' + code; + } + var err = new SocketProtocolError(SCClientSocket.errorStatuses[code] || closeMessage, code); + this._onSCError(err); + } +}; + +SCClientSocket.prototype._onSCEvent = function (event, data, res) { + var handler = this._privateEventHandlerMap[event]; + if (handler) { + handler.call(this, data, res); + } else { + Emitter.prototype.emit.call(this, event, data, function () { + res && res.callback.apply(res, arguments); + }); + } +}; + +SCClientSocket.prototype.decode = function (message) { + return this.transport.decode(message); +}; + +SCClientSocket.prototype.encode = function (object) { + return this.transport.encode(object); +}; + +SCClientSocket.prototype._flushEmitBuffer = function () { + var currentNode = this._emitBuffer.head; + var nextNode; + + while (currentNode) { + nextNode = currentNode.next; + var eventObject = currentNode.data; + currentNode.detach(); + this.transport.emitObject(eventObject); + currentNode = nextNode; + } +}; + +SCClientSocket.prototype._handleEventAckTimeout = function (eventObject, eventNode) { + if (eventNode) { + eventNode.detach(); + } + delete eventObject.timeout; + + var callback = eventObject.callback; + if (callback) { + delete eventObject.callback; + var error = new TimeoutError("Event response for '" + eventObject.event + "' timed out"); + callback.call(eventObject, error, eventObject); + } + // Cleanup any pending response callback in the transport layer too. + if (eventObject.cid) { + this.transport.cancelPendingResponse(eventObject.cid); + } +}; + +SCClientSocket.prototype._emit = function (event, data, callback) { + var self = this; + + if (this.state == this.CLOSED) { + this.connect(); + } + var eventObject = { + event: event, + callback: callback + }; + + var eventNode = new LinkedList.Item(); + + if (this.options.cloneData) { + eventObject.data = clone(data); + } else { + eventObject.data = data; + } + eventNode.data = eventObject; + + eventObject.timeout = setTimeout(function () { + self._handleEventAckTimeout(eventObject, eventNode); + }, this.ackTimeout); + + this._emitBuffer.append(eventNode); + if (this.state == this.OPEN) { + this._flushEmitBuffer(); + } +}; + +SCClientSocket.prototype.send = function (data) { + this.transport.send(data); +}; + +SCClientSocket.prototype.emit = function (event, data, callback) { + if (this._localEvents[event] == null) { + this._emit(event, data, callback); + } else if (event == 'error') { + Emitter.prototype.emit.call(this, event, data); + } else { + var error = new InvalidActionError('The "' + event + '" event is reserved and cannot be emitted on a client socket'); + this._onSCError(error); + } +}; + +SCClientSocket.prototype.publish = function (channelName, data, callback) { + var pubData = { + channel: this._decorateChannelName(channelName), + data: data + }; + this.emit('#publish', pubData, callback); +}; + +SCClientSocket.prototype._triggerChannelSubscribe = function (channel, subscriptionOptions) { + var channelName = channel.name; + + if (channel.state != channel.SUBSCRIBED) { + var oldState = channel.state; + channel.state = channel.SUBSCRIBED; + + var stateChangeData = { + channel: channelName, + oldState: oldState, + newState: channel.state, + subscriptionOptions: subscriptionOptions + }; + channel.emit('subscribeStateChange', stateChangeData); + channel.emit('subscribe', channelName, subscriptionOptions); + Emitter.prototype.emit.call(this, 'subscribeStateChange', stateChangeData); + Emitter.prototype.emit.call(this, 'subscribe', channelName, subscriptionOptions); + } +}; + +SCClientSocket.prototype._triggerChannelSubscribeFail = function (err, channel, subscriptionOptions) { + var channelName = channel.name; + var meetsAuthRequirements = !channel.waitForAuth || this.authState == this.AUTHENTICATED; + + if (channel.state != channel.UNSUBSCRIBED && meetsAuthRequirements) { + channel.state = channel.UNSUBSCRIBED; + + channel.emit('subscribeFail', err, channelName, subscriptionOptions); + Emitter.prototype.emit.call(this, 'subscribeFail', err, channelName, subscriptionOptions); + } +}; + +// Cancel any pending subscribe callback +SCClientSocket.prototype._cancelPendingSubscribeCallback = function (channel) { + if (channel._pendingSubscriptionCid != null) { + this.transport.cancelPendingResponse(channel._pendingSubscriptionCid); + delete channel._pendingSubscriptionCid; + } +}; + +SCClientSocket.prototype._decorateChannelName = function (channelName) { + if (this.channelPrefix) { + channelName = this.channelPrefix + channelName; + } + return channelName; +}; + +SCClientSocket.prototype._undecorateChannelName = function (decoratedChannelName) { + if (this.channelPrefix && decoratedChannelName.indexOf(this.channelPrefix) == 0) { + return decoratedChannelName.replace(this.channelPrefix, ''); + } + return decoratedChannelName; +}; + +SCClientSocket.prototype._trySubscribe = function (channel) { + var self = this; + + var meetsAuthRequirements = !channel.waitForAuth || this.authState == this.AUTHENTICATED; + + // We can only ever have one pending subscribe action at any given time on a channel + if (this.state == this.OPEN && !this.preparingPendingSubscriptions && + channel._pendingSubscriptionCid == null && meetsAuthRequirements) { + + var options = { + noTimeout: true + }; + + var subscriptionOptions = { + channel: this._decorateChannelName(channel.name) + }; + if (channel.waitForAuth) { + options.waitForAuth = true; + subscriptionOptions.waitForAuth = options.waitForAuth; + } + if (channel.data) { + subscriptionOptions.data = channel.data; + } + if (channel.batch) { + options.batch = true; + subscriptionOptions.batch = true; + } + + channel._pendingSubscriptionCid = this.transport.emit( + '#subscribe', subscriptionOptions, options, + function (err) { + delete channel._pendingSubscriptionCid; + if (err) { + self._triggerChannelSubscribeFail(err, channel, subscriptionOptions); + } else { + self._triggerChannelSubscribe(channel, subscriptionOptions); + } + } + ); + Emitter.prototype.emit.call(this, 'subscribeRequest', channel.name, subscriptionOptions); + } +}; + +SCClientSocket.prototype.subscribe = function (channelName, options) { + var channel = this.channels[channelName]; + + if (!channel) { + channel = new SCChannel(channelName, this, options); + this.channels[channelName] = channel; + } else if (options) { + channel.setOptions(options); + } + + if (channel.state == channel.UNSUBSCRIBED) { + channel.state = channel.PENDING; + this._trySubscribe(channel); + } + + return channel; +}; + +SCClientSocket.prototype._triggerChannelUnsubscribe = function (channel, newState) { + var channelName = channel.name; + var oldState = channel.state; + + if (newState) { + channel.state = newState; + } else { + channel.state = channel.UNSUBSCRIBED; + } + this._cancelPendingSubscribeCallback(channel); + + if (oldState == channel.SUBSCRIBED) { + var stateChangeData = { + channel: channelName, + oldState: oldState, + newState: channel.state + }; + channel.emit('subscribeStateChange', stateChangeData); + channel.emit('unsubscribe', channelName); + Emitter.prototype.emit.call(this, 'subscribeStateChange', stateChangeData); + Emitter.prototype.emit.call(this, 'unsubscribe', channelName); + } +}; + +SCClientSocket.prototype._tryUnsubscribe = function (channel) { + var self = this; + + if (this.state == this.OPEN) { + var options = { + noTimeout: true + }; + if (channel.batch) { + options.batch = true; + } + // If there is a pending subscribe action, cancel the callback + this._cancelPendingSubscribeCallback(channel); + + // This operation cannot fail because the TCP protocol guarantees delivery + // so long as the connection remains open. If the connection closes, + // the server will automatically unsubscribe the client and thus complete + // the operation on the server side. + var decoratedChannelName = this._decorateChannelName(channel.name); + this.transport.emit('#unsubscribe', decoratedChannelName, options); + } +}; + +SCClientSocket.prototype.unsubscribe = function (channelName) { + var channel = this.channels[channelName]; + + if (channel) { + if (channel.state != channel.UNSUBSCRIBED) { + + this._triggerChannelUnsubscribe(channel); + this._tryUnsubscribe(channel); + } + } +}; + +SCClientSocket.prototype.channel = function (channelName, options) { + var currentChannel = this.channels[channelName]; + + if (!currentChannel) { + currentChannel = new SCChannel(channelName, this, options); + this.channels[channelName] = currentChannel; + } + return currentChannel; +}; + +SCClientSocket.prototype.destroyChannel = function (channelName) { + var channel = this.channels[channelName]; + + if (channel) { + channel.unwatch(); + channel.unsubscribe(); + delete this.channels[channelName]; + } +}; + +SCClientSocket.prototype.subscriptions = function (includePending) { + var subs = []; + var channel, includeChannel; + for (var channelName in this.channels) { + if (this.channels.hasOwnProperty(channelName)) { + channel = this.channels[channelName]; + + if (includePending) { + includeChannel = channel && (channel.state == channel.SUBSCRIBED || + channel.state == channel.PENDING); + } else { + includeChannel = channel && channel.state == channel.SUBSCRIBED; + } + + if (includeChannel) { + subs.push(channelName); + } + } + } + return subs; +}; + +SCClientSocket.prototype.isSubscribed = function (channelName, includePending) { + var channel = this.channels[channelName]; + if (includePending) { + return !!channel && (channel.state == channel.SUBSCRIBED || + channel.state == channel.PENDING); + } + return !!channel && channel.state == channel.SUBSCRIBED; +}; + +SCClientSocket.prototype.processPendingSubscriptions = function () { + var self = this; + + this.preparingPendingSubscriptions = false; + + var pendingChannels = []; + + for (var i in this.channels) { + if (this.channels.hasOwnProperty(i)) { + var channel = this.channels[i]; + if (channel.state == channel.PENDING) { + pendingChannels.push(channel); + } + } + } + + pendingChannels.sort(function (a, b) { + var ap = a.priority || 0; + var bp = b.priority || 0; + if (ap > bp) { + return -1; + } + if (ap < bp) { + return 1; + } + return 0; + }); + + pendingChannels.forEach(function (channel) { + self._trySubscribe(channel); + }); +}; + +SCClientSocket.prototype.watch = function (channelName, handler) { + if (typeof handler != 'function') { + throw new InvalidArgumentsError('No handler function was provided'); + } + this._channelEmitter.on(channelName, handler); +}; + +SCClientSocket.prototype.unwatch = function (channelName, handler) { + if (handler) { + this._channelEmitter.removeListener(channelName, handler); + } else { + this._channelEmitter.removeAllListeners(channelName); + } +}; + +SCClientSocket.prototype.watchers = function (channelName) { + return this._channelEmitter.listeners(channelName); +}; + +module.exports = SCClientSocket; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},_dereq_("buffer").Buffer) +},{"./auth":2,"./response":4,"./sctransport":6,"base-64":8,"buffer":10,"clone":11,"component-emitter":12,"linked-list":15,"querystring":18,"sc-channel":19,"sc-errors":21,"sc-formatter":22}],6:[function(_dereq_,module,exports){ +(function (global){ +var Emitter = _dereq_('component-emitter'); +var Response = _dereq_('./response').Response; +var querystring = _dereq_('querystring'); +var WebSocket; +var createWebSocket; + +if (global.WebSocket) { + WebSocket = global.WebSocket; + createWebSocket = function (uri, options) { + return new WebSocket(uri); + }; +} else { + WebSocket = _dereq_('ws'); + createWebSocket = function (uri, options) { + return new WebSocket(uri, null, options); + }; +} + +var scErrors = _dereq_('sc-errors'); +var TimeoutError = scErrors.TimeoutError; +var BadConnectionError = scErrors.BadConnectionError; + + +var SCTransport = function (authEngine, codecEngine, options) { + var self = this; + + this.state = this.CLOSED; + this.auth = authEngine; + this.codec = codecEngine; + this.options = options; + this.connectTimeout = options.connectTimeout; + this.pingTimeout = options.ackTimeout; + this.pingTimeoutDisabled = !!options.pingTimeoutDisabled; + this.callIdGenerator = options.callIdGenerator; + this.authTokenName = options.authTokenName; + + this._pingTimeoutTicker = null; + this._callbackMap = {}; + this._batchSendList = []; + + // Open the connection. + + this.state = this.CONNECTING; + var uri = this.uri(); + + var wsSocket = createWebSocket(uri, this.options); + wsSocket.binaryType = this.options.binaryType; + + this.socket = wsSocket; + + wsSocket.onopen = function () { + self._onOpen(); + }; + + wsSocket.onclose = function (event) { + var code; + if (event.code == null) { + // This is to handle an edge case in React Native whereby + // event.code is undefined when the mobile device is locked. + // TODO: This is not perfect since this condition could also apply to + // an abnormal close (no close control frame) which would be a 1006. + code = 1005; + } else { + code = event.code; + } + self._onClose(code, event.reason); + }; + + wsSocket.onmessage = function (message, flags) { + self._onMessage(message.data); + }; + + wsSocket.onerror = function (error) { + // The onclose event will be called automatically after the onerror event + // if the socket is connected - Otherwise, if it's in the middle of + // connecting, we want to close it manually with a 1006 - This is necessary + // to prevent inconsistent behavior when running the client in Node.js + // vs in a browser. + + if (self.state === self.CONNECTING) { + self._onClose(1006); + } + }; + + this._connectTimeoutRef = setTimeout(function () { + self._onClose(4007); + self.socket.close(4007); + }, this.connectTimeout); +}; + +SCTransport.prototype = Object.create(Emitter.prototype); + +SCTransport.CONNECTING = SCTransport.prototype.CONNECTING = 'connecting'; +SCTransport.OPEN = SCTransport.prototype.OPEN = 'open'; +SCTransport.CLOSED = SCTransport.prototype.CLOSED = 'closed'; + +SCTransport.prototype.uri = function () { + var query = this.options.query || {}; + var schema = this.options.secure ? 'wss' : 'ws'; + + if (this.options.timestampRequests) { + query[this.options.timestampParam] = (new Date()).getTime(); + } + + query = querystring.encode(query); + + if (query.length) { + query = '?' + query; + } + + var host; + if (this.options.host) { + host = this.options.host; + } else { + var port = ''; + + if (this.options.port && ((schema == 'wss' && this.options.port != 443) + || (schema == 'ws' && this.options.port != 80))) { + port = ':' + this.options.port; + } + host = this.options.hostname + port; + } + + return schema + '://' + host + this.options.path + query; +}; + +SCTransport.prototype._onOpen = function () { + var self = this; + + clearTimeout(this._connectTimeoutRef); + this._resetPingTimeout(); + + this._handshake(function (err, status) { + if (err) { + var statusCode; + if (status && status.code) { + statusCode = status.code; + } else { + statusCode = 4003; + } + self._onError(err); + self._onClose(statusCode, err.toString()); + self.socket.close(statusCode); + } else { + self.state = self.OPEN; + Emitter.prototype.emit.call(self, 'open', status); + self._resetPingTimeout(); + } + }); +}; + +SCTransport.prototype._handshake = function (callback) { + var self = this; + this.auth.loadToken(this.authTokenName, function (err, token) { + if (err) { + callback(err); + } else { + // Don't wait for this.state to be 'open'. + // The underlying WebSocket (this.socket) is already open. + var options = { + force: true + }; + self.emit('#handshake', { + authToken: token + }, options, function (err, status) { + if (status) { + // Add the token which was used as part of authentication attempt + // to the status object. + status.authToken = token; + if (status.authError) { + status.authError = scErrors.hydrateError(status.authError); + } + } + callback(err, status); + }); + } + }); +}; + +SCTransport.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { + for (var i in this._callbackMap) { + if (this._callbackMap.hasOwnProperty(i)) { + var eventObject = this._callbackMap[i]; + delete this._callbackMap[i]; + + clearTimeout(eventObject.timeout); + delete eventObject.timeout; + + var errorMessage = "Event '" + eventObject.event + + "' was aborted due to a bad connection"; + var badConnectionError = new BadConnectionError(errorMessage, failureType); + + var callback = eventObject.callback; + delete eventObject.callback; + callback.call(eventObject, badConnectionError, eventObject); + } + } +}; + +SCTransport.prototype._onClose = function (code, data) { + delete this.socket.onopen; + delete this.socket.onclose; + delete this.socket.onmessage; + delete this.socket.onerror; + + clearTimeout(this._connectTimeoutRef); + clearTimeout(this._pingTimeoutTicker); + clearTimeout(this._batchTimeout); + + if (this.state == this.OPEN) { + this.state = this.CLOSED; + Emitter.prototype.emit.call(this, 'close', code, data); + this._abortAllPendingEventsDueToBadConnection('disconnect'); + + } else if (this.state == this.CONNECTING) { + this.state = this.CLOSED; + Emitter.prototype.emit.call(this, 'openAbort', code, data); + this._abortAllPendingEventsDueToBadConnection('connectAbort'); + } +}; + +SCTransport.prototype._handleEventObject = function (obj, message) { + if (obj && obj.event != null) { + var response = new Response(this, obj.cid); + Emitter.prototype.emit.call(this, 'event', obj.event, obj.data, response); + } else if (obj && obj.rid != null) { + var eventObject = this._callbackMap[obj.rid]; + if (eventObject) { + clearTimeout(eventObject.timeout); + delete eventObject.timeout; + delete this._callbackMap[obj.rid]; + + if (eventObject.callback) { + var rehydratedError = scErrors.hydrateError(obj.error); + eventObject.callback(rehydratedError, obj.data); + } + } + } else { + Emitter.prototype.emit.call(this, 'event', 'raw', message); + } +}; + +SCTransport.prototype._onMessage = function (message) { + Emitter.prototype.emit.call(this, 'event', 'message', message); + + var obj = this.decode(message); + + // If ping + if (obj == '#1') { + this._resetPingTimeout(); + if (this.socket.readyState == this.socket.OPEN) { + this.sendObject('#2'); + } + } else { + if (Array.isArray(obj)) { + var len = obj.length; + for (var i = 0; i < len; i++) { + this._handleEventObject(obj[i], message); + } + } else { + this._handleEventObject(obj, message); + } + } +}; + +SCTransport.prototype._onError = function (err) { + Emitter.prototype.emit.call(this, 'error', err); +}; + +SCTransport.prototype._resetPingTimeout = function () { + if (this.pingTimeoutDisabled) { + return; + } + var self = this; + + var now = (new Date()).getTime(); + clearTimeout(this._pingTimeoutTicker); + + this._pingTimeoutTicker = setTimeout(function () { + self._onClose(4000); + self.socket.close(4000); + }, this.pingTimeout); +}; + +SCTransport.prototype.getBytesReceived = function () { + return this.socket.bytesReceived; +}; + +SCTransport.prototype.close = function (code, data) { + code = code || 1000; + + if (this.state == this.OPEN) { + var packet = { + code: code, + data: data + }; + this.emit('#disconnect', packet); + + this._onClose(code, data); + this.socket.close(code); + + } else if (this.state == this.CONNECTING) { + this._onClose(code, data); + this.socket.close(code); + } +}; + +SCTransport.prototype.emitObject = function (eventObject, options) { + var simpleEventObject = { + event: eventObject.event, + data: eventObject.data + }; + + if (eventObject.callback) { + simpleEventObject.cid = eventObject.cid = this.callIdGenerator(); + this._callbackMap[eventObject.cid] = eventObject; + } + + this.sendObject(simpleEventObject, options); + + return eventObject.cid || null; +}; + +SCTransport.prototype._handleEventAckTimeout = function (eventObject) { + if (eventObject.cid) { + delete this._callbackMap[eventObject.cid]; + } + delete eventObject.timeout; + + var callback = eventObject.callback; + if (callback) { + delete eventObject.callback; + var error = new TimeoutError("Event response for '" + eventObject.event + "' timed out"); + callback.call(eventObject, error, eventObject); + } +}; + +// The last two optional arguments (a and b) can be options and/or callback +SCTransport.prototype.emit = function (event, data, a, b) { + var self = this; + + var callback, options; + + if (b) { + options = a; + callback = b; + } else { + if (a instanceof Function) { + options = {}; + callback = a; + } else { + options = a; + } + } + + var eventObject = { + event: event, + data: data, + callback: callback + }; + + if (callback && !options.noTimeout) { + eventObject.timeout = setTimeout(function () { + self._handleEventAckTimeout(eventObject); + }, this.options.ackTimeout); + } + + var cid = null; + if (this.state == this.OPEN || options.force) { + cid = this.emitObject(eventObject, options); + } + return cid; +}; + +SCTransport.prototype.cancelPendingResponse = function (cid) { + delete this._callbackMap[cid]; +}; + +SCTransport.prototype.decode = function (message) { + return this.codec.decode(message); +}; + +SCTransport.prototype.encode = function (object) { + return this.codec.encode(object); +}; + +SCTransport.prototype.send = function (data) { + if (this.socket.readyState != this.socket.OPEN) { + this._onClose(1005); + } else { + this.socket.send(data); + } +}; + +SCTransport.prototype.serializeObject = function (object) { + var str, formatError; + try { + str = this.encode(object); + } catch (err) { + formatError = err; + this._onError(formatError); + } + if (!formatError) { + return str; + } + return null; +}; + +SCTransport.prototype.sendObjectBatch = function (object) { + var self = this; + + this._batchSendList.push(object); + if (this._batchTimeout) { + return; + } + + this._batchTimeout = setTimeout(function () { + delete self._batchTimeout; + if (self._batchSendList.length) { + var str = self.serializeObject(self._batchSendList); + if (str != null) { + self.send(str); + } + self._batchSendList = []; + } + }, this.options.pubSubBatchDuration || 0); +}; + +SCTransport.prototype.sendObjectSingle = function (object) { + var str = this.serializeObject(object); + if (str != null) { + this.send(str); + } +}; + +SCTransport.prototype.sendObject = function (object, options) { + if (options && options.batch) { + this.sendObjectBatch(object); + } else { + this.sendObjectSingle(object); + } +}; + +module.exports.SCTransport = SCTransport; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./response":4,"component-emitter":12,"querystring":18,"sc-errors":21,"ws":7}],7:[function(_dereq_,module,exports){ +var global; +if (typeof WorkerGlobalScope !== 'undefined') { + global = self; +} else { + global = typeof window != 'undefined' && window || (function() { return this; })(); +} + +var WebSocket = global.WebSocket || global.MozWebSocket; + +/** + * WebSocket constructor. + * + * The third `opts` options object gets ignored in web browsers, since it's + * non-standard, and throws a TypeError if passed to the constructor. + * See: https://github.com/einaros/ws/issues/227 + * + * @param {String} uri + * @param {Array} protocols (optional) + * @param {Object} opts (optional) + * @api public + */ + +function ws(uri, protocols, opts) { + var instance; + if (protocols) { + instance = new WebSocket(uri, protocols); + } else { + instance = new WebSocket(uri); + } + return instance; +} + +if (WebSocket) ws.prototype = WebSocket.prototype; + +module.exports = WebSocket ? ws : null; + +},{}],8:[function(_dereq_,module,exports){ +(function (global){ +/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports`. + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module`. + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, and use + // it as `root`. + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var InvalidCharacterError = function(message) { + this.message = message; + }; + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + var error = function(message) { + // Note: the error messages used throughout this file match those used by + // the native `atob`/`btoa` implementation in Chromium. + throw new InvalidCharacterError(message); + }; + + var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // http://whatwg.org/html/common-microsyntaxes.html#space-character + var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g; + + // `decode` is designed to be fully compatible with `atob` as described in the + // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob + // The optimized base64-decoding algorithm used is based on @atk’s excellent + // implementation. https://gist.github.com/atk/1020396 + var decode = function(input) { + input = String(input) + .replace(REGEX_SPACE_CHARACTERS, ''); + var length = input.length; + if (length % 4 == 0) { + input = input.replace(/==?$/, ''); + length = input.length; + } + if ( + length % 4 == 1 || + // http://whatwg.org/C#alphanumeric-ascii-characters + /[^+a-zA-Z0-9/]/.test(input) + ) { + error( + 'Invalid character: the string to be decoded is not correctly encoded.' + ); + } + var bitCounter = 0; + var bitStorage; + var buffer; + var output = ''; + var position = -1; + while (++position < length) { + buffer = TABLE.indexOf(input.charAt(position)); + bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; + // Unless this is the first of a group of 4 characters… + if (bitCounter++ % 4) { + // …convert the first 8 bits to a single ASCII character. + output += String.fromCharCode( + 0xFF & bitStorage >> (-2 * bitCounter & 6) + ); + } + } + return output; + }; + + // `encode` is designed to be fully compatible with `btoa` as described in the + // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa + var encode = function(input) { + input = String(input); + if (/[^\0-\xFF]/.test(input)) { + // Note: no need to special-case astral symbols here, as surrogates are + // matched, and the input is supposed to only contain ASCII anyway. + error( + 'The string to be encoded contains characters outside of the ' + + 'Latin1 range.' + ); + } + var padding = input.length % 3; + var output = ''; + var position = -1; + var a; + var b; + var c; + var d; + var buffer; + // Make sure any padding is handled outside of the loop. + var length = input.length - padding; + + while (++position < length) { + // Read three bytes, i.e. 24 bits. + a = input.charCodeAt(position) << 16; + b = input.charCodeAt(++position) << 8; + c = input.charCodeAt(++position); + buffer = a + b + c; + // Turn the 24 bits into four chunks of 6 bits each, and append the + // matching character for each of them to the output. + output += ( + TABLE.charAt(buffer >> 18 & 0x3F) + + TABLE.charAt(buffer >> 12 & 0x3F) + + TABLE.charAt(buffer >> 6 & 0x3F) + + TABLE.charAt(buffer & 0x3F) + ); + } + + if (padding == 2) { + a = input.charCodeAt(position) << 8; + b = input.charCodeAt(++position); + buffer = a + b; + output += ( + TABLE.charAt(buffer >> 10) + + TABLE.charAt((buffer >> 4) & 0x3F) + + TABLE.charAt((buffer << 2) & 0x3F) + + '=' + ); + } else if (padding == 1) { + buffer = input.charCodeAt(position); + output += ( + TABLE.charAt(buffer >> 2) + + TABLE.charAt((buffer << 4) & 0x3F) + + '==' + ); + } + + return output; + }; + + var base64 = { + 'encode': encode, + 'decode': decode, + 'version': '0.1.0' + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return base64; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = base64; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in base64) { + base64.hasOwnProperty(key) && (freeExports[key] = base64[key]); + } + } + } else { // in Rhino or a web browser + root.base64 = base64; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],9:[function(_dereq_,module,exports){ +'use strict' + +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function placeHoldersCount (b64) { + var len = b64.length + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 +} + +function byteLength (b64) { + // base64 is 4/3 + up to two characters of the original data + return (b64.length * 3 / 4) - placeHoldersCount(b64) +} + +function toByteArray (b64) { + var i, l, tmp, placeHolders, arr + var len = b64.length + placeHolders = placeHoldersCount(b64) + + arr = new Arr((len * 3 / 4) - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? len - 4 : len + + var L = 0 + + for (i = 0; i < l; i += 4) { + tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] + arr[L++] = (tmp >> 16) & 0xFF + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + if (placeHolders === 2) { + tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[L++] = tmp & 0xFF + } else if (placeHolders === 1) { + tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = ((uint8[i] << 16) & 0xFF0000) + ((uint8[i + 1] << 8) & 0xFF00) + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var output = '' + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + output += lookup[tmp >> 2] + output += lookup[(tmp << 4) & 0x3F] + output += '==' + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) + output += lookup[tmp >> 10] + output += lookup[(tmp >> 4) & 0x3F] + output += lookup[(tmp << 2) & 0x3F] + output += '=' + } + + parts.push(output) + + return parts.join('') +} + +},{}],10:[function(_dereq_,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = _dereq_('base64-js') +var ieee754 = _dereq_('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 + } catch (e) { + return false + } +} + +Object.defineProperty(Buffer.prototype, 'parent', { + get: function () { + if (!(this instanceof Buffer)) { + return undefined + } + return this.buffer + } +}) + +Object.defineProperty(Buffer.prototype, 'offset', { + get: function () { + if (!(this instanceof Buffer)) { + return undefined + } + return this.byteOffset + } +}) + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('Invalid typed array length') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (isArrayBuffer(value) || (value && isArrayBuffer(value.buffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return fromObject(value) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj) { + if (ArrayBuffer.isView(obj) || 'length' in obj) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } + } + + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.') +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (ArrayBuffer.isView(buf)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isArrayBuffer(string)) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string + } + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.toLocaleString = Buffer.prototype.toString + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + var strLen = string.length + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : new Buffer(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffers from another context (i.e. an iframe) do not pass the `instanceof` check +// but they should be treated as valid. See: https://github.com/feross/buffer/issues/166 +function isArrayBuffer (obj) { + return obj instanceof ArrayBuffer || + (obj != null && obj.constructor != null && obj.constructor.name === 'ArrayBuffer' && + typeof obj.byteLength === 'number') +} + +function numberIsNaN (obj) { + return obj !== obj // eslint-disable-line no-self-compare +} + +},{"base64-js":9,"ieee754":13}],11:[function(_dereq_,module,exports){ +(function (Buffer){ +var clone = (function() { +'use strict'; + +function _instanceof(obj, type) { + return type != null && obj instanceof type; +} + +var nativeMap; +try { + nativeMap = Map; +} catch(_) { + // maybe a reference error because no `Map`. Give it a dummy value that no + // value will ever be an instanceof. + nativeMap = function() {}; +} + +var nativeSet; +try { + nativeSet = Set; +} catch(_) { + nativeSet = function() {}; +} + +var nativePromise; +try { + nativePromise = Promise; +} catch(_) { + nativePromise = function() {}; +} + +/** + * Clones (copies) an Object using deep copying. + * + * This function supports circular references by default, but if you are certain + * there are no circular references in your object, you can save some CPU time + * by calling clone(obj, false). + * + * Caution: if `circular` is false and `parent` contains circular references, + * your program may enter an infinite loop and crash. + * + * @param `parent` - the object to be cloned + * @param `circular` - set to true if the object to be cloned may contain + * circular references. (optional - true by default) + * @param `depth` - set to a number if the object is only to be cloned to + * a particular depth. (optional - defaults to Infinity) + * @param `prototype` - sets the prototype to be used when cloning an object. + * (optional - defaults to parent prototype). + * @param `includeNonEnumerable` - set to true if the non-enumerable properties + * should be cloned as well. Non-enumerable properties on the prototype + * chain will be ignored. (optional - false by default) +*/ +function clone(parent, circular, depth, prototype, includeNonEnumerable) { + if (typeof circular === 'object') { + depth = circular.depth; + prototype = circular.prototype; + includeNonEnumerable = circular.includeNonEnumerable; + circular = circular.circular; + } + // maintain two arrays for circular references, where corresponding parents + // and children have the same index + var allParents = []; + var allChildren = []; + + var useBuffer = typeof Buffer != 'undefined'; + + if (typeof circular == 'undefined') + circular = true; + + if (typeof depth == 'undefined') + depth = Infinity; + + // recurse this function so we don't reset allParents and allChildren + function _clone(parent, depth) { + // cloning null always returns null + if (parent === null) + return null; + + if (depth === 0) + return parent; + + var child; + var proto; + if (typeof parent != 'object') { + return parent; + } + + if (_instanceof(parent, nativeMap)) { + child = new nativeMap(); + } else if (_instanceof(parent, nativeSet)) { + child = new nativeSet(); + } else if (_instanceof(parent, nativePromise)) { + child = new nativePromise(function (resolve, reject) { + parent.then(function(value) { + resolve(_clone(value, depth - 1)); + }, function(err) { + reject(_clone(err, depth - 1)); + }); + }); + } else if (clone.__isArray(parent)) { + child = []; + } else if (clone.__isRegExp(parent)) { + child = new RegExp(parent.source, __getRegExpFlags(parent)); + if (parent.lastIndex) child.lastIndex = parent.lastIndex; + } else if (clone.__isDate(parent)) { + child = new Date(parent.getTime()); + } else if (useBuffer && Buffer.isBuffer(parent)) { + child = new Buffer(parent.length); + parent.copy(child); + return child; + } else if (_instanceof(parent, Error)) { + child = Object.create(parent); + } else { + if (typeof prototype == 'undefined') { + proto = Object.getPrototypeOf(parent); + child = Object.create(proto); + } + else { + child = Object.create(prototype); + proto = prototype; + } + } + + if (circular) { + var index = allParents.indexOf(parent); + + if (index != -1) { + return allChildren[index]; + } + allParents.push(parent); + allChildren.push(child); + } + + if (_instanceof(parent, nativeMap)) { + parent.forEach(function(value, key) { + var keyChild = _clone(key, depth - 1); + var valueChild = _clone(value, depth - 1); + child.set(keyChild, valueChild); + }); + } + if (_instanceof(parent, nativeSet)) { + parent.forEach(function(value) { + var entryChild = _clone(value, depth - 1); + child.add(entryChild); + }); + } + + for (var i in parent) { + var attrs; + if (proto) { + attrs = Object.getOwnPropertyDescriptor(proto, i); + } + + if (attrs && attrs.set == null) { + continue; + } + child[i] = _clone(parent[i], depth - 1); + } + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(parent); + for (var i = 0; i < symbols.length; i++) { + // Don't need to worry about cloning a symbol because it is a primitive, + // like a number or string. + var symbol = symbols[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); + if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { + continue; + } + child[symbol] = _clone(parent[symbol], depth - 1); + if (!descriptor.enumerable) { + Object.defineProperty(child, symbol, { + enumerable: false + }); + } + } + } + + if (includeNonEnumerable) { + var allPropertyNames = Object.getOwnPropertyNames(parent); + for (var i = 0; i < allPropertyNames.length; i++) { + var propertyName = allPropertyNames[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); + if (descriptor && descriptor.enumerable) { + continue; + } + child[propertyName] = _clone(parent[propertyName], depth - 1); + Object.defineProperty(child, propertyName, { + enumerable: false + }); + } + } + + return child; + } + + return _clone(parent, depth); +} + +/** + * Simple flat clone using prototype, accepts only objects, usefull for property + * override on FLAT configuration object (no nested props). + * + * USE WITH CAUTION! This may not behave as you wish if you do not know how this + * works. + */ +clone.clonePrototype = function clonePrototype(parent) { + if (parent === null) + return null; + + var c = function () {}; + c.prototype = parent; + return new c(); +}; + +// private utility functions + +function __objToStr(o) { + return Object.prototype.toString.call(o); +} +clone.__objToStr = __objToStr; + +function __isDate(o) { + return typeof o === 'object' && __objToStr(o) === '[object Date]'; +} +clone.__isDate = __isDate; + +function __isArray(o) { + return typeof o === 'object' && __objToStr(o) === '[object Array]'; +} +clone.__isArray = __isArray; + +function __isRegExp(o) { + return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; +} +clone.__isRegExp = __isRegExp; + +function __getRegExpFlags(re) { + var flags = ''; + if (re.global) flags += 'g'; + if (re.ignoreCase) flags += 'i'; + if (re.multiline) flags += 'm'; + return flags; +} +clone.__getRegExpFlags = __getRegExpFlags; + +return clone; +})(); + +if (typeof module === 'object' && module.exports) { + module.exports = clone; +} + +}).call(this,_dereq_("buffer").Buffer) +},{"buffer":10}],12:[function(_dereq_,module,exports){ + +/** + * Expose `Emitter`. + */ + +if (typeof module !== 'undefined') { + module.exports = Emitter; +} + +/** + * Initialize a new `Emitter`. + * + * @api public + */ + +function Emitter(obj) { + if (obj) return mixin(obj); +}; + +/** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + +function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; +} + +/** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.on = +Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks['$' + event] = this._callbacks['$' + event] || []) + .push(fn); + return this; +}; + +/** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.once = function(event, fn){ + function on() { + this.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; +}; + +/** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.off = +Emitter.prototype.removeListener = +Emitter.prototype.removeAllListeners = +Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks['$' + event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks['$' + event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; +}; + +/** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + +Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks['$' + event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; +}; + +/** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + +Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks['$' + event] || []; +}; + +/** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + +Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; +}; + +},{}],13:[function(_dereq_,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],14:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Constants. + */ + +var errorMessage; + +errorMessage = 'An argument without append, prepend, ' + + 'or detach methods was given to `List'; + +/** + * Creates a new List: A linked list is a bit like an Array, but + * knows nothing about how many items are in it, and knows only about its + * first (`head`) and last (`tail`) items. Each item (e.g. `head`, `tail`, + * &c.) knows which item comes before or after it (its more like the + * implementation of the DOM in JavaScript). + * @global + * @private + * @constructor + * @class Represents an instance of List. + */ + +function List(/*items...*/) { + if (arguments.length) { + return List.from(arguments); + } +} + +var ListPrototype; + +ListPrototype = List.prototype; + +/** + * Creates a new list from the arguments (each a list item) passed in. + * @name List.of + * @param {...ListItem} [items] - Zero or more items to attach. + * @returns {list} - A new instance of List. + */ + +List.of = function (/*items...*/) { + return List.from.call(this, arguments); +}; + +/** + * Creates a new list from the given array-like object (each a list item) + * passed in. + * @name List.from + * @param {ListItem[]} [items] - The items to append. + * @returns {list} - A new instance of List. + */ +List.from = function (items) { + var list = new this(), length, iterator, item; + + if (items && (length = items.length)) { + iterator = -1; + + while (++iterator < length) { + item = items[iterator]; + + if (item !== null && item !== undefined) { + list.append(item); + } + } + } + + return list; +}; + +/** + * List#head + * Default to `null`. + */ +ListPrototype.head = null; + +/** + * List#tail + * Default to `null`. + */ +ListPrototype.tail = null; + +/** + * Returns the list's items as an array. This does *not* detach the items. + * @name List#toArray + * @returns {ListItem[]} - An array of (still attached) ListItems. + */ +ListPrototype.toArray = function () { + var item = this.head, + result = []; + + while (item) { + result.push(item); + item = item.next; + } + + return result; +}; + +/** + * Prepends the given item to the list: Item will be the new first item + * (`head`). + * @name List#prepend + * @param {ListItem} item - The item to prepend. + * @returns {ListItem} - An instance of ListItem (the given item). + */ +ListPrototype.prepend = function (item) { + if (!item) { + return false; + } + + if (!item.append || !item.prepend || !item.detach) { + throw new Error(errorMessage + '#prepend`.'); + } + + var self, head; + + // Cache self. + self = this; + + // If self has a first item, defer prepend to the first items prepend + // method, and return the result. + head = self.head; + + if (head) { + return head.prepend(item); + } + + // ...otherwise, there is no `head` (or `tail`) item yet. + + // Detach the prependee. + item.detach(); + + // Set the prependees parent list to reference self. + item.list = self; + + // Set self's first item to the prependee, and return the item. + self.head = item; + + return item; +}; + +/** + * Appends the given item to the list: Item will be the new last item (`tail`) + * if the list had a first item, and its first item (`head`) otherwise. + * @name List#append + * @param {ListItem} item - The item to append. + * @returns {ListItem} - An instance of ListItem (the given item). + */ + +ListPrototype.append = function (item) { + if (!item) { + return false; + } + + if (!item.append || !item.prepend || !item.detach) { + throw new Error(errorMessage + '#append`.'); + } + + var self, head, tail; + + // Cache self. + self = this; + + // If self has a last item, defer appending to the last items append + // method, and return the result. + tail = self.tail; + + if (tail) { + return tail.append(item); + } + + // If self has a first item, defer appending to the first items append + // method, and return the result. + head = self.head; + + if (head) { + return head.append(item); + } + + // ...otherwise, there is no `tail` or `head` item yet. + + // Detach the appendee. + item.detach(); + + // Set the appendees parent list to reference self. + item.list = self; + + // Set self's first item to the appendee, and return the item. + self.head = item; + + return item; +}; + +/** + * Creates a new ListItem: A linked list item is a bit like DOM node: + * It knows only about its "parent" (`list`), the item before it (`prev`), + * and the item after it (`next`). + * @global + * @private + * @constructor + * @class Represents an instance of ListItem. + */ + +function ListItem() {} + +List.Item = ListItem; + +var ListItemPrototype = ListItem.prototype; + +ListItemPrototype.next = null; + +ListItemPrototype.prev = null; + +ListItemPrototype.list = null; + +/** + * Detaches the item operated on from its parent list. + * @name ListItem#detach + * @returns {ListItem} - The item operated on. + */ +ListItemPrototype.detach = function () { + // Cache self, the parent list, and the previous and next items. + var self = this, + list = self.list, + prev = self.prev, + next = self.next; + + // If the item is already detached, return self. + if (!list) { + return self; + } + + // If self is the last item in the parent list, link the lists last item + // to the previous item. + if (list.tail === self) { + list.tail = prev; + } + + // If self is the first item in the parent list, link the lists first item + // to the next item. + if (list.head === self) { + list.head = next; + } + + // If both the last and first items in the parent list are the same, + // remove the link to the last item. + if (list.tail === list.head) { + list.tail = null; + } + + // If a previous item exists, link its next item to selfs next item. + if (prev) { + prev.next = next; + } + + // If a next item exists, link its previous item to selfs previous item. + if (next) { + next.prev = prev; + } + + // Remove links from self to both the next and previous items, and to the + // parent list. + self.prev = self.next = self.list = null; + + // Return self. + return self; +}; + +/** + * Prepends the given item *before* the item operated on. + * @name ListItem#prepend + * @param {ListItem} item - The item to prepend. + * @returns {ListItem} - The item operated on, or false when that item is not + * attached. + */ +ListItemPrototype.prepend = function (item) { + if (!item || !item.append || !item.prepend || !item.detach) { + throw new Error(errorMessage + 'Item#prepend`.'); + } + + // Cache self, the parent list, and the previous item. + var self = this, + list = self.list, + prev = self.prev; + + // If self is detached, return false. + if (!list) { + return false; + } + + // Detach the prependee. + item.detach(); + + // If self has a previous item... + if (prev) { + // ...link the prependees previous item, to selfs previous item. + item.prev = prev; + + // ...link the previous items next item, to self. + prev.next = item; + } + + // Set the prependees next item to self. + item.next = self; + + // Set the prependees parent list to selfs parent list. + item.list = list; + + // Set the previous item of self to the prependee. + self.prev = item; + + // If self is the first item in the parent list, link the lists first item + // to the prependee. + if (self === list.head) { + list.head = item; + } + + // If the the parent list has no last item, link the lists last item to + // self. + if (!list.tail) { + list.tail = self; + } + + // Return the prependee. + return item; +}; + +/** + * Appends the given item *after* the item operated on. + * @name ListItem#append + * @param {ListItem} item - The item to append. + * @returns {ListItem} - The item operated on, or false when that item is not + * attached. + */ +ListItemPrototype.append = function (item) { + // If item is falsey, return false. + if (!item || !item.append || !item.prepend || !item.detach) { + throw new Error(errorMessage + 'Item#append`.'); + } + + // Cache self, the parent list, and the next item. + var self = this, + list = self.list, + next = self.next; + + // If self is detached, return false. + if (!list) { + return false; + } + + // Detach the appendee. + item.detach(); + + // If self has a next item... + if (next) { + // ...link the appendees next item, to selfs next item. + item.next = next; + + // ...link the next items previous item, to the appendee. + next.prev = item; + } + + // Set the appendees previous item to self. + item.prev = self; + + // Set the appendees parent list to selfs parent list. + item.list = list; + + // Set the next item of self to the appendee. + self.next = item; + + // If the the parent list has no last item or if self is the parent lists + // last item, link the lists last item to the appendee. + if (self === list.tail || !list.tail) { + list.tail = item; + } + + // Return the appendee. + return item; +}; + +/** + * Expose `List`. + */ + +module.exports = List; + +},{}],15:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = _dereq_('./_source/linked-list.js'); + +},{"./_source/linked-list.js":14}],16:[function(_dereq_,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +module.exports = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } + + var regexp = /\+/g; + qs = qs.split(sep); + + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } + + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } + + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; + + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; + } + + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } + + return obj; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],17:[function(_dereq_,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; + + case 'boolean': + return v ? 'true' : 'false'; + + case 'number': + return isFinite(v) ? v : ''; + + default: + return ''; + } +}; + +module.exports = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (obj === null) { + obj = undefined; + } + + if (typeof obj === 'object') { + return map(objectKeys(obj), function(k) { + var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; + if (isArray(obj[k])) { + return map(obj[k], function(v) { + return ks + encodeURIComponent(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + } + }).join(sep); + + } + + if (!name) return ''; + return encodeURIComponent(stringifyPrimitive(name)) + eq + + encodeURIComponent(stringifyPrimitive(obj)); +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +function map (xs, f) { + if (xs.map) return xs.map(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; +} + +var objectKeys = Object.keys || function (obj) { + var res = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; + +},{}],18:[function(_dereq_,module,exports){ +'use strict'; + +exports.decode = exports.parse = _dereq_('./decode'); +exports.encode = exports.stringify = _dereq_('./encode'); + +},{"./decode":16,"./encode":17}],19:[function(_dereq_,module,exports){ +var Emitter = _dereq_('component-emitter'); + +var SCChannel = function (name, client, options) { + var self = this; + + Emitter.call(this); + + this.PENDING = 'pending'; + this.SUBSCRIBED = 'subscribed'; + this.UNSUBSCRIBED = 'unsubscribed'; + + this.name = name; + this.state = this.UNSUBSCRIBED; + this.client = client; + + this.options = options || {}; + this.setOptions(this.options); +}; + +SCChannel.prototype = Object.create(Emitter.prototype); + +SCChannel.prototype.setOptions = function (options) { + if (!options) { + options = {}; + } + this.waitForAuth = options.waitForAuth || false; + this.batch = options.batch || false; + + if (options.data !== undefined) { + this.data = options.data; + } +}; + +SCChannel.prototype.getState = function () { + return this.state; +}; + +SCChannel.prototype.subscribe = function (options) { + this.client.subscribe(this.name, options); +}; + +SCChannel.prototype.unsubscribe = function () { + this.client.unsubscribe(this.name); +}; + +SCChannel.prototype.isSubscribed = function (includePending) { + return this.client.isSubscribed(this.name, includePending); +}; + +SCChannel.prototype.publish = function (data, callback) { + this.client.publish(this.name, data, callback); +}; + +SCChannel.prototype.watch = function (handler) { + this.client.watch(this.name, handler); +}; + +SCChannel.prototype.unwatch = function (handler) { + this.client.unwatch(this.name, handler); +}; + +SCChannel.prototype.watchers = function () { + return this.client.watchers(this.name); +}; + +SCChannel.prototype.destroy = function () { + this.client.destroyChannel(this.name); +}; + +module.exports.SCChannel = SCChannel; + +},{"component-emitter":12}],20:[function(_dereq_,module,exports){ +// Based on https://github.com/dscape/cycle/blob/master/cycle.js + +module.exports = function decycle(object) { +// Make a deep copy of an object or array, assuring that there is at most +// one instance of each object or array in the resulting structure. The +// duplicate references (which might be forming cycles) are replaced with +// an object of the form +// {$ref: PATH} +// where the PATH is a JSONPath string that locates the first occurance. +// So, +// var a = []; +// a[0] = a; +// return JSON.stringify(JSON.decycle(a)); +// produces the string '[{"$ref":"$"}]'. + +// JSONPath is used to locate the unique object. $ indicates the top level of +// the object or array. [NUMBER] or [STRING] indicates a child member or +// property. + + var objects = [], // Keep a reference to each unique object or array + paths = []; // Keep the path to each unique object or array + + return (function derez(value, path) { + +// The derez recurses through the object, producing the deep copy. + + var i, // The loop counter + name, // Property name + nu; // The new object or array + +// typeof null === 'object', so go on if this value is really an object but not +// one of the weird builtin objects. + + if (typeof value === 'object' && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String)) { + +// If the value is an object or array, look to see if we have already +// encountered it. If so, return a $ref/path object. This is a hard way, +// linear search that will get slower as the number of unique objects grows. + + for (i = 0; i < objects.length; i += 1) { + if (objects[i] === value) { + return {$ref: paths[i]}; + } + } + +// Otherwise, accumulate the unique value and its path. + + objects.push(value); + paths.push(path); + +// If it is an array, replicate the array. + + if (Object.prototype.toString.apply(value) === '[object Array]') { + nu = []; + for (i = 0; i < value.length; i += 1) { + nu[i] = derez(value[i], path + '[' + i + ']'); + } + } else { + +// If it is an object, replicate the object. + + nu = {}; + for (name in value) { + if (Object.prototype.hasOwnProperty.call(value, name)) { + nu[name] = derez(value[name], + path + '[' + JSON.stringify(name) + ']'); + } + } + } + return nu; + } + return value; + }(object, '$')); +}; + +},{}],21:[function(_dereq_,module,exports){ +var decycle = _dereq_('./decycle'); + +var isStrict = (function () { return !this; })(); + +function AuthTokenExpiredError(message, expiry) { + this.name = 'AuthTokenExpiredError'; + this.message = message; + this.expiry = expiry; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +AuthTokenExpiredError.prototype = Object.create(Error.prototype); + + +function AuthTokenInvalidError(message) { + this.name = 'AuthTokenInvalidError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +AuthTokenInvalidError.prototype = Object.create(Error.prototype); + + +function AuthTokenNotBeforeError(message, date) { + this.name = 'AuthTokenNotBeforeError'; + this.message = message; + this.date = date; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +AuthTokenNotBeforeError.prototype = Object.create(Error.prototype); + + +// For any other auth token error. +function AuthTokenError(message) { + this.name = 'AuthTokenError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +AuthTokenError.prototype = Object.create(Error.prototype); + + +function SilentMiddlewareBlockedError(message, type) { + this.name = 'SilentMiddlewareBlockedError'; + this.message = message; + this.type = type; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +SilentMiddlewareBlockedError.prototype = Object.create(Error.prototype); + + +function InvalidActionError(message) { + this.name = 'InvalidActionError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +InvalidActionError.prototype = Object.create(Error.prototype); + +function InvalidArgumentsError(message) { + this.name = 'InvalidArgumentsError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +InvalidArgumentsError.prototype = Object.create(Error.prototype); + +function InvalidOptionsError(message) { + this.name = 'InvalidOptionsError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +InvalidOptionsError.prototype = Object.create(Error.prototype); + + +function InvalidMessageError(message) { + this.name = 'InvalidMessageError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +InvalidMessageError.prototype = Object.create(Error.prototype); + + +function SocketProtocolError(message, code) { + this.name = 'SocketProtocolError'; + this.message = message; + this.code = code; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +SocketProtocolError.prototype = Object.create(Error.prototype); + + +function ServerProtocolError(message) { + this.name = 'ServerProtocolError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +ServerProtocolError.prototype = Object.create(Error.prototype); + +function HTTPServerError(message) { + this.name = 'HTTPServerError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +HTTPServerError.prototype = Object.create(Error.prototype); + + +function ResourceLimitError(message) { + this.name = 'ResourceLimitError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +ResourceLimitError.prototype = Object.create(Error.prototype); + + +function TimeoutError(message) { + this.name = 'TimeoutError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +TimeoutError.prototype = Object.create(Error.prototype); + + +function BadConnectionError(message, type) { + this.name = 'BadConnectionError'; + this.message = message; + this.type = type; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +BadConnectionError.prototype = Object.create(Error.prototype); + + +function BrokerError(message) { + this.name = 'BrokerError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +BrokerError.prototype = Object.create(Error.prototype); + + +function ProcessExitError(message, code) { + this.name = 'ProcessExitError'; + this.message = message; + this.code = code; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +ProcessExitError.prototype = Object.create(Error.prototype); + + +function UnknownError(message) { + this.name = 'UnknownError'; + this.message = message; + if (Error.captureStackTrace && !isStrict) { + Error.captureStackTrace(this, arguments.callee); + } else { + this.stack = (new Error()).stack; + } +} +UnknownError.prototype = Object.create(Error.prototype); + + +// Expose all error types. + +module.exports = { + AuthTokenExpiredError: AuthTokenExpiredError, + AuthTokenInvalidError: AuthTokenInvalidError, + AuthTokenNotBeforeError: AuthTokenNotBeforeError, + AuthTokenError: AuthTokenError, + SilentMiddlewareBlockedError: SilentMiddlewareBlockedError, + InvalidActionError: InvalidActionError, + InvalidArgumentsError: InvalidArgumentsError, + InvalidOptionsError: InvalidOptionsError, + InvalidMessageError: InvalidMessageError, + SocketProtocolError: SocketProtocolError, + ServerProtocolError: ServerProtocolError, + HTTPServerError: HTTPServerError, + ResourceLimitError: ResourceLimitError, + TimeoutError: TimeoutError, + BadConnectionError: BadConnectionError, + BrokerError: BrokerError, + ProcessExitError: ProcessExitError, + UnknownError: UnknownError +}; + +module.exports.socketProtocolErrorStatuses = { + 1001: 'Socket was disconnected', + 1002: 'A WebSocket protocol error was encountered', + 1003: 'Server terminated socket because it received invalid data', + 1005: 'Socket closed without status code', + 1006: 'Socket hung up', + 1007: 'Message format was incorrect', + 1008: 'Encountered a policy violation', + 1009: 'Message was too big to process', + 1010: 'Client ended the connection because the server did not comply with extension requirements', + 1011: 'Server encountered an unexpected fatal condition', + 4000: 'Server ping timed out', + 4001: 'Client pong timed out', + 4002: 'Server failed to sign auth token', + 4003: 'Failed to complete handshake', + 4004: 'Client failed to save auth token', + 4005: 'Did not receive #handshake from client before timeout', + 4006: 'Failed to bind socket to message broker', + 4007: 'Client connection establishment timed out', + 4008: 'Server rejected handshake from client' +}; + +module.exports.socketProtocolIgnoreStatuses = { + 1000: 'Socket closed normally', + 1001: 'Socket hung up' +}; + +// Properties related to error domains cannot be serialized. +var unserializableErrorProperties = { + domain: 1, + domainEmitter: 1, + domainThrown: 1 +}; + +// Convert an error into a JSON-compatible type which can later be hydrated +// back to its *original* form. +module.exports.dehydrateError = function dehydrateError(error, includeStackTrace) { + var dehydratedError; + + if (error && typeof error == 'object') { + dehydratedError = { + message: error.message + }; + if (includeStackTrace) { + dehydratedError.stack = error.stack; + } + for (var i in error) { + if (!unserializableErrorProperties[i]) { + dehydratedError[i] = error[i]; + } + } + } else if (typeof error == 'function') { + dehydratedError = '[function ' + (error.name || 'anonymous') + ']'; + } else { + dehydratedError = error; + } + + return decycle(dehydratedError); +}; + +// Convert a dehydrated error back to its *original* form. +module.exports.hydrateError = function hydrateError(error) { + var hydratedError = null; + if (error != null) { + if (typeof error == 'object') { + hydratedError = new Error(error.message); + for (var i in error) { + if (error.hasOwnProperty(i)) { + hydratedError[i] = error[i]; + } + } + } else { + hydratedError = error; + } + } + return hydratedError; +}; + +module.exports.decycle = decycle; + +},{"./decycle":20}],22:[function(_dereq_,module,exports){ +(function (global){ +var base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +var validJSONStartRegex = /^[ \n\r\t]*[{\[]/; + +var arrayBufferToBase64 = function (arraybuffer) { + var bytes = new Uint8Array(arraybuffer); + var len = bytes.length; + var base64 = ''; + + for (var i = 0; i < len; i += 3) { + base64 += base64Chars[bytes[i] >> 2]; + base64 += base64Chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += base64Chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += base64Chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + '='; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + '=='; + } + + return base64; +}; + +var binaryToBase64Replacer = function (key, value) { + if (global.ArrayBuffer && value instanceof global.ArrayBuffer) { + return { + base64: true, + data: arrayBufferToBase64(value) + }; + } else if (global.Buffer) { + if (value instanceof global.Buffer){ + return { + base64: true, + data: value.toString('base64') + }; + } + // Some versions of Node.js convert Buffers to Objects before they are passed to + // the replacer function - Because of this, we need to rehydrate Buffers + // before we can convert them to base64 strings. + if (value && value.type === 'Buffer' && Array.isArray(value.data)) { + var rehydratedBuffer; + if (global.Buffer.from) { + rehydratedBuffer = global.Buffer.from(value.data); + } else { + rehydratedBuffer = new global.Buffer(value.data); + } + return { + base64: true, + data: rehydratedBuffer.toString('base64') + }; + } + } + return value; +}; + +// Decode the data which was transmitted over the wire to a JavaScript Object in a format which SC understands. +// See encode function below for more details. +module.exports.decode = function (input) { + if (input == null) { + return null; + } + // Leave ping or pong message as is + if (input === '#1' || input === '#2') { + return input; + } + var message = input.toString(); + + // Performance optimization to detect invalid JSON packet sooner. + if (!validJSONStartRegex.test(message)) { + return message; + } + + try { + return JSON.parse(message); + } catch (err) {} + return message; +}; + +// Encode a raw JavaScript object (which is in the SC protocol format) into a format for +// transfering it over the wire. In this case, we just convert it into a simple JSON string. +// If you want to create your own custom codec, you can encode the object into any format +// (e.g. binary ArrayBuffer or string with any kind of compression) so long as your decode +// function is able to rehydrate that object back into its original JavaScript Object format +// (which adheres to the SC protocol). +// See https://github.com/SocketCluster/socketcluster/blob/master/socketcluster-protocol.md +// for details about the SC protocol. +module.exports.encode = function (object) { + // Leave ping or pong message as is + if (object === '#1' || object === '#2') { + return object; + } + return JSON.stringify(object, binaryToBase64Replacer); +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],23:[function(_dereq_,module,exports){ +var v1 = _dereq_('./v1'); +var v4 = _dereq_('./v4'); + +var uuid = v4; +uuid.v1 = v1; +uuid.v4 = v4; + +module.exports = uuid; + +},{"./v1":26,"./v4":27}],24:[function(_dereq_,module,exports){ +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +var byteToHex = []; +for (var i = 0; i < 256; ++i) { + byteToHex[i] = (i + 0x100).toString(16).substr(1); +} + +function bytesToUuid(buf, offset) { + var i = offset || 0; + var bth = byteToHex; + return bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]]; +} + +module.exports = bytesToUuid; + +},{}],25:[function(_dereq_,module,exports){ +// Unique ID creation requires a high quality random # generator. In the +// browser this is a little complicated due to unknown quality of Math.random() +// and inconsistent support for the `crypto` API. We do the best we can via +// feature-detection + +// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. +var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues.bind(crypto)) || + (typeof(msCrypto) != 'undefined' && msCrypto.getRandomValues.bind(msCrypto)); +if (getRandomValues) { + // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto + var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef + + module.exports = function whatwgRNG() { + getRandomValues(rnds8); + return rnds8; + }; +} else { + // Math.random()-based (RNG) + // + // If all else fails, use Math.random(). It's fast, but is of unspecified + // quality. + var rnds = new Array(16); + + module.exports = function mathRNG() { + for (var i = 0, r; i < 16; i++) { + if ((i & 0x03) === 0) r = Math.random() * 0x100000000; + rnds[i] = r >>> ((i & 0x03) << 3) & 0xff; + } + + return rnds; + }; +} + +},{}],26:[function(_dereq_,module,exports){ +var rng = _dereq_('./lib/rng'); +var bytesToUuid = _dereq_('./lib/bytesToUuid'); + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html + +var _nodeId; +var _clockseq; + +// Previous uuid creation time +var _lastMSecs = 0; +var _lastNSecs = 0; + +// See https://github.com/broofa/node-uuid for API details +function v1(options, buf, offset) { + var i = buf && offset || 0; + var b = buf || []; + + options = options || {}; + var node = options.node || _nodeId; + var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; + + // node and clockseq need to be initialized to random values if they're not + // specified. We do this lazily to minimize issues related to insufficient + // system entropy. See #189 + if (node == null || clockseq == null) { + var seedBytes = rng(); + if (node == null) { + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + node = _nodeId = [ + seedBytes[0] | 0x01, + seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5] + ]; + } + if (clockseq == null) { + // Per 4.2.2, randomize (14 bit) clockseq + clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; + } + } + + // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime(); + + // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; + + // Time since last uuid creation (in msecs) + var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000; + + // Per 4.2.1.2, Bump clockseq on clock regression + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } + + // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } + + // Per 4.2.1.2 Throw error if too many uuids are requested + if (nsecs >= 10000) { + throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec'); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; + + // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + msecs += 12219292800000; + + // `time_low` + var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; + + // `time_mid` + var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; + + // `time_high_and_version` + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + b[i++] = tmh >>> 16 & 0xff; + + // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + b[i++] = clockseq >>> 8 | 0x80; + + // `clock_seq_low` + b[i++] = clockseq & 0xff; + + // `node` + for (var n = 0; n < 6; ++n) { + b[i + n] = node[n]; + } + + return buf ? buf : bytesToUuid(b); +} + +module.exports = v1; + +},{"./lib/bytesToUuid":24,"./lib/rng":25}],27:[function(_dereq_,module,exports){ +var rng = _dereq_('./lib/rng'); +var bytesToUuid = _dereq_('./lib/bytesToUuid'); + +function v4(options, buf, offset) { + var i = buf && offset || 0; + + if (typeof(options) == 'string') { + buf = options === 'binary' ? new Array(16) : null; + options = null; + } + options = options || {}; + + var rnds = options.random || (options.rng || rng)(); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + // Copy bytes to buffer, if provided + if (buf) { + for (var ii = 0; ii < 16; ++ii) { + buf[i + ii] = rnds[ii]; + } + } + + return buf || bytesToUuid(rnds); +} + +module.exports = v4; + +},{"./lib/bytesToUuid":24,"./lib/rng":25}]},{},[1])(1) +}); diff --git a/socketcluster/server.js b/socketcluster/server.js new file mode 100644 index 0000000..194d5f3 --- /dev/null +++ b/socketcluster/server.js @@ -0,0 +1,106 @@ +/* + This is the SocketCluster master controller file. + It is responsible for bootstrapping the SocketCluster master process. + Be careful when modifying the options object below. + If you plan to run SCC on Kubernetes or another orchestrator at some point + in the future, avoid changing the environment variable names below as + each one has a specific meaning within the SC ecosystem. +*/ + +var path = require('path'); +var argv = require('minimist')(process.argv.slice(2)); +var scHotReboot = require('sc-hot-reboot'); + +var fsUtil = require('socketcluster/fsutil'); +var waitForFile = fsUtil.waitForFile; + +var SocketCluster = require('socketcluster'); + +var workerControllerPath = argv.wc || process.env.SOCKETCLUSTER_WORKER_CONTROLLER; +var brokerControllerPath = argv.bc || process.env.SOCKETCLUSTER_BROKER_CONTROLLER; +var workerClusterControllerPath = argv.wcc || process.env.SOCKETCLUSTER_WORKERCLUSTER_CONTROLLER; +var environment = process.env.ENV || 'dev'; + +var options = { + workers: Number(argv.w) || Number(process.env.SOCKETCLUSTER_WORKERS) || 1, + brokers: Number(argv.b) || Number(process.env.SOCKETCLUSTER_BROKERS) || 1, + port: Number(argv.p) || Number(process.env.SOCKETCLUSTER_PORT) || 8000, + // You can switch to 'sc-uws' for improved performance. + wsEngine: process.env.SOCKETCLUSTER_WS_ENGINE || 'ws', + appName: argv.n || process.env.SOCKETCLUSTER_APP_NAME || null, + workerController: workerControllerPath || path.join(__dirname, 'worker.js'), + brokerController: brokerControllerPath || path.join(__dirname, 'broker.js'), + workerClusterController: workerClusterControllerPath || null, + socketChannelLimit: Number(process.env.SOCKETCLUSTER_SOCKET_CHANNEL_LIMIT) || 1000, + clusterStateServerHost: argv.cssh || process.env.SCC_STATE_SERVER_HOST || null, + clusterStateServerPort: process.env.SCC_STATE_SERVER_PORT || null, + clusterMappingEngine: process.env.SCC_MAPPING_ENGINE || null, + clusterClientPoolSize: process.env.SCC_CLIENT_POOL_SIZE || null, + clusterAuthKey: process.env.SCC_AUTH_KEY || null, + clusterInstanceIp: process.env.SCC_INSTANCE_IP || null, + clusterInstanceIpFamily: process.env.SCC_INSTANCE_IP_FAMILY || null, + clusterStateServerConnectTimeout: Number(process.env.SCC_STATE_SERVER_CONNECT_TIMEOUT) || null, + clusterStateServerAckTimeout: Number(process.env.SCC_STATE_SERVER_ACK_TIMEOUT) || null, + clusterStateServerReconnectRandomness: Number(process.env.SCC_STATE_SERVER_RECONNECT_RANDOMNESS) || null, + crashWorkerOnError: argv['auto-reboot'] != false, + // If using nodemon, set this to true, and make sure that environment is 'dev'. + killMasterOnSignal: false, + environment: environment +}; + +var bootTimeout = Number(process.env.SOCKETCLUSTER_CONTROLLER_BOOT_TIMEOUT) || 10000; +var SOCKETCLUSTER_OPTIONS; + +if (process.env.SOCKETCLUSTER_OPTIONS) { + SOCKETCLUSTER_OPTIONS = JSON.parse(process.env.SOCKETCLUSTER_OPTIONS); +} + +for (var i in SOCKETCLUSTER_OPTIONS) { + if (SOCKETCLUSTER_OPTIONS.hasOwnProperty(i)) { + options[i] = SOCKETCLUSTER_OPTIONS[i]; + } +} + +var start = function () { + var socketCluster = new SocketCluster(options); + + socketCluster.on(socketCluster.EVENT_WORKER_CLUSTER_START, function (workerClusterInfo) { + console.log(' >> WorkerCluster PID:', workerClusterInfo.pid); + }); + + if (socketCluster.options.environment === 'dev') { + // This will cause SC workers to reboot when code changes anywhere in the app directory. + // The second options argument here is passed directly to chokidar. + // See https://github.com/paulmillr/chokidar#api for details. + console.log(` !! The sc-hot-reboot plugin is watching for code changes in the ${__dirname} directory`); + scHotReboot.attach(socketCluster, { + cwd: __dirname, + ignored: ['public', 'node_modules', 'README.md', 'Dockerfile', 'server.js', 'broker.js', /[\/\\]\./, '*.log'] + }); + } +}; + +var bootCheckInterval = Number(process.env.SOCKETCLUSTER_BOOT_CHECK_INTERVAL) || 200; +var bootStartTime = Date.now(); + +// Detect when Docker volumes are ready. +var startWhenFileIsReady = (filePath) => { + var errorMessage = `Failed to locate a controller file at path ${filePath} ` + + `before SOCKETCLUSTER_CONTROLLER_BOOT_TIMEOUT`; + + return waitForFile(filePath, bootCheckInterval, bootStartTime, bootTimeout, errorMessage); +}; + +var filesReadyPromises = [ + startWhenFileIsReady(workerControllerPath), + startWhenFileIsReady(brokerControllerPath), + startWhenFileIsReady(workerClusterControllerPath) +]; +Promise.all(filesReadyPromises) +.then(() => { + start(); +}) +.catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/socketcluster/worker.js b/socketcluster/worker.js new file mode 100644 index 0000000..05868de --- /dev/null +++ b/socketcluster/worker.js @@ -0,0 +1,43 @@ +var SCWorker = require('socketcluster/scworker'); +var express = require('express'); +var serveStatic = require('serve-static'); +var path = require('path'); +var morgan = require('morgan'); +var healthChecker = require('sc-framework-health-check'); + +class Worker extends SCWorker { + run() { + console.log(' >> Worker PID:', process.pid); + var environment = this.options.environment; + + var app = express(); + + var httpServer = this.httpServer; + var scServer = this.scServer; + + if (environment === 'dev') { + // Log every HTTP request. See https://github.com/expressjs/morgan for other + // available formats. + app.use(morgan('dev')); + } + app.use(serveStatic(path.resolve(__dirname, 'public'))); + + // Add GET /health-check express route + healthChecker.attach(this, app); + + httpServer.on('request', app); + + scServer.on('connection', function (socket) { + socket.on('publish', function (message) { + var {channel, data} = message + console.log('Handled publish', channel, data); + scServer.exchange.publish(channel, data); + }); + + socket.on('disconnect', function () { + }); + }); + } +} + +new Worker(); From d0964f28e9ffa53759bbb732b97c2cf93a974548 Mon Sep 17 00:00:00 2001 From: werner mendizabal Date: Wed, 4 Jul 2018 01:54:27 -0500 Subject: [PATCH 2/3] Fix error when retrieving iceServers. --- .gitignore | 2 ++ lib/server.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7b7dae2..dde9f25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules .env newrelic_agent.log +database +test-database diff --git a/lib/server.js b/lib/server.js index 06d644d..e7ac941 100644 --- a/lib/server.js +++ b/lib/server.js @@ -83,7 +83,7 @@ class Server { identityProvider, boomtownSecret: this.boomtownSecret, fetchICEServers: async () => { - iceServerProvider.fetchICEServers() + return iceServerProvider.fetchICEServers() } }) From 7cf678c7c7876ff3f8639e049d7143310e876a70 Mon Sep 17 00:00:00 2001 From: werner mendizabal Date: Sun, 29 Jul 2018 20:52:38 -0500 Subject: [PATCH 3/3] Allow using keycloak tokens --- .env.example | 5 ++ .env.local.example | 5 ++ .gitignore | 1 + app.json | 15 ++++ docker-compose.yml | 32 +++++++ index.js | 7 +- ...rovider.js => github-identity-provider.js} | 2 +- lib/keycloak-identity-provider.js | 80 +++++++++++++++++ lib/server.js | 89 ++++++++++++------- ...st.js => github-identity-provider.test.js} | 12 +-- 10 files changed, 207 insertions(+), 41 deletions(-) rename lib/{identity-provider.js => github-identity-provider.js} (98%) create mode 100644 lib/keycloak-identity-provider.js rename test/{identity-provider.test.js => github-identity-provider.test.js} (86%) diff --git a/.env.example b/.env.example index 5d58f89..5f0c3c7 100644 --- a/.env.example +++ b/.env.example @@ -14,8 +14,13 @@ GITHUB_API_URL=https://api.github.com GITHUB_CLIENT_ID=redacted GITHUB_CLIENT_SECRET=redacted GITHUB_OAUTH_TOKEN=redacted +KEYCLOAK_API_URL=http://localhost:8080/auth +KEYCLOAK_CLIENT_ID=redacted +KEYCLOAK_CLIENT_SECRET=redacted +KEYCLOAK_REALM=redacted HASH_SECRET=redacted COTURN_USERNAME=redacted COTURN_PASSWORD=redacted ACTIVE_PUB_SUB_GATEWAY=pusher ACTIVE_ICE_SERVER_PROVIDER=twilio +ACTIVE_IDENTITY_PROVIDER=github diff --git a/.env.local.example b/.env.local.example index 7be2be1..8bf3333 100644 --- a/.env.local.example +++ b/.env.local.example @@ -14,8 +14,13 @@ GITHUB_API_URL=https://api.github.com GITHUB_CLIENT_ID=redacted GITHUB_CLIENT_SECRET=redacted GITHUB_OAUTH_TOKEN=redacted +KEYCLOAK_API_URL=http://localhost:8080/auth +KEYCLOAK_CLIENT_ID=teletype +KEYCLOAK_CLIENT_SECRET=redacted +KEYCLOAK_REALM=teletype HASH_SECRET=redacted COTURN_USERNAME=teletype COTURN_PASSWORD=password ACTIVE_PUB_SUB_GATEWAY=socketcluster ACTIVE_ICE_SERVER_PROVIDER=coturn +ACTIVE_IDENTITY_PROVIDER=keycloak diff --git a/.gitignore b/.gitignore index dde9f25..b8f60f3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules newrelic_agent.log database test-database +keycloak-database diff --git a/app.json b/app.json index 8bc505c..d3ce28c 100644 --- a/app.json +++ b/app.json @@ -47,11 +47,26 @@ "COTURN_PASSWORD": { "required": true }, + "KEYCLOAK_API_URL": { + "required": true + }, + "KEYCLOAK_CLIENT_ID": { + "required": true + }, + "KEYCLOAK_CLIENT_SECRET": { + "required": true + }, + "KEYCLOAK_REALM": { + "required": true + }, "ACTIVE_PUB_SUB_GATEWAY": { "required": true }, "ACTIVE_ICE_SERVER_PROVIDER": { "required": true + }, + "ACTIVE_IDENTITY_PROVIDER": { + "required": true } }, "formation": {}, diff --git a/docker-compose.yml b/docker-compose.yml index bcc7a67..dac670d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,5 +55,37 @@ services: aliases: - socketcluster + keycloak: + image: jboss/keycloak + ports: + - 8080:8080 + restart: always + environment: + DB_VENDOR: postgres + DB_ADDR: keycloak-database + KEYCLOAK_USER: teletype + KEYCLOAK_PASSWORD: password + networks: + teletype: + aliases: + - keycloak + + keycloak-database: + image: postgres + ports: + - 5434:5432 + volumes: + - ./keycloak-database:/var/lib/postgresql/data + restart: always + environment: + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: password + POSTGRES_DB: keycloak + networks: + teletype: + aliases: + - keycloak-database + + networks: teletype: diff --git a/index.js b/index.js index 1a55e4a..32f2efd 100644 --- a/index.js +++ b/index.js @@ -23,13 +23,18 @@ async function startServer (id) { githubClientId: process.env.GITHUB_CLIENT_ID, githubClientSecret: process.env.GITHUB_CLIENT_SECRET, githubOauthToken: process.env.GITHUB_OAUTH_TOKEN, + keycloakApiUrl: process.env.KEYCLOAK_API_URL, + keycloakClientId: process.env.KEYCLOAK_CLIENT_ID, + keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET, + keycloakRealm: process.env.KEYCLOAK_REALM, boomtownSecret: process.env.BOOMTOWN_SECRET, hashSecret: process.env.HASH_SECRET, port: process.env.PORT || 3000, coturnUsername: process.env.COTURN_USERNAME, coturnPassword: process.env.COTURN_PASSWORD, activePubSubGateway: process.env.ACTIVE_PUB_SUB_GATEWAY, - activeIceServerProvider: process.env.ACTIVE_ICE_SERVER_PROVIDER + activeIceServerProvider: process.env.ACTIVE_ICE_SERVER_PROVIDER, + activeIdentityProvider: process.env.ACTIVE_IDENTITY_PROVIDER }) await server.start() console.log(`Worker ${id} (pid: ${process.pid}): listening on port ${server.port}`) diff --git a/lib/identity-provider.js b/lib/github-identity-provider.js similarity index 98% rename from lib/identity-provider.js rename to lib/github-identity-provider.js index 8a38b01..86b3c4f 100644 --- a/lib/identity-provider.js +++ b/lib/github-identity-provider.js @@ -1,7 +1,7 @@ const {StatusCodeError} = require('request-promise-core/lib/errors') module.exports = -class IdentityProvider { +class GithubIdentityProvider { constructor ({request, apiUrl, clientId, clientSecret, oauthToken}) { this.request = request this.apiUrl = apiUrl diff --git a/lib/keycloak-identity-provider.js b/lib/keycloak-identity-provider.js new file mode 100644 index 0000000..7ccf7e1 --- /dev/null +++ b/lib/keycloak-identity-provider.js @@ -0,0 +1,80 @@ +const {StatusCodeError} = require('request-promise-core/lib/errors') + +module.exports = +class KeycloakIdentityProvider { + constructor ({request, apiUrl, clientId, clientSecret, realm}) { + this.request = request + this.apiUrl = apiUrl + this.clientId = clientId + this.clientSecret = clientSecret + this.realm = realm + } + + make_base_auth(clientId, clientSecret) { + var token = clientId + ':' + clientSecret + var hash = Buffer.from(token).toString('base64') + return "Basic " + hash + } + + async identityForToken (oauthToken) { + try { + var options = { + method: 'POST', + uri: `${this.apiUrl}/realms/${this.realm}/protocol/openid-connect/token/introspect`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'api.teletype.atom.io', + 'Authorization': this.make_base_auth(this.clientId, this.clientSecret) + }, + body: `token=${oauthToken}` + } + const response = await this.request(options) + const user = JSON.parse(response) + + if (!user.active) { + const error = new Error('Token not provided.') + error.statusCode = 400 + throw error + } + return {id: user.sub, login: user.username} + } catch (e) { + let errorMessage, statusCode + if (e instanceof StatusCodeError) { + const error = JSON.parse(e.error) + const description = (error.message != null) ? error.message : e.error + errorMessage = `${this.apiUrl} responded with ${e.statusCode}: ${description}` + statusCode = e.statusCode + } else if (e instanceof Error) { + errorMessage = `Failed to query ${this.apiUrl}: ${e.message}` + statusCode = 400 + } else { + errorMessage = `Failed to query ${this.apiUrl}: ${e.message}` + statusCode = 500 + } + + const error = new Error(errorMessage) + error.statusCode = statusCode + throw error + } + } + + async isOperational () { + try { + var options = { + method: 'POST', + uri: `${this.apiUrl}/realms/${this.realm}/protocol/openid-connect/token/introspect`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'api.teletype.atom.io', + 'Authorization': this.make_base_auth(this.clientId, this.clientSecret) + }, + body: `token=` + } + + await this.request(options) + return true + } catch (e) { + return false + } + } +} diff --git a/lib/server.js b/lib/server.js index e7ac941..9d7eae1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,7 +1,8 @@ const buildControllerLayer = require('./controller-layer') const pgp = require('pg-promise')() const request = require('request-promise-native') -const IdentityProvider = require('./identity-provider') +const GithubIdentityProvider = require('./github-identity-provider') +const KeycloakIdentityProvider = require('./keycloak-identity-provider') const ModelLayer = require('./model-layer') const PusherPubSubGateway = require('./pusher-pub-sub-gateway') const SocketClusterPubSubGateway = require('./socketcluster-pub-sub-gateway') @@ -22,6 +23,10 @@ class Server { this.githubClientId = options.githubClientId this.githubClientSecret = options.githubClientSecret this.githubOauthToken = options.githubOauthToken + this.keycloakApiUrl = options.keycloakApiUrl + this.keycloakClientId = options.keycloakClientId + this.keycloakClientSecret = options.keycloakClientSecret + this.keycloakRealm = options.keycloakRealm this.boomtownSecret = options.boomtownSecret this.hashSecret = options.hashSecret this.port = options.port, @@ -29,52 +34,70 @@ class Server { this.coturnPassword = options.coturnPassword, this.activePubSubGateway = options.activePubSubGateway, this.activeIceServerProvider = options.activeIceServerProvider + this.activeIdentityProvider = options.activeIdentityProvider } async start () { const modelLayer = new ModelLayer({db: pgp(this.databaseURL), hashSecret: this.hashSecret}) - const identityProvider = new IdentityProvider({ - request, - apiUrl: this.githubApiUrl, - clientId: this.githubClientId, - clientSecret: this.githubClientSecret, - oauthToken: this.githubOauthToken - }) + + var identityProvider + + switch(this.activeIdentityProvider) { + case 'keycloak': + identityProvider = new KeycloakIdentityProvider({ + request, + apiUrl: this.keycloakApiUrl, + clientId: this.keycloakClientId, + clientSecret: this.keycloakClientSecret, + realm: this.keycloakRealm + }) + break; + case 'github': + default: + identityProvider = new GithubIdentityProvider({ + request, + apiUrl: this.githubApiUrl, + clientId: this.githubClientId, + clientSecret: this.githubClientSecret, + oauthToken: this.githubOauthToken + }) + break; + } var pubSubGateway switch(this.activePubSubGateway) { - case 'socketcluster': - pubSubGateway = new SocketClusterPubSubGateway({}) - break; + case 'socketcluster': + pubSubGateway = new SocketClusterPubSubGateway({}) + break; - case 'pusher': - default: - pubSubGateway = new PusherPubSubGateway({ - appId: this.pusherAppId, - key: this.pusherKey, - secret: this.pusherSecret, - cluster: this.pusherCluster - }) - break; + case 'pusher': + default: + pubSubGateway = new PusherPubSubGateway({ + appId: this.pusherAppId, + key: this.pusherKey, + secret: this.pusherSecret, + cluster: this.pusherCluster + }) + break; } var iceServerProvider switch(this.activeIceServerProvider) { - case 'coturn': - iceServerProvider = new CoturnIceServerProvider({ - coturnUsername: this.coturnUsername, - coturnPassword: this.coturnPassword - }) - break; + case 'coturn': + iceServerProvider = new CoturnIceServerProvider({ + coturnUsername: this.coturnUsername, + coturnPassword: this.coturnPassword + }) + break; - case 'twilio': - default: - iceServerProvider = new TwilioIceServerProvider({ - twilioAccount: this.twilioAccount, - twilioAuthToken: this.twilioAuthToken - }) - break; + case 'twilio': + default: + iceServerProvider = new TwilioIceServerProvider({ + twilioAccount: this.twilioAccount, + twilioAuthToken: this.twilioAuthToken + }) + break; } const controllerLayer = buildControllerLayer({ diff --git a/test/identity-provider.test.js b/test/github-identity-provider.test.js similarity index 86% rename from test/identity-provider.test.js rename to test/github-identity-provider.test.js index 7f4b87c..69d0f6a 100644 --- a/test/identity-provider.test.js +++ b/test/github-identity-provider.test.js @@ -1,8 +1,8 @@ const assert = require('assert') const {RequestError, StatusCodeError} = require('request-promise-core/lib/errors') -const IdentityProvider = require('../lib/identity-provider') +const GithubIdentityProvider = require('../lib/github-identity-provider') -suite('IdentityProvider', () => { +suite('GithubIdentityProvider', () => { test('returns user associated with OAuth token', async () => { const request = { get: async function (url, {headers}) { @@ -19,7 +19,7 @@ suite('IdentityProvider', () => { } } - const provider = new IdentityProvider({request}) + const provider = new GithubIdentityProvider({request}) const user1 = await provider.identityForToken('user-1-token') assert.deepEqual(user1, {id: '1', login: 'user-1'}) @@ -36,7 +36,7 @@ suite('IdentityProvider', () => { } } - const provider = new IdentityProvider({request}) + const provider = new GithubIdentityProvider({request}) let error = null try { @@ -56,7 +56,7 @@ suite('IdentityProvider', () => { } } - const provider = new IdentityProvider({request}) + const provider = new GithubIdentityProvider({request}) let error = null try { @@ -75,7 +75,7 @@ suite('IdentityProvider', () => { } } - const provider = new IdentityProvider({request}) + const provider = new GithubIdentityProvider({request}) let error = null try {