ref
This commit is contained in:
parent
660b2f7fbb
commit
1e09fcfb64
@ -1,3 +1,3 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 8.11.3
|
||||
- 10.15.3
|
||||
|
@ -2,10 +2,9 @@ FROM node:alpine
|
||||
RUN mkdir /peer-server
|
||||
WORKDIR /peer-server
|
||||
COPY package.json .
|
||||
COPY bin ./bin
|
||||
COPY lib ./lib
|
||||
COPY src ./src
|
||||
COPY app.json .
|
||||
RUN npm install
|
||||
EXPOSE 9000
|
||||
ENTRYPOINT ["node", "bin/peerjs"]
|
||||
ENTRYPOINT ["node", "index.js"]
|
||||
CMD [ "--port", "9000" ]
|
||||
|
98
bin/peerjs
98
bin/peerjs
@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var path = require('path')
|
||||
, pkg = require('../package.json')
|
||||
, fs = require('fs')
|
||||
, version = pkg.version
|
||||
, PeerServer = require('../lib').PeerServer
|
||||
, util = require('../lib/util')
|
||||
, opts = require('optimist')
|
||||
.usage('Usage: $0')
|
||||
.options({
|
||||
debug: {
|
||||
demand: false,
|
||||
alias: 'd',
|
||||
description: 'debug',
|
||||
default: false
|
||||
},
|
||||
timeout: {
|
||||
demand: false,
|
||||
alias: 't',
|
||||
description: 'timeout (milliseconds)',
|
||||
default: 5000
|
||||
},
|
||||
ip_limit: {
|
||||
demand: false,
|
||||
alias: 'i',
|
||||
description: 'IP limit',
|
||||
default: 5000
|
||||
},
|
||||
concurrent_limit: {
|
||||
demand: false,
|
||||
alias: 'c',
|
||||
description: 'concurrent limit',
|
||||
default: 5000
|
||||
},
|
||||
key: {
|
||||
demand: false,
|
||||
alias: 'k',
|
||||
description: 'connection key',
|
||||
default: 'peerjs'
|
||||
},
|
||||
sslkey: {
|
||||
demand: false,
|
||||
description: 'path to SSL key'
|
||||
},
|
||||
sslcert: {
|
||||
demand: false,
|
||||
description: 'path to SSL certificate'
|
||||
},
|
||||
port: {
|
||||
demand: true,
|
||||
alias: 'p',
|
||||
description: 'port'
|
||||
},
|
||||
path: {
|
||||
demand: false,
|
||||
description: 'custom path',
|
||||
default: '/'
|
||||
},
|
||||
allow_discovery: {
|
||||
demand: false,
|
||||
description: 'allow discovery of peers'
|
||||
}
|
||||
})
|
||||
.boolean('allow_discovery')
|
||||
.argv;
|
||||
|
||||
process.on('uncaughtException', function(e) {
|
||||
console.error('Error: ' + e);
|
||||
});
|
||||
|
||||
if (opts.sslkey || opts.sslcert) {
|
||||
if (opts.sslkey && opts.sslcert) {
|
||||
opts.ssl = {
|
||||
key: fs.readFileSync(path.resolve(opts.sslkey)),
|
||||
cert: fs.readFileSync(path.resolve(opts.sslcert))
|
||||
}
|
||||
|
||||
delete opts.sslkey;
|
||||
delete opts.sslcert;
|
||||
} else {
|
||||
util.prettyError('Warning: PeerServer will not run because either ' +
|
||||
'the key or the certificate has not been provided.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var userPath = opts.path;
|
||||
var server = PeerServer(opts, function(server) {
|
||||
var host = server.address().address;
|
||||
var port = server.address().port;
|
||||
|
||||
console.log(
|
||||
'Started PeerServer on %s, port: %s, path: %s (v. %s)',
|
||||
host, port, userPath || '/', version
|
||||
);
|
||||
});
|
5
config/index.js
Normal file
5
config/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
const config = require('./schema');
|
||||
|
||||
config.validate({ allowed: 'strict' });
|
||||
|
||||
module.exports = config;
|
74
config/schema.js
Normal file
74
config/schema.js
Normal file
@ -0,0 +1,74 @@
|
||||
const convict = require('convict');
|
||||
|
||||
module.exports = convict({
|
||||
debug: {
|
||||
doc: 'Enable debug mode',
|
||||
format: Boolean,
|
||||
default: false
|
||||
},
|
||||
env: {
|
||||
doc: 'The application environment.',
|
||||
format: ['prod', 'dev', 'test'],
|
||||
default: 'dev',
|
||||
env: 'NODE_ENV'
|
||||
},
|
||||
port: {
|
||||
doc: 'The port to bind.',
|
||||
format: 'port',
|
||||
default: 9000,
|
||||
env: 'PORT',
|
||||
arg: 'port'
|
||||
},
|
||||
timeout: {
|
||||
doc: '',
|
||||
format: 'duration',
|
||||
default: 5000
|
||||
},
|
||||
key: {
|
||||
doc: 'The key to check incoming clients',
|
||||
format: String,
|
||||
default: 'peerjs'
|
||||
},
|
||||
path: {
|
||||
doc: '',
|
||||
format: String,
|
||||
default: '/'
|
||||
},
|
||||
ip_limit: {
|
||||
doc: 'Max connections per ip',
|
||||
format: 'duration',
|
||||
default: 100
|
||||
},
|
||||
concurrent_limit: {
|
||||
doc: 'Max connections',
|
||||
format: 'duration',
|
||||
default: 5000
|
||||
},
|
||||
allow_discovery: {
|
||||
doc: 'Allow discovery of peers',
|
||||
format: Boolean,
|
||||
default: false
|
||||
},
|
||||
proxied: {
|
||||
doc: 'Set true if server running behind proxy',
|
||||
format: Boolean,
|
||||
default: false
|
||||
},
|
||||
cleanup_out_msgs: {
|
||||
doc: '',
|
||||
format: 'duration',
|
||||
default: 5000
|
||||
},
|
||||
ssl: {
|
||||
key_path: {
|
||||
doc: 'The path to the private key file',
|
||||
format: String,
|
||||
default: ''
|
||||
},
|
||||
cert_path: {
|
||||
doc: 'The path to the cert file',
|
||||
format: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
});
|
99
lib/index.js
99
lib/index.js
@ -1,99 +0,0 @@
|
||||
var express = require('express');
|
||||
var proto = require('./server');
|
||||
var util = require('./util');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
|
||||
function ExpressPeerServer(server, options) {
|
||||
var app = express();
|
||||
|
||||
util.extend(app, proto);
|
||||
|
||||
options = app._options = util.extend({
|
||||
debug: false,
|
||||
timeout: 5000,
|
||||
key: 'peerjs',
|
||||
ip_limit: 5000,
|
||||
concurrent_limit: 5000,
|
||||
allow_discovery: false,
|
||||
proxied: false
|
||||
}, options);
|
||||
|
||||
// Connected clients
|
||||
app._clients = {};
|
||||
|
||||
// Messages waiting for another peer.
|
||||
app._outstanding = {};
|
||||
|
||||
// Mark concurrent users per ip
|
||||
app._ips = {};
|
||||
|
||||
if (options.proxied) {
|
||||
app.set('trust proxy', options.proxied);
|
||||
}
|
||||
|
||||
app.on('mount', function() {
|
||||
if (!server) {
|
||||
throw new Error('Server is not passed to constructor - '+
|
||||
'can\'t start PeerServer');
|
||||
}
|
||||
|
||||
// Initialize HTTP routes. This is only used for the first few milliseconds
|
||||
// before a socket is opened for a Peer.
|
||||
app._initializeHTTP();
|
||||
app._setCleanupIntervals();
|
||||
app._initializeWSS(server);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
function PeerServer(options, callback) {
|
||||
var app = express();
|
||||
|
||||
options = options || {};
|
||||
var path = options.path || '/';
|
||||
var port = options.port || 80;
|
||||
|
||||
delete options.path;
|
||||
|
||||
if (path[0] !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
if (path[path.length - 1] !== '/') {
|
||||
path += '/';
|
||||
}
|
||||
|
||||
var server;
|
||||
if (options.ssl) {
|
||||
if (options.ssl.certificate) {
|
||||
// Preserve compatibility with 0.2.7 API
|
||||
options.ssl.cert = options.ssl.certificate;
|
||||
delete options.ssl.certificate;
|
||||
}
|
||||
|
||||
server = https.createServer(options.ssl, app);
|
||||
delete options.ssl;
|
||||
} else {
|
||||
server = http.createServer(app);
|
||||
}
|
||||
|
||||
var peerjs = ExpressPeerServer(server, options);
|
||||
app.use(path, peerjs);
|
||||
|
||||
if (callback) {
|
||||
server.listen(port, function() {
|
||||
callback(server);
|
||||
});
|
||||
} else {
|
||||
server.listen(port);
|
||||
}
|
||||
|
||||
return peerjs;
|
||||
}
|
||||
|
||||
exports = module.exports = {
|
||||
ExpressPeerServer: ExpressPeerServer,
|
||||
PeerServer: PeerServer
|
||||
};
|
422
lib/server.js
422
lib/server.js
@ -1,422 +0,0 @@
|
||||
var util = require("./util");
|
||||
var bodyParser = require("body-parser");
|
||||
var WebSocketServer = require("ws").Server;
|
||||
var url = require("url");
|
||||
var cors = require("cors");
|
||||
|
||||
var app = (exports = module.exports = {});
|
||||
|
||||
/** Initialize WebSocket server. */
|
||||
app._initializeWSS = function(server) {
|
||||
var self = this;
|
||||
|
||||
if (this.mountpath instanceof Array) {
|
||||
throw new Error("This app can only be mounted on a single path");
|
||||
}
|
||||
|
||||
var path = this.mountpath;
|
||||
var path = path + (path[path.length - 1] != "/" ? "/" : "") + "peerjs";
|
||||
|
||||
// Create WebSocket server as well.
|
||||
this._wss = new WebSocketServer({ path: path, server: server });
|
||||
|
||||
this._wss.on("connection", function(socket, req) {
|
||||
var query = url.parse(req.url, true).query;
|
||||
var id = query.id;
|
||||
var token = query.token;
|
||||
var key = query.key;
|
||||
var ip = req.socket.remoteAddress;
|
||||
|
||||
if (!id || !token || !key) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "ERROR",
|
||||
payload: { msg: "No id, token, or key supplied to websocket server" }
|
||||
})
|
||||
);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self._clients[key] || !self._clients[key][id]) {
|
||||
self._checkKey(key, ip, function(err) {
|
||||
if (!err) {
|
||||
if (!self._clients[key][id]) {
|
||||
self._clients[key][id] = { token: token, ip: ip };
|
||||
self._ips[ip]++;
|
||||
socket.send(JSON.stringify({ type: "OPEN" }));
|
||||
}
|
||||
self._configureWS(socket, key, id, token);
|
||||
} else {
|
||||
socket.send(JSON.stringify({ type: "ERROR", payload: { msg: err } }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self._configureWS(socket, key, id, token);
|
||||
}
|
||||
});
|
||||
|
||||
this._wss.on("error", function (err) {
|
||||
// handle error
|
||||
})
|
||||
};
|
||||
|
||||
app._configureWS = function(socket, key, id, token) {
|
||||
var self = this;
|
||||
var client = this._clients[key][id];
|
||||
|
||||
if (token === client.token) {
|
||||
// res 'close' event will delete client.res for us
|
||||
client.socket = socket;
|
||||
// Client already exists
|
||||
if (client.res) {
|
||||
client.res.end();
|
||||
}
|
||||
} else {
|
||||
// ID-taken, invalid token
|
||||
socket.send(
|
||||
JSON.stringify({ type: "ID-TAKEN", payload: { msg: "ID is taken" } })
|
||||
);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this._processOutstanding(key, id);
|
||||
|
||||
// Cleanup after a socket closes.
|
||||
socket.on("close", function() {
|
||||
self._log("Socket closed:", id);
|
||||
if (client.socket == socket) {
|
||||
self._removePeer(key, id);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle messages from peers.
|
||||
socket.on("message", function(data) {
|
||||
try {
|
||||
var message = JSON.parse(data);
|
||||
|
||||
if (
|
||||
["LEAVE", "CANDIDATE", "OFFER", "ANSWER"].indexOf(message.type) !== -1
|
||||
) {
|
||||
self._handleTransmission(key, {
|
||||
type: message.type,
|
||||
src: id,
|
||||
dst: message.dst,
|
||||
payload: message.payload
|
||||
});
|
||||
} else if (message.type === 'HEARTBEAT') {
|
||||
// Ignore - nothing needs doing here.
|
||||
} else {
|
||||
util.prettyError("Message unrecognized");
|
||||
}
|
||||
} catch (e) {
|
||||
self._log("Invalid message", data);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
// We're going to emit here, because for XHR we don't *know* when someone
|
||||
// disconnects.
|
||||
this.emit("connection", id);
|
||||
};
|
||||
|
||||
app._checkAllowsDiscovery = function(key, cb) {
|
||||
cb(this._options.allow_discovery);
|
||||
};
|
||||
|
||||
app._checkKey = function(key, ip, cb) {
|
||||
if (key == this._options.key) {
|
||||
if (!this._clients[key]) {
|
||||
this._clients[key] = {};
|
||||
}
|
||||
if (!this._outstanding[key]) {
|
||||
this._outstanding[key] = {};
|
||||
}
|
||||
if (!this._ips[ip]) {
|
||||
this._ips[ip] = 0;
|
||||
}
|
||||
// Check concurrent limit
|
||||
if (
|
||||
Object.keys(this._clients[key]).length >= this._options.concurrent_limit
|
||||
) {
|
||||
cb("Server has reached its concurrent user limit");
|
||||
return;
|
||||
}
|
||||
if (this._ips[ip] >= this._options.ip_limit) {
|
||||
cb(ip + " has reached its concurrent user limit");
|
||||
return;
|
||||
}
|
||||
cb(null);
|
||||
} else {
|
||||
cb("Invalid key provided");
|
||||
}
|
||||
};
|
||||
|
||||
/** Initialize HTTP server routes. */
|
||||
app._initializeHTTP = function() {
|
||||
var self = this;
|
||||
|
||||
this.use(cors());
|
||||
|
||||
this.get("/", function(req, res, next) {
|
||||
res.send(require("../app.json"));
|
||||
});
|
||||
|
||||
// Retrieve guaranteed random ID.
|
||||
this.get("/:key/id", function(req, res, next) {
|
||||
res.contentType = "text/html";
|
||||
res.send(self._generateClientId(req.params.key));
|
||||
});
|
||||
|
||||
// Server sets up HTTP streaming when you get post an ID.
|
||||
this.post("/:key/:id/:token/id", function(req, res, next) {
|
||||
var id = req.params.id;
|
||||
var token = req.params.token;
|
||||
var key = req.params.key;
|
||||
var ip = req.connection.remoteAddress;
|
||||
|
||||
if (!self._clients[key] || !self._clients[key][id]) {
|
||||
self._checkKey(key, ip, function(err) {
|
||||
if (!err && !self._clients[key][id]) {
|
||||
self._clients[key][id] = { token: token, ip: ip };
|
||||
self._ips[ip]++;
|
||||
self._startStreaming(res, key, id, token, true);
|
||||
} else {
|
||||
res.send(JSON.stringify({ type: "HTTP-ERROR" }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self._startStreaming(res, key, id, token);
|
||||
}
|
||||
});
|
||||
|
||||
// Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
|
||||
this.get("/:key/peers", function(req, res, next) {
|
||||
var key = req.params.key;
|
||||
if (self._clients[key]) {
|
||||
self._checkAllowsDiscovery(key, function(isAllowed) {
|
||||
if (isAllowed) {
|
||||
res.send(Object.keys(self._clients[key]));
|
||||
} else {
|
||||
res.sendStatus(401);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
var handle = function(req, res, next) {
|
||||
var key = req.params.key;
|
||||
var id = req.params.id;
|
||||
|
||||
var client;
|
||||
if (!self._clients[key] || !(client = self._clients[key][id])) {
|
||||
if (req.params.retry) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
} else {
|
||||
// Retry this request
|
||||
req.params.retry = true;
|
||||
setTimeout(handle, 25, req, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Auth the req
|
||||
if (client.token && req.params.token !== client.token) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
} else {
|
||||
self._handleTransmission(key, {
|
||||
type: req.body.type,
|
||||
src: id,
|
||||
dst: req.body.dst,
|
||||
payload: req.body.payload
|
||||
});
|
||||
res.sendStatus(200);
|
||||
}
|
||||
};
|
||||
|
||||
var jsonParser = bodyParser.json();
|
||||
|
||||
this.post("/:key/:id/:token/offer", jsonParser, handle);
|
||||
|
||||
this.post("/:key/:id/:token/candidate", jsonParser, handle);
|
||||
|
||||
this.post("/:key/:id/:token/answer", jsonParser, handle);
|
||||
|
||||
this.post("/:key/:id/:token/leave", jsonParser, handle);
|
||||
};
|
||||
|
||||
/** Saves a streaming response and takes care of timeouts and headers. */
|
||||
app._startStreaming = function(res, key, id, token, open) {
|
||||
var self = this;
|
||||
|
||||
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
||||
|
||||
var pad = "00";
|
||||
for (var i = 0; i < 10; i++) {
|
||||
pad += pad;
|
||||
}
|
||||
res.write(pad + "\n");
|
||||
|
||||
if (open) {
|
||||
res.write(JSON.stringify({ type: "OPEN" }) + "\n");
|
||||
}
|
||||
|
||||
var client = this._clients[key][id];
|
||||
|
||||
if (token === client.token) {
|
||||
// Client already exists
|
||||
res.on("close", function() {
|
||||
if (client.res === res) {
|
||||
if (!client.socket) {
|
||||
// No new request yet, peer dead
|
||||
self._removePeer(key, id);
|
||||
return;
|
||||
}
|
||||
delete client.res;
|
||||
}
|
||||
});
|
||||
client.res = res;
|
||||
this._processOutstanding(key, id);
|
||||
} else {
|
||||
// ID-taken, invalid token
|
||||
res.end(JSON.stringify({ type: "HTTP-ERROR" }));
|
||||
}
|
||||
};
|
||||
|
||||
app._pruneOutstanding = function() {
|
||||
var keys = Object.keys(this._outstanding);
|
||||
for (var k = 0, kk = keys.length; k < kk; k += 1) {
|
||||
var key = keys[k];
|
||||
var dsts = Object.keys(this._outstanding[key]);
|
||||
for (var i = 0, ii = dsts.length; i < ii; i += 1) {
|
||||
var offers = this._outstanding[key][dsts[i]];
|
||||
var seen = {};
|
||||
for (var j = 0, jj = offers.length; j < jj; j += 1) {
|
||||
var message = offers[j];
|
||||
if (!seen[message.src]) {
|
||||
this._handleTransmission(key, {
|
||||
type: "EXPIRE",
|
||||
src: message.dst,
|
||||
dst: message.src
|
||||
});
|
||||
seen[message.src] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._outstanding[key] = {};
|
||||
}
|
||||
};
|
||||
|
||||
/** Cleanup */
|
||||
app._setCleanupIntervals = function() {
|
||||
var self = this;
|
||||
|
||||
// Clean up ips every 10 minutes
|
||||
setInterval(function() {
|
||||
var keys = Object.keys(self._ips);
|
||||
for (var i = 0, ii = keys.length; i < ii; i += 1) {
|
||||
var key = keys[i];
|
||||
if (self._ips[key] === 0) {
|
||||
delete self._ips[key];
|
||||
}
|
||||
}
|
||||
}, 600000);
|
||||
|
||||
// Clean up outstanding messages every 5 seconds
|
||||
setInterval(function() {
|
||||
self._pruneOutstanding();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
/** Process outstanding peer offers. */
|
||||
app._processOutstanding = function(key, id) {
|
||||
var offers = this._outstanding[key][id];
|
||||
if (!offers) {
|
||||
return;
|
||||
}
|
||||
for (var j = 0, jj = offers.length; j < jj; j += 1) {
|
||||
this._handleTransmission(key, offers[j]);
|
||||
}
|
||||
delete this._outstanding[key][id];
|
||||
};
|
||||
|
||||
app._removePeer = function(key, id) {
|
||||
if (this._clients[key] && this._clients[key][id]) {
|
||||
this._ips[this._clients[key][id].ip]--;
|
||||
delete this._clients[key][id];
|
||||
this.emit("disconnect", id);
|
||||
}
|
||||
};
|
||||
|
||||
/** Handles passing on a message. */
|
||||
app._handleTransmission = function(key, message) {
|
||||
var type = message.type;
|
||||
var src = message.src;
|
||||
var dst = message.dst;
|
||||
var data = JSON.stringify(message);
|
||||
|
||||
var destination = this._clients[key][dst];
|
||||
|
||||
// User is connected!
|
||||
if (destination) {
|
||||
try {
|
||||
this._log(type, "from", src, "to", dst);
|
||||
if (destination.socket) {
|
||||
destination.socket.send(data);
|
||||
} else if (destination.res) {
|
||||
data += "\n";
|
||||
destination.res.write(data);
|
||||
} else {
|
||||
// Neither socket no res available. Peer dead?
|
||||
throw "Peer dead";
|
||||
}
|
||||
} catch (e) {
|
||||
// This happens when a peer disconnects without closing connections and
|
||||
// the associated WebSocket has not closed.
|
||||
// Tell other side to stop trying.
|
||||
this._removePeer(key, dst);
|
||||
this._handleTransmission(key, {
|
||||
type: "LEAVE",
|
||||
src: dst,
|
||||
dst: src
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Wait for this client to connect/reconnect (XHR) for important
|
||||
// messages.
|
||||
if (type !== "LEAVE" && type !== "EXPIRE" && dst) {
|
||||
var self = this;
|
||||
if (!this._outstanding[key][dst]) {
|
||||
this._outstanding[key][dst] = [];
|
||||
}
|
||||
this._outstanding[key][dst].push(message);
|
||||
} else if (type === "LEAVE" && !dst) {
|
||||
this._removePeer(key, src);
|
||||
} else {
|
||||
// Unavailable destination specified with message LEAVE or EXPIRE
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
app._generateClientId = function(key) {
|
||||
var clientId = util.randomId();
|
||||
if (!this._clients[key]) {
|
||||
return clientId;
|
||||
}
|
||||
while (!!this._clients[key][clientId]) {
|
||||
clientId = util.randomId();
|
||||
}
|
||||
return clientId;
|
||||
};
|
||||
|
||||
app._log = function() {
|
||||
if (this._options.debug) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
};
|
31
lib/util.js
31
lib/util.js
@ -1,31 +0,0 @@
|
||||
var util = {
|
||||
debug: false,
|
||||
inherits: function(ctor, superCtor) {
|
||||
ctor.super_ = superCtor;
|
||||
ctor.prototype = Object.create(superCtor.prototype, {
|
||||
constructor: {
|
||||
value: ctor,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
},
|
||||
extend: function(dest, source) {
|
||||
source = source || {};
|
||||
for(var key in source) {
|
||||
if(source.hasOwnProperty(key)) {
|
||||
dest[key] = source[key];
|
||||
}
|
||||
}
|
||||
return dest;
|
||||
},
|
||||
randomId: function () {
|
||||
return (Math.random().toString(36) + '0000000000000000000').substr(2, 16);
|
||||
},
|
||||
prettyError: function (msg) {
|
||||
console.log('ERROR PeerServer: ', msg);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = util;
|
39
package.json
39
package.json
@ -2,33 +2,32 @@
|
||||
"name": "peer",
|
||||
"version": "0.2.9",
|
||||
"description": "PeerJS server component",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"peerjs": "./bin/peerjs"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/peers/peerjs-server.git"
|
||||
},
|
||||
"author": "Michelle Bu, Eric Zhang",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.3",
|
||||
"express": "^4.16.3",
|
||||
"optimist": "~0.6.1",
|
||||
"ws": "6.0.0",
|
||||
"cors": "~2.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "*",
|
||||
"sinon": "*",
|
||||
"mocha": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha test",
|
||||
"start": "bin/peerjs --port ${PORT:=9000}"
|
||||
"start": "node ./src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.3",
|
||||
"convict": "^4.4.1",
|
||||
"cors": "~2.8.4",
|
||||
"express": "^4.16.3",
|
||||
"log4js": "^4.1.0",
|
||||
"ws": "6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^6.0.2",
|
||||
"chai": "^4.2.0",
|
||||
"semistandard": "^13.0.1",
|
||||
"sinon": "^7.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10"
|
||||
}
|
||||
}
|
||||
|
18
src/api/index.js
Normal file
18
src/api/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bodyParser = require('body-parser');
|
||||
const authMiddleware = require('./middleware/auth');
|
||||
const publicContent = require('../../app.json');
|
||||
|
||||
const app = module.exports = express.Router();
|
||||
|
||||
const jsonParser = bodyParser.json();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.get('/', (req, res, next) => {
|
||||
res.send(publicContent);
|
||||
});
|
||||
|
||||
app.use('/:key', authMiddleware, require('./v1/public'));
|
||||
app.use('/:key/:id/:token', authMiddleware, jsonParser, require('./v1/calls'));
|
23
src/api/middleware/auth/index.js
Normal file
23
src/api/middleware/auth/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
const realm = require('../../../services/realm');
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
const { id, token } = req.params;
|
||||
|
||||
const sendAuthError = () => res.sendStatus(401);
|
||||
|
||||
if (!id) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const client = realm.getRealmByKey(id);
|
||||
|
||||
if (!realm) {
|
||||
return sendAuthError();
|
||||
}
|
||||
|
||||
if (client.getToken() && token !== client.getToken()) {
|
||||
return sendAuthError();
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
42
src/api/v1/calls/index.js
Normal file
42
src/api/v1/calls/index.js
Normal file
@ -0,0 +1,42 @@
|
||||
const express = require('express');
|
||||
// const realm = require('../../../realm');
|
||||
|
||||
const app = module.exports = express.Router();
|
||||
|
||||
// const handle = (req, res, next) => {
|
||||
// var id = req.params.id;
|
||||
|
||||
// let client;
|
||||
// if (!(client = realm.getClientById(id))) {
|
||||
// if (req.params.retry) {
|
||||
// res.sendStatus(401);
|
||||
// return;
|
||||
// } else {
|
||||
// // Retry this request
|
||||
// req.params.retry = true;
|
||||
// setTimeout(handle, 25, req, res);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Auth the req
|
||||
// if (client.token && req.params.token !== client.token) {
|
||||
// res.sendStatus(401);
|
||||
// } else {
|
||||
// self._handleTransmission(key, {
|
||||
// type: req.body.type,
|
||||
// src: id,
|
||||
// dst: req.body.dst,
|
||||
// payload: req.body.payload
|
||||
// });
|
||||
// res.sendStatus(200);
|
||||
// }
|
||||
// };
|
||||
|
||||
// app.post('/:key/:id/:token/offer', jsonParser, handle);
|
||||
|
||||
// app.post('/:key/:id/:token/candidate', jsonParser, handle);
|
||||
|
||||
// app.post('/:key/:id/:token/answer', jsonParser, handle);
|
||||
|
||||
// app.post('/:key/:id/:token/leave', jsonParser, handle);
|
65
src/api/v1/public/index.js
Normal file
65
src/api/v1/public/index.js
Normal file
@ -0,0 +1,65 @@
|
||||
const express = require('express');
|
||||
const realm = require('../../../services/realm');
|
||||
const config = require('../../../../config');
|
||||
|
||||
const app = module.exports = express.Router();
|
||||
|
||||
const randomId = () => {
|
||||
return (Math.random().toString(36) + '0000000000000000000').substr(2, 16);
|
||||
};
|
||||
|
||||
const generateClientId = (key) => {
|
||||
let clientId = randomId();
|
||||
|
||||
const realm = realmsCache.getRealmByKey(key);
|
||||
if (!realm) {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
while (realm.getClientById(clientId)) {
|
||||
clientId = randomId();
|
||||
}
|
||||
|
||||
return clientId;
|
||||
};
|
||||
|
||||
// Retrieve guaranteed random ID.
|
||||
app.get('/id', (req, res, next) => {
|
||||
const { key } = req.params;
|
||||
|
||||
res.contentType = 'text/html';
|
||||
res.send(generateClientId(key));
|
||||
});
|
||||
|
||||
// Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
|
||||
app.get('/peers', (req, res, next) => {
|
||||
if (config.get('allow_discovery')) {
|
||||
const clientsIds = realm.getClientsIds();
|
||||
|
||||
return res.send(clientsIds);
|
||||
}
|
||||
|
||||
res.sendStatus(401);
|
||||
});
|
||||
|
||||
// Server sets up HTTP streaming when you get post an ID.
|
||||
// app.post('/:id/:token/id', (req, res, next) => {
|
||||
// var id = req.params.id;
|
||||
// var token = req.params.token;
|
||||
// var key = req.params.key;
|
||||
// var ip = req.connection.remoteAddress;
|
||||
|
||||
// if (!self._clients[key] || !self._clients[key][id]) {
|
||||
// self._checkKey(key, ip, function (err) {
|
||||
// if (!err && !self._clients[key][id]) {
|
||||
// self._clients[key][id] = { token: token, ip: ip };
|
||||
// self._ips[ip]++;
|
||||
// self._startStreaming(res, key, id, token, true);
|
||||
// } else {
|
||||
// res.send(JSON.stringify({ type: 'HTTP-ERROR' }));
|
||||
// }
|
||||
// });
|
||||
// } else {
|
||||
// self._startStreaming(res, key, id, token);
|
||||
// }
|
||||
// });
|
10
src/enums.js
Normal file
10
src/enums.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports.Errors = {};
|
||||
|
||||
module.exports.MessageType = {
|
||||
LEAVE: 'LEAVE',
|
||||
CANDIDATE: 'CANDIDATE',
|
||||
OFFER: 'OFFER',
|
||||
ANSWER: 'ANSWER',
|
||||
EXPIRE: 'EXPIRE',
|
||||
HEARTBEAT: 'HEARTBEAT'
|
||||
};
|
124
src/index.js
Normal file
124
src/index.js
Normal file
@ -0,0 +1,124 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const config = require('../config');
|
||||
const WebSocketServer = require('./services/webSocketServer');
|
||||
const logger = require('./services/logger');
|
||||
const api = require('./api');
|
||||
const messageHandler = require('./messageHandler');
|
||||
const realm = require('./services/realm');
|
||||
const MessageType = require('./enums');
|
||||
|
||||
// parse config
|
||||
let path = config.get('path');
|
||||
const port = config.get('port');
|
||||
|
||||
if (path[0] !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
if (path[path.length - 1] !== '/') {
|
||||
path += '/';
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
if (config.get('proxied')) {
|
||||
app.set('trust proxy', config.get('proxied'));
|
||||
}
|
||||
|
||||
app.on('mount', () => {
|
||||
if (!server) {
|
||||
throw new Error('Server is not passed to constructor - ' +
|
||||
'can\'t start PeerServer');
|
||||
}
|
||||
|
||||
// TODO
|
||||
app._setCleanupIntervals();
|
||||
|
||||
const wss = new WebSocketServer(server, app.mountpath);
|
||||
|
||||
wss.on('connection', client => {
|
||||
const messages = realm.getMessageQueueById(client.getId());
|
||||
|
||||
messages.forEach(message => messageHandler(client, message));
|
||||
|
||||
realm.clearMessageQueue(client.getId());
|
||||
|
||||
logger.info(`client ${client.getId()} was connected`);
|
||||
});
|
||||
|
||||
wss.on('message', (client, message) => {
|
||||
messageHandler(client, message);
|
||||
});
|
||||
|
||||
wss.on('close', client => {
|
||||
logger.info(`client ${client.getId()} was disconnected`);
|
||||
});
|
||||
|
||||
wss.on('error', error => {
|
||||
logger.error(error);
|
||||
});
|
||||
|
||||
app._wss = wss;
|
||||
});
|
||||
|
||||
let server;
|
||||
|
||||
if (config.get('ssl.key_path') && config.get('ssl.cert_path')) {
|
||||
const keyPath = config.get('ssl.key_path');
|
||||
const certPath = config.get('ssl.cert_path');
|
||||
|
||||
const opts = {
|
||||
key: fs.readFileSync(path.resolve(keyPath)),
|
||||
cert: fs.readFileSync(path.resolve(certPath))
|
||||
};
|
||||
|
||||
server = https.createServer(opts, app);
|
||||
} else {
|
||||
server = http.createServer(app);
|
||||
}
|
||||
|
||||
app.use(path, api);
|
||||
|
||||
server.listen(port, () => {
|
||||
const host = server.address().address;
|
||||
const port = server.address().port;
|
||||
|
||||
logger.info(
|
||||
'Started PeerServer on %s, port: %s',
|
||||
host, port
|
||||
);
|
||||
});
|
||||
|
||||
const pruneOutstanding = () => {
|
||||
const destinationClientsIds = realm.messageQueue.keys();
|
||||
|
||||
for (const destinationClientId of destinationClientsIds) {
|
||||
const messages = realm.getMessageQueueById(destinationClientId);
|
||||
|
||||
const seen = {};
|
||||
|
||||
for (const message of messages) {
|
||||
if (!seen[message.src]) {
|
||||
messageHandler(null, {
|
||||
type: MessageType.EXPIRE,
|
||||
src: message.dst,
|
||||
dst: message.src
|
||||
});
|
||||
seen[message.src] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
realm.messageQueue.clear();
|
||||
|
||||
logger.debug(`message queue was cleared`);
|
||||
};
|
||||
|
||||
// Clean up outstanding messages
|
||||
setInterval(() => {
|
||||
pruneOutstanding();
|
||||
}, config.get('cleanup_out_msgs'));
|
55
src/messageHandler/handlers/transmission/index.js
Normal file
55
src/messageHandler/handlers/transmission/index.js
Normal file
@ -0,0 +1,55 @@
|
||||
const realm = require('../../../services/realm');
|
||||
const logger = require('../../../services/logger');
|
||||
const MessageType = require('../../../enums');
|
||||
|
||||
const handler = (client, message) => {
|
||||
const type = message.type;
|
||||
const srcId = message.src;
|
||||
const dstId = message.dst;
|
||||
|
||||
const destinationClient = realm.getClientById(dstId);
|
||||
|
||||
// User is connected!
|
||||
if (destinationClient) {
|
||||
try {
|
||||
logger.debug(type, 'from', srcId, 'to', dstId);
|
||||
|
||||
if (destinationClient.socket) {
|
||||
const data = JSON.stringify(message);
|
||||
|
||||
destinationClient.socket.send(data);
|
||||
} else {
|
||||
// Neither socket no res available. Peer dead?
|
||||
throw new Error('Peer dead');
|
||||
}
|
||||
} catch (e) {
|
||||
// This happens when a peer disconnects without closing connections and
|
||||
// the associated WebSocket has not closed.
|
||||
// Tell other side to stop trying.
|
||||
if (destinationClient.socket) {
|
||||
destinationClient.socket.close();
|
||||
} else {
|
||||
realm.removeClientById(destinationClient.getId());
|
||||
}
|
||||
|
||||
handler(client, {
|
||||
type: MessageType.LEAVE,
|
||||
src: dstId,
|
||||
dst: srcId
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Wait for this client to connect/reconnect (XHR) for important
|
||||
// messages.
|
||||
if (type !== MessageType.LEAVE && type !== MessageType.EXPIRE && dstId) {
|
||||
realm.addMessageToQueue(dstId, message);
|
||||
} else if (type === MessageType.LEAVE && !dstId) {
|
||||
realm.removeClientById(srcId);
|
||||
} else {
|
||||
// Unavailable destination specified with message LEAVE or EXPIRE
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = handler;
|
40
src/messageHandler/index.js
Normal file
40
src/messageHandler/index.js
Normal file
@ -0,0 +1,40 @@
|
||||
const logger = require('../services/logger');
|
||||
const MessageType = require('../enums');
|
||||
const transmissionHandler = require('./handlers/transmission');
|
||||
|
||||
const handlers = {};
|
||||
|
||||
const registerHandler = (messageType, handler) => {
|
||||
handlers[messageType] = handler;
|
||||
};
|
||||
|
||||
module.exports = (client, message) => {
|
||||
const { type } = message;
|
||||
|
||||
const handler = handlers[type];
|
||||
|
||||
if (!handler) {
|
||||
return logger.error('Message unrecognized');
|
||||
}
|
||||
|
||||
handler(client, message);
|
||||
};
|
||||
|
||||
const handleTransmission = (client, message) => {
|
||||
transmissionHandler(client, {
|
||||
type: message.type,
|
||||
src: client.getId(),
|
||||
dst: message.dst,
|
||||
payload: message.payload
|
||||
});
|
||||
};
|
||||
|
||||
const handleHeartbeat = (client, message) => {
|
||||
|
||||
};
|
||||
|
||||
registerHandler(MessageType.HEARTBEAT, handleHeartbeat);
|
||||
registerHandler(MessageType.OFFER, handleTransmission);
|
||||
registerHandler(MessageType.ANSWER, handleTransmission);
|
||||
registerHandler(MessageType.CANDIDATE, handleTransmission);
|
||||
registerHandler(MessageType.LEAVE, handleTransmission);
|
30
src/models/client.js
Normal file
30
src/models/client.js
Normal file
@ -0,0 +1,30 @@
|
||||
class Client {
|
||||
constructor ({ id, token, ip }) {
|
||||
this.id = id;
|
||||
this.token = token;
|
||||
this.ip = ip;
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
getId () {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getToken () {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
getIp () {
|
||||
return this.ip;
|
||||
}
|
||||
|
||||
setSocket (socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
send (data) {
|
||||
this.socket.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Client;
|
3
src/services/errors/index.js
Normal file
3
src/services/errors/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
INVALID_KEY: 'Invalid key provided'
|
||||
};
|
6
src/services/logger/index.js
Normal file
6
src/services/logger/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
const log4js = require('log4js');
|
||||
|
||||
const logger = log4js.getLogger();
|
||||
logger.level = 'ALL';
|
||||
|
||||
module.exports = logger;
|
44
src/services/realm/index.js
Normal file
44
src/services/realm/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
class Realm {
|
||||
constructor () {
|
||||
this.clients = new Map();
|
||||
this.messageQueue = new Map();
|
||||
}
|
||||
|
||||
getClientsIds () {
|
||||
return [...this.clients.keys()];
|
||||
}
|
||||
|
||||
getClientById (clientId) {
|
||||
return this.clients.get(clientId);
|
||||
}
|
||||
|
||||
setClient (client, id) {
|
||||
this.clients.set(id, client);
|
||||
}
|
||||
|
||||
removeClientById (id) {
|
||||
const client = this.getClientById(id);
|
||||
|
||||
if (!client) return false;
|
||||
|
||||
this.clients.delete(id);
|
||||
}
|
||||
|
||||
getMessageQueueById (id) {
|
||||
return this.messageQueue.get(id);
|
||||
}
|
||||
|
||||
addMessageToQueue (id, message) {
|
||||
if (!this.getMessageQueueById(id)) {
|
||||
this.messageQueue.set(id, []);
|
||||
}
|
||||
|
||||
this.getMessageQueueById(id).push(message);
|
||||
}
|
||||
|
||||
clearMessageQueue (id) {
|
||||
this.messageQueue.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Realm();
|
154
src/services/webSocketServer/index.js
Normal file
154
src/services/webSocketServer/index.js
Normal file
@ -0,0 +1,154 @@
|
||||
const WSS = require('ws').Server;
|
||||
const url = require('url');
|
||||
const EventEmitter = require('events');
|
||||
const logger = require('../logger');
|
||||
|
||||
const config = require('../../../config');
|
||||
const realm = require('../realm');
|
||||
const Client = require('../../models/client');
|
||||
|
||||
class WebSocketServer extends EventEmitter {
|
||||
constructor (server, mountpath) {
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
|
||||
this._ips = {};
|
||||
|
||||
if (mountpath instanceof Array) {
|
||||
throw new Error('This app can only be mounted on a single path');
|
||||
}
|
||||
|
||||
let path = mountpath;
|
||||
path = path + (path[path.length - 1] !== '/' ? '/' : '') + 'peerjs';
|
||||
|
||||
this._wss = new WSS({ path, server });
|
||||
|
||||
this._wss.on('connection', this._onSocketConnection);
|
||||
this._wss.on('error', this._onSocketError);
|
||||
}
|
||||
|
||||
_onSocketConnection (socket, req) {
|
||||
const { query = {} } = url.parse(req.url, true);
|
||||
|
||||
const { id, token, key } = query;
|
||||
|
||||
if (!id || !token || !key) {
|
||||
return this._sendErrorAndClose(socket, 'No id, token, or key supplied to websocket server');
|
||||
}
|
||||
|
||||
if (key !== config.get('key')) {
|
||||
return this._sendErrorAndClose(socket, 'Invalid key provided');
|
||||
}
|
||||
|
||||
const client = realm.getClientById(id);
|
||||
|
||||
if (client) {
|
||||
if (token !== client.getToken()) {
|
||||
// ID-taken, invalid token
|
||||
socket.send(JSON.stringify({
|
||||
type: 'ID-TAKEN',
|
||||
payload: { msg: 'ID is taken' }
|
||||
}));
|
||||
|
||||
return socket.close();
|
||||
}
|
||||
|
||||
return this._configureWS(socket, client);
|
||||
}
|
||||
|
||||
this._registerClient({ socket, id, token });
|
||||
}
|
||||
|
||||
_onSocketError (error) {
|
||||
// handle error
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
_registerClient ({ socket, id, token }) {
|
||||
const ip = socket.remoteAddress;
|
||||
|
||||
if (!this._ips[ip]) {
|
||||
this._ips[ip] = 0;
|
||||
}
|
||||
|
||||
// Check concurrent limit
|
||||
const clientsCount = realm.getClientsIds().length;
|
||||
|
||||
if (clientsCount >= config.get('concurrent_limit')) {
|
||||
return this._sendErrorAndClose(socket, 'Server has reached its concurrent user limit');
|
||||
}
|
||||
|
||||
const connectionsPerIP = this._ips[ip];
|
||||
|
||||
if (connectionsPerIP >= config.get('ip_limit')) {
|
||||
return this._sendErrorAndClose(socket, `${ip} has reached its concurrent user limit`);
|
||||
}
|
||||
|
||||
const oldClient = realm.getClientById(id);
|
||||
|
||||
if (oldClient) {
|
||||
return this._sendErrorAndClose(socket, `${id} already registered`);
|
||||
}
|
||||
|
||||
const newClient = new Client({ id, token, ip });
|
||||
realm.setClient(newClient, id);
|
||||
socket.send(JSON.stringify({ type: 'OPEN' }));
|
||||
this._ips[ip]++;
|
||||
this._configureWS(socket, newClient);
|
||||
}
|
||||
|
||||
_configureWS (socket, client) {
|
||||
if (client.socket && socket !== client.socket) {
|
||||
// TODO remove old ip, add new ip
|
||||
}
|
||||
|
||||
client.setSocket(socket);
|
||||
|
||||
// Cleanup after a socket closes.
|
||||
socket.on('close', () => {
|
||||
logger.info('Socket closed:', client.getId());
|
||||
|
||||
const ip = socket.remoteAddress;
|
||||
|
||||
if (this._ips[ip]) {
|
||||
this._ips[ip]--;
|
||||
|
||||
if (this._ips[ip] === 0) {
|
||||
delete this._ips[ip];
|
||||
}
|
||||
}
|
||||
|
||||
if (client.socket === socket) {
|
||||
realm.removeClientById(client.getId());
|
||||
this.emit('close', client);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle messages from peers.
|
||||
socket.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data);
|
||||
|
||||
this.emit('message', client, message);
|
||||
} catch (e) {
|
||||
logger.error('Invalid message', data);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
this.emit('connection', client);
|
||||
}
|
||||
|
||||
_sendErrorAndClose (socket, msg) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'ERROR',
|
||||
payload: { msg }
|
||||
})
|
||||
);
|
||||
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketServer;
|
@ -1,56 +1,56 @@
|
||||
var ExpressPeerServer = require('../').ExpressPeerServer;
|
||||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
const ExpressPeerServer = require('../').ExpressPeerServer;
|
||||
const { expect } = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe('ExpressPeerServer', function() {
|
||||
describe('method', function() {
|
||||
describe('ExpressPeerServer', function () {
|
||||
describe('method', function () {
|
||||
var p;
|
||||
|
||||
before(function() {
|
||||
p = ExpressPeerServer(undefined, {port: 8000});
|
||||
before(function () {
|
||||
p = ExpressPeerServer(undefined, { port: 8000 });
|
||||
});
|
||||
|
||||
describe('#_checkKey', function() {
|
||||
it('should reject keys that are not the default', function(done) {
|
||||
p._checkKey('bad key', null, function(response) {
|
||||
describe('#_checkKey', function () {
|
||||
it('should reject keys that are not the default', function (done) {
|
||||
p._checkKey('bad key', null, function (response) {
|
||||
expect(response).to.be('Invalid key provided');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept valid key/ip pairs', function(done) {
|
||||
p._checkKey('peerjs', 'myip', function(response) {
|
||||
it('should accept valid key/ip pairs', function (done) {
|
||||
p._checkKey('peerjs', 'myip', function (response) {
|
||||
expect(response).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject ips that are at their limit', function(done) {
|
||||
it('should reject ips that are at their limit', function (done) {
|
||||
p._options.ip_limit = 0;
|
||||
p._checkKey('peerjs', 'myip', function(response) {
|
||||
p._checkKey('peerjs', 'myip', function (response) {
|
||||
expect(response).to.be('myip has reached its concurrent user limit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject when the server is at its limit', function(done) {
|
||||
it('should reject when the server is at its limit', function (done) {
|
||||
p._options.concurrent_limit = 0;
|
||||
p._checkKey('peerjs', 'myip', function(response) {
|
||||
p._checkKey('peerjs', 'myip', function (response) {
|
||||
expect(response).to.be('Server has reached its concurrent user limit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_removePeer', function() {
|
||||
before(function() {
|
||||
var fake = {ip: '0.0.0.0'};
|
||||
describe('#_removePeer', function () {
|
||||
before(function () {
|
||||
var fake = { ip: '0.0.0.0' };
|
||||
p._ips[fake.ip] = 1;
|
||||
p._clients['peerjs'] = {};
|
||||
p._clients['peerjs']['test'] = fake;
|
||||
});
|
||||
|
||||
it('should decrement the number of ips being used and remove the connection', function() {
|
||||
it('should decrement the number of ips being used and remove the connection', function () {
|
||||
expect(p._ips['0.0.0.0']).to.be(1);
|
||||
p._removePeer('peerjs', 'test');
|
||||
expect(p._ips['0.0.0.0']).to.be(0);
|
||||
@ -58,94 +58,94 @@ describe('ExpressPeerServer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_handleTransmission', function() {
|
||||
describe('#_handleTransmission', function () {
|
||||
var KEY = 'peerjs';
|
||||
var ID = 'test';
|
||||
|
||||
before(function() {
|
||||
before(function () {
|
||||
p._clients[KEY] = {};
|
||||
});
|
||||
|
||||
it('should send to the socket when appropriate', function() {
|
||||
it('should send to the socket when appropriate', function () {
|
||||
var send = sinon.spy();
|
||||
var write = sinon.spy();
|
||||
var message = {dst: ID};
|
||||
var message = { dst: ID };
|
||||
p._clients[KEY][ID] = {
|
||||
socket: {
|
||||
send: send
|
||||
send: send
|
||||
},
|
||||
res: {
|
||||
write: write
|
||||
write: write
|
||||
}
|
||||
}
|
||||
};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(send.calledWith(JSON.stringify(message))).to.be(true);
|
||||
expect(write.calledWith(JSON.stringify(message))).to.be(false);
|
||||
});
|
||||
|
||||
it('should write to the response with a newline when appropriate', function() {
|
||||
it('should write to the response with a newline when appropriate', function () {
|
||||
var write = sinon.spy();
|
||||
var message = {dst: ID};
|
||||
var message = { dst: ID };
|
||||
p._clients[KEY][ID] = {
|
||||
res: {
|
||||
write: write
|
||||
write: write
|
||||
}
|
||||
}
|
||||
};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(write.calledWith(JSON.stringify(message) + '\n')).to.be(true);
|
||||
});
|
||||
|
||||
// no destination.
|
||||
it('should push to outstanding messages if the destination is not found', function() {
|
||||
var message = {dst: ID};
|
||||
it('should push to outstanding messages if the destination is not found', function () {
|
||||
var message = { dst: ID };
|
||||
p._outstanding[KEY] = {};
|
||||
p._clients[KEY] = {};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(p._outstanding[KEY][ID][0]).to.be(message);
|
||||
});
|
||||
|
||||
it('should not push to outstanding messages if the message is a LEAVE or EXPIRE', function() {
|
||||
var message = {dst: ID, type: 'LEAVE'};
|
||||
it('should not push to outstanding messages if the message is a LEAVE or EXPIRE', function () {
|
||||
var message = { dst: ID, type: 'LEAVE' };
|
||||
p._outstanding[KEY] = {};
|
||||
p._clients[KEY] = {};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(p._outstanding[KEY][ID]).to.be(undefined);
|
||||
|
||||
message = {dst: ID, type: 'EXPIRE'};
|
||||
message = { dst: ID, type: 'EXPIRE' };
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(p._outstanding[KEY][ID]).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should remove the peer if there is no dst in the message', function() {
|
||||
var message = {type: 'LEAVE'};
|
||||
it('should remove the peer if there is no dst in the message', function () {
|
||||
var message = { type: 'LEAVE' };
|
||||
p._removePeer = sinon.spy();
|
||||
p._outstanding[KEY] = {};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(p._removePeer.calledWith(KEY, undefined)).to.be(true);
|
||||
});
|
||||
|
||||
it('should remove the peer and send a LEAVE message if the socket appears to be closed', function() {
|
||||
it('should remove the peer and send a LEAVE message if the socket appears to be closed', function () {
|
||||
var send = sinon.stub().throws();
|
||||
var message = {dst: ID};
|
||||
var leaveMessage = {type: 'LEAVE', dst: undefined, src: ID};
|
||||
var message = { dst: ID };
|
||||
var leaveMessage = { type: 'LEAVE', dst: undefined, src: ID };
|
||||
var oldHandleTransmission = p._handleTransmission;
|
||||
p._removePeer = function() {
|
||||
p._removePeer = function () {
|
||||
// Hacks!
|
||||
p._handleTransmission = sinon.spy();
|
||||
};
|
||||
p._clients[KEY][ID] = {
|
||||
socket: {
|
||||
send: send
|
||||
send: send
|
||||
}
|
||||
}
|
||||
};
|
||||
p._handleTransmission(KEY, message);
|
||||
expect(p._handleTransmission.calledWith(KEY, leaveMessage)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_generateClientId', function() {
|
||||
it('should generate a 16-character ID', function() {
|
||||
expect(p._generateClientId('anykey').length).to.be(16);
|
||||
describe('#_generateClientId', function () {
|
||||
it('should generate a 16-character ID', function () {
|
||||
expect(p._generateClientId('anykey').length).to.be(16);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
581
yarn.lock
581
yarn.lock
@ -1,581 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sinonjs/commons@^1.0.1":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e"
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/formatio@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
|
||||
dependencies:
|
||||
samsam "1.3.0"
|
||||
|
||||
"@sinonjs/samsam@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.0.0.tgz#9163742ac35c12d3602dece74317643b35db6a80"
|
||||
|
||||
accepts@~1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
|
||||
dependencies:
|
||||
mime-types "~2.1.18"
|
||||
negotiator "0.6.1"
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
body-parser@1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
content-type "~1.0.4"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.1"
|
||||
http-errors "~1.6.2"
|
||||
iconv-lite "0.4.19"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.5.1"
|
||||
raw-body "2.3.2"
|
||||
type-is "~1.6.15"
|
||||
|
||||
body-parser@^1.18.3:
|
||||
version "1.18.3"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
content-type "~1.0.4"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
http-errors "~1.6.3"
|
||||
iconv-lite "0.4.23"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.5.2"
|
||||
raw-body "2.3.3"
|
||||
type-is "~1.6.16"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
browser-stdout@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
|
||||
commander@2.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
|
||||
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.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
|
||||
|
||||
content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
|
||||
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"
|
||||
|
||||
cors@~2.8.4:
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686"
|
||||
dependencies:
|
||||
object-assign "^4"
|
||||
vary "^1"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
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"
|
||||
|
||||
depd@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
|
||||
|
||||
depd@~1.1.1, depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
|
||||
diff@3.5.0, diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
|
||||
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.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
|
||||
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.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
|
||||
expect.js@*:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b"
|
||||
|
||||
express@^4.16.3:
|
||||
version "4.16.3"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
|
||||
dependencies:
|
||||
accepts "~1.3.5"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.18.2"
|
||||
content-disposition "0.5.2"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.3.1"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "1.1.1"
|
||||
fresh "0.5.2"
|
||||
merge-descriptors "1.0.1"
|
||||
methods "~1.1.2"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.2"
|
||||
path-to-regexp "0.1.7"
|
||||
proxy-addr "~2.0.3"
|
||||
qs "6.5.1"
|
||||
range-parser "~1.2.0"
|
||||
safe-buffer "5.1.1"
|
||||
send "0.16.2"
|
||||
serve-static "1.13.2"
|
||||
setprototypeof "1.1.0"
|
||||
statuses "~1.4.0"
|
||||
type-is "~1.6.16"
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
finalhandler@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.2"
|
||||
statuses "~1.4.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
||||
glob@7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
growl@1.10.5:
|
||||
version "1.10.5"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
|
||||
he@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||
|
||||
http-errors@1.6.2:
|
||||
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.3"
|
||||
statuses ">= 1.3.1 < 2"
|
||||
|
||||
http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.0"
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
iconv-lite@0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
|
||||
iconv-lite@0.4.23:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
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:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
ipaddr.js@1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
just-extend@^1.1.27:
|
||||
version "1.1.27"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
|
||||
lolex@^2.3.2, lolex@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be"
|
||||
|
||||
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.35.0:
|
||||
version "1.35.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47"
|
||||
|
||||
mime-types@~2.1.18:
|
||||
version "2.1.19"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0"
|
||||
dependencies:
|
||||
mime-db "~1.35.0"
|
||||
|
||||
mime@1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||
|
||||
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"
|
||||
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.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mocha@*:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
|
||||
dependencies:
|
||||
browser-stdout "1.3.1"
|
||||
commander "2.15.1"
|
||||
debug "3.1.0"
|
||||
diff "3.5.0"
|
||||
escape-string-regexp "1.0.5"
|
||||
glob "7.1.2"
|
||||
growl "1.10.5"
|
||||
he "1.1.1"
|
||||
minimatch "3.0.4"
|
||||
mkdirp "0.5.1"
|
||||
supports-color "5.4.0"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
nise@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.2.tgz#a9a3800e3994994af9e452333d549d60f72b8e8c"
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^2.0.0"
|
||||
just-extend "^1.1.27"
|
||||
lolex "^2.3.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
text-encoding "^0.6.4"
|
||||
|
||||
object-assign@^4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
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"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
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"
|
||||
|
||||
parseurl@~1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
|
||||
|
||||
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-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-to-regexp@^1.7.0:
|
||||
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"
|
||||
|
||||
proxy-addr@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93"
|
||||
dependencies:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.8.0"
|
||||
|
||||
qs@6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||
|
||||
qs@6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
||||
range-parser@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||
|
||||
raw-body@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
http-errors "1.6.2"
|
||||
iconv-lite "0.4.19"
|
||||
unpipe "1.0.0"
|
||||
|
||||
raw-body@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
http-errors "1.6.3"
|
||||
iconv-lite "0.4.23"
|
||||
unpipe "1.0.0"
|
||||
|
||||
safe-buffer@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
samsam@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
|
||||
|
||||
send@0.16.2:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
destroy "~1.0.4"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "~1.6.2"
|
||||
mime "1.4.1"
|
||||
ms "2.0.0"
|
||||
on-finished "~2.3.0"
|
||||
range-parser "~1.2.0"
|
||||
statuses "~1.4.0"
|
||||
|
||||
serve-static@1.13.2:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.2"
|
||||
send "0.16.2"
|
||||
|
||||
setprototypeof@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
|
||||
|
||||
setprototypeof@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
||||
|
||||
sinon@*:
|
||||
version "6.1.5"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.1.5.tgz#41451502d43cd5ffb9d051fbf507952400e81d09"
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.0.1"
|
||||
"@sinonjs/formatio" "^2.0.0"
|
||||
"@sinonjs/samsam" "^2.0.0"
|
||||
diff "^3.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
lolex "^2.7.1"
|
||||
nise "^1.4.2"
|
||||
supports-color "^5.4.0"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
|
||||
statuses@~1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||
|
||||
supports-color@5.4.0, supports-color@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
text-encoding@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
|
||||
type-is@~1.6.15, type-is@~1.6.16:
|
||||
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.18"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
|
||||
vary@^1, vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
ws@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.0.0.tgz#eaa494aded00ac4289d455bac8d84c7c651cef35"
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
Loading…
x
Reference in New Issue
Block a user