From 752001f5d7df763101c268f8d9240ce8351206d4 Mon Sep 17 00:00:00 2001 From: Luke Hines Date: Wed, 2 Mar 2016 17:11:29 +0000 Subject: [PATCH 01/45] Allows dash characters in subdomains --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 707c510..cba69c0 100644 --- a/server.js +++ b/server.js @@ -134,7 +134,7 @@ function maybe_bounce(req, res, sock, head) { var client_req = http.request(opt, function(client_res) { // write response code and headers res.writeHead(client_res.statusCode, client_res.headers); - + client_res.pipe(res); on_finished(client_res, function(err) { done(); @@ -221,7 +221,7 @@ module.exports = function(opt) { var req_id = req.params.req_id; // limit requested hostnames to 63 characters - if (! /^[a-z0-9]{4,63}$/.test(req_id)) { + if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(req_id)) { var err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); err.statusCode = 403; return next(err); -- 2.54.0 From 9e40570faf033b5121728a14a591305d6d975944 Mon Sep 17 00:00:00 2001 From: Luke Hines Date: Wed, 2 Mar 2016 17:26:34 +0000 Subject: [PATCH 02/45] Adds test for domain with dash --- test/basic.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/basic.js b/test/basic.js index 5e81ec5..fd861b7 100644 --- a/test/basic.js +++ b/test/basic.js @@ -118,6 +118,21 @@ test('request specific domain', function(done) { }); }); +test('request domain with dash', function(done) { + var opt = { + host: 'http://localhost:' + lt_server_port, + subdomain: 'abcd-1234' + }; + + localtunnel(test._fake_port, opt, function(err, tunnel) { + assert.ifError(err); + var url = tunnel.url; + assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); + test._fake_url = url; + done(err); + }); +}); + test('request domain that is too long', function(done) { var opt = { host: 'http://localhost:' + lt_server_port, -- 2.54.0 From a2a58f4c6f1d2760c0786a8bb40e46f8133f6fd1 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sat, 9 Jul 2016 17:06:13 -0700 Subject: [PATCH 03/45] refactor with async/await Trying to be more robust about error handling and failure. --- .babelrc | 6 + .npmrc | 1 + Dockerfile | 6 +- bin/server | 21 ++-- lib/BindingAgent.js | 8 +- lib/rand_id.js | 8 +- package.json | 18 +-- proxy.js | 267 +++++++++++++++++++++++--------------------- server.js | 172 +++++++++++++++++----------- test/mocha.opts | 4 + 10 files changed, 290 insertions(+), 221 deletions(-) create mode 100644 .babelrc create mode 100644 .npmrc create mode 100644 test/mocha.opts diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ffd53e0 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "plugins": [ + "transform-es2015-modules-commonjs", + "transform-async-to-generator", + ] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1dab4ed --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact = true diff --git a/Dockerfile b/Dockerfile index 8436615..619cd69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,11 @@ -FROM mhart/alpine-node:4.2.1 +FROM mhart/alpine-node:6.3.0 RUN mkdir -p /app WORKDIR /app ADD package.json /app/ -RUN apk add --update make git g++ python && \ - npm install --production && \ - apk del git make g++ python && \ +RUN npm install --production && \ rm -rf /tmp/* /root/.npm /root/.node-gyp ADD . /app diff --git a/bin/server b/bin/server index f33fd99..b929958 100755 --- a/bin/server +++ b/bin/server @@ -1,12 +1,13 @@ #!/usr/bin/env node -require('stackup'); -var log = require('bookrc'); -var localenv = require('localenv'); -var debug = require('debug')('localtunnel'); -var optimist = require('optimist'); +require('localenv'); +require('babel-register'); -var argv = optimist +const log = require('bookrc'); +const debug = require('debug')('localtunnel'); +const optimist = require('optimist'); + +const argv = optimist .usage('Usage: $0 --port [num]') .options('secure', { default: false, @@ -27,14 +28,18 @@ if (argv.help) { process.exit(); } -var server = require('../server')({ +const server = require('../server')({ max_tcp_sockets: argv['max-sockets'], secure: argv.secure }); -server.listen(argv.port, function() { +server.listen(argv.port, () => { debug('server listening on port: %d', server.address().port); }); +process.on('SIGINT', () => { + process.exit(); +}); + // vim: ft=javascript diff --git a/lib/BindingAgent.js b/lib/BindingAgent.js index 8aa9ac2..c0d7ad9 100644 --- a/lib/BindingAgent.js +++ b/lib/BindingAgent.js @@ -1,6 +1,6 @@ -var http = require('http'); -var util = require('util'); -var assert = require('assert'); +import http from 'http'; +import util from 'util'; +import assert from 'assert'; // binding agent will return a given options.socket as the socket for the agent // this is useful if you already have a socket established and want the request @@ -20,4 +20,4 @@ function create_connection(port, host, options) { return this.socket; } -module.exports = BindingAgent; +export default BindingAgent; diff --git a/lib/rand_id.js b/lib/rand_id.js index d0b1c13..f76f6d8 100644 --- a/lib/rand_id.js +++ b/lib/rand_id.js @@ -1,11 +1,11 @@ // all url safe // can't use uppercase because hostnames are lowercased -var chars = 'abcdefghijklmnopqrstuvwxyz'; +const chars = 'abcdefghijklmnopqrstuvwxyz'; -module.exports = function rand_id() { - var randomstring = ''; +export default function rand_id() { + let randomstring = ''; for (var i=0; i<10; ++i) { - var rnum = Math.floor(Math.random() * chars.length); + const rnum = Math.floor(Math.random() * chars.length); randomstring += chars[rnum]; } diff --git a/package.json b/package.json index c3cc4a8..907e465 100644 --- a/package.json +++ b/package.json @@ -6,24 +6,28 @@ "license": "MIT", "repository": { "type": "git", - "url": "git://github.com/shtylman/localtunnel-server.git" + "url": "git://github.com/localtunnel/server.git" }, "dependencies": { + "babel-plugin-transform-async-to-generator": "6.8.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.10.3", + "babel-polyfill": "6.9.1", + "babel-register": "6.9.0", + "bluebird": "3.4.1", "book": "1.3.1", "book-git": "0.0.2", - "book-raven": "1.1.0", + "book-raven": "1.2.0", "bookrc": "0.0.1", "debug": "2.2.0", - "express": "4.13.3", - "http-proxy": "1.12.0", + "express": "4.14.0", + "http-proxy": "1.14.0", "localenv": "0.2.2", "on-finished": "2.3.0", "optimist": "0.6.1", - "stackup": "1.0.1", - "tldjs": "1.6.1" + "tldjs": "1.6.2" }, "devDependencies": { - "mocha": "2.0.1", + "mocha": "2.5.3", "localtunnel": "1.8.0", "ws": "0.8.0" }, diff --git a/proxy.js b/proxy.js index 3d9db14..1000930 100644 --- a/proxy.js +++ b/proxy.js @@ -1,28 +1,51 @@ -var net = require('net'); -var EventEmitter = require('events').EventEmitter; +import net from 'net'; +import EventEmitter from 'events'; +import log from 'bookrc'; +import Debug from 'debug'; -var log = require('bookrc'); -var debug = require('debug')('localtunnel-server'); +const debug = Debug('localtunnel:server'); -var Proxy = function(opt, cb) { +const Proxy = function(opt) { if (!(this instanceof Proxy)) { - return new Proxy(opt, cb); + return new Proxy(opt); } - var self = this; + const self = this; self.sockets = []; self.waiting = []; - - var id = opt.id; + self.id = opt.id; // default max is 10 - var max_tcp_sockets = opt.max_tcp_sockets || 10; + self.max_tcp_sockets = opt.max_tcp_sockets || 10; // new tcp server to service requests for this client - var client_server = net.createServer(); + self.server = net.createServer(); - client_server.on('error', function(err) { + // track initial user connection setup + self.conn_timeout = undefined; + + self.debug = Debug(`localtunnel:server:${self.id}`); +}; + +Proxy.prototype.__proto__ = EventEmitter.prototype; + +Proxy.prototype.start = function(cb) { + const self = this; + const server = self.server; + + if (self.started) { + cb(new Error('already started')); + return; + } + self.started = true; + + server.on('close', self._cleanup.bind(self)); + server.on('connection', self._handle_socket.bind(self)); + + server.on('error', function(err) { + // where do these errors come from? + // other side creates a connection and then is killed? if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') { return; } @@ -30,129 +53,121 @@ var Proxy = function(opt, cb) { log.error(err); }); - // track initial user connection setup - var conn_timeout; - - // user has 5 seconds to connect before their slot is given up - function maybe_tcp_close() { - clearTimeout(conn_timeout); - conn_timeout = setTimeout(function() { - - // sometimes the server is already closed but the event has not fired? - try { - clearTimeout(conn_timeout); - client_server.close(); - } catch (err) { - cleanup(); - } - }, 5000); - } - - maybe_tcp_close(); - - function cleanup() { - debug('closed tcp socket for client(%s)', id); - - clearTimeout(conn_timeout); - - // clear waiting by ending responses, (requests?) - self.waiting.forEach(function(waiting) { - waiting(null); - }); - - self.emit('end'); - } - - // no longer accepting connections for this id - client_server.on('close', cleanup); - - // new tcp connection from lt client - client_server.on('connection', function(socket) { - // no more socket connections allowed - if (self.sockets.length >= max_tcp_sockets) { - return socket.end(); - } - - debug('new connection on port: %s', id); - - // a single connection is enough to keep client id slot open - clearTimeout(conn_timeout); - - socket.once('close', function(had_error) { - debug('client %s closed socket (error: %s)', id, had_error); - - // what if socket was servicing a request at this time? - // then it will be put back in available after right? - - // remove this socket - var idx = self.sockets.indexOf(socket); - if (idx >= 0) { - self.sockets.splice(idx, 1); - } - - // need to track total sockets, not just active available - debug('remaining client sockets: %s', self.sockets.length); - - // no more sockets for this ident - if (self.sockets.length === 0) { - debug('all client(%s) sockets disconnected', id); - maybe_tcp_close(); - } - }); - - // close will be emitted after this - socket.on('error', function(err) { - // we don't log here to avoid logging crap for misbehaving clients - socket.destroy(); - }); - - self.sockets.push(socket); - - var wait_cb = self.waiting.shift(); - if (wait_cb) { - debug('handling queued request'); - self.next_socket(wait_cb); - } - }); - - client_server.listen(function() { - var port = client_server.address().port; - debug('tcp server listening on port: %d', port); + server.listen(function() { + const port = server.address().port; + self.debug('tcp server listening on port: %d', port); cb(null, { // port for lt client tcp connections port: port, // maximum number of tcp connections allowed by lt client - max_conn_count: max_tcp_sockets + max_conn_count: self.max_tcp_sockets }); }); + + self._maybe_destroy(); }; -Proxy.prototype.__proto__ = EventEmitter.prototype; +Proxy.prototype._maybe_destroy = function() { + const self = this; -Proxy.prototype.next_socket = function(cb) { - var self = this; + clearTimeout(self.conn_timeout); + self.conn_timeout = setTimeout(function() { + // sometimes the server is already closed but the event has not fired? + try { + clearTimeout(self.conn_timeout); + self.server.close(); + } + catch (err) { + self._cleanup(); + } + }, 5000); +} - // socket is a tcp connection back to the user hosting the site - var sock = self.sockets.shift(); +// new socket connection from client for tunneling requests to client +Proxy.prototype._handle_socket = function(socket) { + const self = this; - // TODO how to handle queue? - // queue request - if (!sock) { - debug('no more client, queue callback'); - return self.waiting.push(cb); + // no more socket connections allowed + if (self.sockets.length >= self.max_tcp_sockets) { + return socket.end(); } - var done_called = false; - // put the socket back - function done() { - if (done_called) { - throw new Error('done called multiple times'); + self.debug('new connection from: %s:%s', socket.address().address, socket.address().port); + + // a single connection is enough to keep client id slot open + clearTimeout(self.conn_timeout); + + socket.once('close', function(had_error) { + self.debug('closed socket (error: %s)', had_error); + + // what if socket was servicing a request at this time? + // then it will be put back in available after right? + // we need a list of sockets servicing requests? + + // remove this socket + const idx = self.sockets.indexOf(socket); + if (idx >= 0) { + self.sockets.splice(idx, 1); } - done_called = true; + // need to track total sockets, not just active available + self.debug('remaining client sockets: %s', self.sockets.length); + + // no more sockets for this ident + if (self.sockets.length === 0) { + self.debug('all sockets disconnected'); + self._maybe_destroy(); + } + }); + + // close will be emitted after this + socket.on('error', function(err) { + // we don't log here to avoid logging crap for misbehaving clients + socket.destroy(); + }); + + self.sockets.push(socket); + + const wait_cb = self.waiting.shift(); + if (wait_cb) { + self.debug('handling queued request'); + self.next_socket(wait_cb); + } +}; + +Proxy.prototype._cleanup = function() { + const self = this; + self.debug('closed tcp socket for client(%s)', self.id); + + clearTimeout(self.conn_timeout); + + // clear waiting by ending responses, (requests?) + self.waiting.forEach(handler => handler(null)); + + self.emit('end'); +}; + +Proxy.prototype.next_socket = function(handler) { + const self = this; + + // socket is a tcp connection back to the user hosting the site + const sock = self.sockets.shift(); + + if (!sock) { + self.debug('no more client, queue callback'); + self.waiting.push(handler); + return; + } + + self.debug('processing request'); + handler(sock) + .catch((err) => { + log.error(err); + }) + .finally(() => { if (!sock.destroyed) { - debug('retuning socket'); + self.debug('retuning socket'); self.sockets.push(sock); } @@ -161,19 +176,17 @@ Proxy.prototype.next_socket = function(cb) { return; } - var wait = self.waiting.shift(); - debug('processing queued cb'); + const wait = self.waiting.shift(); + self.debug('processing queued cb'); if (wait) { - return self.next_socket(cb); + self.next_socket(cb); + return; } - }; - - debug('processing request'); - cb(sock, done); + }); }; Proxy.prototype._done = function() { - var self = this; + const self = this; }; -module.exports = Proxy; +export default Proxy; diff --git a/server.js b/server.js index 707c510..87516fa 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,19 @@ -var log = require('bookrc'); -var express = require('express'); -var tldjs = require('tldjs'); -var on_finished = require('on-finished'); -var debug = require('debug')('localtunnel-server'); -var http_proxy = require('http-proxy'); -var http = require('http'); +import log from 'bookrc'; +import express from 'express'; +import tldjs from 'tldjs'; +import on_finished from 'on-finished'; +import Debug from 'debug'; +import http_proxy from 'http-proxy'; +import http from 'http'; +import Promise from 'bluebird'; -var BindingAgent = require('./lib/BindingAgent'); +import Proxy from './proxy'; +import rand_id from './lib/rand_id'; +import BindingAgent from './lib/BindingAgent'; -var proxy = http_proxy.createProxyServer({ +const debug = Debug('localtunnel:server'); + +const proxy = http_proxy.createProxyServer({ target: 'http://localtunnel.github.io' }); @@ -23,51 +28,54 @@ proxy.on('proxyReq', function(proxyReq, req, res, options) { proxyReq.setHeader('host', 'localtunnel.github.io'); }); -var Proxy = require('./proxy'); -var rand_id = require('./lib/rand_id'); - -var PRODUCTION = process.env.NODE_ENV === 'production'; +const PRODUCTION = process.env.NODE_ENV === 'production'; // id -> client http server -var clients = Object.create(null); +const clients = Object.create(null); // proxy statistics -var stats = { +const stats = { tunnels: 0 }; +// handle proxying a request to a client +// will wait for a tunnel socket to become available function maybe_bounce(req, res, sock, head) { // without a hostname, we won't know who the request is for - var hostname = req.headers.host; + const hostname = req.headers.host; if (!hostname) { return false; } - var subdomain = tldjs.getSubdomain(hostname); + const subdomain = tldjs.getSubdomain(hostname); if (!subdomain) { return false; } - var client_id = subdomain; - var client = clients[client_id]; + const client = clients[subdomain]; // no such subdomain // we use 502 error to the client to signify we can't service the request if (!client) { - res.statusCode = 502; - res.end('localtunnel error: no active client for \'' + client_id + '\''); - req.connection.destroy(); + if (res) { + res.statusCode = 502; + res.end(`no active client for '${subdomain}'`); + req.connection.destroy(); + } + else if (sock) { + sock.destroy(); + } + return true; } - var finished = false; + let finished = false; if (sock) { sock.once('end', function() { finished = true; }); } - - if (res) { + else if (res) { // flag if we already finished before we get a socket // we can't respond to these requests on_finished(res, function(err) { @@ -75,16 +83,19 @@ function maybe_bounce(req, res, sock, head) { req.connection.destroy(); }); } + // not something we are expecting, need a sock or a res + else { + req.connection.destroy(); + return true; + } // TODO add a timeout, if we run out of sockets, then just 502 // get client port - client.next_socket(function(socket, done) { - done = done || function() {}; - + client.next_socket(async (socket) => { // the request already finished or client disconnected if (finished) { - return done(); + return; } // happens when client upstream is disconnected @@ -103,9 +114,9 @@ function maybe_bounce(req, res, sock, head) { // and directly pipe the socket data // avoids having to rebuild the request and handle upgrades via the http client if (res === null) { - var arr = [req.method + ' ' + req.url + ' HTTP/' + req.httpVersion]; - for (var i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { - arr.push(req.rawHeaders[i] + ': ' + req.rawHeaders[i+1]); + const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`]; + for (let i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { + arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}`); } arr.push(''); @@ -113,40 +124,55 @@ function maybe_bounce(req, res, sock, head) { socket.pipe(sock).pipe(socket); socket.write(arr.join('\r\n')); - socket.once('end', function() { - done(); + + await new Promise((resolve) => { + socket.once('end', resolve); }); return; } - var agent = new BindingAgent({ + // regular http request + + const agent = new BindingAgent({ socket: socket }); - var opt = { + const opt = { path: req.url, agent: agent, method: req.method, headers: req.headers }; - var client_req = http.request(opt, function(client_res) { - // write response code and headers - res.writeHead(client_res.statusCode, client_res.headers); - - client_res.pipe(res); - on_finished(client_res, function(err) { - done(); - }); - }); + await new Promise((resolve) => { + // what if error making this request? + const client_req = http.request(opt, function(client_res) { + // write response code and headers + res.writeHead(client_res.statusCode, client_res.headers); - req.pipe(client_req); + client_res.pipe(res); + on_finished(client_res, function(err) { + resolve(); + }); + }); + + // happens if the other end dies while we are making the request + // so we just end the req and move on + // we can't really do more with the response here because headers + // may already be sent + client_req.on('error', (err) => { + req.connection.destroy(); + }); + + req.pipe(client_req); + }); }); return true; } +// create a new tunnel with `id` function new_client(id, opt, cb) { // can't ask for id already is use @@ -155,43 +181,49 @@ function new_client(id, opt, cb) { id = rand_id(); } - var popt = { + const popt = { id: id, max_tcp_sockets: opt.max_tcp_sockets }; - var client = Proxy(popt, function(err, info) { - if (err) { - return cb(err); - } + const client = Proxy(popt); - ++stats.tunnels; - clients[id] = client; - - info.id = id; - - cb(err, info); - }); + // add to clients map immediately + // avoiding races with other clients requesting same id + clients[id] = client; client.on('end', function() { --stats.tunnels; delete clients[id]; }); + + client.start((err, info) => { + if (err) { + delete clients[id]; + cb(err); + return; + } + + ++stats.tunnels; + + info.id = id; + cb(err, info); + }); } module.exports = function(opt) { opt = opt || {}; - var schema = opt.secure ? 'https' : 'http'; + const schema = opt.secure ? 'https' : 'http'; - var app = express(); + const app = express(); app.get('/', function(req, res, next) { if (req.query['new'] === undefined) { return next(); } - var req_id = rand_id(); + const req_id = rand_id(); debug('making new client with id %s', req_id); new_client(req_id, opt, function(err, info) { if (err) { @@ -199,7 +231,7 @@ module.exports = function(opt) { return res.end(err.message); } - var url = schema + '://' + req_id + '.' + req.headers.host; + const url = schema + '://' + req_id + '.' + req.headers.host; info.url = url; res.json(info); }); @@ -217,12 +249,18 @@ module.exports = function(opt) { proxy.web(req, res); }); + app.get('/api/status', function(req, res, next) { + res.json({ + tunnels: stats.tunnels, + }); + }); + app.get('/:req_id', function(req, res, next) { - var req_id = req.params.req_id; + const req_id = req.params.req_id; // limit requested hostnames to 63 characters if (! /^[a-z0-9]{4,63}$/.test(req_id)) { - var err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); + const err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); err.statusCode = 403; return next(err); } @@ -233,7 +271,7 @@ module.exports = function(opt) { return next(err); } - var url = schema + '://' + req_id + '.' + req.headers.host; + const url = schema + '://' + req_id + '.' + req.headers.host; info.url = url; res.json(info); }); @@ -241,13 +279,13 @@ module.exports = function(opt) { }); app.use(function(err, req, res, next) { - var status = err.statusCode || err.status || 500; + const status = err.statusCode || err.status || 500; res.status(status).json({ message: err.message }); }); - var server = http.createServer(); + const server = http.createServer(); server.on('request', function(req, res) { debug('request %s', req.url); diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..40f1956 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +--check-leaks +--reporter spec +--ui qunit +--compilers js:babel-register -- 2.54.0 From 965be6e1ff6acb7f690fdc004befb1a819ccc104 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sat, 9 Jul 2016 17:07:51 -0700 Subject: [PATCH 04/45] remove devops folder moved to separate nginx repo --- devops/nginx/nginx.conf | 33 ----------------- devops/nginx/sites/localtunnel | 65 ---------------------------------- devops/run.sh | 5 --- 3 files changed, 103 deletions(-) delete mode 100644 devops/nginx/nginx.conf delete mode 100644 devops/nginx/sites/localtunnel delete mode 100644 devops/run.sh diff --git a/devops/nginx/nginx.conf b/devops/nginx/nginx.conf deleted file mode 100644 index d5d5af1..0000000 --- a/devops/nginx/nginx.conf +++ /dev/null @@ -1,33 +0,0 @@ -user nginx; -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - - -events { - worker_connections 10240; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log off; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - gzip on; - gzip_min_length 1000; - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites/*; -} diff --git a/devops/nginx/sites/localtunnel b/devops/nginx/sites/localtunnel deleted file mode 100644 index 8238f11..0000000 --- a/devops/nginx/sites/localtunnel +++ /dev/null @@ -1,65 +0,0 @@ -proxy_http_version 1.1; - -# http://nginx.org/en/docs/http/websocket.html -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - -upstream lt-server { - server 127.0.0.1:2000; -} - -server { - listen 80 default_server; - listen [::]:80 default_server ipv6only=on; - - server_name .localtunnel.me; - - location / { - proxy_pass http://lt-server/; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto http; - proxy_set_header X-NginX-Proxy true; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_redirect off; - } -} - -server { - listen 443 default_server ssl spdy; - listen [::]:443 default_server ipv6only=on; - - server_name .localtunnel.me; - - ssl on; - - ssl_certificate /etc/nginx/ssl/STAR.localtunnel.me.crt; - ssl_certificate_key /etc/nginx/ssl/STAR.localtunnel.me.key; - - ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers RC4:HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; - - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - location / { - proxy_pass http://lt-server/; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header X-NginX-Proxy true; - proxy_set_header Connection $connection_upgrade; - - proxy_redirect off; - } -} diff --git a/devops/run.sh b/devops/run.sh deleted file mode 100644 index 74417cd..0000000 --- a/devops/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -docker run --restart always -m 350m --name localtunnel -d --net host defunctzombie/localtunnel-server:0.0.5 bin/server --secure --port 2000 --max-sockets 5 - -docker run --restart always --name nginx -d --net host -v /home/core/nginx/nginx.conf:/etc/nginx/nginx.conf -v /home/core/nginx/sites:/etc/nginx/sites -v /home/core/nginx/ssl:/etc/nginx/ssl nginx:1.7.8 -- 2.54.0 From f1fa607ad9f63c266d3b0acaccd0d6cf450f13e2 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sat, 9 Jul 2016 17:22:14 -0700 Subject: [PATCH 05/45] README: add deploy instructions --- README.md | 16 ++++++++++++++++ bin/server | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 0309807..f8c5d8c 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,19 @@ lt --host http://sub.example.tld:1234 --port 9000 You will be assigned a URL similar to `qdci.sub.example.com:1234`. If your server is acting as a reverse proxy (i.e. nginx) and is able to listen on port 80, then you do not need the `:1234` part of the hostname for the `lt` client. + +## Deploy + +You can deploy your own localtunnel server using the prebuilt docker image. + +**Note** This assumes that you have a proxy in front of the server to handle the http(s) requests and forward them to the localtunnel server on port 3000. You can use our [localtunnel-nginx](https://github.com/localtunnel/nginx) so accomplish this. + +If you do not want ssl support for your own tunnel (not recommended), then you can just run the below with `--port 80` instead. + +``` +docker run -d \ + --restart always \ + --name localtunnel \ + --net host \ + defunctzombie/localtunnel-server:latest --port 3000 +``` diff --git a/bin/server b/bin/server index b929958..e352884 100755 --- a/bin/server +++ b/bin/server @@ -41,5 +41,9 @@ process.on('SIGINT', () => { process.exit(); }); +process.on('SIGTERM', () => { + process.exit(); +}); + // vim: ft=javascript -- 2.54.0 From d7fbf3c39e9de6e56a786d1b2119c3bd899f316b Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Jul 2016 20:22:35 -0700 Subject: [PATCH 06/45] fix processing waiting callbacks inside next_socket --- proxy.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proxy.js b/proxy.js index 1000930..75d5e3e 100644 --- a/proxy.js +++ b/proxy.js @@ -128,7 +128,11 @@ Proxy.prototype._handle_socket = function(socket) { }); self.sockets.push(socket); + self._process_waiting(); +}; +Proxy.prototype._process_waiting = function() { + const self = this; const wait_cb = self.waiting.shift(); if (wait_cb) { self.debug('handling queued request'); @@ -176,12 +180,7 @@ Proxy.prototype.next_socket = function(handler) { return; } - const wait = self.waiting.shift(); - self.debug('processing queued cb'); - if (wait) { - self.next_socket(cb); - return; - } + self._process_waiting(); }); }; -- 2.54.0 From dbf91cfa207f048807453401bf7f14ccb105bc56 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Jul 2016 22:24:20 -0700 Subject: [PATCH 07/45] fix sending response when no client socket available --- server.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 87516fa..87bf4bc 100644 --- a/server.js +++ b/server.js @@ -98,14 +98,25 @@ function maybe_bounce(req, res, sock, head) { return; } - // happens when client upstream is disconnected + // happens when client upstream is disconnected (or disconnects) + // and the proxy iterates the waiting list and clears the callbacks // we gracefully inform the user and kill their conn // without this, the browser will leave some connections open // and try to use them again for new requests // we cannot have this as we need bouncy to assign the requests again + // TODO(roman) we could instead have a timeout above + // if no socket becomes available within some time, + // we just tell the user no resource available to service request else if (!socket) { - res.statusCode = 504; - res.end(); + if (res) { + res.statusCode = 504; + res.end(); + } + + if (sock) { + sock.destroy(); + } + req.connection.destroy(); return; } -- 2.54.0 From eae9718ab3011f3f76c0e52ca1cafd9e507f242b Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 24 Jul 2016 21:34:34 -0400 Subject: [PATCH 08/45] redirect to github site for landing page avoid proxying all the landing page requests --- server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 87bf4bc..a21dcd8 100644 --- a/server.js +++ b/server.js @@ -249,13 +249,15 @@ module.exports = function(opt) { }); app.get('/', function(req, res, next) { - proxy.web(req, res); + res.redirect('https://localtunnel.github.io/www/'); }); + // TODO(roman) remove after deploying redirect above app.get('/assets/*', function(req, res, next) { proxy.web(req, res); }); + // TODO(roman) remove after deploying redirect above app.get('/favicon.ico', function(req, res, next) { proxy.web(req, res); }); -- 2.54.0 From c81ed7ff4adc560e839ac8cba3ff21ab9d9f594a Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 24 Jul 2016 21:37:18 -0400 Subject: [PATCH 09/45] travis: update to test with node 6.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9842ff8..07c93e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js sudo: false node_js: - - "4" + - "6.2" -- 2.54.0 From 5ee2ded1fdc2ee8b2091c65fb84149fbcabb769a Mon Sep 17 00:00:00 2001 From: Bryan Helmig Date: Sat, 3 Sep 2016 15:00:33 -0700 Subject: [PATCH 10/45] test: fix redirect (#53) --- test/basic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/basic.js b/test/basic.js index 5e81ec5..408d4a2 100644 --- a/test/basic.js +++ b/test/basic.js @@ -35,7 +35,7 @@ test('landing page', function(done) { }); res.on('end', function() { - assert(body.indexOf('Localtunnel ~ Expose yourself to the world') > 0); + assert(body.indexOf('Redirecting to https://localtunnel.github.io/www/') > 0); done(); }); }); -- 2.54.0 From 2a6561e7745f950cd4ce96acf513564096987a4a Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 27 Nov 2016 09:49:10 -0800 Subject: [PATCH 11/45] add error handlers to requests and sockets Trying to flush out cause of sporadic socket failures and identify which socket is not being handled. --- server.js | 17 +++++++++++++++++ test/basic.js | 12 ++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/server.js b/server.js index a21dcd8..e52a300 100644 --- a/server.js +++ b/server.js @@ -301,6 +301,15 @@ module.exports = function(opt) { const server = http.createServer(); server.on('request', function(req, res) { + + req.on('error', (err) => { + console.error('request', err); + }); + + res.on('error', (err) => { + console.error('response', err); + }); + debug('request %s', req.url); if (maybe_bounce(req, res, null, null)) { return; @@ -310,6 +319,14 @@ module.exports = function(opt) { }); server.on('upgrade', function(req, socket, head) { + req.on('error', (err) => { + console.error('ws req', err); + }); + + socket.on('error', (err) => { + console.error('ws socket', err); + }); + if (maybe_bounce(req, null, socket, head)) { return; }; diff --git a/test/basic.js b/test/basic.js index 408d4a2..ed66574 100644 --- a/test/basic.js +++ b/test/basic.js @@ -28,16 +28,8 @@ test('landing page', function(done) { var req = http.request(opt, function(res) { res.setEncoding('utf8'); - var body = ''; - - res.on('data', function(chunk) { - body += chunk; - }); - - res.on('end', function() { - assert(body.indexOf('Redirecting to https://localtunnel.github.io/www/') > 0); - done(); - }); + assert.equal(res.headers.location, 'https://localtunnel.github.io/www/') + done(); }); req.end(); -- 2.54.0 From c67d1a4d371df6466dc6cd9aea97cfe2498367a7 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 27 Nov 2016 12:37:10 -0800 Subject: [PATCH 12/45] bookrc: add error print to further diagnose ECONNRESETS --- bookrc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookrc.js b/bookrc.js index b8763e7..f13a7e2 100644 --- a/bookrc.js +++ b/bookrc.js @@ -1,11 +1,12 @@ /// bookrc logging setup -var log = require('book').default(); +const log = require('book').default(); //log.use(require('book-git')(__dirname)); log.use(require('book-raven')(process.env.SENTRY_DSN)); process.on('uncaughtException', function(err) { log.error(err); + console.error(err); process.exit(1); }); -- 2.54.0 From 30df06ff148664d99953f9a00cd3175157f031a2 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 27 Nov 2016 12:47:59 -0800 Subject: [PATCH 13/45] add memory usage to status api endpoint --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index e52a300..e812b00 100644 --- a/server.js +++ b/server.js @@ -265,6 +265,7 @@ module.exports = function(opt) { app.get('/api/status', function(req, res, next) { res.json({ tunnels: stats.tunnels, + mem: process.memoryUsage(), }); }); -- 2.54.0 From 48475a404b632739fc1a16efb1d74e48f78ab75f Mon Sep 17 00:00:00 2001 From: Arron Gibson Date: Wed, 8 Feb 2017 23:56:19 -0500 Subject: [PATCH 14/45] readme: Fix minor typo (#58) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8c5d8c..c557c94 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ If your server is acting as a reverse proxy (i.e. nginx) and is able to listen o You can deploy your own localtunnel server using the prebuilt docker image. -**Note** This assumes that you have a proxy in front of the server to handle the http(s) requests and forward them to the localtunnel server on port 3000. You can use our [localtunnel-nginx](https://github.com/localtunnel/nginx) so accomplish this. +**Note** This assumes that you have a proxy in front of the server to handle the http(s) requests and forward them to the localtunnel server on port 3000. You can use our [localtunnel-nginx](https://github.com/localtunnel/nginx) to accomplish this. If you do not want ssl support for your own tunnel (not recommended), then you can just run the below with `--port 80` instead. -- 2.54.0 From 2539755155c9489bf0e2678b66f2b54a12b84602 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Sun, 10 Dec 2017 08:35:44 -0800 Subject: [PATCH 15/45] Use generated id in the returned url (#59) --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index e812b00..0de6d50 100644 --- a/server.js +++ b/server.js @@ -285,7 +285,7 @@ module.exports = function(opt) { return next(err); } - const url = schema + '://' + req_id + '.' + req.headers.host; + const url = schema + '://' + info.id + '.' + req.headers.host; info.url = url; res.json(info); }); -- 2.54.0 From ddad1851fc95a1b098d51f069e924f1606340ee7 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 09:27:57 -0800 Subject: [PATCH 16/45] add yarn.lock --- yarn.lock | 1275 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1275 insertions(+) create mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..35d5434 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1275 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +asn1@0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" + +assert-plus@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" + +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + +async@^2.0.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + dependencies: + lodash "^4.14.0" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.26.0, babel-core@^6.9.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.0" + debug "^2.6.8" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.7" + slash "^1.0.0" + source-map "^0.5.6" + +babel-generator@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.6" + trim-right "^1.0.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-remap-async-to-generator@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-transform-async-to-generator@6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.8.0.tgz#fbb154f2912e00b9f0972131d7e03ebbe45684c2" + dependencies: + babel-helper-remap-async-to-generator "^6.8.0" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.0.0" + +babel-plugin-transform-es2015-modules-commonjs@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f" + dependencies: + babel-plugin-transform-strict-mode "^6.8.0" + babel-runtime "^6.0.0" + babel-template "^6.8.0" + babel-types "^6.8.0" + +babel-plugin-transform-strict-mode@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.9.1.tgz#6b6f2f63700c4979bbe2eeb8bdebbac6ddff75bc" + dependencies: + babel-runtime "^6.9.1" + core-js "^2.4.0" + regenerator-runtime "^0.9.5" + +babel-register@6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.9.0.tgz#dd5f3572ef5bd4082ca05471e942e4a91b162ff0" + dependencies: + babel-core "^6.9.0" + babel-runtime "^6.9.0" + core-js "^2.4.0" + home-or-tmp "^1.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + path-exists "^1.0.0" + source-map-support "^0.2.10" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.0.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0, babel-runtime@^6.9.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.8.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.24.1, babel-types@^6.26.0, babel-types@^6.8.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +bindings@1.2.x, bindings@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" + +bl@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.0.3.tgz#fc5421a28fd4226036c3b3891a66a25bc64d226e" + dependencies: + readable-stream "~2.0.5" + +bluebird@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.1.tgz#b731ddf48e2dd3bedac2e75e1215a11bcb91fa07" + +book-git@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/book-git/-/book-git-0.0.2.tgz#25be85525e99f3a768a3492b817713da4b2060dd" + +book-raven@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/book-raven/-/book-raven-1.2.0.tgz#686935d4efa422fe85e9be4bfb970aee7d08ee24" + dependencies: + mocha "2.5.3" + nock "8.0.0" + raven "0.11.0" + xtend "4.0.1" + +book@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/book/-/book-1.3.1.tgz#3194fb229bd8748d6bde3c69b0badfed5f58a13d" + +bookrc@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/bookrc/-/bookrc-0.0.1.tgz#bff79d58d57774aed504585ba2dadd51d3ff5892" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +bufferutil@1.2.x: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-1.2.1.tgz#37be5d36e1e06492221e68d474b1ac58e510cbd7" + dependencies: + bindings "1.2.x" + nan "^2.0.5" + +camelcase@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + +"chai@>=1.9.2 <4.0.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + +chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +cliui@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" + +commander@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" + +commander@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.5.0.tgz#d777b6a4d847d423e5d475da864294ac1ff5aa9d" + +commander@^2.9.0: + version "2.12.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +content-disposition@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" + +content-type@~1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +convert-source-map@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.0.tgz#90eb469ddce905c866de687efc43131d8801f9d0" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +core-js@^2.4.0, core-js@^2.5.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.2.tgz#bc4648656e7dc9dc80d7d3c7bbc172d96e744e63" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +ctype@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" + +debug@2.2.0, debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + +debug@^2.2.0, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +depd@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +diff@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" + +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + +express@4.14.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66" + dependencies: + accepts "~1.3.3" + array-flatten "1.1.1" + content-disposition "0.5.1" + content-type "~1.0.2" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "~2.2.0" + depd "~1.1.0" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.7.0" + finalhandler "0.5.0" + fresh "0.3.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.1" + path-to-regexp "0.1.7" + proxy-addr "~1.1.2" + qs "6.2.0" + range-parser "~1.2.0" + send "0.14.1" + serve-static "~1.11.1" + type-is "~1.6.13" + utils-merge "1.0.0" + vary "~1.1.0" + +extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +finalhandler@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" + dependencies: + debug "~2.2.0" + escape-html "~1.0.3" + on-finished "~2.3.0" + statuses "~1.3.0" + unpipe "~1.0.0" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~1.0.0-rc3: + version "1.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" + dependencies: + async "^2.0.1" + combined-stream "^1.0.5" + mime-types "^2.1.11" + +forwarded@~0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fresh@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +glob@3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" + dependencies: + inherits "2" + minimatch "0.3" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +growl@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" + +har-validator@~2.0.2: + version "2.0.6" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" + dependencies: + chalk "^1.1.1" + commander "^2.9.0" + is-my-json-valid "^2.12.4" + pinkie-promise "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +hawk@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +home-or-tmp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-1.0.0.tgz#4b9f1e40800c3e50c6c27f781676afcce71f3985" + dependencies: + os-tmpdir "^1.0.1" + user-home "^1.1.1" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +http-errors@~1.5.0, http-errors@~1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" + dependencies: + inherits "2.0.3" + setprototypeof "1.0.2" + statuses ">= 1.3.1 < 2" + +http-proxy@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.14.0.tgz#be32ab34dd5229e87840f4c27cb335ee195b2a83" + dependencies: + eventemitter3 "1.x.x" + requires-port "1.x.x" + +http-signature@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" + dependencies: + asn1 "0.1.11" + assert-plus "^0.1.5" + ctype "0.5.3" + +inherits@2, inherits@2.0.3, inherits@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +invariant@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +ipaddr.js@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-my-json-valid@^2.12.4: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +jade@0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" + dependencies: + commander "0.6.1" + mkdirp "0.3.0" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +localenv@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/localenv/-/localenv-0.2.2.tgz#c508f29d3485bdc9341d3ead17f61c5abd1b0bab" + dependencies: + commander "2.5.0" + +localtunnel@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.8.0.tgz#e8feba5fa015bfa2cc1c5daff0973eaf3adf3337" + dependencies: + debug "2.2.0" + openurl "1.1.0" + request "2.65.0" + yargs "3.29.0" + +lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.8.2: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + +lsmod@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@^2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mime@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" + +minimatch@0.3: + version "0.3.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mkdirp@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" + +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" + dependencies: + commander "2.3.0" + debug "2.2.0" + diff "1.4.0" + escape-string-regexp "1.0.2" + glob "3.2.11" + growl "1.9.2" + jade "0.26.3" + mkdirp "0.5.1" + supports-color "1.2.0" + to-iso-string "0.0.2" + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +nan@^2.0.5: + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + +nan@~2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +nock@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-8.0.0.tgz#f86d676568c73a3bb2144ebc80791d447bb334d2" + dependencies: + chai ">=1.9.2 <4.0.0" + debug "^2.2.0" + deep-equal "^1.0.0" + json-stringify-safe "^5.0.1" + lodash "^4.8.2" + mkdirp "^0.5.0" + propagate "0.4.0" + qs "^6.0.2" + +node-uuid@~1.4.1, node-uuid@~1.4.3: + version "1.4.8" + resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +on-finished@2.3.0, on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +openurl@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.0.tgz#e2f2189d999c04823201f083f0f1a7cd8903187a" + +optimist@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +parseurl@~1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +path-exists@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" + +path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +private@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +propagate@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481" + +proxy-addr@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" + dependencies: + forwarded "~0.1.0" + ipaddr.js "1.4.0" + +qs@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" + +qs@^6.0.2: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raven@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/raven/-/raven-0.11.0.tgz#32981138a93e4c8ad08cfc17e46b85b453dc107b" + dependencies: + cookie "0.1.0" + lsmod "1.0.0" + node-uuid "~1.4.1" + stack-trace "0.0.7" + +readable-stream@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-runtime@^0.9.5: + version "0.9.6" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@2.65.0: + version "2.65.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.65.0.tgz#cc1a3bc72b96254734fc34296da322f9486ddeba" + dependencies: + aws-sign2 "~0.6.0" + bl "~1.0.0" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~1.0.0-rc3" + har-validator "~2.0.2" + hawk "~3.1.0" + http-signature "~0.11.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + node-uuid "~1.4.3" + oauth-sign "~0.8.0" + qs "~5.2.0" + stringstream "~0.0.4" + tough-cookie "~2.2.0" + tunnel-agent "~0.4.1" + +requires-port@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +send@0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" + dependencies: + debug "~2.2.0" + depd "~1.1.0" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.7.0" + fresh "0.3.0" + http-errors "~1.5.0" + mime "1.3.4" + ms "0.7.1" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.0" + +send@0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" + dependencies: + debug "~2.2.0" + depd "~1.1.0" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.7.0" + fresh "0.3.0" + http-errors "~1.5.1" + mime "1.3.4" + ms "0.7.2" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-static@~1.11.1: + version "1.11.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.1" + send "0.14.2" + +setprototypeof@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +source-map-support@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" + dependencies: + source-map "0.1.32" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map@0.1.32: + version "0.1.32" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +stack-trace@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.7.tgz#c72e089744fc3659f508cdce3621af5634ec0fff" + +"statuses@>= 1.3.1 < 2": + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +statuses@~1.3.0, statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +supports-color@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +tldjs@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-1.6.2.tgz#0d8b7145651f1052ac0b3c688843296636183b13" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-iso-string@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" + +tough-cookie@~2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.2.2.tgz#c83a1830f4e5ef0b93ef2a3488e724f8de016ac7" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tunnel-agent@~0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + +type-is@~1.6.13: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +utf-8-validate@1.2.x: + version "1.2.2" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-1.2.2.tgz#8bb871a4741e085c70487ca7acdbd7d6d36029eb" + dependencies: + bindings "~1.2.1" + nan "~2.4.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +utils-merge@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" + +vary@~1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +window-size@^0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +ws@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-0.8.0.tgz#ac60ebad312121d01e16cc3383d7ec67ad0f0f1f" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + optionalDependencies: + bufferutil "1.2.x" + utf-8-validate "1.2.x" + +xtend@4.0.1, xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yargs@3.29.0: + version "3.29.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.29.0.tgz#1aab9660eae79d8b8f675bcaeeab6ee34c2cf69c" + dependencies: + camelcase "^1.2.1" + cliui "^3.0.3" + decamelize "^1.0.0" + os-locale "^1.4.0" + window-size "^0.1.2" + y18n "^3.2.0" -- 2.54.0 From 55a7f8eeb6f52b7ec211c738c2f418c21ab731ba Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 09:36:53 -0800 Subject: [PATCH 17/45] update Dockerfile to node 9.2.1 --- Dockerfile | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 619cd69..eb56d30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,13 @@ -FROM mhart/alpine-node:6.3.0 +FROM node:9.2.1-alpine -RUN mkdir -p /app WORKDIR /app -ADD package.json /app/ +COPY package.json /app/ +COPY yarn.lock /app/ -RUN npm install --production && \ - rm -rf /tmp/* /root/.npm /root/.node-gyp +RUN yarn install --production && yarn cache clean -ADD . /app +COPY . /app ENV NODE_ENV production ENTRYPOINT ["bin/server"] -- 2.54.0 From db5b632a9f8f3387f32695a1d2b0e51b30c33a25 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 09:56:58 -0800 Subject: [PATCH 18/45] add dev script to package.json --- package.json | 6 +- yarn.lock | 452 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 455 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 907e465..e838166 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,14 @@ "tldjs": "1.6.2" }, "devDependencies": { - "mocha": "2.5.3", "localtunnel": "1.8.0", + "mocha": "2.5.3", + "node-dev": "3.1.3", "ws": "0.8.0" }, "scripts": { "test": "mocha --ui qunit --reporter spec", - "start": "./bin/server" + "start": "./bin/server", + "dev": "node-dev bin/server --port 3000" } } diff --git a/yarn.lock b/yarn.lock index 35d5434..6d83084 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,6 +21,14 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansicolors@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -296,10 +304,32 @@ bufferutil@1.2.x: bindings "1.2.x" nan "^2.0.5" +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + camelcase@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +cardinal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-1.0.0.tgz#50e21c1b0aa37729f9377def196b5a9cec932ee9" + dependencies: + ansicolors "~0.2.1" + redeyed "~1.0.0" + caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -322,6 +352,19 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + dependencies: + colors "1.0.3" + +cli-usage@^0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/cli-usage/-/cli-usage-0.1.4.tgz#7c01e0dc706c234b39c933838c8e20b2175776e2" + dependencies: + marked "^0.3.6" + marked-terminal "^1.6.2" + cliui@^3.0.3: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -334,6 +377,10 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -402,6 +449,23 @@ ctype@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +dateformat@~1.0.4-1.2.3: + version "1.0.12" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" + dependencies: + get-stdin "^4.0.1" + meow "^3.3.0" + +debounce@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" + debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -414,7 +478,7 @@ debug@^2.2.0, debug@^2.6.8: dependencies: ms "2.0.0" -decamelize@^1.0.0: +decamelize@^1.0.0, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -450,6 +514,12 @@ diff@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" +dynamic-dedupe@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.2.0.tgz#50f7c28684831ecf1c170aab67a1d5311cdd76ce" + dependencies: + xtend "~2.0.6" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -458,6 +528,12 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -470,6 +546,10 @@ escape-string-regexp@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" +esprima@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -517,6 +597,12 @@ extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +filewatcher@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/filewatcher/-/filewatcher-3.0.1.tgz#f4a1957355ddaf443ccd78a895f3d55e23c8a034" + dependencies: + debounce "^1.0.0" + finalhandler@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" @@ -527,6 +613,17 @@ finalhandler@0.5.0: statuses "~1.3.0" unpipe "~1.0.0" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +foreach@~2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -557,6 +654,10 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + glob@3.2.11: version "3.2.11" resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" @@ -568,10 +669,18 @@ globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +growly@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + har-validator@~2.0.2: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" @@ -614,6 +723,10 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + http-errors@~1.5.0, http-errors@~1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" @@ -637,6 +750,16 @@ http-signature@~0.11.0: assert-plus "^0.1.5" ctype "0.5.3" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indexof@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + inherits@2, inherits@2.0.3, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -655,6 +778,16 @@ ipaddr.js@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + is-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" @@ -676,14 +809,30 @@ is-my-json-valid@^2.12.4: jsonpointer "^4.0.0" xtend "^4.0.0" +is-object@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" + is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is@~0.2.6: + version "0.2.7" + resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -721,6 +870,16 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + localenv@0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/localenv/-/localenv-0.2.2.tgz#c508f29d3485bdc9341d3ead17f61c5abd1b0bab" @@ -736,6 +895,79 @@ localtunnel@1.8.0: request "2.65.0" yargs "3.29.0" +lodash._arraycopy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1" + +lodash._arrayeach@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz#bab156b2a90d3f1bbd5c653403349e5e5933ef9e" + +lodash._baseassign@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" + dependencies: + lodash._basecopy "^3.0.0" + lodash.keys "^3.0.0" + +lodash._baseclone@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz#303519bf6393fe7e42f34d8b630ef7794e3542b7" + dependencies: + lodash._arraycopy "^3.0.0" + lodash._arrayeach "^3.0.0" + lodash._baseassign "^3.0.0" + lodash._basefor "^3.0.0" + lodash.isarray "^3.0.0" + lodash.keys "^3.0.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basefor@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2" + +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + +lodash.clonedeep@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz#a0a1e40d82a5ea89ff5b147b8444ed63d92827db" + dependencies: + lodash._baseclone "^3.0.0" + lodash._bindcallback "^3.0.0" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.8.2: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -746,6 +978,13 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -754,10 +993,43 @@ lsmod@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +marked-terminal@^1.6.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-1.7.0.tgz#c8c460881c772c7604b64367007ee5f77f125904" + dependencies: + cardinal "^1.0.0" + chalk "^1.1.3" + cli-table "^0.3.1" + lodash.assign "^4.2.0" + node-emoji "^1.4.1" + +marked@^0.3.6: + version "0.3.7" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.7.tgz#80ef3bbf1bd00d1c9cfebe42ba1b8c85da258d0d" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -797,6 +1069,10 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" +minimist@^1.1.1, minimist@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -863,10 +1139,48 @@ nock@8.0.0: propagate "0.4.0" qs "^6.0.2" +node-dev@3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/node-dev/-/node-dev-3.1.3.tgz#582719223ebdef5d63059e6a7fbcd2399fc0f84d" + dependencies: + dateformat "~1.0.4-1.2.3" + dynamic-dedupe "^0.2.0" + filewatcher "~3.0.0" + minimist "^1.1.3" + node-notifier "^4.0.2" + resolve "^1.0.0" + +node-emoji@^1.4.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" + dependencies: + lodash.toarray "^4.4.0" + +node-notifier@^4.0.2: + version "4.6.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-4.6.1.tgz#056d14244f3dcc1ceadfe68af9cff0c5473a33f3" + dependencies: + cli-usage "^0.1.1" + growly "^1.2.0" + lodash.clonedeep "^3.0.0" + minimist "^1.1.1" + semver "^5.1.0" + shellwords "^0.1.0" + which "^1.0.5" + node-uuid@~1.4.1, node-uuid@~1.4.3: version "1.4.8" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -875,6 +1189,18 @@ oauth-sign@~0.8.0: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-keys@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.2.0.tgz#cddec02998b091be42bf1035ae32e49f1cb6ea67" + dependencies: + foreach "~2.0.1" + indexof "~0.0.1" + is "~0.2.6" + on-finished@2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -910,6 +1236,12 @@ os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + parseurl@~1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -918,14 +1250,36 @@ path-exists@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -980,6 +1334,21 @@ raven@0.11.0: node-uuid "~1.4.1" stack-trace "0.0.7" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + readable-stream@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -991,6 +1360,19 @@ readable-stream@~2.0.5: string_decoder "~0.10.x" util-deprecate "~1.0.1" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +redeyed@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" + dependencies: + esprima "~3.0.0" + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -1033,6 +1415,16 @@ requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" +resolve@^1.0.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +"semver@2 || 3 || 4 || 5", semver@^5.1.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + send@0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" @@ -1082,10 +1474,18 @@ setprototypeof@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" +shellwords@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -1118,6 +1518,20 @@ source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + stack-trace@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.7.tgz#c72e089744fc3659f508cdce3621af5634ec0fff" @@ -1152,6 +1566,18 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + supports-color@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" @@ -1176,6 +1602,10 @@ tough-cookie@~2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.2.2.tgz#c83a1830f4e5ef0b93ef2a3488e724f8de016ac7" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -1226,10 +1656,23 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + vary@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" +which@^1.0.5: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + window-size@^0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" @@ -1259,6 +1702,13 @@ xtend@4.0.1, xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +xtend@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.0.6.tgz#5ea657a6dba447069c2e59c58a1138cb0c5e6cee" + dependencies: + is-object "~0.1.2" + object-keys "~0.2.0" + y18n@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" -- 2.54.0 From 6318db60751c6683a19249a8d97877f8fc31884c Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:14:59 -0800 Subject: [PATCH 19/45] remove babel async to generator transform --- .babelrc | 1 - package.json | 1 - proxy.js | 22 +++++++++++++++++----- yarn.lock | 41 +---------------------------------------- 4 files changed, 18 insertions(+), 47 deletions(-) diff --git a/.babelrc b/.babelrc index ffd53e0..2ce79c5 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,5 @@ { "plugins": [ "transform-es2015-modules-commonjs", - "transform-async-to-generator", ] } diff --git a/package.json b/package.json index e838166..bc7868d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "url": "git://github.com/localtunnel/server.git" }, "dependencies": { - "babel-plugin-transform-async-to-generator": "6.8.0", "babel-plugin-transform-es2015-modules-commonjs": "6.10.3", "babel-polyfill": "6.9.1", "babel-register": "6.9.0", diff --git a/proxy.js b/proxy.js index 75d5e3e..26143a9 100644 --- a/proxy.js +++ b/proxy.js @@ -166,10 +166,7 @@ Proxy.prototype.next_socket = function(handler) { self.debug('processing request'); handler(sock) - .catch((err) => { - log.error(err); - }) - .finally(() => { + .then(() => { if (!sock.destroyed) { self.debug('retuning socket'); self.sockets.push(sock); @@ -181,7 +178,22 @@ Proxy.prototype.next_socket = function(handler) { } self._process_waiting(); - }); + }) + .catch((err) => { + log.error(err); + + if (!sock.destroyed) { + self.debug('retuning socket'); + self.sockets.push(sock); + } + + // no sockets left to process waiting requests + if (self.sockets.length === 0) { + return; + } + + self._process_waiting(); + }) }; Proxy.prototype._done = function() { diff --git a/yarn.lock b/yarn.lock index 6d83084..eea2e61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -100,33 +100,6 @@ babel-generator@^6.26.0: source-map "^0.5.6" trim-right "^1.0.1" -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-remap-async-to-generator@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" @@ -140,18 +113,6 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - -babel-plugin-transform-async-to-generator@6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.8.0.tgz#fbb154f2912e00b9f0972131d7e03ebbe45684c2" - dependencies: - babel-helper-remap-async-to-generator "^6.8.0" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.0.0" - babel-plugin-transform-es2015-modules-commonjs@6.10.3: version "6.10.3" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f" @@ -218,7 +179,7 @@ babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.8.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: -- 2.54.0 From 308c2d4b8585835954879b069f44fe6144a919ee Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:15:28 -0800 Subject: [PATCH 20/45] remove babel polyfill --- package.json | 1 - yarn.lock | 14 +------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/package.json b/package.json index bc7868d..eac3701 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "babel-plugin-transform-es2015-modules-commonjs": "6.10.3", - "babel-polyfill": "6.9.1", "babel-register": "6.9.0", "bluebird": "3.4.1", "book": "1.3.1", diff --git a/yarn.lock b/yarn.lock index eea2e61..f996796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -129,14 +129,6 @@ babel-plugin-transform-strict-mode@^6.8.0: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-polyfill@6.9.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.9.1.tgz#6b6f2f63700c4979bbe2eeb8bdebbac6ddff75bc" - dependencies: - babel-runtime "^6.9.1" - core-js "^2.4.0" - regenerator-runtime "^0.9.5" - babel-register@6.9.0: version "6.9.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.9.0.tgz#dd5f3572ef5bd4082ca05471e942e4a91b162ff0" @@ -162,7 +154,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.0.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0, babel-runtime@^6.9.1: +babel-runtime@^6.0.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1338,10 +1330,6 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" -regenerator-runtime@^0.9.5: - version "0.9.6" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029" - repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" -- 2.54.0 From 54f0eae039310c56880901057619af8d92ca5154 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:17:14 -0800 Subject: [PATCH 21/45] upgrade babel register and babel plugin transform commonjs --- package.json | 4 +-- yarn.lock | 72 ++++++++++------------------------------------------ 2 files changed, 16 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index eac3701..5af88d2 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "url": "git://github.com/localtunnel/server.git" }, "dependencies": { - "babel-plugin-transform-es2015-modules-commonjs": "6.10.3", - "babel-register": "6.9.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-register": "6.26.0", "bluebird": "3.4.1", "book": "1.3.1", "book-git": "0.0.2", diff --git a/yarn.lock b/yarn.lock index f996796..de80e68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,10 +9,6 @@ accepts@~1.3.3: mime-types "~2.1.16" negotiator "0.6.1" -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -63,7 +59,7 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.26.0, babel-core@^6.9.0: +babel-core@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" dependencies: @@ -113,36 +109,23 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-modules-commonjs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz#e4993a455518ca1a3c580bfda35c074e40659c5f" +babel-plugin-transform-es2015-modules-commonjs@6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" dependencies: - babel-plugin-transform-strict-mode "^6.8.0" - babel-runtime "^6.0.0" - babel-template "^6.8.0" - babel-types "^6.8.0" + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" -babel-plugin-transform-strict-mode@^6.8.0: +babel-plugin-transform-strict-mode@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-register@6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.9.0.tgz#dd5f3572ef5bd4082ca05471e942e4a91b162ff0" - dependencies: - babel-core "^6.9.0" - babel-runtime "^6.9.0" - core-js "^2.4.0" - home-or-tmp "^1.0.0" - lodash "^4.2.0" - mkdirp "^0.5.1" - path-exists "^1.0.0" - source-map-support "^0.2.10" - -babel-register@^6.26.0: +babel-register@6.26.0, babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -154,14 +137,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.0.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0: +babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.8.0: +babel-template@^6.24.1, babel-template@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -185,7 +168,7 @@ babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.24.1, babel-types@^6.26.0, babel-types@^6.8.0: +babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -662,13 +645,6 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" -home-or-tmp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-1.0.0.tgz#4b9f1e40800c3e50c6c27f781676afcce71f3985" - dependencies: - os-tmpdir "^1.0.1" - user-home "^1.1.1" - home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -921,7 +897,7 @@ lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" -lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.8.2: +lodash@^4.14.0, lodash@^4.17.4, lodash@^4.8.2: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -1199,10 +1175,6 @@ parseurl@~1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" -path-exists@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" - path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -1445,24 +1417,12 @@ sntp@1.x.x: dependencies: hoek "2.x.x" -source-map-support@^0.2.10: - version "0.2.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" - dependencies: - source-map "0.1.32" - source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: source-map "^0.5.6" -source-map@0.1.32: - version "0.1.32" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" - dependencies: - amdefine ">=0.0.4" - source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -1586,10 +1546,6 @@ unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - utf-8-validate@1.2.x: version "1.2.2" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-1.2.2.tgz#8bb871a4741e085c70487ca7acdbd7d6d36029eb" -- 2.54.0 From 61708ec69878545601e0296ed67251c3a838e874 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:20:36 -0800 Subject: [PATCH 22/45] update book to 1.3.3 --- bookrc.js | 6 +-- package.json | 5 +-- proxy.js | 2 +- server.js | 2 +- yarn.lock | 115 +++++++++------------------------------------------ 5 files changed, 23 insertions(+), 107 deletions(-) diff --git a/bookrc.js b/bookrc.js index f13a7e2..9147619 100644 --- a/bookrc.js +++ b/bookrc.js @@ -1,12 +1,8 @@ /// bookrc logging setup const log = require('book').default(); -//log.use(require('book-git')(__dirname)); -log.use(require('book-raven')(process.env.SENTRY_DSN)); - -process.on('uncaughtException', function(err) { +process.on('uncaughtException', (err) => { log.error(err); - console.error(err); process.exit(1); }); diff --git a/package.json b/package.json index 5af88d2..01ad671 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,7 @@ "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", "babel-register": "6.26.0", "bluebird": "3.4.1", - "book": "1.3.1", - "book-git": "0.0.2", - "book-raven": "1.2.0", - "bookrc": "0.0.1", + "book": "1.3.3", "debug": "2.2.0", "express": "4.14.0", "http-proxy": "1.14.0", diff --git a/proxy.js b/proxy.js index 26143a9..a791a7f 100644 --- a/proxy.js +++ b/proxy.js @@ -1,6 +1,6 @@ import net from 'net'; import EventEmitter from 'events'; -import log from 'bookrc'; +import log from 'book'; import Debug from 'debug'; const debug = Debug('localtunnel:server'); diff --git a/server.js b/server.js index 0de6d50..8e5f6f0 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -import log from 'bookrc'; +import log from 'book'; import express from 'express'; import tldjs from 'tldjs'; import on_finished from 'on-finished'; diff --git a/yarn.lock b/yarn.lock index de80e68..9e8f9d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,10 +37,6 @@ assert-plus@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" - async@^2.0.1: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" @@ -199,26 +195,11 @@ bluebird@3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.1.tgz#b731ddf48e2dd3bedac2e75e1215a11bcb91fa07" -book-git@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/book-git/-/book-git-0.0.2.tgz#25be85525e99f3a768a3492b817713da4b2060dd" - -book-raven@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/book-raven/-/book-raven-1.2.0.tgz#686935d4efa422fe85e9be4bfb970aee7d08ee24" +book@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/book/-/book-1.3.3.tgz#53654254bfe083db102e461544ae9f84be199985" dependencies: - mocha "2.5.3" - nock "8.0.0" - raven "0.11.0" - xtend "4.0.1" - -book@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/book/-/book-1.3.1.tgz#3194fb229bd8748d6bde3c69b0badfed5f58a13d" - -bookrc@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/bookrc/-/bookrc-0.0.1.tgz#bff79d58d57774aed504585ba2dadd51d3ff5892" + error-stack-parser "1.3.6" boom@2.x.x: version "2.10.1" @@ -270,14 +251,6 @@ caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" -"chai@>=1.9.2 <4.0.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" - dependencies: - assertion-error "^1.0.1" - deep-eql "^0.1.3" - type-detect "^1.0.0" - chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -359,10 +332,6 @@ cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" -cookie@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.0.tgz#90eb469ddce905c866de687efc43131d8801f9d0" - cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" @@ -408,7 +377,7 @@ debug@2.2.0, debug@~2.2.0: dependencies: ms "0.7.1" -debug@^2.2.0, debug@^2.6.8: +debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -418,16 +387,6 @@ decamelize@^1.0.0, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deep-eql@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" - dependencies: - type-detect "0.1.1" - -deep-equal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -470,6 +429,12 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +error-stack-parser@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292" + dependencies: + stackframe "^0.3.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -781,7 +746,7 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -897,7 +862,7 @@ lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" -lodash@^4.14.0, lodash@^4.17.4, lodash@^4.8.2: +lodash@^4.14.0, lodash@^4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -918,10 +883,6 @@ lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" - map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -1010,7 +971,7 @@ mkdirp@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" -mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@0.5.1, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -1055,19 +1016,6 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -nock@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/nock/-/nock-8.0.0.tgz#f86d676568c73a3bb2144ebc80791d447bb334d2" - dependencies: - chai ">=1.9.2 <4.0.0" - debug "^2.2.0" - deep-equal "^1.0.0" - json-stringify-safe "^5.0.1" - lodash "^4.8.2" - mkdirp "^0.5.0" - propagate "0.4.0" - qs "^6.0.2" - node-dev@3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/node-dev/-/node-dev-3.1.3.tgz#582719223ebdef5d63059e6a7fbcd2399fc0f84d" @@ -1097,7 +1045,7 @@ node-notifier@^4.0.2: shellwords "^0.1.0" which "^1.0.5" -node-uuid@~1.4.1, node-uuid@~1.4.3: +node-uuid@~1.4.3: version "1.4.8" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" @@ -1223,10 +1171,6 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -propagate@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481" - proxy-addr@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" @@ -1238,10 +1182,6 @@ qs@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" -qs@^6.0.2: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - qs@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" @@ -1250,15 +1190,6 @@ range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raven@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/raven/-/raven-0.11.0.tgz#32981138a93e4c8ad08cfc17e46b85b453dc107b" - dependencies: - cookie "0.1.0" - lsmod "1.0.0" - node-uuid "~1.4.1" - stack-trace "0.0.7" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -1441,9 +1372,9 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" -stack-trace@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.7.tgz#c72e089744fc3659f508cdce3621af5634ec0fff" +stackframe@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" "statuses@>= 1.3.1 < 2": version "1.4.0" @@ -1523,14 +1454,6 @@ tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" -type-detect@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" - -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - type-is@~1.6.13: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -1603,7 +1526,7 @@ ws@0.8.0: bufferutil "1.2.x" utf-8-validate "1.2.x" -xtend@4.0.1, xtend@^4.0.0: +xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" -- 2.54.0 From 908ab316c388d5192c3f8e97e9533021040ce083 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:22:05 -0800 Subject: [PATCH 23/45] update bluebird to 3.5.1 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 01ad671..a59b124 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dependencies": { "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", "babel-register": "6.26.0", - "bluebird": "3.4.1", + "bluebird": "3.5.1", "book": "1.3.3", "debug": "2.2.0", "express": "4.14.0", diff --git a/yarn.lock b/yarn.lock index 9e8f9d6..9b48a07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,9 +191,9 @@ bl@~1.0.0: dependencies: readable-stream "~2.0.5" -bluebird@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.1.tgz#b731ddf48e2dd3bedac2e75e1215a11bcb91fa07" +bluebird@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" book@1.3.3: version "1.3.3" -- 2.54.0 From 3e12b7ef313888eaf44cf3ecdb39d094f8834bfd Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:24:24 -0800 Subject: [PATCH 24/45] upgrade debug to 3.1.0 --- package.json | 2 +- yarn.lock | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a59b124..99905de 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "babel-register": "6.26.0", "bluebird": "3.5.1", "book": "1.3.3", - "debug": "2.2.0", + "debug": "3.1.0", "express": "4.14.0", "http-proxy": "1.14.0", "localenv": "0.2.2", diff --git a/yarn.lock b/yarn.lock index 9b48a07..a71b587 100644 --- a/yarn.lock +++ b/yarn.lock @@ -377,6 +377,12 @@ debug@2.2.0, debug@~2.2.0: dependencies: ms "0.7.1" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" -- 2.54.0 From ab9b47d2b3790a11a7574821bcf33596bb645357 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:27:03 -0800 Subject: [PATCH 25/45] remove http-proxy dependency --- package.json | 1 - server.js | 26 -------------------------- yarn.lock | 15 --------------- 3 files changed, 42 deletions(-) diff --git a/package.json b/package.json index 99905de..5280deb 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "book": "1.3.3", "debug": "3.1.0", "express": "4.14.0", - "http-proxy": "1.14.0", "localenv": "0.2.2", "on-finished": "2.3.0", "optimist": "0.6.1", diff --git a/server.js b/server.js index 8e5f6f0..36104f1 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,6 @@ import express from 'express'; import tldjs from 'tldjs'; import on_finished from 'on-finished'; import Debug from 'debug'; -import http_proxy from 'http-proxy'; import http from 'http'; import Promise from 'bluebird'; @@ -13,21 +12,6 @@ import BindingAgent from './lib/BindingAgent'; const debug = Debug('localtunnel:server'); -const proxy = http_proxy.createProxyServer({ - target: 'http://localtunnel.github.io' -}); - -proxy.on('error', function(err) { - log.error(err); -}); - -proxy.on('proxyReq', function(proxyReq, req, res, options) { - // rewrite the request so it hits the correct url on github - // also make sure host header is what we expect - proxyReq.path = '/www' + proxyReq.path; - proxyReq.setHeader('host', 'localtunnel.github.io'); -}); - const PRODUCTION = process.env.NODE_ENV === 'production'; // id -> client http server @@ -252,16 +236,6 @@ module.exports = function(opt) { res.redirect('https://localtunnel.github.io/www/'); }); - // TODO(roman) remove after deploying redirect above - app.get('/assets/*', function(req, res, next) { - proxy.web(req, res); - }); - - // TODO(roman) remove after deploying redirect above - app.get('/favicon.ico', function(req, res, next) { - proxy.web(req, res); - }); - app.get('/api/status', function(req, res, next) { res.json({ tunnels: stats.tunnels, diff --git a/yarn.lock b/yarn.lock index a71b587..1e7196c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -465,10 +465,6 @@ etag@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - express@4.14.0: version "4.14.0" resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66" @@ -635,13 +631,6 @@ http-errors@~1.5.0, http-errors@~1.5.1: setprototypeof "1.0.2" statuses ">= 1.3.1 < 2" -http-proxy@1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.14.0.tgz#be32ab34dd5229e87840f4c27cb335ee195b2a83" - dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" - http-signature@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" @@ -1269,10 +1258,6 @@ request@2.65.0: tough-cookie "~2.2.0" tunnel-agent "~0.4.1" -requires-port@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - resolve@^1.0.0: version "1.5.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" -- 2.54.0 From 764b58f8832a439fcc6cf3d8e5b74d9a24587cea Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:30:23 -0800 Subject: [PATCH 26/45] fix incorrect require in bin/server --- bin/server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/server b/bin/server index e352884..b489fd9 100755 --- a/bin/server +++ b/bin/server @@ -3,7 +3,7 @@ require('localenv'); require('babel-register'); -const log = require('bookrc'); +const log = require('book'); const debug = require('debug')('localtunnel'); const optimist = require('optimist'); -- 2.54.0 From 4a4b72b492211248632303338e62c187c323c2ae Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:32:01 -0800 Subject: [PATCH 27/45] update travis to test using node 9.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 07c93e1..f94a53e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js sudo: false node_js: - - "6.2" + - "9.2" -- 2.54.0 From 9933611319b1797fbbc615c861c00fc7d04503a4 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 10:37:10 -0800 Subject: [PATCH 28/45] move proxy.js to lib/Proxy.js --- proxy.js => lib/Proxy.js | 0 server.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename proxy.js => lib/Proxy.js (100%) diff --git a/proxy.js b/lib/Proxy.js similarity index 100% rename from proxy.js rename to lib/Proxy.js diff --git a/server.js b/server.js index 36104f1..2f49dbb 100644 --- a/server.js +++ b/server.js @@ -6,7 +6,7 @@ import Debug from 'debug'; import http from 'http'; import Promise from 'bluebird'; -import Proxy from './proxy'; +import Proxy from './lib/Proxy'; import rand_id from './lib/rand_id'; import BindingAgent from './lib/BindingAgent'; -- 2.54.0 From b4a36c78b07fbf6c393476491b8f5778a2455d2d Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 10 Dec 2017 11:34:29 -0800 Subject: [PATCH 29/45] replace express with koa --- package.json | 2 +- server.js | 186 ++++++++++++++++-------------- yarn.lock | 318 ++++++++++++++++++++------------------------------- 3 files changed, 229 insertions(+), 277 deletions(-) diff --git a/package.json b/package.json index 5280deb..3ed63dd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "bluebird": "3.5.1", "book": "1.3.3", "debug": "3.1.0", - "express": "4.14.0", + "koa": "2.4.1", "localenv": "0.2.2", "on-finished": "2.3.0", "optimist": "0.6.1", diff --git a/server.js b/server.js index 2f49dbb..946dc8f 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ import log from 'book'; -import express from 'express'; +import Koa from 'koa'; import tldjs from 'tldjs'; import on_finished from 'on-finished'; import Debug from 'debug'; @@ -24,7 +24,23 @@ const stats = { // handle proxying a request to a client // will wait for a tunnel socket to become available -function maybe_bounce(req, res, sock, head) { +function DoBounce(req, res, sock) { + req.on('error', (err) => { + console.error('request', err); + }); + + if (res) { + res.on('error', (err) => { + console.error('response', err); + }); + } + + if (sock) { + sock.on('error', (err) => { + console.error('response', err); + }); + } + // without a hostname, we won't know who the request is for const hostname = req.headers.host; if (!hostname) { @@ -168,10 +184,9 @@ function maybe_bounce(req, res, sock, head) { } // create a new tunnel with `id` -function new_client(id, opt, cb) { - +// if the id is already used, a random id is assigned +const NewClient = async (id, opt) => { // can't ask for id already is use - // TODO check this new id again if (clients[id]) { id = rand_id(); } @@ -192,17 +207,21 @@ function new_client(id, opt, cb) { delete clients[id]; }); - client.start((err, info) => { - if (err) { - delete clients[id]; - cb(err); - return; - } + return new Promise((resolve, reject) => { + // each local client has a tcp server to link with the remove localtunnel client + // this starts the server and waits until it is listening + client.start((err, info) => { + if (err) { + // clear the reserved client id + delete clients[id]; + reject(err); + return; + } - ++stats.tunnels; - - info.id = id; - cb(err, info); + ++stats.tunnels; + info.id = id; + resolve(info); + }); }); } @@ -211,98 +230,95 @@ module.exports = function(opt) { const schema = opt.secure ? 'https' : 'http'; - const app = express(); + const app = new Koa(); - app.get('/', function(req, res, next) { - if (req.query['new'] === undefined) { - return next(); + // api status endpoint + app.use(async (ctx, next) => { + const path = ctx.request.path; + if (path !== '/api/status') { + await next(); + return; } - const req_id = rand_id(); - debug('making new client with id %s', req_id); - new_client(req_id, opt, function(err, info) { - if (err) { - res.statusCode = 500; - return res.end(err.message); - } - - const url = schema + '://' + req_id + '.' + req.headers.host; - info.url = url; - res.json(info); - }); - }); - - app.get('/', function(req, res, next) { - res.redirect('https://localtunnel.github.io/www/'); - }); - - app.get('/api/status', function(req, res, next) { - res.json({ + ctx.body = { tunnels: stats.tunnels, mem: process.memoryUsage(), - }); + }; }); - app.get('/:req_id', function(req, res, next) { - const req_id = req.params.req_id; + // root endpoint + app.use(async (ctx, next) => { + const path = ctx.request.path; + + // skip anything not on the root path + if (path !== '/') { + await next(); + return; + } + + const isNewClientRequest = ctx.query['new'] !== undefined; + if (isNewClientRequest) { + const req_id = rand_id(); + debug('making new client with id %s', req_id); + const info = await NewClient(req_id, opt); + + const url = schema + '://' + info.id + '.' + ctx.request.host; + info.url = url; + ctx.body = info; + return; + } + + // no new client request, send to landing page + ctx.redirect('https://localtunnel.github.io/www/'); + }); + + // anything after the / path is a request for a specific client name + // This is a backwards compat feature + app.use(async (ctx, next) => { + const parts = ctx.request.path.split('/'); + + // any request with several layers of paths is not allowed + // rejects /foo/bar + // allow /foo + if (parts.length !== 2) { + await next(); + return; + } + + const req_id = parts[1]; // limit requested hostnames to 63 characters if (! /^[a-z0-9]{4,63}$/.test(req_id)) { - const err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); - err.statusCode = 403; - return next(err); + const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'; + ctx.status = 403; + ctx.body = { + message: msg, + }; + return; } debug('making new client with id %s', req_id); - new_client(req_id, opt, function(err, info) { - if (err) { - return next(err); - } + const info = await NewClient(req_id, opt); - const url = schema + '://' + info.id + '.' + req.headers.host; - info.url = url; - res.json(info); - }); - - }); - - app.use(function(err, req, res, next) { - const status = err.statusCode || err.status || 500; - res.status(status).json({ - message: err.message - }); + const url = schema + '://' + info.id + '.' + ctx.request.host; + info.url = url; + ctx.body = info; + return; }); const server = http.createServer(); - server.on('request', function(req, res) { - - req.on('error', (err) => { - console.error('request', err); - }); - - res.on('error', (err) => { - console.error('response', err); - }); - - debug('request %s', req.url); - if (maybe_bounce(req, res, null, null)) { + const appCallback = app.callback(); + server.on('request', (req, res) => { + if (DoBounce(req, res, null)) { return; - }; + } - app(req, res); + appCallback(req, res); }); - server.on('upgrade', function(req, socket, head) { - req.on('error', (err) => { - console.error('ws req', err); - }); - - socket.on('error', (err) => { - console.error('ws socket', err); - }); - - if (maybe_bounce(req, null, socket, head)) { + server.on('upgrade', (req, socket, head) => { + if (DoBounce(req, null, socket)) { return; }; diff --git a/yarn.lock b/yarn.lock index 1e7196c..cb78bca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -accepts@~1.3.3: +accepts@^1.2.2: version "1.3.4" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" dependencies: @@ -21,14 +21,14 @@ ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" +any-promise@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - asn1@0.1.11: version "0.1.11" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" @@ -282,6 +282,10 @@ cliui@^3.0.3: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -316,11 +320,11 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -content-disposition@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" +content-disposition@~0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" -content-type@~1.0.2: +content-type@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -328,13 +332,12 @@ convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +cookies@~0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b" + dependencies: + depd "~1.1.1" + keygrip "~1.0.2" core-js@^2.4.0, core-js@^2.5.0: version "2.5.2" @@ -371,18 +374,18 @@ debounce@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" -debug@2.2.0, debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - -debug@3.1.0: +debug@*, debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -393,15 +396,23 @@ decamelize@^1.0.0, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" -depd@~1.1.0: +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@1.1.1, depd@^1.1.0, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" -destroy@~1.0.4: +destroy@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -425,23 +436,23 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" - error-ex@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" dependencies: is-arrayish "^0.2.1" +error-inject@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" + error-stack-parser@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292" dependencies: stackframe "^0.3.1" -escape-html@~1.0.3: +escape-html@~1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -461,41 +472,6 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" - -express@4.14.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66" - dependencies: - accepts "~1.3.3" - array-flatten "1.1.1" - content-disposition "0.5.1" - content-type "~1.0.2" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "~2.2.0" - depd "~1.1.0" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.7.0" - finalhandler "0.5.0" - fresh "0.3.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.1" - path-to-regexp "0.1.7" - proxy-addr "~1.1.2" - qs "6.2.0" - range-parser "~1.2.0" - send "0.14.1" - serve-static "~1.11.1" - type-is "~1.6.13" - utils-merge "1.0.0" - vary "~1.1.0" - extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -506,16 +482,6 @@ filewatcher@~3.0.0: dependencies: debounce "^1.0.0" -finalhandler@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" - dependencies: - debug "~2.2.0" - escape-html "~1.0.3" - on-finished "~2.3.0" - statuses "~1.3.0" - unpipe "~1.0.0" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -539,13 +505,9 @@ form-data@~1.0.0-rc3: combined-stream "^1.0.5" mime-types "^2.1.11" -forwarded@~0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - -fresh@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" +fresh@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" generate-function@^2.0.0: version "2.0.0" @@ -623,12 +585,20 @@ hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" -http-errors@~1.5.0, http-errors@~1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" +http-assert@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.3.0.tgz#a31a5cf88c873ecbb5796907d4d6f132e8c01e4a" dependencies: + deep-equal "~1.0.1" + http-errors "~1.6.1" + +http-errors@^1.2.8, http-errors@~1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" inherits "2.0.3" - setprototypeof "1.0.2" + setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" http-signature@~0.11.0: @@ -663,10 +633,6 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ipaddr.js@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -689,6 +655,10 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" +is-generator-function@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.6.tgz#9e71653cd15fff341c79c4151460a131d31e9fc4" + is-my-json-valid@^2.12.4: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" @@ -753,6 +723,60 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +keygrip@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91" + +koa-compose@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7" + dependencies: + any-promise "^1.1.0" + +koa-compose@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.0.0.tgz#2800a513d9c361ef0d63852b038e4f6f2d5a773c" + +koa-convert@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0" + dependencies: + co "^4.6.0" + koa-compose "^3.0.0" + +koa-is-json@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" + +koa@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.4.1.tgz#d449cfb970a7e9da571f699eda40bb9e32eb1484" + dependencies: + accepts "^1.2.2" + content-disposition "~0.5.0" + content-type "^1.0.0" + cookies "~0.7.0" + debug "*" + delegates "^1.0.0" + depd "^1.1.0" + destroy "^1.0.3" + error-inject "~1.0.0" + escape-html "~1.0.1" + fresh "^0.5.2" + http-assert "^1.1.0" + http-errors "^1.2.8" + is-generator-function "^1.0.3" + koa-compose "^4.0.0" + koa-convert "^1.2.0" + koa-is-json "^1.0.0" + mime-types "^2.0.7" + on-finished "^2.1.0" + only "0.0.2" + parseurl "^1.3.0" + statuses "^1.2.0" + type-is "^1.5.5" + vary "^1.0.0" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -915,28 +939,16 @@ meow@^3.3.0: redent "^1.0.0" trim-newlines "^1.0.0" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: +mime-types@^2.0.7, mime-types@^2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" -mime@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" - minimatch@0.3: version "0.3.0" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" @@ -991,10 +1003,6 @@ ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1073,12 +1081,16 @@ object-keys@~0.2.0: indexof "~0.0.1" is "~0.2.6" -on-finished@2.3.0, on-finished@~2.3.0: +on-finished@2.3.0, on-finished@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" dependencies: ee-first "1.1.1" +only@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" + openurl@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.0.tgz#e2f2189d999c04823201f083f0f1a7cd8903187a" @@ -1114,7 +1126,7 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parseurl@~1.3.1: +parseurl@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -1132,10 +1144,6 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -1166,25 +1174,10 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -proxy-addr@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" - dependencies: - forwarded "~0.1.0" - ipaddr.js "1.4.0" - -qs@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" - qs@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" -range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -1268,54 +1261,9 @@ resolve@^1.0.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -send@0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" - dependencies: - debug "~2.2.0" - depd "~1.1.0" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.7.0" - fresh "0.3.0" - http-errors "~1.5.0" - mime "1.3.4" - ms "0.7.1" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.0" - -send@0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" - dependencies: - debug "~2.2.0" - depd "~1.1.0" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.7.0" - fresh "0.3.0" - http-errors "~1.5.1" - mime "1.3.4" - ms "0.7.2" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.1" - -serve-static@~1.11.1: - version "1.11.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" - dependencies: - encodeurl "~1.0.1" - escape-html "~1.0.3" - parseurl "~1.3.1" - send "0.14.2" - -setprototypeof@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" shellwords@^0.1.0: version "0.1.1" @@ -1367,14 +1315,10 @@ stackframe@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" -"statuses@>= 1.3.1 < 2": +"statuses@>= 1.3.1 < 2", statuses@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" -statuses@~1.3.0, statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -1445,7 +1389,7 @@ tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" -type-is@~1.6.13: +type-is@^1.5.5: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" dependencies: @@ -1456,10 +1400,6 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - utf-8-validate@1.2.x: version "1.2.2" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-1.2.2.tgz#8bb871a4741e085c70487ca7acdbd7d6d36029eb" @@ -1471,10 +1411,6 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -utils-merge@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" - validate-npm-package-license@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" @@ -1482,7 +1418,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -vary@~1.1.0: +vary@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" -- 2.54.0 From 5fb9dbaaa128ff7989990bec9c7931c6d4fbcd68 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Mon, 11 Dec 2017 21:16:55 -0800 Subject: [PATCH 30/45] extract ClientManager from server.js Make client manager more robust when piping connections. --- bin/server | 9 ++ bookrc.js | 10 -- lib/ClientManager.js | 204 +++++++++++++++++++++++++++++++++ lib/Proxy.js | 60 +++++----- package.json | 1 + server.js | 262 +++++++------------------------------------ test/basic.js | 10 +- yarn.lock | 23 ++++ 8 files changed, 313 insertions(+), 266 deletions(-) delete mode 100644 bookrc.js create mode 100644 lib/ClientManager.js diff --git a/bin/server b/bin/server index b489fd9..34c7b0f 100755 --- a/bin/server +++ b/bin/server @@ -45,5 +45,14 @@ process.on('SIGTERM', () => { process.exit(); }); +process.on('uncaughtException', (err) => { + log.error(err); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + log.error(reason); +}); + // vim: ft=javascript diff --git a/bookrc.js b/bookrc.js deleted file mode 100644 index 9147619..0000000 --- a/bookrc.js +++ /dev/null @@ -1,10 +0,0 @@ -/// bookrc logging setup -const log = require('book').default(); - -process.on('uncaughtException', (err) => { - log.error(err); - process.exit(1); -}); - -module.exports = log; - diff --git a/lib/ClientManager.js b/lib/ClientManager.js new file mode 100644 index 0000000..0aef2f3 --- /dev/null +++ b/lib/ClientManager.js @@ -0,0 +1,204 @@ +import Proxy from './Proxy'; + +// maybe remove? +import on_finished from 'on-finished'; +import http from 'http'; +import pump from 'pump'; + +import rand_id from './rand_id'; +import BindingAgent from './BindingAgent'; + +const NoOp = () => {}; + +// Manage sets of clients +// +// A client is a "user session" established to service a remote localtunnel client +class ClientManager { + constructor(opt) { + this.opt = opt; + + this.reqId = 0; + + // id -> client instance + this.clients = Object.create(null); + + // statistics + this.stats = { + tunnels: 0 + }; + } + + stats() { + return this.stats; + } + + // create a new tunnel with `id` + // if the id is already used, a random id is assigned + async newClient (id) { + const clients = this.clients; + const stats = this.stats; + + // can't ask for id already is use + if (clients[id]) { + id = rand_id(); + } + + const popt = { + id: id, + max_tcp_sockets: this.opt.max_tcp_sockets + }; + + const client = Proxy(popt); + + // add to clients map immediately + // avoiding races with other clients requesting same id + clients[id] = client; + + client.on('end', () => { + --stats.tunnels; + delete clients[id]; + }); + + return new Promise((resolve, reject) => { + // each local client has a tcp server to link with the remove localtunnel client + // this starts the server and waits until it is listening + client.start((err, info) => { + if (err) { + // clear the reserved client id + delete clients[id]; + reject(err); + return; + } + + ++stats.tunnels; + info.id = id; + resolve(info); + }); + }); + } + + hasClient(id) { + return this.clients[id]; + } + + // handle http request + handleRequest(clientId, req, res) { + const client = this.clients[clientId]; + if (!client) { + return; + } + + const reqId = this.reqId; + this.reqId = this.reqId + 1; + + let endRes = () => { + endRes = NoOp; + res.end(); + }; + + on_finished(res, () => { + endRes = NoOp; + }); + + client.nextSocket((clientSocket) => { + // response ended before we even got a socket to respond on + if (endRes === NoOp) { + return; + } + + // happens when client upstream is disconnected (or disconnects) + // and the proxy iterates the waiting list and clears the callbacks + // we gracefully inform the user and kill their conn + // without this, the browser will leave some connections open + // and try to use them again for new requests + // TODO(roman) we could instead have a timeout above + // if no socket becomes available within some time, + // we just tell the user no resource available to service request + if (!clientSocket) { + endRes(); + return; + } + + const agent = new BindingAgent({ + socket: clientSocket, + }); + + const opt = { + path: req.url, + agent: agent, + method: req.method, + headers: req.headers + }; + + return new Promise((resolve) => { + // what if error making this request? + const clientReq = http.request(opt, (clientRes) => { + // write response code and headers + res.writeHead(clientRes.statusCode, clientRes.headers); + + // when this pump is done, we end our response + pump(clientRes, res, (err) => { + endRes(); + resolve(); + }); + }); + + // we don't care about when this ends, only if there is error + pump(req, clientReq, (err) => { + if (err) { + endRes(); + resolve(); + } + }); + }); + }); + } + + // handle http upgrade + handleUpgrade(clientId, req, sock) { + const client = this.clients[clientId]; + if (!client) { + return; + } + + client.nextSocket(async (clientSocket) => { + if (!sock.readable || !sock.writable) { + sock.end(); + return; + } + + // happens when client upstream is disconnected (or disconnects) + // and the proxy iterates the waiting list and clears the callbacks + // we gracefully inform the user and kill their conn + // without this, the browser will leave some connections open + // and try to use them again for new requests + // TODO(roman) we could instead have a timeout above + // if no socket becomes available within some time, + // we just tell the user no resource available to service request + if (!clientSocket) { + sock.end(); + return; + } + + // websocket requests are special in that we simply re-create the header info + // then directly pipe the socket data + // avoids having to rebuild the request and handle upgrades via the http client + const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`]; + for (let i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { + arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}`); + } + + arr.push(''); + arr.push(''); + + clientSocket.pipe(sock).pipe(clientSocket); + clientSocket.write(arr.join('\r\n')); + + await new Promise((resolve) => { + sock.once('end', resolve); + }); + }); + } +} + +export default ClientManager; diff --git a/lib/Proxy.js b/lib/Proxy.js index a791a7f..ccd0ebf 100644 --- a/lib/Proxy.js +++ b/lib/Proxy.js @@ -16,6 +16,8 @@ const Proxy = function(opt) { self.waiting = []; self.id = opt.id; + self.activeSockets = 0; + // default max is 10 self.max_tcp_sockets = opt.max_tcp_sockets || 10; @@ -72,6 +74,8 @@ Proxy.prototype._maybe_destroy = function() { const self = this; clearTimeout(self.conn_timeout); + + // After last socket is gone, we give opportunity to connect again quickly self.conn_timeout = setTimeout(function() { // sometimes the server is already closed but the event has not fired? try { @@ -81,7 +85,7 @@ Proxy.prototype._maybe_destroy = function() { catch (err) { self._cleanup(); } - }, 5000); + }, 1000); } // new socket connection from client for tunneling requests to client @@ -89,16 +93,19 @@ Proxy.prototype._handle_socket = function(socket) { const self = this; // no more socket connections allowed - if (self.sockets.length >= self.max_tcp_sockets) { + if (self.activeSockets >= self.max_tcp_sockets) { return socket.end(); } + self.activeSockets = self.activeSockets + 1; + self.debug('new connection from: %s:%s', socket.address().address, socket.address().port); // a single connection is enough to keep client id slot open clearTimeout(self.conn_timeout); socket.once('close', function(had_error) { + self.activeSockets = self.activeSockets - 1; self.debug('closed socket (error: %s)', had_error); // what if socket was servicing a request at this time? @@ -133,10 +140,10 @@ Proxy.prototype._handle_socket = function(socket) { Proxy.prototype._process_waiting = function() { const self = this; - const wait_cb = self.waiting.shift(); - if (wait_cb) { + const fn = self.waiting.shift(); + if (fn) { self.debug('handling queued request'); - self.next_socket(wait_cb); + self.nextSocket(fn); } }; @@ -152,48 +159,31 @@ Proxy.prototype._cleanup = function() { self.emit('end'); }; -Proxy.prototype.next_socket = function(handler) { +Proxy.prototype.nextSocket = async function(fn) { const self = this; // socket is a tcp connection back to the user hosting the site const sock = self.sockets.shift(); - if (!sock) { - self.debug('no more client, queue callback'); - self.waiting.push(handler); + self.debug('no more clients, queue callback'); + self.waiting.push(fn); return; } self.debug('processing request'); - handler(sock) - .then(() => { - if (!sock.destroyed) { - self.debug('retuning socket'); - self.sockets.push(sock); - } + await fn(sock); - // no sockets left to process waiting requests - if (self.sockets.length === 0) { - return; - } + if (!sock.destroyed) { + self.debug('retuning socket'); + self.sockets.push(sock); + } - self._process_waiting(); - }) - .catch((err) => { - log.error(err); + // no sockets left to process waiting requests + if (self.sockets.length === 0) { + return; + } - if (!sock.destroyed) { - self.debug('retuning socket'); - self.sockets.push(sock); - } - - // no sockets left to process waiting requests - if (self.sockets.length === 0) { - return; - } - - self._process_waiting(); - }) + self._process_waiting(); }; Proxy.prototype._done = function() { diff --git a/package.json b/package.json index 3ed63dd..0ba80a0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "localenv": "0.2.2", "on-finished": "2.3.0", "optimist": "0.6.1", + "pump": "2.0.0", "tldjs": "1.6.2" }, "devDependencies": { diff --git a/server.js b/server.js index 946dc8f..b67d708 100644 --- a/server.js +++ b/server.js @@ -1,233 +1,24 @@ import log from 'book'; import Koa from 'koa'; import tldjs from 'tldjs'; -import on_finished from 'on-finished'; import Debug from 'debug'; import http from 'http'; import Promise from 'bluebird'; -import Proxy from './lib/Proxy'; +import ClientManager from './lib/ClientManager'; import rand_id from './lib/rand_id'; -import BindingAgent from './lib/BindingAgent'; const debug = Debug('localtunnel:server'); -const PRODUCTION = process.env.NODE_ENV === 'production'; - -// id -> client http server -const clients = Object.create(null); - -// proxy statistics -const stats = { - tunnels: 0 -}; - -// handle proxying a request to a client -// will wait for a tunnel socket to become available -function DoBounce(req, res, sock) { - req.on('error', (err) => { - console.error('request', err); - }); - - if (res) { - res.on('error', (err) => { - console.error('response', err); - }); - } - - if (sock) { - sock.on('error', (err) => { - console.error('response', err); - }); - } - - // without a hostname, we won't know who the request is for - const hostname = req.headers.host; - if (!hostname) { - return false; - } - - const subdomain = tldjs.getSubdomain(hostname); - if (!subdomain) { - return false; - } - - const client = clients[subdomain]; - - // no such subdomain - // we use 502 error to the client to signify we can't service the request - if (!client) { - if (res) { - res.statusCode = 502; - res.end(`no active client for '${subdomain}'`); - req.connection.destroy(); - } - else if (sock) { - sock.destroy(); - } - - return true; - } - - let finished = false; - if (sock) { - sock.once('end', function() { - finished = true; - }); - } - else if (res) { - // flag if we already finished before we get a socket - // we can't respond to these requests - on_finished(res, function(err) { - finished = true; - req.connection.destroy(); - }); - } - // not something we are expecting, need a sock or a res - else { - req.connection.destroy(); - return true; - } - - // TODO add a timeout, if we run out of sockets, then just 502 - - // get client port - client.next_socket(async (socket) => { - // the request already finished or client disconnected - if (finished) { - return; - } - - // happens when client upstream is disconnected (or disconnects) - // and the proxy iterates the waiting list and clears the callbacks - // we gracefully inform the user and kill their conn - // without this, the browser will leave some connections open - // and try to use them again for new requests - // we cannot have this as we need bouncy to assign the requests again - // TODO(roman) we could instead have a timeout above - // if no socket becomes available within some time, - // we just tell the user no resource available to service request - else if (!socket) { - if (res) { - res.statusCode = 504; - res.end(); - } - - if (sock) { - sock.destroy(); - } - - req.connection.destroy(); - return; - } - - // websocket requests are special in that we simply re-create the header info - // and directly pipe the socket data - // avoids having to rebuild the request and handle upgrades via the http client - if (res === null) { - const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`]; - for (let i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { - arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}`); - } - - arr.push(''); - arr.push(''); - - socket.pipe(sock).pipe(socket); - socket.write(arr.join('\r\n')); - - await new Promise((resolve) => { - socket.once('end', resolve); - }); - - return; - } - - // regular http request - - const agent = new BindingAgent({ - socket: socket - }); - - const opt = { - path: req.url, - agent: agent, - method: req.method, - headers: req.headers - }; - - await new Promise((resolve) => { - // what if error making this request? - const client_req = http.request(opt, function(client_res) { - // write response code and headers - res.writeHead(client_res.statusCode, client_res.headers); - - client_res.pipe(res); - on_finished(client_res, function(err) { - resolve(); - }); - }); - - // happens if the other end dies while we are making the request - // so we just end the req and move on - // we can't really do more with the response here because headers - // may already be sent - client_req.on('error', (err) => { - req.connection.destroy(); - }); - - req.pipe(client_req); - }); - }); - - return true; -} - -// create a new tunnel with `id` -// if the id is already used, a random id is assigned -const NewClient = async (id, opt) => { - // can't ask for id already is use - if (clients[id]) { - id = rand_id(); - } - - const popt = { - id: id, - max_tcp_sockets: opt.max_tcp_sockets - }; - - const client = Proxy(popt); - - // add to clients map immediately - // avoiding races with other clients requesting same id - clients[id] = client; - - client.on('end', function() { - --stats.tunnels; - delete clients[id]; - }); - - return new Promise((resolve, reject) => { - // each local client has a tcp server to link with the remove localtunnel client - // this starts the server and waits until it is listening - client.start((err, info) => { - if (err) { - // clear the reserved client id - delete clients[id]; - reject(err); - return; - } - - ++stats.tunnels; - info.id = id; - resolve(info); - }); - }); +function GetClientIdFromHostname(hostname) { + return tldjs.getSubdomain(hostname); } module.exports = function(opt) { opt = opt || {}; + const manager = new ClientManager(opt); + const schema = opt.secure ? 'https' : 'http'; const app = new Koa(); @@ -240,6 +31,8 @@ module.exports = function(opt) { return; } + const stats = manager.stats(); + ctx.body = { tunnels: stats.tunnels, mem: process.memoryUsage(), @@ -260,7 +53,7 @@ module.exports = function(opt) { if (isNewClientRequest) { const req_id = rand_id(); debug('making new client with id %s', req_id); - const info = await NewClient(req_id, opt); + const info = await manager.newClient(req_id); const url = schema + '://' + info.id + '.' + ctx.request.host; info.url = url; @@ -298,7 +91,7 @@ module.exports = function(opt) { } debug('making new client with id %s', req_id); - const info = await NewClient(req_id, opt); + const info = await manager.newClient(req_id); const url = schema + '://' + info.id + '.' + ctx.request.host; info.url = url; @@ -310,17 +103,46 @@ module.exports = function(opt) { const appCallback = app.callback(); server.on('request', (req, res) => { - if (DoBounce(req, res, null)) { + // without a hostname, we won't know who the request is for + const hostname = req.headers.host; + if (!hostname) { + res.statusCode = 400; + res.end('Host header is required'); return; } - appCallback(req, res); + const clientId = GetClientIdFromHostname(hostname); + if (!clientId) { + appCallback(req, res); + return; + } + + if (manager.hasClient(clientId)) { + manager.handleRequest(clientId, req, res); + return; + } + + res.statusCode = 404; + res.end('404'); }); server.on('upgrade', (req, socket, head) => { - if (DoBounce(req, null, socket)) { + const hostname = req.headers.host; + if (!hostname) { + sock.destroy(); return; - }; + } + + const clientId = GetClientIdFromHostname(hostname); + if (!clientId) { + sock.destroy(); + return; + } + + if (manager.hasClient(clientId)) { + manager.handleUpgrade(clientId, req, socket); + return; + } socket.destroy(); }); diff --git a/test/basic.js b/test/basic.js index ed66574..f1c829d 100644 --- a/test/basic.js +++ b/test/basic.js @@ -5,9 +5,17 @@ var localtunnel = require('localtunnel'); var localtunnel_server = require('../server')(); +process.on('uncaughtException', (err) => { + console.error(err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error(reason); +}); + suite('basic'); -var lt_server_port +var lt_server_port; before('set up localtunnel server', function(done) { var server = localtunnel_server.listen(function() { diff --git a/yarn.lock b/yarn.lock index cb78bca..265f847 100644 --- a/yarn.lock +++ b/yarn.lock @@ -436,6 +436,12 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +end-of-stream@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" + dependencies: + once "^1.4.0" + error-ex@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" @@ -1087,6 +1093,12 @@ on-finished@2.3.0, on-finished@^2.1.0: dependencies: ee-first "1.1.1" +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + only@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" @@ -1174,6 +1186,13 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +pump@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.0.tgz#7946da1c8d622b098e2ceb2d3476582470829c9d" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + qs@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" @@ -1443,6 +1462,10 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + ws@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/ws/-/ws-0.8.0.tgz#ac60ebad312121d01e16cc3383d7ec67ad0f0f1f" -- 2.54.0 From 3d6798a2bf9611fa171104bd28dc0981f6e817c5 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Mon, 11 Dec 2017 21:29:11 -0800 Subject: [PATCH 31/45] fix api status endpoint --- lib/ClientManager.js | 4 ---- server.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/ClientManager.js b/lib/ClientManager.js index 0aef2f3..2d798a1 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -28,10 +28,6 @@ class ClientManager { }; } - stats() { - return this.stats; - } - // create a new tunnel with `id` // if the id is already used, a random id is assigned async newClient (id) { diff --git a/server.js b/server.js index b67d708..5dd08b0 100644 --- a/server.js +++ b/server.js @@ -31,7 +31,7 @@ module.exports = function(opt) { return; } - const stats = manager.stats(); + const stats = manager.stats; ctx.body = { tunnels: stats.tunnels, -- 2.54.0 From 2a9bc271179c7269f01fb5e133bf4e4b0e2ce8a9 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Mon, 11 Dec 2017 21:35:40 -0800 Subject: [PATCH 32/45] do not exit on uncaught exception --- bin/server | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/server b/bin/server index 34c7b0f..16a943d 100755 --- a/bin/server +++ b/bin/server @@ -47,7 +47,6 @@ process.on('SIGTERM', () => { process.on('uncaughtException', (err) => { log.error(err); - process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { -- 2.54.0 From d7852a3dd1b7987d2c805f87cb38d0078352073e Mon Sep 17 00:00:00 2001 From: Alex Huang Date: Mon, 2 Apr 2018 10:37:45 +1000 Subject: [PATCH 33/45] Add option to set server address (#80) Since this server may run behind a reverse proxy like Nginx, it should be able to bind to localhost only rather than listening to the whole network. --- bin/server | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/server b/bin/server index 16a943d..ecf1d4f 100755 --- a/bin/server +++ b/bin/server @@ -17,6 +17,10 @@ const argv = optimist default: '80', describe: 'listen on this port for outside requests' }) + .options('address', { + default: '0.0.0.0', + describe: 'IP address to bind to' + }) .options('max-sockets', { default: 10, describe: 'maximum number of tcp sockets each client is allowed to establish at one time (the tunnels)' @@ -33,7 +37,7 @@ const server = require('../server')({ secure: argv.secure }); -server.listen(argv.port, () => { +server.listen(argv.port, argv.address, () => { debug('server listening on port: %d', server.address().port); }); -- 2.54.0 From b1c296a409afe5040149beac39dd2583d91afae1 Mon Sep 17 00:00:00 2001 From: Ajay Gandhi Date: Sun, 1 Apr 2018 18:00:21 -0700 Subject: [PATCH 34/45] Generate human readable IDs for client URLs (#77) Based on @FranzSkuffka's suggested enhancement in PR 207 on localtunnel/localtunnel (https://github.com/localtunnel/localtunnel/pull/207), this PR uses the "human-readable-ids" package to generate IDs of the form "adjective-noun-number". These IDs are easier to remember and arguably more useful in testing since they can be retyped without copying and pasting. Some sample IDs generated by this package are: * heavy-puma-90 * grump-walrus-64 * purple-starfish-58 * nasty-seahorse-9 --- README.md | 2 +- lib/ClientManager.js | 4 ++-- lib/rand_id.js | 13 ------------- package.json | 1 + server.js | 4 ++-- yarn.lock | 10 ++++++++++ 6 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 lib/rand_id.js diff --git a/README.md b/README.md index c557c94..8918376 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ You can now use your domain with the `--host` flag for the `lt` client. lt --host http://sub.example.tld:1234 --port 9000 ``` -You will be assigned a URL similar to `qdci.sub.example.com:1234`. +You will be assigned a URL similar to `heavy-puma-9.sub.example.com:1234`. If your server is acting as a reverse proxy (i.e. nginx) and is able to listen on port 80, then you do not need the `:1234` part of the hostname for the `lt` client. diff --git a/lib/ClientManager.js b/lib/ClientManager.js index 2d798a1..a4199f9 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -4,8 +4,8 @@ import Proxy from './Proxy'; import on_finished from 'on-finished'; import http from 'http'; import pump from 'pump'; +import { hri } from "human-readable-ids"; -import rand_id from './rand_id'; import BindingAgent from './BindingAgent'; const NoOp = () => {}; @@ -36,7 +36,7 @@ class ClientManager { // can't ask for id already is use if (clients[id]) { - id = rand_id(); + id = hri.random(); } const popt = { diff --git a/lib/rand_id.js b/lib/rand_id.js deleted file mode 100644 index f76f6d8..0000000 --- a/lib/rand_id.js +++ /dev/null @@ -1,13 +0,0 @@ -// all url safe -// can't use uppercase because hostnames are lowercased -const chars = 'abcdefghijklmnopqrstuvwxyz'; - -export default function rand_id() { - let randomstring = ''; - for (var i=0; i<10; ++i) { - const rnum = Math.floor(Math.random() * chars.length); - randomstring += chars[rnum]; - } - - return randomstring; -} diff --git a/package.json b/package.json index 0ba80a0..0908e04 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "bluebird": "3.5.1", "book": "1.3.3", "debug": "3.1.0", + "human-readable-ids": "1.0.3", "koa": "2.4.1", "localenv": "0.2.2", "on-finished": "2.3.0", diff --git a/server.js b/server.js index 5dd08b0..7ba2106 100644 --- a/server.js +++ b/server.js @@ -4,9 +4,9 @@ import tldjs from 'tldjs'; import Debug from 'debug'; import http from 'http'; import Promise from 'bluebird'; +import { hri } from 'human-readable-ids'; import ClientManager from './lib/ClientManager'; -import rand_id from './lib/rand_id'; const debug = Debug('localtunnel:server'); @@ -51,7 +51,7 @@ module.exports = function(opt) { const isNewClientRequest = ctx.query['new'] !== undefined; if (isNewClientRequest) { - const req_id = rand_id(); + const req_id = hri.random(); debug('making new client with id %s', req_id); const info = await manager.newClient(req_id); diff --git a/yarn.lock b/yarn.lock index 265f847..156462e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -615,6 +615,12 @@ http-signature@~0.11.0: assert-plus "^0.1.5" ctype "0.5.3" +human-readable-ids@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/human-readable-ids/-/human-readable-ids-1.0.3.tgz#c8c6c6e95085ccb668087b7dd767834e26ca26d4" + dependencies: + knuth-shuffle "^1.0.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -733,6 +739,10 @@ keygrip@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91" +knuth-shuffle@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/knuth-shuffle/-/knuth-shuffle-1.0.8.tgz#929a467b0efd8d297bdcf318ca988a9f1037f80d" + koa-compose@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7" -- 2.54.0 From 37df802cc06e9cde69c59085419a6f46db817da5 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 1 Apr 2018 18:04:48 -0700 Subject: [PATCH 35/45] Allow dash characters in subdomain. Thank you @zackslash via https://github.com/localtunnel/server/pull/47 Rebased to latest master. --- server.js | 2 +- test/basic.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 7ba2106..495b5a9 100644 --- a/server.js +++ b/server.js @@ -81,7 +81,7 @@ module.exports = function(opt) { const req_id = parts[1]; // limit requested hostnames to 63 characters - if (! /^[a-z0-9]{4,63}$/.test(req_id)) { + if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(req_id)) { const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'; ctx.status = 403; ctx.body = { diff --git a/test/basic.js b/test/basic.js index f1c829d..786e931 100644 --- a/test/basic.js +++ b/test/basic.js @@ -118,6 +118,21 @@ test('request specific domain', function(done) { }); }); +test('request domain with dash', function(done) { + var opt = { + host: 'http://localhost:' + lt_server_port, + subdomain: 'abcd-1234' + }; + + localtunnel(test._fake_port, opt, function(err, tunnel) { + assert.ifError(err); + var url = tunnel.url; + assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); + test._fake_url = url; + done(err); + }); +}); + test('request domain that is too long', function(done) { var opt = { host: 'http://localhost:' + lt_server_port, -- 2.54.0 From 6b07a86ec14e3461027cffb980fa94dbb506e5cd Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 1 Apr 2018 18:24:49 -0700 Subject: [PATCH 36/45] switch babel-register to esm --- bin/server | 17 +-- package.json | 4 +- server.js | 3 +- test/basic.js | 12 ++- test/mocha.opts | 2 +- test/queue.js | 12 ++- test/simple.js | 12 ++- test/websocket.js | 16 +-- yarn.lock | 264 ++-------------------------------------------- 9 files changed, 49 insertions(+), 293 deletions(-) diff --git a/bin/server b/bin/server index ecf1d4f..d5e3fa3 100755 --- a/bin/server +++ b/bin/server @@ -1,11 +1,14 @@ -#!/usr/bin/env node +#!/usr/bin/env node -r esm -require('localenv'); -require('babel-register'); +import 'localenv'; +import optimist from 'optimist'; -const log = require('book'); -const debug = require('debug')('localtunnel'); -const optimist = require('optimist'); +import log from 'book'; +import Debug from 'debug'; + +import CreateServer from '../server'; + +const debug = Debug('localtunnel'); const argv = optimist .usage('Usage: $0 --port [num]') @@ -32,7 +35,7 @@ if (argv.help) { process.exit(); } -const server = require('../server')({ +const server = CreateServer({ max_tcp_sockets: argv['max-sockets'], secure: argv.secure }); diff --git a/package.json b/package.json index 0908e04..e64988d 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,9 @@ "url": "git://github.com/localtunnel/server.git" }, "dependencies": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-register": "6.26.0", - "bluebird": "3.5.1", "book": "1.3.3", "debug": "3.1.0", + "esm": "3.0.14", "human-readable-ids": "1.0.3", "koa": "2.4.1", "localenv": "0.2.2", diff --git a/server.js b/server.js index 495b5a9..ebe22c2 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,6 @@ import Koa from 'koa'; import tldjs from 'tldjs'; import Debug from 'debug'; import http from 'http'; -import Promise from 'bluebird'; import { hri } from 'human-readable-ids'; import ClientManager from './lib/ClientManager'; @@ -14,7 +13,7 @@ function GetClientIdFromHostname(hostname) { return tldjs.getSubdomain(hostname); } -module.exports = function(opt) { +export default function(opt) { opt = opt || {}; const manager = new ClientManager(opt); diff --git a/test/basic.js b/test/basic.js index 786e931..c50b7e2 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,9 +1,11 @@ -var http = require('http'); -var url = require('url'); -var assert = require('assert'); -var localtunnel = require('localtunnel'); +import http from 'http'; +import url from 'url'; +import assert from 'assert'; +import localtunnel from 'localtunnel'; -var localtunnel_server = require('../server')(); +import CreateServer from '../server'; + +const localtunnel_server = CreateServer(); process.on('uncaughtException', (err) => { console.error(err); diff --git a/test/mocha.opts b/test/mocha.opts index 40f1956..ce1c914 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,4 @@ --check-leaks --reporter spec --ui qunit ---compilers js:babel-register +--require esm diff --git a/test/queue.js b/test/queue.js index 103891c..b945586 100644 --- a/test/queue.js +++ b/test/queue.js @@ -1,11 +1,13 @@ -var http = require('http'); -var url = require('url'); -var assert = require('assert'); -var localtunnel = require('localtunnel'); +import http from 'http'; +import url from 'url'; +import assert from 'assert'; +import localtunnel from 'localtunnel'; + +import CreateServer from '../server'; suite('queue'); -var localtunnel_server = require('../server')({ +var localtunnel_server = CreateServer({ max_tcp_sockets: 1 }); diff --git a/test/simple.js b/test/simple.js index 5dab5e2..da3472c 100644 --- a/test/simple.js +++ b/test/simple.js @@ -1,9 +1,11 @@ -var http = require('http'); -var url = require('url'); -var assert = require('assert'); -var localtunnel = require('localtunnel'); +import http from 'http'; +import url from 'url'; +import assert from 'assert'; +import localtunnel from 'localtunnel'; -var localtunnel_server = require('../server')({ +import CreateServer from '../server'; + +const localtunnel_server = CreateServer({ max_tcp_sockets: 2 }); diff --git a/test/websocket.js b/test/websocket.js index 2d70f72..6c6db98 100644 --- a/test/websocket.js +++ b/test/websocket.js @@ -1,11 +1,13 @@ -var http = require('http'); -var url = require('url'); -var assert = require('assert'); -var localtunnel = require('localtunnel'); -var WebSocket = require('ws'); -var WebSocketServer = require('ws').Server; +import http from 'http'; +import url from 'url'; +import assert from 'assert'; +import localtunnel from 'localtunnel'; +import WebSocket from 'ws'; +import { Server as WebSocketServer } from 'ws'; -var localtunnel_server = require('../server')({ +import CreateServer from '../server'; + +const localtunnel_server = CreateServer({ max_tcp_sockets: 2 }); diff --git a/yarn.lock b/yarn.lock index 156462e..7f84082 100644 --- a/yarn.lock +++ b/yarn.lock @@ -47,140 +47,6 @@ aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.7" - slash "^1.0.0" - source-map "^0.5.6" - -babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.6" - trim-right "^1.0.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-commonjs@6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-register@6.26.0, babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - bindings@1.2.x, bindings@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" @@ -191,10 +57,6 @@ bl@~1.0.0: dependencies: readable-stream "~2.0.5" -bluebird@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" - book@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/book/-/book-1.3.3.tgz#53654254bfe083db102e461544ae9f84be199985" @@ -207,13 +69,6 @@ boom@2.x.x: dependencies: hoek "2.x.x" -brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - bufferutil@1.2.x: version "1.2.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-1.2.1.tgz#37be5d36e1e06492221e68d474b1ac58e510cbd7" @@ -316,10 +171,6 @@ commander@^2.9.0: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - content-disposition@~0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -328,10 +179,6 @@ content-type@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" -convert-source-map@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" - cookies@~0.7.0: version "0.7.1" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b" @@ -339,10 +186,6 @@ cookies@~0.7.0: depd "~1.1.1" keygrip "~1.0.2" -core-js@^2.4.0, core-js@^2.5.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.2.tgz#bc4648656e7dc9dc80d7d3c7bbc172d96e744e63" - core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -386,12 +229,6 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - decamelize@^1.0.0, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -416,12 +253,6 @@ destroy@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - dependencies: - repeating "^2.0.0" - diff@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" @@ -470,14 +301,14 @@ escape-string-regexp@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" +esm@3.0.14: + version "3.0.14" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.0.14.tgz#a9a79b7348c8708050ff42499b5ecbec019d08dc" + esprima@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -536,10 +367,6 @@ glob@3.2.11: inherits "2" minimatch "0.3" -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -580,13 +407,6 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" @@ -635,12 +455,6 @@ inherits@2, inherits@2.0.3, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" - dependencies: - loose-envify "^1.0.0" - invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -715,22 +529,10 @@ jade@0.26.3: commander "0.6.1" mkdirp "0.3.0" -js-tokens@^3.0.0, js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" @@ -897,16 +699,10 @@ lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" -lodash@^4.14.0, lodash@^4.17.4: +lodash@^4.14.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -loose-envify@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -972,12 +768,6 @@ minimatch@0.3: lru-cache "2" sigmund "~1.0.0" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -994,7 +784,7 @@ mkdirp@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" -mkdirp@0.5.1, mkdirp@^0.5.1: +mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -1128,20 +918,12 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" dependencies: lcid "^1.0.0" -os-tmpdir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -1158,10 +940,6 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" -path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -1188,10 +966,6 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -private@^0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -1246,10 +1020,6 @@ redeyed@~1.0.0: dependencies: esprima "~3.0.0" -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" @@ -1306,26 +1076,12 @@ signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" dependencies: hoek "2.x.x" -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - dependencies: - source-map "^0.5.6" - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -1394,10 +1150,6 @@ tldjs@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-1.6.2.tgz#0d8b7145651f1052ac0b3c688843296636183b13" -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - to-iso-string@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" @@ -1410,10 +1162,6 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" -- 2.54.0 From 86dce49dd1d40a64a2f121ff3aa271719417810f Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 1 Apr 2018 19:57:48 -0700 Subject: [PATCH 37/45] add --domain option to enable localtunnel servers on subdomains --- README.md | 2 ++ bin/server | 6 +++++- package.json | 2 +- server.js | 11 +++++++---- test/domain.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 12 +++++++++--- 6 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 test/domain.js diff --git a/README.md b/README.md index 8918376..5b46d5d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ bin/server --port 1234 The localtunnel server is now running and waiting for client requests on port 1234. You will most likely want to set up a reverse proxy to listen on port 80 (or start localtunnel on port 80 directly). +**NOTE** By default, localtunnel will use subdomains for clients, if you plan to host your localtunnel server itself on a subdomain you will need to use the _--domain_ option and specify the domain name behind which you are hosting localtunnel. (i.e. my-localtunnel-server.example.com) + #### use your server You can now use your domain with the `--host` flag for the `lt` client. diff --git a/bin/server b/bin/server index d5e3fa3..a6bba9d 100755 --- a/bin/server +++ b/bin/server @@ -24,6 +24,9 @@ const argv = optimist default: '0.0.0.0', describe: 'IP address to bind to' }) + .options('domain', { + describe: 'Specify the base domain name. This is optional if hosting localtunnel from a regular example.com domain. This is required if hosting a localtunnel server from a subdomain (i.e. lt.example.dom where clients will be client-app.lt.example.come)', + }) .options('max-sockets', { default: 10, describe: 'maximum number of tcp sockets each client is allowed to establish at one time (the tunnels)' @@ -37,7 +40,8 @@ if (argv.help) { const server = CreateServer({ max_tcp_sockets: argv['max-sockets'], - secure: argv.secure + secure: argv.secure, + domain: argv.domain, }); server.listen(argv.port, argv.address, () => { diff --git a/package.json b/package.json index e64988d..aad8c63 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "on-finished": "2.3.0", "optimist": "0.6.1", "pump": "2.0.0", - "tldjs": "1.6.2" + "tldjs": "2.3.1" }, "devDependencies": { "localtunnel": "1.8.0", diff --git a/server.js b/server.js index ebe22c2..86b2d57 100644 --- a/server.js +++ b/server.js @@ -9,13 +9,16 @@ import ClientManager from './lib/ClientManager'; const debug = Debug('localtunnel:server'); -function GetClientIdFromHostname(hostname) { - return tldjs.getSubdomain(hostname); -} - export default function(opt) { opt = opt || {}; + const validHosts = (opt.domain) ? [opt.domain] : undefined; + const myTldjs = tldjs.fromUserSettings({ validHosts }); + + function GetClientIdFromHostname(hostname) { + return myTldjs.getSubdomain(hostname); + } + const manager = new ClientManager(opt); const schema = opt.secure ? 'https' : 'http'; diff --git a/test/domain.js b/test/domain.js new file mode 100644 index 0000000..6fa2b02 --- /dev/null +++ b/test/domain.js @@ -0,0 +1,52 @@ +import http from 'http'; +import url from 'url'; +import assert from 'assert'; +import localtunnel from 'localtunnel'; + +import CreateServer from '../server'; + +const localtunnel_server = CreateServer({ + domain: 'domain.example.com', +}); + +process.on('uncaughtException', (err) => { + console.error(err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error(reason); +}); + +suite('domain'); + +var lt_server_port; + +before('set up localtunnel server', function(done) { + var server = localtunnel_server.listen(function() { + lt_server_port = server.address().port; + done(); + }); +}); + +test('landing page', function(done) { + var opt = { + host: 'localhost', + port: lt_server_port, + headers: { + host: 'domain.example.com' + }, + path: '/' + } + + var req = http.request(opt, function(res) { + res.setEncoding('utf8'); + assert.equal(res.headers.location, 'https://localtunnel.github.io/www/') + done(); + }); + + req.end(); +}); + +after('shutdown', function() { + localtunnel_server.close(); +}); diff --git a/yarn.lock b/yarn.lock index 7f84082..d6628aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -977,6 +977,10 @@ pump@2.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + qs@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" @@ -1146,9 +1150,11 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -tldjs@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-1.6.2.tgz#0d8b7145651f1052ac0b3c688843296636183b13" +tldjs@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-2.3.1.tgz#cf09c3eb5d7403a9e214b7d65f3cf9651c0ab039" + dependencies: + punycode "^1.4.1" to-iso-string@0.0.2: version "0.0.2" -- 2.54.0 From 423327d0ac43dfd5cfccb79014c96c210387ac22 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Sun, 1 Apr 2018 20:24:55 -0700 Subject: [PATCH 38/45] update dockerfile entrypoint to use esm Does not work using #! in bin/server without this. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index eb56d30..fb8ac3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,4 @@ RUN yarn install --production && yarn cache clean COPY . /app ENV NODE_ENV production -ENTRYPOINT ["bin/server"] +ENTRYPOINT ["node", "-r", "esm", "./bin/server"] -- 2.54.0 From 30fd566c3aab6e0bc048f77dc77151d4e0236792 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Thu, 5 Apr 2018 18:06:44 -0700 Subject: [PATCH 39/45] remove stale .babelrc --- .babelrc | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .babelrc diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 2ce79c5..0000000 --- a/.babelrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "plugins": [ - "transform-es2015-modules-commonjs", - ] -} -- 2.54.0 From c27100b98ea4f5309b8da14b94714b53b1c62aac Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Tue, 15 May 2018 14:49:19 -0400 Subject: [PATCH 40/45] refactor to use http agent interface for tunnels --- README.md | 10 + lib/BindingAgent.js | 23 -- lib/Client.js | 69 ++++ lib/Client.test.js | 158 +++++++++ lib/ClientManager.js | 216 ++++-------- lib/ClientManager.test.js | 51 +++ lib/Proxy.js | 193 ----------- lib/TunnelAgent.js | 171 +++++++++ lib/TunnelAgent.test.js | 176 ++++++++++ package.json | 14 +- server.js | 34 +- server.test.js | 85 +++++ test/basic.js | 166 --------- test/domain.js | 52 --- test/mocha.opts | 4 - test/queue.js | 105 ------ test/simple.js | 70 ---- test/websocket.js | 76 ---- yarn.lock | 713 +++++++++++++------------------------- 19 files changed, 1041 insertions(+), 1345 deletions(-) delete mode 100644 lib/BindingAgent.js create mode 100644 lib/Client.js create mode 100644 lib/Client.test.js create mode 100644 lib/ClientManager.test.js delete mode 100644 lib/Proxy.js create mode 100644 lib/TunnelAgent.js create mode 100644 lib/TunnelAgent.test.js create mode 100644 server.test.js delete mode 100644 test/basic.js delete mode 100644 test/domain.js delete mode 100644 test/mocha.opts delete mode 100644 test/queue.js delete mode 100644 test/simple.js delete mode 100644 test/websocket.js diff --git a/README.md b/README.md index 5b46d5d..9c829f0 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ You will be assigned a URL similar to `heavy-puma-9.sub.example.com:1234`. If your server is acting as a reverse proxy (i.e. nginx) and is able to listen on port 80, then you do not need the `:1234` part of the hostname for the `lt` client. +## REST API + +### POST /api/tunnels + +Create a new tunnel. A LocalTunnel client posts to this enpoint to request a new tunnel with a specific name or a randomly assigned name. + +### GET /api/status + +General server information. + ## Deploy You can deploy your own localtunnel server using the prebuilt docker image. diff --git a/lib/BindingAgent.js b/lib/BindingAgent.js deleted file mode 100644 index c0d7ad9..0000000 --- a/lib/BindingAgent.js +++ /dev/null @@ -1,23 +0,0 @@ -import http from 'http'; -import util from 'util'; -import assert from 'assert'; - -// binding agent will return a given options.socket as the socket for the agent -// this is useful if you already have a socket established and want the request -// to use that socket instead of making a new one -function BindingAgent(options) { - options = options || {}; - http.Agent.call(this, options); - - this.socket = options.socket; - assert(this.socket, 'socket is required for BindingAgent'); - this.createConnection = create_connection; -} - -util.inherits(BindingAgent, http.Agent); - -function create_connection(port, host, options) { - return this.socket; -} - -export default BindingAgent; diff --git a/lib/Client.js b/lib/Client.js new file mode 100644 index 0000000..aae3df0 --- /dev/null +++ b/lib/Client.js @@ -0,0 +1,69 @@ +import http from 'http'; + +import TunnelAgent from './TunnelAgent'; + +// A client encapsulates req/res handling using an agent +// +// If an agent is destroyed, the request handling will error +// The caller is responsible for handling a failed request +class Client { + constructor(options) { + this.agent = options.agent; + } + + handleRequest(req, res) { + const opt = { + path: req.url, + agent: this.agent, + method: req.method, + headers: req.headers + }; + + const clientReq = http.request(opt, (clientRes) => { + // write response code and headers + res.writeHead(clientRes.statusCode, clientRes.headers); + clientRes.pipe(res); + }); + + // this can happen when underlying agent produces an error + // in our case we 504 gateway error this? + // if we have already sent headers? + clientReq.once('error', (err) => { + + }); + + req.pipe(clientReq); + } + + handleUpgrade(req, socket) { + this.agent.createConnection({}, (err, conn) => { + // any errors getting a connection mean we cannot service this request + if (err) { + socket.end(); + return; + } + + // socket met have disconnected while we waiting for a socket + if (!socket.readable || !socket.writable) { + socket.end(); + return; + } + + // websocket requests are special in that we simply re-create the header info + // then directly pipe the socket data + // avoids having to rebuild the request and handle upgrades via the http client + const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`]; + for (let i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { + arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}`); + } + + arr.push(''); + arr.push(''); + + conn.pipe(socket).pipe(conn); + conn.write(arr.join('\r\n')); + }); + } +} + +export default Client; \ No newline at end of file diff --git a/lib/Client.test.js b/lib/Client.test.js new file mode 100644 index 0000000..8f53be1 --- /dev/null +++ b/lib/Client.test.js @@ -0,0 +1,158 @@ +import assert from 'assert'; +import http from 'http'; +import { Duplex } from 'stream'; +import EventEmitter from 'events'; +import WebSocket from 'ws'; +import net from 'net'; + +import Client from './Client'; + +class DummySocket extends Duplex { + constructor(options) { + super(options); + } + + _write(chunk, encoding, callback) { + callback(); + } + + _read(size) { + this.push('HTTP/1.1 304 Not Modified\r\nX-Powered-By: dummy\r\n\r\n\r\n'); + this.push(null); + } +} + +class DummyWebsocket extends Duplex { + constructor(options) { + super(options); + this.sentHeader = false; + } + + _write(chunk, encoding, callback) { + const str = chunk.toString(); + // if chunk contains `GET / HTTP/1.1` -> queue headers + // otherwise echo back received data + if (str.indexOf('GET / HTTP/1.1') === 0) { + const arr = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + ]; + this.push(arr.join('\r\n')); + this.push('\r\n\r\n'); + } + else { + this.push(str); + } + callback(); + } + + _read(size) { + // nothing to implement + } +} + +class DummyAgent extends http.Agent { + constructor() { + super(); + } + + createConnection(options, cb) { + cb(null, new DummySocket()); + } +} + +describe('Client', () => { + it('should handle request', async () => { + const agent = new DummyAgent(); + const client = new Client({ agent }); + + const server = http.createServer((req, res) => { + client.handleRequest(req, res); + }); + + await new Promise(resolve => server.listen(resolve)); + + const address = server.address(); + const opt = { + host: 'localhost', + port: address.port, + path: '/', + }; + + const res = await new Promise((resolve) => { + const req = http.get(opt, (res) => { + resolve(res); + }); + req.end(); + }); + assert.equal(res.headers['x-powered-by'], 'dummy'); + server.close(); + }); + + it('should handle upgrade', async () => { + // need a websocket server and a socket for it + class DummyWebsocketAgent extends http.Agent { + constructor() { + super(); + } + + createConnection(options, cb) { + cb(null, new DummyWebsocket()); + } + } + + const agent = new DummyWebsocketAgent(); + const client = new Client({ agent }); + + const server = http.createServer(); + server.on('upgrade', (req, socket, head) => { + client.handleUpgrade(req, socket); + }); + + await new Promise(resolve => server.listen(resolve)); + + const address = server.address(); + + const netClient = await new Promise((resolve) => { + const newClient = net.createConnection({ port: address.port }, () => { + resolve(newClient); + }); + }); + + const out = [ + 'GET / HTTP/1.1', + 'Connection: Upgrade', + 'Upgrade: websocket' + ]; + + netClient.write(out.join('\r\n') + '\r\n\r\n'); + + { + const data = await new Promise((resolve) => { + netClient.once('data', (chunk) => { + resolve(chunk.toString()); + }); + }); + const exp = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + ]; + assert.equal(exp.join('\r\n') + '\r\n\r\n', data); + } + + { + netClient.write('foobar'); + const data = await new Promise((resolve) => { + netClient.once('data', (chunk) => { + resolve(chunk.toString()); + }); + }); + assert.equal('foobar', data); + } + + netClient.destroy(); + server.close(); + }); +}); diff --git a/lib/ClientManager.js b/lib/ClientManager.js index a4199f9..b41270f 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -1,36 +1,31 @@ -import Proxy from './Proxy'; +import { hri } from 'human-readable-ids'; +import Debug from 'debug'; -// maybe remove? -import on_finished from 'on-finished'; -import http from 'http'; -import pump from 'pump'; -import { hri } from "human-readable-ids"; - -import BindingAgent from './BindingAgent'; - -const NoOp = () => {}; +import Client from './Client'; +import TunnelAgent from './TunnelAgent'; // Manage sets of clients // // A client is a "user session" established to service a remote localtunnel client class ClientManager { constructor(opt) { - this.opt = opt; - - this.reqId = 0; + this.opt = opt || {}; // id -> client instance - this.clients = Object.create(null); + this.clients = new Map(); // statistics this.stats = { tunnels: 0 }; + + this.debug = Debug('lt:ClientManager'); } // create a new tunnel with `id` // if the id is already used, a random id is assigned - async newClient (id) { + // if the tunnel could not be created, throws an error + async newClient(id) { const clients = this.clients; const stats = this.stats; @@ -39,161 +34,68 @@ class ClientManager { id = hri.random(); } - const popt = { - id: id, - max_tcp_sockets: this.opt.max_tcp_sockets - }; + const maxSockets = this.opt.max_tcp_sockets; + const agent = new TunnelAgent({ + maxSockets: 10, + }); - const client = Proxy(popt); + agent.on('online', () => { + this.debug('client online %s', id); + }); + + agent.on('offline', () => { + // TODO(roman): grace period for re-connecting + // this period is short as the client is expected to maintain connections actively + // if they client does not reconnect on a dropped connection they need to re-establish + this.debug('client offline %s', id); + this.removeClient(id); + }); + + // TODO(roman): an agent error removes the client, the user needs to re-connect? + // how does a user realize they need to re-connect vs some random client being assigned same port? + agent.once('error', (err) => { + this.removeClient(id); + }); + + const client = new Client({ agent }); // add to clients map immediately // avoiding races with other clients requesting same id clients[id] = client; - client.on('end', () => { - --stats.tunnels; - delete clients[id]; - }); + // try/catch used here to remove client id + try { + const info = await agent.listen(); + ++stats.tunnels; + return { + id: id, + port: info.port, + max_conn_count: maxSockets, + }; + } + catch (err) { + this.removeClient(id); + // rethrow error for upstream to handle + throw err; + } + } - return new Promise((resolve, reject) => { - // each local client has a tcp server to link with the remove localtunnel client - // this starts the server and waits until it is listening - client.start((err, info) => { - if (err) { - // clear the reserved client id - delete clients[id]; - reject(err); - return; - } - - ++stats.tunnels; - info.id = id; - resolve(info); - }); - }); + removeClient(id) { + const client = this.clients[id]; + if (!client) { + return; + } + --this.stats.tunnels; + delete this.clients[id]; + client.agent.destroy(); } hasClient(id) { return this.clients[id]; } - // handle http request - handleRequest(clientId, req, res) { - const client = this.clients[clientId]; - if (!client) { - return; - } - - const reqId = this.reqId; - this.reqId = this.reqId + 1; - - let endRes = () => { - endRes = NoOp; - res.end(); - }; - - on_finished(res, () => { - endRes = NoOp; - }); - - client.nextSocket((clientSocket) => { - // response ended before we even got a socket to respond on - if (endRes === NoOp) { - return; - } - - // happens when client upstream is disconnected (or disconnects) - // and the proxy iterates the waiting list and clears the callbacks - // we gracefully inform the user and kill their conn - // without this, the browser will leave some connections open - // and try to use them again for new requests - // TODO(roman) we could instead have a timeout above - // if no socket becomes available within some time, - // we just tell the user no resource available to service request - if (!clientSocket) { - endRes(); - return; - } - - const agent = new BindingAgent({ - socket: clientSocket, - }); - - const opt = { - path: req.url, - agent: agent, - method: req.method, - headers: req.headers - }; - - return new Promise((resolve) => { - // what if error making this request? - const clientReq = http.request(opt, (clientRes) => { - // write response code and headers - res.writeHead(clientRes.statusCode, clientRes.headers); - - // when this pump is done, we end our response - pump(clientRes, res, (err) => { - endRes(); - resolve(); - }); - }); - - // we don't care about when this ends, only if there is error - pump(req, clientReq, (err) => { - if (err) { - endRes(); - resolve(); - } - }); - }); - }); - } - - // handle http upgrade - handleUpgrade(clientId, req, sock) { - const client = this.clients[clientId]; - if (!client) { - return; - } - - client.nextSocket(async (clientSocket) => { - if (!sock.readable || !sock.writable) { - sock.end(); - return; - } - - // happens when client upstream is disconnected (or disconnects) - // and the proxy iterates the waiting list and clears the callbacks - // we gracefully inform the user and kill their conn - // without this, the browser will leave some connections open - // and try to use them again for new requests - // TODO(roman) we could instead have a timeout above - // if no socket becomes available within some time, - // we just tell the user no resource available to service request - if (!clientSocket) { - sock.end(); - return; - } - - // websocket requests are special in that we simply re-create the header info - // then directly pipe the socket data - // avoids having to rebuild the request and handle upgrades via the http client - const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`]; - for (let i=0 ; i < (req.rawHeaders.length-1) ; i+=2) { - arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}`); - } - - arr.push(''); - arr.push(''); - - clientSocket.pipe(sock).pipe(clientSocket); - clientSocket.write(arr.join('\r\n')); - - await new Promise((resolve) => { - sock.once('end', resolve); - }); - }); + getClient(id) { + return this.clients[id]; } } diff --git a/lib/ClientManager.test.js b/lib/ClientManager.test.js new file mode 100644 index 0000000..63eb60e --- /dev/null +++ b/lib/ClientManager.test.js @@ -0,0 +1,51 @@ +import assert from 'assert'; +import net from 'net'; + +import ClientManager from './ClientManager'; + +describe('ClientManager', () => { + it('should construct with no tunnels', () => { + const manager = new ClientManager(); + assert.equal(manager.stats.tunnels, 0); + }); + + it('should create a new client with random id', async () => { + const manager = new ClientManager(); + const client = await manager.newClient(); + assert(manager.hasClient(client.id)); + manager.removeClient(client.id); + }); + + it('should create a new client with id', async () => { + const manager = new ClientManager(); + const client = await manager.newClient('foobar'); + assert(manager.hasClient('foobar')); + manager.removeClient('foobar'); + }); + + it('should create a new client with random id if previous exists', async () => { + const manager = new ClientManager(); + const clientA = await manager.newClient('foobar'); + const clientB = await manager.newClient('foobar'); + assert(clientA.id, 'foobar'); + assert(manager.hasClient(clientB.id)); + assert(clientB.id != clientA.id); + manager.removeClient(clientB.id); + manager.removeClient('foobar'); + }); + + it('should remove client once it goes offline', async () => { + const manager = new ClientManager(); + const client = await manager.newClient('foobar'); + + const socket = await new Promise((resolve) => { + const netClient = net.createConnection({ port: client.port }, () => { + resolve(netClient); + }); + }); + const closePromise = new Promise(resolve => socket.once('close', resolve)); + socket.end(); + await closePromise; + assert(!manager.hasClient('foobar')); + }); +}); diff --git a/lib/Proxy.js b/lib/Proxy.js deleted file mode 100644 index ccd0ebf..0000000 --- a/lib/Proxy.js +++ /dev/null @@ -1,193 +0,0 @@ -import net from 'net'; -import EventEmitter from 'events'; -import log from 'book'; -import Debug from 'debug'; - -const debug = Debug('localtunnel:server'); - -const Proxy = function(opt) { - if (!(this instanceof Proxy)) { - return new Proxy(opt); - } - - const self = this; - - self.sockets = []; - self.waiting = []; - self.id = opt.id; - - self.activeSockets = 0; - - // default max is 10 - self.max_tcp_sockets = opt.max_tcp_sockets || 10; - - // new tcp server to service requests for this client - self.server = net.createServer(); - - // track initial user connection setup - self.conn_timeout = undefined; - - self.debug = Debug(`localtunnel:server:${self.id}`); -}; - -Proxy.prototype.__proto__ = EventEmitter.prototype; - -Proxy.prototype.start = function(cb) { - const self = this; - const server = self.server; - - if (self.started) { - cb(new Error('already started')); - return; - } - self.started = true; - - server.on('close', self._cleanup.bind(self)); - server.on('connection', self._handle_socket.bind(self)); - - server.on('error', function(err) { - // where do these errors come from? - // other side creates a connection and then is killed? - if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') { - return; - } - - log.error(err); - }); - - server.listen(function() { - const port = server.address().port; - self.debug('tcp server listening on port: %d', port); - - cb(null, { - // port for lt client tcp connections - port: port, - // maximum number of tcp connections allowed by lt client - max_conn_count: self.max_tcp_sockets - }); - }); - - self._maybe_destroy(); -}; - -Proxy.prototype._maybe_destroy = function() { - const self = this; - - clearTimeout(self.conn_timeout); - - // After last socket is gone, we give opportunity to connect again quickly - self.conn_timeout = setTimeout(function() { - // sometimes the server is already closed but the event has not fired? - try { - clearTimeout(self.conn_timeout); - self.server.close(); - } - catch (err) { - self._cleanup(); - } - }, 1000); -} - -// new socket connection from client for tunneling requests to client -Proxy.prototype._handle_socket = function(socket) { - const self = this; - - // no more socket connections allowed - if (self.activeSockets >= self.max_tcp_sockets) { - return socket.end(); - } - - self.activeSockets = self.activeSockets + 1; - - self.debug('new connection from: %s:%s', socket.address().address, socket.address().port); - - // a single connection is enough to keep client id slot open - clearTimeout(self.conn_timeout); - - socket.once('close', function(had_error) { - self.activeSockets = self.activeSockets - 1; - self.debug('closed socket (error: %s)', had_error); - - // what if socket was servicing a request at this time? - // then it will be put back in available after right? - // we need a list of sockets servicing requests? - - // remove this socket - const idx = self.sockets.indexOf(socket); - if (idx >= 0) { - self.sockets.splice(idx, 1); - } - - // need to track total sockets, not just active available - self.debug('remaining client sockets: %s', self.sockets.length); - - // no more sockets for this ident - if (self.sockets.length === 0) { - self.debug('all sockets disconnected'); - self._maybe_destroy(); - } - }); - - // close will be emitted after this - socket.on('error', function(err) { - // we don't log here to avoid logging crap for misbehaving clients - socket.destroy(); - }); - - self.sockets.push(socket); - self._process_waiting(); -}; - -Proxy.prototype._process_waiting = function() { - const self = this; - const fn = self.waiting.shift(); - if (fn) { - self.debug('handling queued request'); - self.nextSocket(fn); - } -}; - -Proxy.prototype._cleanup = function() { - const self = this; - self.debug('closed tcp socket for client(%s)', self.id); - - clearTimeout(self.conn_timeout); - - // clear waiting by ending responses, (requests?) - self.waiting.forEach(handler => handler(null)); - - self.emit('end'); -}; - -Proxy.prototype.nextSocket = async function(fn) { - const self = this; - - // socket is a tcp connection back to the user hosting the site - const sock = self.sockets.shift(); - if (!sock) { - self.debug('no more clients, queue callback'); - self.waiting.push(fn); - return; - } - - self.debug('processing request'); - await fn(sock); - - if (!sock.destroyed) { - self.debug('retuning socket'); - self.sockets.push(sock); - } - - // no sockets left to process waiting requests - if (self.sockets.length === 0) { - return; - } - - self._process_waiting(); -}; - -Proxy.prototype._done = function() { - const self = this; -}; - -export default Proxy; diff --git a/lib/TunnelAgent.js b/lib/TunnelAgent.js new file mode 100644 index 0000000..215b1f8 --- /dev/null +++ b/lib/TunnelAgent.js @@ -0,0 +1,171 @@ +import { Agent } from 'http'; +import net from 'net'; +import assert from 'assert'; +import log from 'book'; +import Debug from 'debug'; + +const DEFAULT_MAX_SOCKETS = 10; + +// Implements an http.Agent interface to a pool of tunnel sockets +// A tunnel socket is a connection _from_ a client that will +// service http requests. This agent is usable wherever one can use an http.Agent +class TunnelAgent extends Agent { + constructor(options = {}) { + super({ + keepAlive: true, + // only allow keepalive to hold on to one socket + // this prevents it from holding on to all the sockets so they can be used for upgrades + maxFreeSockets: 1, + }); + + // sockets we can hand out via createConnection + this.availableSockets = []; + + // when a createConnection cannot return a socket, it goes into a queue + // once a socket is available it is handed out to the next callback + this.waitingCreateConn = []; + + this.debug = Debug('lt:TunnelAgent'); + + // track maximum allowed sockets + this.activeSockets = 0; + this.maxTcpSockets = options.maxTcpSockets || DEFAULT_MAX_SOCKETS; + + // new tcp server to service requests for this client + this.server = net.createServer(); + + // flag to avoid double starts + this.started = false; + } + + listen() { + const server = this.server; + if (this.started) { + throw new Error('already started'); + } + this.started = true; + + server.on('close', this._onClose.bind(this)); + server.on('connection', this._onConnection.bind(this)); + server.on('error', (err) => { + // where do these errors come from? + // other side creates a connection and then is killed? + if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') { + return; + } + log.error(err); + }); + + return new Promise((resolve) => { + server.listen(() => { + const port = server.address().port; + this.debug('tcp server listening on port: %d', port); + + resolve({ + // port for lt client tcp connections + port: port, + }); + }); + }); + } + + _onClose() { + this.debug('closed tcp socket'); + clearTimeout(this.connTimeout); + // we will not invoke these callbacks? + // TODO(roman): we could invoke these with errors...? + // this makes downstream have to handle this + this.waitingCreateConn = []; + this.emit('end'); + } + + // new socket connection from client for tunneling requests to client + _onConnection(socket) { + // no more socket connections allowed + if (this.activeSockets >= this.maxTcpSockets) { + this.debug('no more sockets allowed'); + socket.destroy(); + return false; + } + + // a new socket becomes available + if (this.activeSockets == 0) { + this.emit('online'); + } + + this.activeSockets += 1; + this.debug('new connection from: %s:%s', socket.address().address, socket.address().port); + + // a single connection is enough to keep client id slot open + clearTimeout(this.connTimeout); + + socket.once('close', (had_error) => { + this.debug('closed socket (error: %s)', had_error); + this.debug('removing socket'); + this.activeSockets -= 1; + // remove the socket from available list + const idx = this.availableSockets.indexOf(socket); + if (idx >= 0) { + this.availableSockets.splice(idx, 1); + } + // need to track total sockets, not just active available + this.debug('remaining client sockets: %s', this.availableSockets.length); + // no more sockets for this session + // the session will become inactive if client does not reconnect + if (this.availableSockets.length <= 0) { + this.debug('all sockets disconnected'); + this.emit('offline'); + } + }); + + // close will be emitted after this + socket.once('error', (err) => { + // we do not log these errors, sessions can drop from clients for many reasons + // these are not actionable errors for our server + socket.destroy(); + }); + + // make socket available for those waiting on sockets + this.availableSockets.push(socket); + + // flush anyone waiting on sockets + this._callWaitingCreateConn(); + } + + // invoke when a new socket is available and there may be waiting createConnection calls + _callWaitingCreateConn() { + const fn = this.waitingCreateConn.shift(); + if (!fn) { + return; + } + + this.debug('handling queued request'); + this.createConnection({}, fn); + } + + // fetch a socket from the available socket pool for the agent + // if no socket is available, queue + // cb(err, socket) + createConnection(options, cb) { + // socket is a tcp connection back to the user hosting the site + const sock = this.availableSockets.shift(); + + // no available sockets + // wait until we have one + if (!sock) { + this.waitingCreateConn.push(cb); + this.debug('waiting'); + return; + } + + this.debug('socket given'); + cb(null, sock); + } + + destroy() { + this.server.close(); + super.destroy(); + } +} + +export default TunnelAgent; diff --git a/lib/TunnelAgent.test.js b/lib/TunnelAgent.test.js new file mode 100644 index 0000000..62839c0 --- /dev/null +++ b/lib/TunnelAgent.test.js @@ -0,0 +1,176 @@ +import http from 'http'; +import net from 'net'; +import assert from 'assert'; + +import TunnelAgent from './TunnelAgent'; + +describe('TunnelAgent', () => { + it('should create an empty agent', async () => { + const agent = new TunnelAgent(); + assert.equal(agent.started, false); + + const info = await agent.listen(); + assert.ok(info.port > 0); + agent.destroy(); + }); + + it('should create a new server and accept connections', async () => { + const agent = new TunnelAgent(); + assert.equal(agent.started, false); + + const info = await agent.listen(); + const sock = net.createConnection({ port: info.port }); + + // in this test we wait for the socket to be connected + await new Promise(resolve => sock.once('connect', resolve)); + + const agentSock = await new Promise((resolve, reject) => { + agent.createConnection({}, (err, sock) => { + if (err) { + reject(err); + } + resolve(sock); + }); + }); + + agentSock.write('foo'); + await new Promise(resolve => sock.once('readable', resolve)); + + assert.equal('foo', sock.read().toString()); + agent.destroy(); + sock.destroy(); + }); + + it('should reject connections over the max', async () => { + const agent = new TunnelAgent({ + maxTcpSockets: 2, + }); + assert.equal(agent.started, false); + + const info = await agent.listen(); + const sock1 = net.createConnection({ port: info.port }); + const sock2 = net.createConnection({ port: info.port }); + + // two valid socket connections + const p1 = new Promise(resolve => sock1.once('connect', resolve)); + const p2 = new Promise(resolve => sock2.once('connect', resolve)); + await Promise.all([p1, p2]); + + const sock3 = net.createConnection({ port: info.port }); + const p3 = await new Promise(resolve => sock3.once('close', resolve)); + + agent.destroy(); + sock1.destroy(); + sock2.destroy(); + sock3.destroy(); + }); + + it('should queue createConnection requests', async () => { + const agent = new TunnelAgent(); + assert.equal(agent.started, false); + + const info = await agent.listen(); + + // create a promise for the next connection + let fulfilled = false; + const waitSockPromise = new Promise((resolve, reject) => { + agent.createConnection({}, (err, sock) => { + fulfilled = true; + if (err) { + reject(err); + } + resolve(sock); + }); + }); + + // check that the next socket is not yet available + await new Promise(resolve => setTimeout(resolve, 500)); + assert(!fulfilled); + + // connect, this will make a socket available + const sock = net.createConnection({ port: info.port }); + await new Promise(resolve => sock.once('connect', resolve)); + + const anotherAgentSock = await waitSockPromise; + agent.destroy(); + sock.destroy(); + }); + + it('should should emit a online event when a socket connects', async () => { + const agent = new TunnelAgent(); + const info = await agent.listen(); + + const onlinePromise = new Promise(resolve => agent.once('online', resolve)); + + const sock = net.createConnection({ port: info.port }); + await new Promise(resolve => sock.once('connect', resolve)); + + await onlinePromise; + agent.destroy(); + sock.destroy(); + }); + + it('should emit offline event when socket disconnects', async () => { + const agent = new TunnelAgent(); + const info = await agent.listen(); + + const offlinePromise = new Promise(resolve => agent.once('offline', resolve)); + + const sock = net.createConnection({ port: info.port }); + await new Promise(resolve => sock.once('connect', resolve)); + + sock.end(); + await offlinePromise; + agent.destroy(); + sock.destroy(); + }); + + it('should emit offline event only when last socket disconnects', async () => { + const agent = new TunnelAgent(); + const info = await agent.listen(); + + const offlinePromise = new Promise(resolve => agent.once('offline', resolve)); + + const sockA = net.createConnection({ port: info.port }); + await new Promise(resolve => sockA.once('connect', resolve)); + const sockB = net.createConnection({ port: info.port }); + await new Promise(resolve => sockB.once('connect', resolve)); + + sockA.end(); + + const timeout = new Promise(resolve => setTimeout(resolve, 500)); + await Promise.race([offlinePromise, timeout]); + + sockB.end(); + await offlinePromise; + + agent.destroy(); + }); + + it('should error an http request', async () => { + class ErrorAgent extends http.Agent { + constructor() { + super(); + } + + createConnection(options, cb) { + cb(new Error('foo')); + } + } + + const agent = new ErrorAgent(); + + const opt = { + host: 'localhost', + port: 1234, + path: '/', + agent: agent, + }; + + const err = await new Promise((resolve) => { + const req = http.get(opt, (res) => {}); + req.once('error', resolve); + }); + assert.equal(err.message, 'foo'); + }); +}); diff --git a/package.json b/package.json index aad8c63..1624eef 100644 --- a/package.json +++ b/package.json @@ -11,23 +11,21 @@ "dependencies": { "book": "1.3.3", "debug": "3.1.0", - "esm": "3.0.14", + "esm": "3.0.34", "human-readable-ids": "1.0.3", - "koa": "2.4.1", + "koa": "2.5.1", "localenv": "0.2.2", - "on-finished": "2.3.0", "optimist": "0.6.1", - "pump": "2.0.0", "tldjs": "2.3.1" }, "devDependencies": { - "localtunnel": "1.8.0", - "mocha": "2.5.3", + "mocha": "5.1.1", "node-dev": "3.1.3", - "ws": "0.8.0" + "supertest": "3.1.0", + "ws": "5.1.1" }, "scripts": { - "test": "mocha --ui qunit --reporter spec", + "test": "mocha --check-leaks --require esm './**/*.test.js'", "start": "./bin/server", "dev": "node-dev bin/server --port 3000" } diff --git a/server.js b/server.js index 86b2d57..c5c395a 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,7 @@ export default function(opt) { const validHosts = (opt.domain) ? [opt.domain] : undefined; const myTldjs = tldjs.fromUserSettings({ validHosts }); + const landingPage = opt.landing || 'https://localtunnel.github.io/www/'; function GetClientIdFromHostname(hostname) { return myTldjs.getSubdomain(hostname); @@ -53,9 +54,9 @@ export default function(opt) { const isNewClientRequest = ctx.query['new'] !== undefined; if (isNewClientRequest) { - const req_id = hri.random(); - debug('making new client with id %s', req_id); - const info = await manager.newClient(req_id); + const reqId = hri.random(); + debug('making new client with id %s', reqId); + const info = await manager.newClient(reqId); const url = schema + '://' + info.id + '.' + ctx.request.host; info.url = url; @@ -64,7 +65,7 @@ export default function(opt) { } // no new client request, send to landing page - ctx.redirect('https://localtunnel.github.io/www/'); + ctx.redirect(landingPage); }); // anything after the / path is a request for a specific client name @@ -80,10 +81,10 @@ export default function(opt) { return; } - const req_id = parts[1]; + const reqId = parts[1]; // limit requested hostnames to 63 characters - if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(req_id)) { + if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) { const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'; ctx.status = 403; ctx.body = { @@ -92,8 +93,8 @@ export default function(opt) { return; } - debug('making new client with id %s', req_id); - const info = await manager.newClient(req_id); + debug('making new client with id %s', reqId); + const info = await manager.newClient(reqId); const url = schema + '://' + info.id + '.' + ctx.request.host; info.url = url; @@ -104,6 +105,7 @@ export default function(opt) { const server = http.createServer(); const appCallback = app.callback(); + server.on('request', (req, res) => { // without a hostname, we won't know who the request is for const hostname = req.headers.host; @@ -119,13 +121,14 @@ export default function(opt) { return; } - if (manager.hasClient(clientId)) { - manager.handleRequest(clientId, req, res); + const client = manager.getClient(clientId); + if (!client) { + res.statusCode = 404; + res.end('404'); return; } - res.statusCode = 404; - res.end('404'); + client.handleRequest(req, res); }); server.on('upgrade', (req, socket, head) => { @@ -141,12 +144,13 @@ export default function(opt) { return; } - if (manager.hasClient(clientId)) { - manager.handleUpgrade(clientId, req, socket); + const client = manager.getClient(clientId); + if (!client) { + sock.destroy(); return; } - socket.destroy(); + client.handleUpgrade(req, socket); }); return server; diff --git a/server.test.js b/server.test.js new file mode 100644 index 0000000..3f72f2d --- /dev/null +++ b/server.test.js @@ -0,0 +1,85 @@ +import request from 'supertest'; +import assert from 'assert'; +import { Server as WebSocketServer } from 'ws'; +import WebSocket from 'ws'; +import net from 'net'; + +import createServer from './server'; + +describe('Server', () => { + it('server starts and stops', async () => { + const server = createServer(); + await new Promise(resolve => server.listen(resolve)); + await new Promise(resolve => server.close(resolve)); + }); + + it('should redirect root requests to landing page', async () => { + const server = createServer(); + const res = await request(server).get('/'); + assert.equal('https://localtunnel.github.io/www/', res.headers.location); + }); + + it('should support custom base domains', async () => { + const server = createServer({ + domain: 'domain.example.com', + }); + + const res = await request(server).get('/'); + assert.equal('https://localtunnel.github.io/www/', res.headers.location); + }); + + it('reject long domain name requests', async () => { + const server = createServer(); + const res = await request(server).get('/thisdomainisoutsidethesizeofwhatweallowwhichissixtythreecharacters'); + assert.equal(res.body.message, 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); + }); + + it('should upgrade websocket requests', async () => { + const hostname = 'websocket-test'; + const server = createServer({ + domain: 'example.com', + }); + await new Promise(resolve => server.listen(resolve)); + + const res = await request(server).get('/websocket-test'); + const localTunnelPort = res.body.port; + + const wss = await new Promise((resolve) => { + const wsServer = new WebSocketServer({ port: 0 }, () => { + resolve(wsServer); + }); + }); + + const websocketServerPort = wss.address().port; + + const ltSocket = net.createConnection({ port: localTunnelPort }); + const wsSocket = net.createConnection({ port: websocketServerPort }); + ltSocket.pipe(wsSocket).pipe(ltSocket); + + wss.once('connection', (ws) => { + ws.once('message', (message) => { + ws.send(message); + }); + }); + + const ws = new WebSocket('http://localhost:' + server.address().port, { + headers: { + host: hostname + '.example.com', + } + }); + + ws.on('open', () => { + ws.send('something'); + }); + + await new Promise((resolve) => { + ws.once('message', (msg) => { + assert.equal(msg, 'something'); + resolve(); + }); + }); + + wss.close(); + await new Promise(resolve => server.close(resolve)); + }); +}); \ No newline at end of file diff --git a/test/basic.js b/test/basic.js deleted file mode 100644 index c50b7e2..0000000 --- a/test/basic.js +++ /dev/null @@ -1,166 +0,0 @@ -import http from 'http'; -import url from 'url'; -import assert from 'assert'; -import localtunnel from 'localtunnel'; - -import CreateServer from '../server'; - -const localtunnel_server = CreateServer(); - -process.on('uncaughtException', (err) => { - console.error(err); -}); - -process.on('unhandledRejection', (reason, promise) => { - console.error(reason); -}); - -suite('basic'); - -var lt_server_port; - -before('set up localtunnel server', function(done) { - var server = localtunnel_server.listen(function() { - lt_server_port = server.address().port; - done(); - }); -}); - -test('landing page', function(done) { - var opt = { - host: 'localhost', - port: lt_server_port, - headers: { - host: 'example.com' - }, - path: '/' - } - - var req = http.request(opt, function(res) { - res.setEncoding('utf8'); - assert.equal(res.headers.location, 'https://localtunnel.github.io/www/') - done(); - }); - - req.end(); -}); - -before('set up local http server', function(done) { - var server = http.createServer(); - server.on('request', function(req, res) { - res.write('foo'); - res.end(); - }); - server.listen(function() { - var port = server.address().port; - - test._fake_port = port; - done(); - }); -}); - -before('set up localtunnel client', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert.ifError(err); - var url = tunnel.url; - assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); - test._fake_url = url; - done(err); - }); -}); - -test('query localtunnel server w/ ident', function(done) { - var uri = test._fake_url; - var hostname = url.parse(uri).hostname; - - var opt = { - host: 'localhost', - port: lt_server_port, - headers: { - host: hostname + '.tld' - }, - path: '/' - } - - var req = http.request(opt, function(res) { - res.setEncoding('utf8'); - var body = ''; - - res.on('data', function(chunk) { - body += chunk; - }); - - res.on('end', function() { - assert.equal('foo', body); - - // TODO(shtylman) shutdown client - done(); - }); - }); - - req.end(); -}); - -test('request specific domain', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - subdomain: 'abcd' - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert.ifError(err); - var url = tunnel.url; - assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); - test._fake_url = url; - done(err); - }); -}); - -test('request domain with dash', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - subdomain: 'abcd-1234' - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert.ifError(err); - var url = tunnel.url; - assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); - test._fake_url = url; - done(err); - }); -}); - -test('request domain that is too long', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - subdomain: 'thisdomainisoutsidethesizeofwhatweallowwhichissixtythreecharacters' - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert(err); - assert.equal(err.message, 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); - done(); - }); -}); - -test('request uppercase domain', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - subdomain: 'ABCD' - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert(err); - assert.equal(err.message, 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'); - done(); - }); -}); - -after('shutdown', function() { - localtunnel_server.close(); -}); diff --git a/test/domain.js b/test/domain.js deleted file mode 100644 index 6fa2b02..0000000 --- a/test/domain.js +++ /dev/null @@ -1,52 +0,0 @@ -import http from 'http'; -import url from 'url'; -import assert from 'assert'; -import localtunnel from 'localtunnel'; - -import CreateServer from '../server'; - -const localtunnel_server = CreateServer({ - domain: 'domain.example.com', -}); - -process.on('uncaughtException', (err) => { - console.error(err); -}); - -process.on('unhandledRejection', (reason, promise) => { - console.error(reason); -}); - -suite('domain'); - -var lt_server_port; - -before('set up localtunnel server', function(done) { - var server = localtunnel_server.listen(function() { - lt_server_port = server.address().port; - done(); - }); -}); - -test('landing page', function(done) { - var opt = { - host: 'localhost', - port: lt_server_port, - headers: { - host: 'domain.example.com' - }, - path: '/' - } - - var req = http.request(opt, function(res) { - res.setEncoding('utf8'); - assert.equal(res.headers.location, 'https://localtunnel.github.io/www/') - done(); - }); - - req.end(); -}); - -after('shutdown', function() { - localtunnel_server.close(); -}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index ce1c914..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---check-leaks ---reporter spec ---ui qunit ---require esm diff --git a/test/queue.js b/test/queue.js deleted file mode 100644 index b945586..0000000 --- a/test/queue.js +++ /dev/null @@ -1,105 +0,0 @@ -import http from 'http'; -import url from 'url'; -import assert from 'assert'; -import localtunnel from 'localtunnel'; - -import CreateServer from '../server'; - -suite('queue'); - -var localtunnel_server = CreateServer({ - max_tcp_sockets: 1 -}); - -var server; -var lt_server_port; - -before('set up localtunnel server', function(done) { - var lt_server = localtunnel_server.listen(function() { - lt_server_port = lt_server.address().port; - done(); - }); -}); - -before('set up local http server', function(done) { - server = http.createServer(); - server.on('request', function(req, res) { - // respond sometime later - setTimeout(function() { - res.setHeader('x-count', req.headers['x-count']); - res.end('foo'); - }, 500); - }); - - server.listen(function() { - var port = server.address().port; - - test._fake_port = port; - done(); - }); -}); - -before('set up localtunnel client', function(done) { - var opt = { - host: 'http://localhost:' + lt_server_port, - }; - - localtunnel(test._fake_port, opt, function(err, tunnel) { - assert.ifError(err); - var url = tunnel.url; - assert.ok(new RegExp('^http:\/\/.*localhost:' + lt_server_port + '$').test(url)); - test._fake_url = url; - done(err); - }); -}); - -test('query localtunnel server w/ ident', function(done) { - var uri = test._fake_url; - var hostname = url.parse(uri).hostname; - - var count = 0; - var opt = { - host: 'localhost', - port: lt_server_port, - agent: false, - headers: { - host: hostname + '.tld' - }, - path: '/' - } - - var num_requests = 2; - var responses = 0; - - function maybe_done() { - if (++responses >= num_requests) { - done(); - } - } - - function make_req() { - opt.headers['x-count'] = count++; - http.get(opt, function(res) { - res.setEncoding('utf8'); - var body = ''; - - res.on('data', function(chunk) { - body += chunk; - }); - - res.on('end', function() { - assert.equal('foo', body); - maybe_done(); - }); - }); - } - - for (var i=0 ; i= 1.3.1 < 2" - -http-signature@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" - dependencies: - asn1 "0.1.11" - assert-plus "^0.1.5" - ctype "0.5.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" human-readable-ids@1.0.3: version "1.0.3" @@ -451,14 +367,17 @@ indexof@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" -inherits@2, inherits@2.0.3, inherits@~2.0.1: +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -475,33 +394,14 @@ is-finite@^1.0.0: dependencies: number-is-nan "^1.0.0" -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - is-generator-function@^1.0.3: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.6.tgz#9e71653cd15fff341c79c4151460a131d31e9fc4" - -is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" is-object@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -518,25 +418,6 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - -jade@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" - dependencies: - commander "0.6.1" - mkdirp "0.3.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - keygrip@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91" @@ -566,9 +447,9 @@ koa-is-json@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" -koa@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/koa/-/koa-2.4.1.tgz#d449cfb970a7e9da571f699eda40bb9e32eb1484" +koa@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.5.1.tgz#79f8b95f8d72d04fe9a58a8da5ebd6d341103f9c" dependencies: accepts "^1.2.2" content-disposition "~0.5.0" @@ -595,12 +476,6 @@ koa@2.4.1: type-is "^1.5.5" vary "^1.0.0" -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - dependencies: - invert-kv "^1.0.0" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -617,15 +492,6 @@ localenv@0.2.2: dependencies: commander "2.5.0" -localtunnel@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.8.0.tgz#e8feba5fa015bfa2cc1c5daff0973eaf3adf3337" - dependencies: - debug "2.2.0" - openurl "1.1.0" - request "2.65.0" - yargs "3.29.0" - lodash._arraycopy@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1" @@ -699,10 +565,6 @@ lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" -lodash@^4.14.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -710,17 +572,13 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" -marked-terminal@^1.6.2: - version "1.7.0" - resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-1.7.0.tgz#c8c460881c772c7604b64367007ee5f77f125904" +marked-terminal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-2.0.0.tgz#5eaf568be66f686541afa52a558280310a31de2d" dependencies: cardinal "^1.0.0" chalk "^1.1.3" @@ -728,9 +586,9 @@ marked-terminal@^1.6.2: lodash.assign "^4.2.0" node-emoji "^1.4.1" -marked@^0.3.6: - version "0.3.7" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.7.tgz#80ef3bbf1bd00d1c9cfebe42ba1b8c85da258d0d" +marked@^0.3.12: + version "0.3.19" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" media-typer@0.3.0: version "0.3.0" @@ -751,22 +609,29 @@ meow@^3.3.0: redent "^1.0.0" trim-newlines "^1.0.0" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +methods@^1.1.1, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -mime-types@^2.0.7, mime-types@^2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - dependencies: - mime-db "~1.30.0" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -minimatch@0.3: - version "0.3.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" +mime-types@^2.0.7, mime-types@^2.1.12, mime-types@~2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: - lru-cache "2" - sigmund "~1.0.0" + mime-db "~1.33.0" + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + +minimatch@3.0.4, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" minimist@0.0.8: version "0.0.8" @@ -780,47 +645,32 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -mkdirp@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" - mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" -mocha@2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" +mocha@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.1.1.tgz#b774c75609dac05eb48f4d9ba1d827b97fde8a7b" dependencies: - commander "2.3.0" - debug "2.2.0" - diff "1.4.0" - escape-string-regexp "1.0.2" - glob "3.2.11" - growl "1.9.2" - jade "0.26.3" + browser-stdout "1.3.1" + commander "2.11.0" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + minimatch "3.0.4" mkdirp "0.5.1" - supports-color "1.2.0" - to-iso-string "0.0.2" - -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + supports-color "4.4.0" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -nan@^2.0.5: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" - -nan@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232" - negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -854,10 +704,6 @@ node-notifier@^4.0.2: shellwords "^0.1.0" which "^1.0.5" -node-uuid@~1.4.3: - version "1.4.8" - resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" - normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -871,10 +717,6 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -oauth-sign@~0.8.0: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -887,13 +729,13 @@ object-keys@~0.2.0: indexof "~0.0.1" is "~0.2.6" -on-finished@2.3.0, on-finished@^2.1.0: +on-finished@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" dependencies: ee-first "1.1.1" -once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -903,10 +745,6 @@ only@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" -openurl@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.0.tgz#e2f2189d999c04823201f083f0f1a7cd8903187a" - optimist@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -914,16 +752,6 @@ optimist@0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - dependencies: - lcid "^1.0.0" - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -940,6 +768,10 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -966,24 +798,17 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - -pump@2.0.0: +process-nextick-args@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.0.tgz#7946da1c8d622b098e2ceb2d3476582470829c9d" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" +qs@^6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" read-pkg-up@^1.0.1: version "1.0.1" @@ -1000,15 +825,16 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" +readable-stream@^2.0.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: core-util-is "~1.0.0" - inherits "~2.0.1" + inherits "~2.0.3" isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" util-deprecate "~1.0.1" redent@^1.0.0: @@ -1030,101 +856,69 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@2.65.0: - version "2.65.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.65.0.tgz#cc1a3bc72b96254734fc34296da322f9486ddeba" - dependencies: - aws-sign2 "~0.6.0" - bl "~1.0.0" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~1.0.0-rc3" - har-validator "~2.0.2" - hawk "~3.1.0" - http-signature "~0.11.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - node-uuid "~1.4.3" - oauth-sign "~0.8.0" - qs "~5.2.0" - stringstream "~0.0.4" - tough-cookie "~2.2.0" - tunnel-agent "~0.4.1" - resolve@^1.0.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" dependencies: path-parse "^1.0.5" -"semver@2 || 3 || 4 || 5", semver@^5.1.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +"semver@2 || 3 || 4 || 5", semver@^5.1.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" shellwords@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - hoek "2.x.x" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" dependencies: - spdx-license-ids "^1.0.2" + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" - -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" stackframe@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" -"statuses@>= 1.3.1 < 2", statuses@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.4.0 < 2", statuses@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" + safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - -stringstream@~0.0.4: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: +strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" dependencies: @@ -1142,9 +936,33 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -supports-color@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" +superagent@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.1.1" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.0.5" + +supertest@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.1.0.tgz#f9ebaf488e60f2176021ec580bdd23ad269e7bc6" + dependencies: + methods "~1.1.2" + superagent "3.8.2" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" supports-color@^2.0.0: version "2.0.0" @@ -1156,50 +974,27 @@ tldjs@2.3.1: dependencies: punycode "^1.4.1" -to-iso-string@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" - -tough-cookie@~2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.2.2.tgz#c83a1830f4e5ef0b93ef2a3488e724f8de016ac7" - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - type-is@^1.5.5: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: media-typer "0.3.0" - mime-types "~2.1.15" - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - -utf-8-validate@1.2.x: - version "1.2.2" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-1.2.2.tgz#8bb871a4741e085c70487ca7acdbd7d6d36029eb" - dependencies: - bindings "~1.2.1" - nan "~2.4.0" + mime-types "~2.1.18" util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" vary@^1.0.0: version "1.1.2" @@ -1211,38 +1006,19 @@ which@^1.0.5: dependencies: isexe "^2.0.0" -window-size@^0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -ws@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-0.8.0.tgz#ac60ebad312121d01e16cc3383d7ec67ad0f0f1f" +ws@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.1.1.tgz#1d43704689711ac1942fd2f283e38f825c4b8b95" dependencies: - options ">=0.0.5" - ultron "1.0.x" - optionalDependencies: - bufferutil "1.2.x" - utf-8-validate "1.2.x" - -xtend@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + async-limiter "~1.0.0" xtend@~2.0.6: version "2.0.6" @@ -1250,18 +1026,3 @@ xtend@~2.0.6: dependencies: is-object "~0.1.2" object-keys "~0.2.0" - -y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - -yargs@3.29.0: - version "3.29.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.29.0.tgz#1aab9660eae79d8b8f675bcaeeab6ee34c2cf69c" - dependencies: - camelcase "^1.2.1" - cliui "^3.0.3" - decamelize "^1.0.0" - os-locale "^1.4.0" - window-size "^0.1.2" - y18n "^3.2.0" -- 2.54.0 From 743895720c0517d16a83b4e52a524ed5f6dac512 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Tue, 15 May 2018 19:18:12 -0400 Subject: [PATCH 41/45] Dockerfile: update to node v10 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fb8ac3c..ddda30b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:9.2.1-alpine +FROM node:10.1.0-alpine WORKDIR /app -- 2.54.0 From 317db73bdcdcfb23f64056b64da9ba4034374775 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Wed, 16 May 2018 10:21:56 -0400 Subject: [PATCH 42/45] use pump to pipe sockets Ensures that destination socket close or destroy also does the same for the source socket. --- lib/Client.js | 32 +++++++++++++--- lib/ClientManager.js | 11 +++++- lib/ClientManager.test.js | 8 +++- lib/TunnelAgent.js | 78 +++++++++++++++++++-------------------- package.json | 1 + server.js | 2 +- yarn.lock | 15 +++++++- 7 files changed, 97 insertions(+), 50 deletions(-) diff --git a/lib/Client.js b/lib/Client.js index aae3df0..ccb7274 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -1,6 +1,6 @@ import http from 'http'; - -import TunnelAgent from './TunnelAgent'; +import Debug from 'debug'; +import pump from 'pump'; // A client encapsulates req/res handling using an agent // @@ -9,9 +9,11 @@ import TunnelAgent from './TunnelAgent'; class Client { constructor(options) { this.agent = options.agent; + this.debug = Debug('lt:Client'); } handleRequest(req, res) { + this.debug('> %s', req.url); const opt = { path: req.url, agent: this.agent, @@ -20,23 +22,38 @@ class Client { }; const clientReq = http.request(opt, (clientRes) => { + this.debug('< %s', req.url); // write response code and headers res.writeHead(clientRes.statusCode, clientRes.headers); - clientRes.pipe(res); + + // using pump is deliberate - see the pump docs for why + pump(clientRes, res); }); // this can happen when underlying agent produces an error // in our case we 504 gateway error this? // if we have already sent headers? clientReq.once('error', (err) => { - + // TODO(roman): if headers not sent - respond with gateway unavailable }); - req.pipe(clientReq); + // using pump is deliberate - see the pump docs for why + pump(req, clientReq); } handleUpgrade(req, socket) { + this.debug('> [up] %s', req.url); + socket.once('error', (err) => { + // These client side errors can happen if the client dies while we are reading + // We don't need to surface these in our logs. + if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') { + return; + } + console.error(err); + }); + this.agent.createConnection({}, (err, conn) => { + this.debug('< [up] %s', req.url); // any errors getting a connection mean we cannot service this request if (err) { socket.end(); @@ -45,6 +62,7 @@ class Client { // socket met have disconnected while we waiting for a socket if (!socket.readable || !socket.writable) { + conn.destroy(); socket.end(); return; } @@ -60,7 +78,9 @@ class Client { arr.push(''); arr.push(''); - conn.pipe(socket).pipe(conn); + // using pump is deliberate - see the pump docs for why + pump(conn, socket); + pump(socket, conn); conn.write(arr.join('\r\n')); }); } diff --git a/lib/ClientManager.js b/lib/ClientManager.js index b41270f..6279982 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -20,6 +20,8 @@ class ClientManager { }; this.debug = Debug('lt:ClientManager'); + + this.graceTimeout = null; } // create a new tunnel with `id` @@ -36,11 +38,13 @@ class ClientManager { const maxSockets = this.opt.max_tcp_sockets; const agent = new TunnelAgent({ + clientId: id, maxSockets: 10, }); agent.on('online', () => { this.debug('client online %s', id); + clearTimeout(this.graceTimeout); }); agent.on('offline', () => { @@ -48,7 +52,11 @@ class ClientManager { // this period is short as the client is expected to maintain connections actively // if they client does not reconnect on a dropped connection they need to re-establish this.debug('client offline %s', id); - this.removeClient(id); + + // client is given a grace period in which they can re-connect before they are _removed_ + this.graceTimeout = setTimeout(() => { + this.removeClient(id); + }, 1000); }); // TODO(roman): an agent error removes the client, the user needs to re-connect? @@ -81,6 +89,7 @@ class ClientManager { } removeClient(id) { + this.debug('removing client: %s', id); const client = this.clients[id]; if (!client) { return; diff --git a/lib/ClientManager.test.js b/lib/ClientManager.test.js index 63eb60e..3282553 100644 --- a/lib/ClientManager.test.js +++ b/lib/ClientManager.test.js @@ -46,6 +46,12 @@ describe('ClientManager', () => { const closePromise = new Promise(resolve => socket.once('close', resolve)); socket.end(); await closePromise; + + // should still have client - grace period has not expired + assert(manager.hasClient('foobar')); + + // wait past grace period (1s) + await new Promise(resolve => setTimeout(resolve, 1500)); assert(!manager.hasClient('foobar')); - }); + }).timeout(5000); }); diff --git a/lib/TunnelAgent.js b/lib/TunnelAgent.js index 215b1f8..f940aa5 100644 --- a/lib/TunnelAgent.js +++ b/lib/TunnelAgent.js @@ -25,10 +25,10 @@ class TunnelAgent extends Agent { // once a socket is available it is handed out to the next callback this.waitingCreateConn = []; - this.debug = Debug('lt:TunnelAgent'); + this.debug = Debug(`lt:TunnelAgent[${options.clientId}]`); // track maximum allowed sockets - this.activeSockets = 0; + this.connectedSockets = 0; this.maxTcpSockets = options.maxTcpSockets || DEFAULT_MAX_SOCKETS; // new tcp server to service requests for this client @@ -36,6 +36,7 @@ class TunnelAgent extends Agent { // flag to avoid double starts this.started = false; + this.closed = false; } listen() { @@ -48,8 +49,7 @@ class TunnelAgent extends Agent { server.on('close', this._onClose.bind(this)); server.on('connection', this._onConnection.bind(this)); server.on('error', (err) => { - // where do these errors come from? - // other side creates a connection and then is killed? + // These errors happen from killed connections, we don't worry about them if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') { return; } @@ -70,11 +70,12 @@ class TunnelAgent extends Agent { } _onClose() { + this.closed = true; this.debug('closed tcp socket'); - clearTimeout(this.connTimeout); - // we will not invoke these callbacks? - // TODO(roman): we could invoke these with errors...? - // this makes downstream have to handle this + // flush any waiting connections + for (const conn of this.waitingCreateConn) { + conn(new Error('closed'), null); + } this.waitingCreateConn = []; this.emit('end'); } @@ -82,37 +83,23 @@ class TunnelAgent extends Agent { // new socket connection from client for tunneling requests to client _onConnection(socket) { // no more socket connections allowed - if (this.activeSockets >= this.maxTcpSockets) { + if (this.connectedSockets >= this.maxTcpSockets) { this.debug('no more sockets allowed'); socket.destroy(); return false; } - // a new socket becomes available - if (this.activeSockets == 0) { - this.emit('online'); - } - - this.activeSockets += 1; - this.debug('new connection from: %s:%s', socket.address().address, socket.address().port); - - // a single connection is enough to keep client id slot open - clearTimeout(this.connTimeout); - - socket.once('close', (had_error) => { - this.debug('closed socket (error: %s)', had_error); - this.debug('removing socket'); - this.activeSockets -= 1; + socket.once('close', (hadError) => { + this.debug('closed socket (error: %s)', hadError); + this.connectedSockets -= 1; // remove the socket from available list const idx = this.availableSockets.indexOf(socket); if (idx >= 0) { this.availableSockets.splice(idx, 1); } - // need to track total sockets, not just active available - this.debug('remaining client sockets: %s', this.availableSockets.length); - // no more sockets for this session - // the session will become inactive if client does not reconnect - if (this.availableSockets.length <= 0) { + + this.debug('connected sockets: %s', this.connectedSockets); + if (this.connectedSockets <= 0) { this.debug('all sockets disconnected'); this.emit('offline'); } @@ -125,28 +112,38 @@ class TunnelAgent extends Agent { socket.destroy(); }); - // make socket available for those waiting on sockets - this.availableSockets.push(socket); + if (this.connectedSockets === 0) { + this.emit('online'); + } - // flush anyone waiting on sockets - this._callWaitingCreateConn(); - } + this.connectedSockets += 1; + this.debug('new connection from: %s:%s', socket.address().address, socket.address().port); - // invoke when a new socket is available and there may be waiting createConnection calls - _callWaitingCreateConn() { + // if there are queued callbacks, give this socket now and don't queue into available const fn = this.waitingCreateConn.shift(); - if (!fn) { + if (fn) { + this.debug('giving socket to queued conn request'); + setTimeout(() => { + fn(null, socket); + }, 0); return; } - this.debug('handling queued request'); - this.createConnection({}, fn); + // make socket available for those waiting on sockets + this.availableSockets.push(socket); } // fetch a socket from the available socket pool for the agent // if no socket is available, queue // cb(err, socket) createConnection(options, cb) { + if (this.closed) { + cb(new Error('closed')); + return; + } + + this.debug('create connection'); + // socket is a tcp connection back to the user hosting the site const sock = this.availableSockets.shift(); @@ -154,7 +151,8 @@ class TunnelAgent extends Agent { // wait until we have one if (!sock) { this.waitingCreateConn.push(cb); - this.debug('waiting'); + this.debug('waiting connected: %s', this.connectedSockets); + this.debug('waiting available: %s', this.availableSockets.length); return; } diff --git a/package.json b/package.json index 1624eef..047eb25 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "koa": "2.5.1", "localenv": "0.2.2", "optimist": "0.6.1", + "pump": "3.0.0", "tldjs": "2.3.1" }, "devDependencies": { diff --git a/server.js b/server.js index c5c395a..fb5124b 100644 --- a/server.js +++ b/server.js @@ -146,7 +146,7 @@ export default function(opt) { const client = manager.getClient(clientId); if (!client) { - sock.destroy(); + socket.destroy(); return; } diff --git a/yarn.lock b/yarn.lock index 9c33483..eba9339 100644 --- a/yarn.lock +++ b/yarn.lock @@ -217,6 +217,12 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + error-ex@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" @@ -735,7 +741,7 @@ on-finished@^2.1.0: dependencies: ee-first "1.1.1" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -802,6 +808,13 @@ process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" +pump@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -- 2.54.0 From cf070d4ce99852cf110ccbca8b0ed23515b984b7 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Wed, 16 May 2018 16:41:05 -0400 Subject: [PATCH 43/45] add /api/tunnels/:id/status endpoint Provides connected_sockets information for a tunnel. --- lib/Client.js | 4 ++++ lib/TunnelAgent.js | 6 ++++++ lib/TunnelAgent.test.js | 7 +++++++ package.json | 1 + server.js | 29 ++++++++++++++++++++--------- server.test.js | 24 ++++++++++++++++++++++++ yarn.lock | 29 +++++++++++++++++++++++++++-- 7 files changed, 89 insertions(+), 11 deletions(-) diff --git a/lib/Client.js b/lib/Client.js index ccb7274..16b522e 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -12,6 +12,10 @@ class Client { this.debug = Debug('lt:Client'); } + stats() { + return this.agent.stats(); + } + handleRequest(req, res) { this.debug('> %s', req.url); const opt = { diff --git a/lib/TunnelAgent.js b/lib/TunnelAgent.js index f940aa5..efc2231 100644 --- a/lib/TunnelAgent.js +++ b/lib/TunnelAgent.js @@ -39,6 +39,12 @@ class TunnelAgent extends Agent { this.closed = false; } + stats() { + return { + connectedSockets: this.connectedSockets, + }; + } + listen() { const server = this.server; if (this.started) { diff --git a/lib/TunnelAgent.test.js b/lib/TunnelAgent.test.js index 62839c0..9505f35 100644 --- a/lib/TunnelAgent.test.js +++ b/lib/TunnelAgent.test.js @@ -173,4 +173,11 @@ describe('TunnelAgent', () => { }); assert.equal(err.message, 'foo'); }); + + it('should return stats', async () => { + const agent = new TunnelAgent(); + assert.deepEqual(agent.stats(), { + connectedSockets: 0, + }); + }); }); diff --git a/package.json b/package.json index 047eb25..0693b8d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "esm": "3.0.34", "human-readable-ids": "1.0.3", "koa": "2.5.1", + "koa-router": "7.4.0", "localenv": "0.2.2", "optimist": "0.6.1", "pump": "3.0.0", diff --git a/server.js b/server.js index fb5124b..5a53c47 100644 --- a/server.js +++ b/server.js @@ -4,6 +4,7 @@ import tldjs from 'tldjs'; import Debug from 'debug'; import http from 'http'; import { hri } from 'human-readable-ids'; +import Router from 'koa-router'; import ClientManager from './lib/ClientManager'; @@ -25,23 +26,33 @@ export default function(opt) { const schema = opt.secure ? 'https' : 'http'; const app = new Koa(); + const router = new Router(); - // api status endpoint - app.use(async (ctx, next) => { - const path = ctx.request.path; - if (path !== '/api/status') { - await next(); - return; - } - + router.get('/api/status', async (ctx, next) => { const stats = manager.stats; - ctx.body = { tunnels: stats.tunnels, mem: process.memoryUsage(), }; }); + router.get('/api/tunnels/:id/status', async (ctx, next) => { + const clientId = ctx.params.id; + const client = manager.getClient(clientId); + if (!client) { + ctx.throw(404); + return; + } + + const stats = client.stats(); + ctx.body = { + connected_sockets: stats.connectedSockets, + }; + }); + + app.use(router.routes()); + app.use(router.allowedMethods()); + // root endpoint app.use(async (ctx, next) => { const path = ctx.request.path; diff --git a/server.test.js b/server.test.js index 3f72f2d..10bf8bc 100644 --- a/server.test.js +++ b/server.test.js @@ -82,4 +82,28 @@ describe('Server', () => { wss.close(); await new Promise(resolve => server.close(resolve)); }); + + it('should support the /api/tunnels/:id/status endpoint', async () => { + const server = createServer(); + await new Promise(resolve => server.listen(resolve)); + + // no such tunnel yet + const res = await request(server).get('/api/tunnels/foobar-test/status'); + assert.equal(res.statusCode, 404); + + // request a new client called foobar-test + { + const res = await request(server).get('/foobar-test'); + } + + { + const res = await request(server).get('/api/tunnels/foobar-test/status'); + assert.equal(res.statusCode, 200); + assert.deepEqual(res.body, { + connected_sockets: 0, + }); + } + + await new Promise(resolve => server.close(resolve)); + }); }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index eba9339..9ad744b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -348,7 +348,7 @@ http-assert@^1.1.0: deep-equal "~1.0.1" http-errors "~1.6.1" -http-errors@^1.2.8, http-errors@~1.6.1: +http-errors@^1.2.8, http-errors@^1.3.1, http-errors@~1.6.1: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" dependencies: @@ -416,6 +416,10 @@ is@~0.2.6: version "0.2.7" resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -453,6 +457,17 @@ koa-is-json@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" +koa-router@7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/koa-router/-/koa-router-7.4.0.tgz#aee1f7adc02d5cb31d7d67465c9eacc825e8c5e0" + dependencies: + debug "^3.1.0" + http-errors "^1.3.1" + koa-compose "^3.0.0" + methods "^1.0.1" + path-to-regexp "^1.1.1" + urijs "^1.19.0" + koa@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/koa/-/koa-2.5.1.tgz#79f8b95f8d72d04fe9a58a8da5ebd6d341103f9c" @@ -615,7 +630,7 @@ meow@^3.3.0: redent "^1.0.0" trim-newlines "^1.0.0" -methods@^1.1.1, methods@~1.1.2: +methods@^1.0.1, methods@^1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -782,6 +797,12 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-to-regexp@^1.1.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -998,6 +1019,10 @@ type-is@^1.5.5: media-typer "0.3.0" mime-types "~2.1.18" +urijs@^1.19.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -- 2.54.0 From d9fca62c17c10f24a1fa15d5130e05e61267a44e Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Wed, 16 May 2018 17:07:58 -0400 Subject: [PATCH 44/45] grace period disconnect should be per-client --- lib/Client.js | 45 ++++++++++++++++++++++++++++++++++++--- lib/Client.test.js | 1 - lib/ClientManager.js | 35 +++++++++--------------------- lib/ClientManager.test.js | 33 ++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/lib/Client.js b/lib/Client.js index 16b522e..369eb07 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -1,21 +1,60 @@ import http from 'http'; import Debug from 'debug'; import pump from 'pump'; +import EventEmitter from 'events'; // A client encapsulates req/res handling using an agent // // If an agent is destroyed, the request handling will error // The caller is responsible for handling a failed request -class Client { +class Client extends EventEmitter { constructor(options) { - this.agent = options.agent; - this.debug = Debug('lt:Client'); + super(); + + const agent = this.agent = options.agent; + const id = this.id = options.id; + + this.debug = Debug(`lt:Client[${this.id}]`); + + // client is given a grace period in which they can connect before they are _removed_ + this.graceTimeout = setTimeout(() => { + this.close(); + }, 1000).unref(); + + agent.on('online', () => { + this.debug('client online %s', id); + clearTimeout(this.graceTimeout); + }); + + agent.on('offline', () => { + this.debug('client offline %s', id); + + // if there was a previous timeout set, we don't want to double trigger + clearTimeout(this.graceTimeout); + + // client is given a grace period in which they can re-connect before they are _removed_ + this.graceTimeout = setTimeout(() => { + this.close(); + }, 1000).unref(); + }); + + // TODO(roman): an agent error removes the client, the user needs to re-connect? + // how does a user realize they need to re-connect vs some random client being assigned same port? + agent.once('error', (err) => { + this.close(); + }); } stats() { return this.agent.stats(); } + close() { + clearTimeout(this.graceTimeout); + this.agent.destroy(); + this.emit('close'); + } + handleRequest(req, res) { this.debug('> %s', req.url); const opt = { diff --git a/lib/Client.test.js b/lib/Client.test.js index 8f53be1..5a0afd0 100644 --- a/lib/Client.test.js +++ b/lib/Client.test.js @@ -1,7 +1,6 @@ import assert from 'assert'; import http from 'http'; import { Duplex } from 'stream'; -import EventEmitter from 'events'; import WebSocket from 'ws'; import net from 'net'; diff --git a/lib/ClientManager.js b/lib/ClientManager.js index 6279982..e1a7838 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -21,6 +21,7 @@ class ClientManager { this.debug = Debug('lt:ClientManager'); + // This is totally wrong :facepalm: this needs to be per-client... this.graceTimeout = null; } @@ -42,35 +43,19 @@ class ClientManager { maxSockets: 10, }); - agent.on('online', () => { - this.debug('client online %s', id); - clearTimeout(this.graceTimeout); + const client = new Client({ + id, + agent, }); - agent.on('offline', () => { - // TODO(roman): grace period for re-connecting - // this period is short as the client is expected to maintain connections actively - // if they client does not reconnect on a dropped connection they need to re-establish - this.debug('client offline %s', id); - - // client is given a grace period in which they can re-connect before they are _removed_ - this.graceTimeout = setTimeout(() => { - this.removeClient(id); - }, 1000); - }); - - // TODO(roman): an agent error removes the client, the user needs to re-connect? - // how does a user realize they need to re-connect vs some random client being assigned same port? - agent.once('error', (err) => { - this.removeClient(id); - }); - - const client = new Client({ agent }); - // add to clients map immediately // avoiding races with other clients requesting same id clients[id] = client; + client.once('close', () => { + this.removeClient(id); + }); + // try/catch used here to remove client id try { const info = await agent.listen(); @@ -96,11 +81,11 @@ class ClientManager { } --this.stats.tunnels; delete this.clients[id]; - client.agent.destroy(); + client.close(); } hasClient(id) { - return this.clients[id]; + return !!this.clients[id]; } getClient(id) { diff --git a/lib/ClientManager.test.js b/lib/ClientManager.test.js index 3282553..d85abe5 100644 --- a/lib/ClientManager.test.js +++ b/lib/ClientManager.test.js @@ -54,4 +54,37 @@ describe('ClientManager', () => { await new Promise(resolve => setTimeout(resolve, 1500)); assert(!manager.hasClient('foobar')); }).timeout(5000); + + it('should remove correct client once it goes offline', async () => { + const manager = new ClientManager(); + const clientFoo = await manager.newClient('foo'); + const clientBar = await manager.newClient('bar'); + + const socket = await new Promise((resolve) => { + const netClient = net.createConnection({ port: clientFoo.port }, () => { + resolve(netClient); + }); + }); + + await new Promise(resolve => setTimeout(resolve, 1500)); + + // foo should still be ok + assert(manager.hasClient('foo')); + + // clientBar shound be removed - nothing connected to it + assert(!manager.hasClient('bar')); + + manager.removeClient('foo'); + socket.end(); + }).timeout(5000); + + it('should remove clients if they do not connect within 5 seconds', async () => { + const manager = new ClientManager(); + const clientFoo = await manager.newClient('foo'); + assert(manager.hasClient('foo')); + + // wait past grace period (1s) + await new Promise(resolve => setTimeout(resolve, 1500)); + assert(!manager.hasClient('foo')); + }).timeout(5000); }); -- 2.54.0 From 78c06f8fc43ce0eacea2aeb88f2d1d63afaf4e19 Mon Sep 17 00:00:00 2001 From: Jo Hyun Woo Date: Wed, 27 Mar 2019 00:56:17 +0900 Subject: [PATCH 45/45] fix typo in server.js (#109) `sock` is not defined in the scope. --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 5a53c47..21a4af6 100644 --- a/server.js +++ b/server.js @@ -145,13 +145,13 @@ export default function(opt) { server.on('upgrade', (req, socket, head) => { const hostname = req.headers.host; if (!hostname) { - sock.destroy(); + socket.destroy(); return; } const clientId = GetClientIdFromHostname(hostname); if (!clientId) { - sock.destroy(); + socket.destroy(); return; } -- 2.54.0