diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7f8c917 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +node_modules +typings diff --git a/.gitignore b/.gitignore index 6e7e88d..e0a089c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -node_modules/ -typings/ +*~ +*.swp +/node_modules +/typings .vscode config.json +/state diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..965609e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM node:carbon-alpine + +RUN apk update && apk upgrade && \ + apk add --no-cache drill git + +USER node +RUN mkdir /home/node/app +WORKDIR /home/node/app +COPY --chown=node:node . . +RUN mkdir -p logs && ln -s /dev/null ./logs/server.log && \ + npm install -q state@0.2.* typings@2.1.* ts-node@4.0.* && \ + ln -s ./node_modules/state/ ./state && \ + npm install -q && \ + npm cache clean --force + +EXPOSE 16522 17273 +CMD [ "sh", "-c", "REMOTE_IP=$(drill server2.vesync.com. @8.8.8.8 | awk '/^server2.vesync.com/{print $NF;exit}') npm run build:live" ] +# The below requires root to be able to update the hosts file, so if you want to use this then you have to +# remove the "USER node" line above +#CMD echo 34.204.178.244 server2.vesync.com >>/etc/hosts && REMOTE_IP=server2.vesync.com npm run build:live diff --git a/README.md b/README.md index 434ab41..97f0ab8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,28 @@ You'll then need to make sure your server is still able to resolve server2.vesyn Once that's done and your proxy is running, you may need to power cycle the outlet to encourage it to find your server. +### Docker + +If you want to use this project with Docker, you only need to arrange your local DNS -- see above. +Then run the Docker image from Docker Hub +[tonyapuzzo/vesync-wsproxy](https://hub.docker.com/r/tonyapuzzo/vesync-wsproxy/) +or build it yourself as follows: + +```bash +docker build -t tagToUse . +docker run -d --name vprox -p 16522:16522 -p 17273:17273 tagToUse +``` + +Use `docker logs vprox` to see the logging. The image looks up the real IP for `server2.vesync.com` +at startup using a direct DNS call to Google's nameserver at `8.8.8.8`. If your network uses +DD-WRTs "Forced DNS Redirection" or similar, then the Docker image won't be able to resolve the +correct IP. In that case, you can instead go figure out the real IP and then run the image with an +override command line, something like the following: + +```bash +docker run --name vprox -p 16522:16522 -p 17273:17273 tagToUse ash -c 'REMOTE_IP=34.204.178.244 npm run build:live' +``` + ### General Goals - enable the outlet to remain "smart" if the server goes offline (the outlet already remembers configured timers, but it would lose the ability to be turned on/off or configured.) - watch and validate the network traffic between the outlet and some strange server (block unrecognized traffic over the websocket by default. /upgrade is technically recognized, but still blocked.) diff --git a/dist/server.js b/dist/server.js deleted file mode 100644 index 1f5f12c..0000000 --- a/dist/server.js +++ /dev/null @@ -1,202 +0,0 @@ -// MitM proxy for Etekcity Voltson Smart Wi-Fi Outlet -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -// network configuration: -// 1. have the DNS server that the outlet is using return your server IP address for "server2.vesync.com" -// ( for example with dnsmasq, use `address=/server2.vesync.com/192.168.0.5` ) -// 2. Ensure that your server still knows how to access the real server. -// ( for example, add to your /etc/hosts something like `104.200.30.164 server2.vesync.com`) -// for wifi setup, it looks like the firmware has support for both air kiss and esp-touch -// esp-touch apparently fires opaque UDP packets over the WIFI and the device is expected to use the -// length field of those packets to decode the wifi bssid, password, etc. -const WebSocket = require("ws"); -const express = require("express"); -const DeviceState = require("./state"); -const winston = require("winston"); -const LOCAL_PORT = 17273; // port for the local websocket server -const LOCAL_PATH = "/gnws"; -// OTHER REMOTE URL found in user1.bin: -// ws://192.168.1.99:17273/gnws // intriguing. -// ws://server2.vesync.com:17275/gnwss // doesn't seem to be setup -// ws://server2.vesync.com:17273/gnws -const REMOTE_URL = "ws://server2.vesync.com:17273/gnws"; -const SERVICE_PORT = 16522; // port for the web service -// logger stuff -const logger = new winston.Logger({ - transports: [ - new winston.transports.File({ - level: 'info', - filename: './logs/server.log', - handleExceptions: true, - json: true, - maxsize: 524880, - maxFiles: 5, - colorize: false - }), - new winston.transports.Console({ - level: 'debug', - handleExceptions: true, - json: false, - colorize: true - }) - ], - exitOnError: false -}); -// basic websocket proxy -function startWebsocketProxy(localPort, localPath, remoteUrl) { - DeviceState.setLogger(logger); - const wss = new WebSocket.Server({ port: localPort, path: localPath }); - wss.on('connection', function (local_ws) { - let state; - logger.info("Device connected."); - // start buffering device payloads immediately - const buffer = []; - let remote_ready = false; - let connected = true; - setTimeout(() => { - if (!state) { - logger.warn("no login seen after 2 seconds. killing websocket."); - cleanup(); - } - }, 2000); // 2 seconds to see a login packet, else we bail. - local_ws.on('message', function (message) { - if (!state) { - // must be a login message. - state = DeviceState.getDeviceStateByLogin(message); - state.setInjector({ - sendToDevice: (json) => { - local_ws.send(JSON.stringify(json)); - }, - sendToCloud: (json) => { - remote_ws.send(JSON.stringify(json)); - } - }); - } - message = state.handleDeviceMessage(message); - if (message) { - if (remote_ready) { - connected && remote_ws.send(message); - } - else { - buffer.push(message); - } - } - }).on('close', cleanup).on('error', cleanup); - // open connection to other side - const remote_ws = new WebSocket(remoteUrl, {}); - remote_ws.on('open', function () { - logger.info("Cloud connected."); - // flush buffer - while (buffer.length) { - remote_ws.send(buffer.shift()); - } - // both connections are established. start piping. - remote_ready = true; - remote_ws.on('message', function (message) { - message = state.handleCloudMessage(message); - if (message) { - connected && local_ws.send(message); - } - }).on('close', cleanup).on('error', cleanup); - }); - function cleanup(code, description) { - if (code || description) { - logger.warn("WebSocket error: ", code, description); - } - connected = false; // prevent further attempts to .send(), which would throw. - if (state) { - state.unsetInjector(); - } - try { - remote_ws.close(); - } - catch (e) { } - try { - local_ws.close(); - } - catch (e) { } - logger.info("Connections closed."); - } - }); -} -// basic web service -function startWebService(servicePort) { - const app = express(); - function stringParam(req, name, minSize = 0, maxSize = 1000) { - const value = req.query[name]; - if (value.length >= minSize || value.length <= maxSize) { - return value; - } - } - // GET is very much the wrong verb for this, but it's so easy to test with. - // also, some kind of AUTH might be handy here.. - app.get('/relay', function (req, res) { - const id = stringParam(req, "id", 5, 40); - const on = stringParam(req, "on", 1, 1); - // grab a DeviceState object - const state = DeviceState.getDeviceStateById(id); - const relay = on ? "open" : "break"; - state.injectRelay(relay); - res.send("OK. relay=" + relay); - }); - app.get('/power', function (req, res) { - const id = stringParam(req, "id", 5, 40); - ; - const state = DeviceState.getDeviceStateById(id); - state.once("power", () => { - const power_tmp = state.energy.power.split(":").map((s) => parseInt(s, 16)); - const power = (power_tmp[0] + power_tmp[1]) / 8192; // XXX probably not quite right. - const voltage_tmp = state.energy.voltage.split(":").map((s) => parseInt(s, 16)); - const voltage = (voltage_tmp[0] + voltage_tmp[1]) / 8192; // XXX same issue - res.send({ relay: state.state.relay, watts: power, volts: voltage }); - }); - state.injectGetRuntime(); - }); - // open relay IF it's night time, for 2 minutes. - // If it was already open, do nothing. - app.get('/nightlight', function (req, res) { - const id = stringParam(req, "id", 5, 40); - const time = new Date(); - const state = DeviceState.getDeviceStateById(id); - if (time.getHours() > 19 || time.getHours() < 7) { - if (state.state.relay !== "open") { - state.injectRelay("open"); - setTimeout(() => { - state.injectRelay("break"); - }, 2 * 60 * 1000); - } - } - res.send("OK, whatever."); - }); - // logger stuff - const access_logger = new winston.Logger({ - transports: [ - new winston.transports.File({ - level: 'info', - filename: './logs/access.log', - handleExceptions: true, - json: true, - maxsize: 524880, - maxFiles: 5, - colorize: false - }), - new winston.transports.Console({ - level: 'debug', - handleExceptions: true, - json: false, - colorize: true - }) - ], - exitOnError: false - }); - class MyStream { - write(text) { - access_logger.info(text); - } - } - app.use(require("morgan")("combined", { "stream": new MyStream() })); - app.listen(servicePort); -} -startWebsocketProxy(LOCAL_PORT, LOCAL_PATH, REMOTE_URL); -startWebService(SERVICE_PORT); -logger.info("Server started"); diff --git a/dist/state.js b/dist/state.js deleted file mode 100644 index 8415aac..0000000 --- a/dist/state.js +++ /dev/null @@ -1,344 +0,0 @@ -// maintain state for a given device -// this needs to: -// - work with multiple devices cleanly -// - understand the websocket packets passing through -// - accept state changes from outside of the websocket stream -// - inject packets in the websocket stream to keep the device and cloud states consistent -"use strict"; -// -> use "id" to uniquely identify devices. -const events = require("events"); -const messageSchemas = { - "login": { - "account": "string", - "id": "string", - "deviceName": "string", - "deviceVersion": "string", - "deviceVersionCode": "number", - "type": "string", - "apptype": "string", - "firmName": "string", - "firmVersion": "string", - "firmVersionCode": "number", - "key": "number", - "relay": "string" - }, - "loginreply": { - "uri": "string", - "error": "number", - "wd": "number", - "year": "number", - "month": "number", - "day": "number", - "ms": "number", - "hh": "number", - "hl": "number", - "lh": "number", - "ll": "number" - }, - "ka": { - "uri": "string" - }, - "kr": { - "uri": "string", - "error": "number", - "wd": "number", - "year": "number", - "month": "number", - "day": "number", - "ms": "number" - }, - "report": { - "uri": "string", - "e": "string", - "t": "string" // This is likely to be a measure of time during which the energy was used - }, - "gettriggercount": { - "uri": "string", - // sent from cloud - "cid": "string", - "aboveInteger": "number", - "aboveFraction": "number", - "belowInteger": "number", - "belowFraction": "number", - "aboveAction": "number", - "belowAction": "number", - // sent by device - "aboveTriggerCount": "number", - "belowTriggerCount": "number" - }, - "getruntime": { - "uri": "string", - "cid": "string" - }, - "runtimeinfo": { - "uri": "string", - "relay": "string", - "meastate": "string", - "power": "string", - "voltage": "string", - "current": "string" - }, - "relay": { - "uri": "string", - "cid": "string", - "action": "string" - }, - "setcontrolflags": { - "uri": "string", - "flag": "number", - "set": "number" - }, - "timer": { - "uri": "string", - // sent from cloud - "action": "string", - "id": "number", - "year": "number", - "month": "number", - "day": "number", - "start_time": "number", - "start_action": "number", - "duration": "number", - "end_action": "number", - "loop": "number", - "cd": "number", - // sent by device - "error": "number" - }, - "evtimer": { - "uri": "string", - "aname": "string", - "relay": "string", - "id": "number" - }, - "upgrade": { - "uri": "string", - "url": "string", - "newVersion": "string" // "1.75". whatever. - }, - "state": { - "uri": "string", - "relay": "string" - }, - "trigger": { - "uri": "string", - "type": "number" - } -}; -let logger; -class DeviceState extends events.EventEmitter { - constructor(state = {}) { - super(); - this.state = state; - this.energy = { - power: "0:0", - voltage: "0:0" - }; - } - static setLogger(_logger) { - logger = _logger; - } - static getDeviceStateByLogin(loginMessage) { - const json = JSON.parse(loginMessage); - return DeviceState.getDeviceStateById(json.id, true); - } - static getDeviceStateById(id, create = false) { - if (!DeviceState.states[id]) { - DeviceState.states[id] = new DeviceState(); - } - return DeviceState.states[id]; - } - validateMessage(json, schemaId) { - const schema = messageSchemas[schemaId]; - const keys = Object.keys(json); - let failedToValidate = false; // optimistic; - keys.forEach((key) => { - const type = typeof json[key]; - if (schema[key] !== type) { - failedToValidate = true; - if (schema[key]) { - logger.warn("Unexpected type for field [" + key + "] in payload ", json); - } - else { - logger.warn("Unknown field [" + key + "] found in payload ", json); - } - } - }); - return !failedToValidate; - } - handleDeviceMessage(message) { - logger.info("DEVICE -", (new Date).toLocaleString(), "\n", message); - const json = JSON.parse(message); - let validated = false; - if (json.account) { - // login message. - validated = this.validateMessage(json, "login"); - this.state = json; - // great to see what an /upgrade command looks like - /* - json.firmVersion = "1.65"; - json.firmVersionCode = 65; - message = JSON.stringify(json); - */ - } - else { - switch (json.uri) { - case "/ka": - validated = this.validateMessage(json, "ka"); - break; - case "/kr": - validated = this.validateMessage(json, "kr"); // doesn't actually have any fields. that's okay. - break; - case "/report": - validated = this.validateMessage(json, "report"); - break; - case "/evtimer": - validated = this.validateMessage(json, "evtimer"); - this.state.relay = json.relay; // update internal state accordingly - this.emit("relay"); - break; - case "/getTriggerCnt": - validated = this.validateMessage(json, "gettriggercount"); - // XXX track triggers? maybe later. - break; - case "/runtimeInfo": - validated = this.validateMessage(json, "runtimeinfo"); - this.state.relay = json.relay; - // XXX track those over time if we want pretty graphs/trends/whatever. - this.energy.power = json.power; - this.energy.voltage = json.voltage; - this.emit("relay"); - this.emit("power"); - break; - case "/setCtlFlags": - validated = this.validateMessage(json, "setcontrolflags"); - // no state here. this is a write only API apparently. - break; - case "/timer": - validated = this.validateMessage(json, "timer"); - break; - case "/state": - validated = this.validateMessage(json, "state"); - this.state.relay = json.relay; - this.emit("relay"); - break; - case "/trigger": - validated = this.validateMessage(json, "trigger"); - break; - // other URIs I should expect to pop out of this device include - // "/assignGuid", - // "/complete", - // "/beginMeasure", - // "/delDevice", - // "/setTrigger", - // "/delTrigger", - // "/clrTriggerCnt", - // "/resetID", - // "/softRestore" - // "/resetManufactureFlag" - // "/beginConfigReply" - default: - // unknown message. scary, but exciting. - logger.warn("Unknown device payload ", json); - } - } - if (validated) { - return message; - } - else { - logger.error("BLOCKED DEVICE MESSAGE: \n", message); - } - } - handleCloudMessage(message) { - logger.info("CLOUD -", (new Date).toLocaleString(), "\n", message); - const json = JSON.parse(message); - let validated = false; - switch (json.uri) { - case "/loginReply": - validated = this.validateMessage(json, "loginreply"); - break; - case "/ka": - validated = this.validateMessage(json, "ka"); - break; - case "/kr": - validated = this.validateMessage(json, "kr"); - break; - case "/getTriggerCnt": - validated = this.validateMessage(json, "gettriggercount"); - break; - case "/getRuntime": - validated = this.validateMessage(json, "getruntime"); - break; - case "/relay": - validated = this.validateMessage(json, "relay"); - // don't use state given here. the device is the Source of Truth. - break; - case "/setCtlFlags": - validated = this.validateMessage(json, "setcontrolflags"); - // XXX store flags somewhere? - break; - case "/timer": - validated = this.validateMessage(json, "timer"); - // XXX if we want to track timers, it needs to be here - break; - // other URIs I should expect to come from the cloud include - // "/upgrade" <- literally a firmware upgrade. CAREFUL WITH THIS ONE - // "/assignGuid" - // "/calibration" - // "/complete" - // "/delDevice" - // "/setTrigger" - // "/delTrigger" - // "/clrTriggerCnt" - // "/resetID" - // "/softRestore" - // "/beginMeasure" - // "/setManufactureFlag" - // "/beginConfigRequest" ( wifiID wifiPassword account wifiGateway wifiDNS wifiBssid serverIP ) - // "/discoverReply" - // "/register" - default: - // unknown message from the cloud. mostly just scary. - logger.warn("Unknown cloud payload ", json); - } - if (validated) { - return message; - } - else { - logger.error("BLOCKED CLOUD MESSAGE: \n", message); - } - } - setInjector(injector) { - this.injector = injector; - } - unsetInjector() { - delete this.injector; - } - injectRelay(relay) { - if (!this.injector) { - throw "Nope. can't inject relay state right now. sorry."; - } - this.state.relay = relay; - this.injector.sendToDevice({ - cid: this.state.id, - uri: "/relay", - action: relay - }); - this.injector.sendToCloud({ - uri: "/state", - relay - }); - } - injectGetRuntime() { - if (!this.injector) { - throw "Nope. can't inject getRuntime right now. sorry."; - } - this.injector.sendToDevice({ - cid: this.state.id, - uri: "/getRuntime" - }); - } -} -// factory pattern. #sorrynotsorry. -DeviceState.states = {}; -module.exports = DeviceState; diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..ed65753 --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1,2 @@ +access.log +server.log diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000..3f17c63 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,1327 @@ +{ + "name": "wsproxy", + "version": "0.0.0", + "dependencies": { + "@types/arrify": { + "version": "1.0.2", + "from": "@types/arrify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/@types/arrify/-/arrify-1.0.2.tgz" + }, + "@types/diff": { + "version": "3.2.2", + "from": "@types/diff@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-3.2.2.tgz" + }, + "@types/minimist": { + "version": "1.2.0", + "from": "@types/minimist@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz" + }, + "@types/mkdirp": { + "version": "0.5.2", + "from": "@types/mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz" + }, + "@types/node": { + "version": "8.0.58", + "from": "@types/node@>=8.0.27 <9.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.58.tgz" + }, + "@types/source-map-support": { + "version": "0.4.0", + "from": "@types/source-map-support@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.4.0.tgz" + }, + "@types/strip-bom": { + "version": "3.0.0", + "from": "@types/strip-bom@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz" + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "from": "@types/strip-json-comments@0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz" + }, + "@types/v8flags": { + "version": "2.0.0", + "from": "types/npm-v8flags#de224ae1cd5fd7dbb4e7158a6cc7a29e5315930d", + "resolved": "git://github.com/types/npm-v8flags.git#de224ae1cd5fd7dbb4e7158a6cc7a29e5315930d" + }, + "@types/yn": { + "version": "2.0.0", + "from": "types/npm-yn#ca75f6c82940fae6a06fb41d2d37a6aa9b4ea8e9", + "resolved": "git://github.com/types/npm-yn.git#ca75f6c82940fae6a06fb41d2d37a6aa9b4ea8e9" + }, + "abbrev": { + "version": "1.1.1", + "from": "abbrev@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + }, + "accepts": { + "version": "1.3.4", + "from": "accepts@>=1.3.4 <1.4.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz" + }, + "agent-base": { + "version": "2.1.1", + "from": "agent-base@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz" + }, + "ansi-align": { + "version": "2.0.0", + "from": "ansi-align@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz" + }, + "ansi-escapes": { + "version": "1.4.0", + "from": "ansi-escapes@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz" + }, + "ansi-regex": { + "version": "2.1.1", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + }, + "ansi-styles": { + "version": "3.2.0", + "from": "ansi-styles@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz" + }, + "any-promise": { + "version": "1.3.0", + "from": "any-promise@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + }, + "archy": { + "version": "1.0.0", + "from": "archy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" + }, + "array-flatten": { + "version": "1.1.1", + "from": "array-flatten@1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + }, + "array-uniq": { + "version": "1.0.3", + "from": "array-uniq@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" + }, + "arrify": { + "version": "1.0.1", + "from": "arrify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + }, + "async": { + "version": "1.0.0", + "from": "async@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz" + }, + "asynckit": { + "version": "0.4.0", + "from": "asynckit@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + }, + "balanced-match": { + "version": "1.0.0", + "from": "balanced-match@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" + }, + "basic-auth": { + "version": "2.0.0", + "from": "basic-auth@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz" + }, + "bluebird": { + "version": "3.5.1", + "from": "bluebird@>=3.1.1 <4.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz" + }, + "body-parser": { + "version": "1.18.2", + "from": "body-parser@1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz" + }, + "boxen": { + "version": "1.3.0", + "from": "boxen@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "from": "ansi-styles@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz" + }, + "chalk": { + "version": "2.3.0", + "from": "chalk@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz" + }, + "supports-color": { + "version": "4.5.0", + "from": "supports-color@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz" + } + } + }, + "brace-expansion": { + "version": "1.1.8", + "from": "brace-expansion@>=1.1.7 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" + }, + "bytes": { + "version": "3.0.0", + "from": "bytes@3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + }, + "camelcase": { + "version": "4.1.0", + "from": "camelcase@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz" + }, + "capture-stack-trace": { + "version": "1.0.0", + "from": "capture-stack-trace@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz" + }, + "chalk": { + "version": "2.3.0", + "from": "chalk@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz" + }, + "cli-boxes": { + "version": "1.0.0", + "from": "cli-boxes@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz" + }, + "cli-cursor": { + "version": "1.0.2", + "from": "cli-cursor@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz" + }, + "cli-truncate": { + "version": "1.1.0", + "from": "cli-truncate@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz" + }, + "clone": { + "version": "1.0.3", + "from": "clone@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz" + }, + "color-convert": { + "version": "1.9.1", + "from": "color-convert@>=1.9.0 <2.0.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz" + }, + "color-name": { + "version": "1.1.3", + "from": "color-name@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + }, + "colors": { + "version": "1.0.3", + "from": "colors@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" + }, + "columnify": { + "version": "1.5.4", + "from": "columnify@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <2.0.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "commander": { + "version": "2.12.2", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.6.0", + "from": "concat-stream@>=1.4.7 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz" + }, + "configstore": { + "version": "3.1.1", + "from": "configstore@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz" + }, + "content-disposition": { + "version": "0.5.2", + "from": "content-disposition@0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" + }, + "content-type": { + "version": "1.0.4", + "from": "content-type@>=1.0.4 <1.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + }, + "cookie": { + "version": "0.3.1", + "from": "cookie@0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz" + }, + "cookie-signature": { + "version": "1.0.6", + "from": "cookie-signature@1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "create-error-class": { + "version": "3.0.2", + "from": "create-error-class@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz" + }, + "cross-spawn": { + "version": "5.1.0", + "from": "cross-spawn@>=5.0.1 <6.0.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz" + }, + "crypto-random-string": { + "version": "1.0.0", + "from": "crypto-random-string@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz" + }, + "cycle": { + "version": "1.0.3", + "from": "cycle@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz" + }, + "debug": { + "version": "2.6.9", + "from": "debug@2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + }, + "deep-extend": { + "version": "0.4.2", + "from": "deep-extend@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz" + }, + "defaults": { + "version": "1.0.3", + "from": "defaults@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "depd": { + "version": "1.1.1", + "from": "depd@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz" + }, + "destroy": { + "version": "1.0.4", + "from": "destroy@>=1.0.4 <1.1.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + }, + "detect-indent": { + "version": "5.0.0", + "from": "detect-indent@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz" + }, + "diff": { + "version": "3.4.0", + "from": "diff@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz" + }, + "dot-prop": { + "version": "4.2.0", + "from": "dot-prop@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz" + }, + "duplexer3": { + "version": "0.1.4", + "from": "duplexer3@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" + }, + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + }, + "elegant-spinner": { + "version": "1.0.1", + "from": "elegant-spinner@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz" + }, + "encodeurl": { + "version": "1.0.1", + "from": "encodeurl@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz" + }, + "error-ex": { + "version": "1.3.1", + "from": "error-ex@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz" + }, + "escape-html": { + "version": "1.0.3", + "from": "escape-html@>=1.0.3 <1.1.0", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + }, + "etag": { + "version": "1.8.1", + "from": "etag@>=1.8.1 <1.9.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + }, + "execa": { + "version": "0.7.0", + "from": "execa@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz" + }, + "exit-hook": { + "version": "1.1.1", + "from": "exit-hook@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz" + }, + "express": { + "version": "4.16.2", + "from": "express@>=4.14.1 <5.0.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz" + }, + "extend": { + "version": "3.0.1", + "from": "extend@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz" + }, + "eyes": { + "version": "0.1.8", + "from": "eyes@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz" + }, + "finalhandler": { + "version": "1.1.0", + "from": "finalhandler@1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz" + }, + "form-data": { + "version": "2.3.1", + "from": "form-data@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz" + }, + "forwarded": { + "version": "0.1.2", + "from": "forwarded@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz" + }, + "fresh": { + "version": "0.5.2", + "from": "fresh@0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + }, + "fs.realpath": { + "version": "1.0.0", + "from": "fs.realpath@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + }, + "function-bind": { + "version": "1.1.1", + "from": "function-bind@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + }, + "get-stream": { + "version": "3.0.0", + "from": "get-stream@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz" + }, + "glob": { + "version": "7.1.2", + "from": "glob@>=7.0.5 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + }, + "global-dirs": { + "version": "0.1.1", + "from": "global-dirs@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz" + }, + "got": { + "version": "6.7.1", + "from": "got@>=6.7.1 <7.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz" + }, + "graceful-fs": { + "version": "4.1.11", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" + }, + "has": { + "version": "1.0.1", + "from": "has@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz" + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-flag": { + "version": "2.0.0", + "from": "has-flag@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz" + }, + "has-unicode": { + "version": "2.0.1", + "from": "has-unicode@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + }, + "homedir-polyfill": { + "version": "1.0.1", + "from": "homedir-polyfill@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz" + }, + "http-errors": { + "version": "1.6.2", + "from": "http-errors@>=1.6.2 <1.7.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "dependencies": { + "setprototypeof": { + "version": "1.0.3", + "from": "setprototypeof@1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz" + } + } + }, + "http-proxy-agent": { + "version": "1.0.0", + "from": "http-proxy-agent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz" + }, + "https-proxy-agent": { + "version": "1.0.0", + "from": "https-proxy-agent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz" + }, + "iconv-lite": { + "version": "0.4.19", + "from": "iconv-lite@0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz" + }, + "import-lazy": { + "version": "2.1.0", + "from": "import-lazy@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz" + }, + "imurmurhash": { + "version": "0.1.4", + "from": "imurmurhash@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + }, + "inflight": { + "version": "1.0.6", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + }, + "inherits": { + "version": "2.0.3", + "from": "inherits@2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + }, + "ini": { + "version": "1.3.5", + "from": "ini@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" + }, + "invariant": { + "version": "2.2.2", + "from": "invariant@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz" + }, + "ipaddr.js": { + "version": "1.5.2", + "from": "ipaddr.js@1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz" + }, + "is-absolute": { + "version": "0.2.6", + "from": "is-absolute@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz" + }, + "is-arrayish": { + "version": "0.2.1", + "from": "is-arrayish@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "from": "is-fullwidth-code-point@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + }, + "is-installed-globally": { + "version": "0.1.0", + "from": "is-installed-globally@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz" + }, + "is-npm": { + "version": "1.0.0", + "from": "is-npm@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz" + }, + "is-obj": { + "version": "1.0.1", + "from": "is-obj@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + }, + "is-path-inside": { + "version": "1.0.1", + "from": "is-path-inside@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz" + }, + "is-plain-obj": { + "version": "1.1.0", + "from": "is-plain-obj@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + }, + "is-redirect": { + "version": "1.0.0", + "from": "is-redirect@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz" + }, + "is-relative": { + "version": "0.2.1", + "from": "is-relative@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz" + }, + "is-retry-allowed": { + "version": "1.1.0", + "from": "is-retry-allowed@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz" + }, + "is-stream": { + "version": "1.1.0", + "from": "is-stream@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" + }, + "is-unc-path": { + "version": "0.1.2", + "from": "is-unc-path@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz" + }, + "is-windows": { + "version": "0.2.0", + "from": "is-windows@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isexe": { + "version": "2.0.0", + "from": "isexe@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "js-tokens": { + "version": "3.0.2", + "from": "js-tokens@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + }, + "jspm-config": { + "version": "0.3.4", + "from": "jspm-config@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/jspm-config/-/jspm-config-0.3.4.tgz" + }, + "latest-version": { + "version": "3.1.0", + "from": "latest-version@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz" + }, + "listify": { + "version": "1.0.0", + "from": "listify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/listify/-/listify-1.0.0.tgz" + }, + "lockfile": { + "version": "1.0.3", + "from": "lockfile@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz" + }, + "log-update": { + "version": "1.0.2", + "from": "log-update@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz" + }, + "loose-envify": { + "version": "1.3.1", + "from": "loose-envify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz" + }, + "lowercase-keys": { + "version": "1.0.0", + "from": "lowercase-keys@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz" + }, + "lru-cache": { + "version": "4.1.1", + "from": "lru-cache@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz" + }, + "make-dir": { + "version": "1.1.0", + "from": "make-dir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz" + }, + "make-error": { + "version": "1.3.0", + "from": "make-error@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz" + }, + "make-error-cause": { + "version": "1.2.2", + "from": "make-error-cause@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz" + }, + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "merge-descriptors": { + "version": "1.0.1", + "from": "merge-descriptors@1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + }, + "methods": { + "version": "1.1.2", + "from": "methods@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + }, + "mime": { + "version": "1.4.1", + "from": "mime@1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz" + }, + "mime-db": { + "version": "1.30.0", + "from": "mime-db@>=1.30.0 <1.31.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" + }, + "mime-types": { + "version": "2.1.17", + "from": "mime-types@>=2.1.16 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "morgan": { + "version": "1.9.0", + "from": "morgan@>=1.8.1 <2.0.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz" + }, + "ms": { + "version": "2.0.0", + "from": "ms@2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + }, + "negotiator": { + "version": "0.6.1", + "from": "negotiator@0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + }, + "nopt": { + "version": "1.0.10", + "from": "nopt@>=1.0.10 <1.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz" + }, + "npm-run-path": { + "version": "2.0.2", + "from": "npm-run-path@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" + }, + "object.pick": { + "version": "1.3.0", + "from": "object.pick@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" + }, + "omicron": { + "version": "0.2.0", + "from": "omicron@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/omicron/-/omicron-0.2.0.tgz" + }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + }, + "on-headers": { + "version": "1.0.1", + "from": "on-headers@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz" + }, + "once": { + "version": "1.4.0", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + }, + "onetime": { + "version": "1.1.0", + "from": "onetime@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz" + }, + "p-finally": { + "version": "1.0.0", + "from": "p-finally@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" + }, + "package-json": { + "version": "4.0.1", + "from": "package-json@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "dependencies": { + "semver": { + "version": "5.4.1", + "from": "semver@>=5.1.0 <6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz" + } + } + }, + "parse-json": { + "version": "2.2.0", + "from": "parse-json@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" + }, + "parse-passwd": { + "version": "1.0.0", + "from": "parse-passwd@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" + }, + "parseurl": { + "version": "1.3.2", + "from": "parseurl@>=1.3.2 <1.4.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz" + }, + "path-is-absolute": { + "version": "1.0.1", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + }, + "path-is-inside": { + "version": "1.0.2", + "from": "path-is-inside@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" + }, + "path-key": { + "version": "2.0.1", + "from": "path-key@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" + }, + "path-to-regexp": { + "version": "0.1.7", + "from": "path-to-regexp@0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + }, + "pify": { + "version": "3.0.0", + "from": "pify@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + }, + "popsicle": { + "version": "9.2.0", + "from": "popsicle@>=9.0.0 <10.0.0", + "resolved": "https://registry.npmjs.org/popsicle/-/popsicle-9.2.0.tgz" + }, + "popsicle-proxy-agent": { + "version": "3.0.0", + "from": "popsicle-proxy-agent@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/popsicle-proxy-agent/-/popsicle-proxy-agent-3.0.0.tgz" + }, + "popsicle-retry": { + "version": "3.2.1", + "from": "popsicle-retry@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/popsicle-retry/-/popsicle-retry-3.2.1.tgz" + }, + "popsicle-rewrite": { + "version": "1.0.0", + "from": "popsicle-rewrite@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/popsicle-rewrite/-/popsicle-rewrite-1.0.0.tgz" + }, + "popsicle-status": { + "version": "2.0.1", + "from": "popsicle-status@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/popsicle-status/-/popsicle-status-2.0.1.tgz" + }, + "prepend-http": { + "version": "1.0.4", + "from": "prepend-http@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz" + }, + "process-nextick-args": { + "version": "1.0.7", + "from": "process-nextick-args@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" + }, + "promise-finally": { + "version": "3.0.0", + "from": "promise-finally@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/promise-finally/-/promise-finally-3.0.0.tgz" + }, + "proxy-addr": { + "version": "2.0.2", + "from": "proxy-addr@>=2.0.2 <2.1.0", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz" + }, + "pseudomap": { + "version": "1.0.2", + "from": "pseudomap@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" + }, + "punycode": { + "version": "1.4.1", + "from": "punycode@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + }, + "qs": { + "version": "6.5.1", + "from": "qs@6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz" + }, + "range-parser": { + "version": "1.2.0", + "from": "range-parser@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + }, + "raw-body": { + "version": "2.3.2", + "from": "raw-body@2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz" + }, + "rc": { + "version": "1.2.2", + "from": "rc@>=1.1.5 <2.0.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz" + }, + "readable-stream": { + "version": "2.3.3", + "from": "readable-stream@>=2.2.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + }, + "registry-auth-token": { + "version": "3.3.1", + "from": "registry-auth-token@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz" + }, + "registry-url": { + "version": "3.1.0", + "from": "registry-url@>=3.0.3 <4.0.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz" + }, + "restore-cursor": { + "version": "1.0.1", + "from": "restore-cursor@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz" + }, + "rimraf": { + "version": "2.6.2", + "from": "rimraf@>=2.4.4 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + }, + "safe-buffer": { + "version": "5.1.1", + "from": "safe-buffer@5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + }, + "semver": { + "version": "5.0.3", + "from": "semver@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz" + }, + "semver-diff": { + "version": "2.1.0", + "from": "semver-diff@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz" + }, + "send": { + "version": "0.16.1", + "from": "send@0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz" + }, + "serve-static": { + "version": "1.13.1", + "from": "serve-static@1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz" + }, + "setprototypeof": { + "version": "1.1.0", + "from": "setprototypeof@1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + }, + "shebang-command": { + "version": "1.2.0", + "from": "shebang-command@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" + }, + "shebang-regex": { + "version": "1.0.0", + "from": "shebang-regex@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + }, + "signal-exit": { + "version": "3.0.2", + "from": "signal-exit@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz" + }, + "slice-ansi": { + "version": "1.0.0", + "from": "slice-ansi@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz" + }, + "sort-keys": { + "version": "1.1.2", + "from": "sort-keys@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" + }, + "source-map": { + "version": "0.6.1", + "from": "source-map@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + }, + "source-map-support": { + "version": "0.5.0", + "from": "source-map-support@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.0.tgz" + }, + "stack-trace": { + "version": "0.0.10", + "from": "stack-trace@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + }, + "state": { + "version": "0.2.0", + "from": "state@latest", + "resolved": "https://registry.npmjs.org/state/-/state-0.2.0.tgz" + }, + "statuses": { + "version": "1.3.1", + "from": "statuses@>=1.3.1 <1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@>=1.0.3 <1.1.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz" + }, + "string-template": { + "version": "1.0.0", + "from": "string-template@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz" + }, + "string-width": { + "version": "2.1.1", + "from": "string-width@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "from": "ansi-regex@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" + }, + "strip-ansi": { + "version": "4.0.0", + "from": "strip-ansi@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "strip-bom": { + "version": "3.0.0", + "from": "strip-bom@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + }, + "strip-eof": { + "version": "1.0.0", + "from": "strip-eof@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" + }, + "strip-json-comments": { + "version": "2.0.1", + "from": "strip-json-comments@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + }, + "supports-color": { + "version": "4.5.0", + "from": "supports-color@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz" + }, + "term-size": { + "version": "1.2.0", + "from": "term-size@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz" + }, + "thenify": { + "version": "3.3.0", + "from": "thenify@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz" + }, + "throat": { + "version": "3.2.0", + "from": "throat@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-3.2.0.tgz" + }, + "timed-out": { + "version": "4.0.1", + "from": "timed-out@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz" + }, + "touch": { + "version": "1.0.0", + "from": "touch@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-1.0.0.tgz" + }, + "tough-cookie": { + "version": "2.3.3", + "from": "tough-cookie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz" + }, + "ts-node": { + "version": "4.0.1", + "from": "ts-node@latest", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.0.1.tgz" + }, + "tsconfig": { + "version": "7.0.0", + "from": "tsconfig@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz" + }, + "type-is": { + "version": "1.6.15", + "from": "type-is@>=1.6.15 <1.7.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "typescript": { + "version": "2.6.2", + "from": "typescript@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz" + }, + "typings": { + "version": "2.1.1", + "from": "typings@latest", + "resolved": "https://registry.npmjs.org/typings/-/typings-2.1.1.tgz", + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, + "chalk": { + "version": "1.1.3", + "from": "chalk@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + } + } + }, + "typings-core": { + "version": "2.3.3", + "from": "typings-core@>=2.3.3 <3.0.0", + "resolved": "https://registry.npmjs.org/typings-core/-/typings-core-2.3.3.tgz" + }, + "ultron": { + "version": "1.1.1", + "from": "ultron@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz" + }, + "unc-path-regex": { + "version": "0.1.2", + "from": "unc-path-regex@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz" + }, + "unique-string": { + "version": "1.0.0", + "from": "unique-string@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz" + }, + "unpipe": { + "version": "1.0.0", + "from": "unpipe@1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "unzip-response": { + "version": "2.0.1", + "from": "unzip-response@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz" + }, + "update-notifier": { + "version": "2.3.0", + "from": "update-notifier@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "from": "ansi-styles@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz" + }, + "chalk": { + "version": "2.3.0", + "from": "chalk@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz" + }, + "supports-color": { + "version": "4.5.0", + "from": "supports-color@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz" + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "from": "url-parse-lax@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "utils-merge": { + "version": "1.0.1", + "from": "utils-merge@1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + }, + "v8flags": { + "version": "3.0.1", + "from": "v8flags@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.1.tgz" + }, + "vary": { + "version": "1.1.2", + "from": "vary@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + }, + "wcwidth": { + "version": "1.0.1", + "from": "wcwidth@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" + }, + "which": { + "version": "1.3.0", + "from": "which@>=1.2.9 <2.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz" + }, + "widest-line": { + "version": "2.0.0", + "from": "widest-line@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz" + }, + "winston": { + "version": "2.4.0", + "from": "winston@>=2.3.1 <3.0.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz" + }, + "wordwrap": { + "version": "1.0.0", + "from": "wordwrap@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + }, + "write-file-atomic": { + "version": "2.3.0", + "from": "write-file-atomic@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz" + }, + "ws": { + "version": "2.3.1", + "from": "ws@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", + "dependencies": { + "safe-buffer": { + "version": "5.0.1", + "from": "safe-buffer@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz" + } + } + }, + "xdg-basedir": { + "version": "3.0.0", + "from": "xdg-basedir@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "yallist": { + "version": "2.1.2", + "from": "yallist@>=2.1.2 <3.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz" + }, + "yn": { + "version": "2.0.0", + "from": "yn@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz" + }, + "zip-object": { + "version": "0.1.0", + "from": "zip-object@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/zip-object/-/zip-object-0.1.0.tgz" + } + } +} diff --git a/package.json b/package.json index 8555ec1..4dfe277 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,15 @@ "commander": "^2.9.0", "express": "^4.14.1", "morgan": "^1.8.1", + "state": "^0.2.0", + "ts-node": "^4.0.1", + "typings": "^2.1.1", "winston": "^2.3.1", "ws": "^2.1.0" }, "devDependencies": { "nodemon": "^1.11.0", - "ts-node": "^2.1.0", + "ts-node": "^4.0.1", "typescript": "^2.2.1" }, "scripts": { diff --git a/src/server.ts b/src/server.ts index e279aa3..6b8311c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -25,7 +25,9 @@ const LOCAL_PATH = "/gnws"; // ws://server2.vesync.com:17275/gnwss // doesn't seem to be setup // ws://server2.vesync.com:17273/gnws -const REMOTE_URL = "ws://server2.vesync.com:17273/gnws"; +const REMOTE_IP = process.env.REMOTE_IP; +const REMOTE_HOST = (!REMOTE_IP) ? "server2.vesync.com" : REMOTE_IP; +const REMOTE_URL = "ws://" + REMOTE_HOST + ":17273/gnws"; const SERVICE_PORT = 16522; // port for the web service // logger stuff @@ -70,6 +72,7 @@ function startWebsocketProxy(localPort: number, localPath: string, remoteUrl: st local_ws.on('message', function(message: string) { if (!state) { // must be a login message. + logger.info("loginmessage: ", message); state = DeviceState.getDeviceStateByLogin(message); state.setInjector({ sendToDevice: (json) => { @@ -268,4 +271,4 @@ function startWebService(servicePort: number) { startWebsocketProxy(LOCAL_PORT, LOCAL_PATH, REMOTE_URL); startWebService(SERVICE_PORT); -logger.info("Server started"); +logger.info("Proxy server started for", REMOTE_URL); diff --git a/src/state.ts b/src/state.ts index 6d6fe3d..33455ca 100644 --- a/src/state.ts +++ b/src/state.ts @@ -10,6 +10,7 @@ import events = require("events"); import winston = require('winston'); + interface DeviceStateData { account: string; // the vesync account managed through the mobile app id: string; // unique id for the given device @@ -155,8 +156,20 @@ class DeviceState extends events.EventEmitter { // factory pattern. #sorrynotsorry. private static states: { [id:string]: DeviceState } = {}; public static getDeviceStateByLogin(loginMessage: string) { - const json = JSON.parse(loginMessage); - return DeviceState.getDeviceStateById(json.id, true); + try { + const json = JSON.parse(loginMessage); + return DeviceState.getDeviceStateById(json.id, true); + } + catch (e) { + const encoding = 'hex'; + const { StringDecoder } = require('string_decoder'); + const decoder = new StringDecoder(encoding); + logger.debug("Failed to parse loginMessage", loginMessage); + const loginMessageDecoded = decoder.write(loginMessage); + logger.debug("loginMessageDecoded:", loginMessageDecoded); + + throw e; + } } // public static getDeviceStateById(id: string, create:boolean = false) { @@ -376,4 +389,4 @@ class DeviceState extends events.EventEmitter { } } -export = DeviceState; \ No newline at end of file +export = DeviceState;