init
This commit is contained in:
21
node_modules/ws/LICENSE
generated
vendored
Normal file
21
node_modules/ws/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
259
node_modules/ws/README.md
generated
vendored
Normal file
259
node_modules/ws/README.md
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
# ws: a Node.js WebSocket library
|
||||
|
||||
[](https://www.npmjs.com/package/ws)
|
||||
[](https://travis-ci.org/websockets/ws)
|
||||
[](https://ci.appveyor.com/project/lpinca/ws)
|
||||
[](https://coveralls.io/r/websockets/ws?branch=master)
|
||||
|
||||
`ws` is a simple to use, blazing fast, and thoroughly tested WebSocket client
|
||||
and server implementation.
|
||||
|
||||
Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/
|
||||
for the full reports.
|
||||
|
||||
## Protocol support
|
||||
|
||||
* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
|
||||
* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`)
|
||||
|
||||
## Installing
|
||||
|
||||
```
|
||||
npm install --save ws
|
||||
```
|
||||
|
||||
### Opt-in for performance
|
||||
|
||||
There are 2 optional modules that can be installed along side with the `ws`
|
||||
module. These modules are binary addons which improve certain operations, but as
|
||||
they are binary addons they require compilation which can fail if no c++
|
||||
compiler is installed on the host system.
|
||||
|
||||
- `npm install --save bufferutil`: Improves internal buffer operations which
|
||||
allows for faster processing of masked WebSocket frames and general buffer
|
||||
operations.
|
||||
- `npm install --save utf-8-validate`: The specification requires validation of
|
||||
invalid UTF-8 chars, some of these validations could not be done in JavaScript
|
||||
hence the need for a binary addon. In most cases you will already be
|
||||
validating the input that you receive for security purposes leading to double
|
||||
validation. But if you want to be 100% spec-conforming and have fast
|
||||
validation of UTF-8 then this module is a must.
|
||||
|
||||
## API Docs
|
||||
|
||||
See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md)
|
||||
for Node.js-like docs for the ws classes.
|
||||
|
||||
## WebSocket compression
|
||||
|
||||
`ws` supports the [permessage-deflate extension][permessage-deflate] extension
|
||||
which enables the client and server to negotiate a compression algorithm and
|
||||
its parameters, and then selectively apply it to the data payloads of each
|
||||
WebSocket message.
|
||||
|
||||
The extension is enabled by default but adds a significant overhead in terms of
|
||||
performance and memory comsumption. We suggest to use WebSocket compression
|
||||
only if it is really needed.
|
||||
|
||||
To disable the extension you can set the `perMessageDeflate` option to `false`.
|
||||
On the server:
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({
|
||||
perMessageDeflate: false,
|
||||
port: 8080
|
||||
});
|
||||
```
|
||||
|
||||
On the client:
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path', {
|
||||
perMessageDeflate: false
|
||||
});
|
||||
```
|
||||
|
||||
## Usage examples
|
||||
|
||||
### Sending and receiving text data
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path');
|
||||
|
||||
ws.on('open', function open() {
|
||||
ws.send('something');
|
||||
});
|
||||
|
||||
ws.on('message', function incoming(data, flags) {
|
||||
// flags.binary will be set if a binary data is received.
|
||||
// flags.masked will be set if the data was masked.
|
||||
});
|
||||
```
|
||||
|
||||
### Sending binary data
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path');
|
||||
|
||||
ws.on('open', function open() {
|
||||
const array = new Float32Array(5);
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
array[i] = i / 2;
|
||||
}
|
||||
|
||||
ws.send(array);
|
||||
});
|
||||
```
|
||||
|
||||
### Server example
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(message) {
|
||||
console.log('received: %s', message);
|
||||
});
|
||||
|
||||
ws.send('something');
|
||||
});
|
||||
```
|
||||
|
||||
### Broadcast example
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
// Broadcast to all.
|
||||
wss.broadcast = function broadcast(data) {
|
||||
wss.clients.forEach(function each(client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(data) {
|
||||
// Broadcast to everyone else.
|
||||
wss.clients.forEach(function each(client) {
|
||||
if (client !== ws && client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### ExpressJS example
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.send({ msg: "hello" });
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocket.Server({ server });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
const location = url.parse(ws.upgradeReq.url, true);
|
||||
// You might use location.query.access_token to authenticate or share sessions
|
||||
// or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
|
||||
|
||||
ws.on('message', function incoming(message) {
|
||||
console.log('received: %s', message);
|
||||
});
|
||||
|
||||
ws.send('something');
|
||||
});
|
||||
|
||||
server.listen(8080, function listening() {
|
||||
console.log('Listening on %d', server.address().port);
|
||||
});
|
||||
```
|
||||
|
||||
### echo.websocket.org demo
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('wss://echo.websocket.org/', {
|
||||
origin: 'https://websocket.org'
|
||||
});
|
||||
|
||||
ws.on('open', function open() {
|
||||
console.log('connected');
|
||||
ws.send(Date.now());
|
||||
});
|
||||
|
||||
ws.on('close', function close() {
|
||||
console.log('disconnected');
|
||||
});
|
||||
|
||||
ws.on('message', function incoming(data, flags) {
|
||||
console.log(`Roundtrip time: ${Date.now() - data} ms`, flags);
|
||||
|
||||
setTimeout(function timeout() {
|
||||
ws.send(Date.now());
|
||||
}, 500);
|
||||
});
|
||||
```
|
||||
|
||||
### Other examples
|
||||
|
||||
For a full example with a browser client communicating with a ws server, see the
|
||||
examples folder.
|
||||
|
||||
Otherwise, see the test cases.
|
||||
|
||||
## Error handling best practices
|
||||
|
||||
```js
|
||||
// If the WebSocket is closed before the following send is attempted
|
||||
ws.send('something');
|
||||
|
||||
// Errors (both immediate and async write errors) can be detected in an optional
|
||||
// callback. The callback is also the only way of being notified that data has
|
||||
// actually been sent.
|
||||
ws.send('something', function ack(error) {
|
||||
// If error is not defined, the send has been completed, otherwise the error
|
||||
// object will indicate what failed.
|
||||
});
|
||||
|
||||
// Immediate errors can also be handled with `try...catch`, but **note** that
|
||||
// since sends are inherently asynchronous, socket write failures will *not* be
|
||||
// captured when this technique is used.
|
||||
try { ws.send('something'); }
|
||||
catch (e) { /* handle error */ }
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
We're using the GitHub [`releases`](https://github.com/websockets/ws/releases)
|
||||
for changelog entries.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[permessage-deflate]: https://tools.ietf.org/html/rfc7692
|
||||
33
node_modules/ws/SECURITY.md
generated
vendored
Normal file
33
node_modules/ws/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Security Guidelines
|
||||
|
||||
Please contact us directly at **security@3rd-Eden.com** for any bug that might
|
||||
impact the security of this project. Please prefix the subject of your email
|
||||
with `[security]` in lowercase and square brackets. Our email filters will
|
||||
automatically prevent these messages from being moved to our spam box.
|
||||
|
||||
You will receive an acknowledgement of your report within **24 hours**.
|
||||
|
||||
All emails that do not include security vulnerabilities will be removed and
|
||||
blocked instantly.
|
||||
|
||||
## Exceptions
|
||||
|
||||
If you do not receive an acknowledgement within the said time frame please give
|
||||
us the benefit of the doubt as it's possible that we haven't seen it yet. In
|
||||
this case please send us a message **without details** using one of the
|
||||
following methods:
|
||||
|
||||
- Contact the lead developers of this project on their personal e-mails. You
|
||||
can find the e-mails in the git logs, for example using the following command:
|
||||
`git --no-pager show -s --format='%an <%ae>' <gitsha>` where `<gitsha>` is the
|
||||
SHA1 of their latest commit in the project.
|
||||
- Create a GitHub issue stating contact details and the severity of the issue.
|
||||
|
||||
Once we have acknowledged receipt of your report and confirmed the bug
|
||||
ourselves we will work with you to fix the vulnerability and publicly acknowledge
|
||||
your responsible disclosure, if you wish. In addition to that we will report
|
||||
all vulnerabilities to the [Node Security Project](https://nodesecurity.io/).
|
||||
|
||||
## History
|
||||
|
||||
04 Jan 2016: [Buffer vulnerablity](https://github.com/websockets/ws/releases/tag/1.0.1)
|
||||
15
node_modules/ws/index.js
generated
vendored
Normal file
15
node_modules/ws/index.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const WebSocket = require('./lib/WebSocket');
|
||||
|
||||
WebSocket.Server = require('./lib/WebSocketServer');
|
||||
WebSocket.Receiver = require('./lib/Receiver');
|
||||
WebSocket.Sender = require('./lib/Sender');
|
||||
|
||||
module.exports = WebSocket;
|
||||
71
node_modules/ws/lib/BufferUtil.js
generated
vendored
Normal file
71
node_modules/ws/lib/BufferUtil.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* Merges an array of buffers into a new buffer.
|
||||
*
|
||||
* @param {Buffer[]} list The array of buffers to concat
|
||||
* @param {Number} totalLength The total length of buffers in the list
|
||||
* @return {Buffer} The resulting buffer
|
||||
* @public
|
||||
*/
|
||||
const concat = (list, totalLength) => {
|
||||
const target = Buffer.allocUnsafe(totalLength);
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
const buf = list[i];
|
||||
buf.copy(target, offset);
|
||||
offset += buf.length;
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
try {
|
||||
const bufferUtil = require('bufferutil');
|
||||
|
||||
module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
/**
|
||||
* Masks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} source The buffer to mask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @param {Buffer} output The buffer where to store the result
|
||||
* @param {Number} offset The offset at which to start writing
|
||||
* @param {Number} length The number of bytes to mask.
|
||||
* @public
|
||||
*/
|
||||
const mask = (source, mask, output, offset, length) => {
|
||||
for (var i = 0; i < length; i++) {
|
||||
output[offset + i] = source[i] ^ mask[i & 3];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unmasks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} buffer The buffer to unmask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @public
|
||||
*/
|
||||
const unmask = (buffer, mask) => {
|
||||
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
|
||||
const length = buffer.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
buffer[i] ^= mask[i & 3];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { concat, mask, unmask };
|
||||
}
|
||||
10
node_modules/ws/lib/Constants.js
generated
vendored
Normal file
10
node_modules/ws/lib/Constants.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
|
||||
exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
exports.EMPTY_BUFFER = Buffer.alloc(0);
|
||||
exports.NOOP = () => {};
|
||||
28
node_modules/ws/lib/ErrorCodes.js
generated
vendored
Normal file
28
node_modules/ws/lib/ErrorCodes.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
isValidErrorCode: function (code) {
|
||||
return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) ||
|
||||
(code >= 3000 && code <= 4999);
|
||||
},
|
||||
1000: 'normal',
|
||||
1001: 'going away',
|
||||
1002: 'protocol error',
|
||||
1003: 'unsupported data',
|
||||
1004: 'reserved',
|
||||
1005: 'reserved for extensions',
|
||||
1006: 'reserved for extensions',
|
||||
1007: 'inconsistent or invalid data',
|
||||
1008: 'policy violation',
|
||||
1009: 'message too big',
|
||||
1010: 'extension handshake missing',
|
||||
1011: 'an unexpected condition prevented the request from being fulfilled',
|
||||
1012: 'service restart',
|
||||
1013: 'try again later'
|
||||
};
|
||||
155
node_modules/ws/lib/EventTarget.js
generated
vendored
Normal file
155
node_modules/ws/lib/EventTarget.js
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class representing an event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class Event {
|
||||
/**
|
||||
* Create a new `Event`.
|
||||
*
|
||||
* @param {String} type The name of the event
|
||||
* @param {Object} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (type, target) {
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a message event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class MessageEvent extends Event {
|
||||
/**
|
||||
* Create a new `MessageEvent`.
|
||||
*
|
||||
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
|
||||
* @param {Boolean} isBinary Specifies if `data` is binary
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (data, isBinary, target) {
|
||||
super('message', target);
|
||||
|
||||
this.binary = isBinary; // non-standard.
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a close event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class CloseEvent extends Event {
|
||||
/**
|
||||
* Create a new `CloseEvent`.
|
||||
*
|
||||
* @param {Number} code The status code explaining why the connection is being closed
|
||||
* @param {String} reason A human-readable string explaining why the connection is closing
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (code, reason, target) {
|
||||
super('close', target);
|
||||
|
||||
this.wasClean = code === undefined || code === 1000;
|
||||
this.reason = reason;
|
||||
this.target = target;
|
||||
this.type = 'close';
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an open event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class OpenEvent extends Event {
|
||||
/**
|
||||
* Create a new `OpenEvent`.
|
||||
*
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (target) {
|
||||
super('open', target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides methods for emulating the `EventTarget` interface. It's not
|
||||
* meant to be used directly.
|
||||
*
|
||||
* @mixin
|
||||
*/
|
||||
const EventTarget = {
|
||||
/**
|
||||
* Register an event listener.
|
||||
*
|
||||
* @param {String} method A string representing the event type to listen for
|
||||
* @param {Function} listener The listener to add
|
||||
* @public
|
||||
*/
|
||||
addEventListener (method, listener) {
|
||||
if (typeof listener !== 'function') return;
|
||||
|
||||
function onMessage (data, flags) {
|
||||
listener.call(this, new MessageEvent(data, !!flags.binary, this));
|
||||
}
|
||||
|
||||
function onClose (code, message) {
|
||||
listener.call(this, new CloseEvent(code, message, this));
|
||||
}
|
||||
|
||||
function onError (event) {
|
||||
event.type = 'error';
|
||||
event.target = this;
|
||||
listener.call(this, event);
|
||||
}
|
||||
|
||||
function onOpen () {
|
||||
listener.call(this, new OpenEvent(this));
|
||||
}
|
||||
|
||||
if (method === 'message') {
|
||||
onMessage._listener = listener;
|
||||
this.on(method, onMessage);
|
||||
} else if (method === 'close') {
|
||||
onClose._listener = listener;
|
||||
this.on(method, onClose);
|
||||
} else if (method === 'error') {
|
||||
onError._listener = listener;
|
||||
this.on(method, onError);
|
||||
} else if (method === 'open') {
|
||||
onOpen._listener = listener;
|
||||
this.on(method, onOpen);
|
||||
} else {
|
||||
this.on(method, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @param {String} method A string representing the event type to remove
|
||||
* @param {Function} listener The listener to remove
|
||||
* @public
|
||||
*/
|
||||
removeEventListener (method, listener) {
|
||||
const listeners = this.listeners(method);
|
||||
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i] === listener || listeners[i]._listener === listener) {
|
||||
this.removeListener(method, listeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
67
node_modules/ws/lib/Extensions.js
generated
vendored
Normal file
67
node_modules/ws/lib/Extensions.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Parse the `Sec-WebSocket-Extensions` header into an object.
|
||||
*
|
||||
* @param {String} value field value of the header
|
||||
* @return {Object} The parsed object
|
||||
* @public
|
||||
*/
|
||||
const parse = (value) => {
|
||||
value = value || '';
|
||||
|
||||
const extensions = {};
|
||||
|
||||
value.split(',').forEach((v) => {
|
||||
const params = v.split(';');
|
||||
const token = params.shift().trim();
|
||||
const paramsList = extensions[token] = extensions[token] || [];
|
||||
const parsedParams = {};
|
||||
|
||||
params.forEach((param) => {
|
||||
const parts = param.trim().split('=');
|
||||
const key = parts[0];
|
||||
var value = parts[1];
|
||||
|
||||
if (value === undefined) {
|
||||
value = true;
|
||||
} else {
|
||||
// unquote value
|
||||
if (value[0] === '"') {
|
||||
value = value.slice(1);
|
||||
}
|
||||
if (value[value.length - 1] === '"') {
|
||||
value = value.slice(0, value.length - 1);
|
||||
}
|
||||
}
|
||||
(parsedParams[key] = parsedParams[key] || []).push(value);
|
||||
});
|
||||
|
||||
paramsList.push(parsedParams);
|
||||
});
|
||||
|
||||
return extensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
|
||||
*
|
||||
* @param {Object} value The object to format
|
||||
* @return {String} A string representing the given value
|
||||
* @public
|
||||
*/
|
||||
const format = (value) => {
|
||||
return Object.keys(value).map((token) => {
|
||||
var paramsList = value[token];
|
||||
if (!Array.isArray(paramsList)) paramsList = [paramsList];
|
||||
return paramsList.map((params) => {
|
||||
return [token].concat(Object.keys(params).map((k) => {
|
||||
var p = params[k];
|
||||
if (!Array.isArray(p)) p = [p];
|
||||
return p.map((v) => v === true ? k : `${k}=${v}`).join('; ');
|
||||
})).join('; ');
|
||||
}).join(', ');
|
||||
}).join(', ');
|
||||
};
|
||||
|
||||
module.exports = { format, parse };
|
||||
384
node_modules/ws/lib/PerMessageDeflate.js
generated
vendored
Normal file
384
node_modules/ws/lib/PerMessageDeflate.js
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
|
||||
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||||
const EMPTY_BLOCK = Buffer.from([0x00]);
|
||||
const DEFAULT_WINDOW_BITS = 15;
|
||||
const DEFAULT_MEM_LEVEL = 8;
|
||||
|
||||
/**
|
||||
* Per-message Deflate implementation.
|
||||
*/
|
||||
class PerMessageDeflate {
|
||||
constructor (options, isServer, maxPayload) {
|
||||
this._options = options || {};
|
||||
this._isServer = !!isServer;
|
||||
this._inflate = null;
|
||||
this._deflate = null;
|
||||
this.params = null;
|
||||
this._maxPayload = maxPayload || 0;
|
||||
this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold;
|
||||
}
|
||||
|
||||
static get extensionName () {
|
||||
return 'permessage-deflate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create extension parameters offer.
|
||||
*
|
||||
* @return {Object} Extension parameters
|
||||
* @public
|
||||
*/
|
||||
offer () {
|
||||
const params = {};
|
||||
|
||||
if (this._options.serverNoContextTakeover) {
|
||||
params.server_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.clientNoContextTakeover) {
|
||||
params.client_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.serverMaxWindowBits) {
|
||||
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||
}
|
||||
if (this._options.clientMaxWindowBits) {
|
||||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
} else if (this._options.clientMaxWindowBits == null) {
|
||||
params.client_max_window_bits = true;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension offer.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @public
|
||||
*/
|
||||
accept (paramsList) {
|
||||
paramsList = this.normalizeParams(paramsList);
|
||||
|
||||
var params;
|
||||
if (this._isServer) {
|
||||
params = this.acceptAsServer(paramsList);
|
||||
} else {
|
||||
params = this.acceptAsClient(paramsList);
|
||||
}
|
||||
|
||||
this.params = params;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all resources used by the extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cleanup () {
|
||||
if (this._inflate) {
|
||||
if (this._inflate.writeInProgress) {
|
||||
this._inflate.pendingClose = true;
|
||||
} else {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
}
|
||||
}
|
||||
if (this._deflate) {
|
||||
if (this._deflate.writeInProgress) {
|
||||
this._deflate.pendingClose = true;
|
||||
} else {
|
||||
this._deflate.close();
|
||||
this._deflate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension offer from client.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsServer (paramsList) {
|
||||
const accepted = {};
|
||||
const result = paramsList.some((params) => {
|
||||
if ((
|
||||
this._options.serverNoContextTakeover === false &&
|
||||
params.server_no_context_takeover
|
||||
) || (
|
||||
this._options.serverMaxWindowBits === false &&
|
||||
params.server_max_window_bits
|
||||
) || (
|
||||
typeof this._options.serverMaxWindowBits === 'number' &&
|
||||
typeof params.server_max_window_bits === 'number' &&
|
||||
this._options.serverMaxWindowBits > params.server_max_window_bits
|
||||
) || (
|
||||
typeof this._options.clientMaxWindowBits === 'number' &&
|
||||
!params.client_max_window_bits
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._options.serverNoContextTakeover ||
|
||||
params.server_no_context_takeover
|
||||
) {
|
||||
accepted.server_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.clientNoContextTakeover) {
|
||||
accepted.client_no_context_takeover = true;
|
||||
}
|
||||
if (
|
||||
this._options.clientNoContextTakeover !== false &&
|
||||
params.client_no_context_takeover
|
||||
) {
|
||||
accepted.client_no_context_takeover = true;
|
||||
}
|
||||
if (typeof this._options.serverMaxWindowBits === 'number') {
|
||||
accepted.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||
} else if (typeof params.server_max_window_bits === 'number') {
|
||||
accepted.server_max_window_bits = params.server_max_window_bits;
|
||||
}
|
||||
if (typeof this._options.clientMaxWindowBits === 'number') {
|
||||
accepted.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
} else if (
|
||||
this._options.clientMaxWindowBits !== false &&
|
||||
typeof params.client_max_window_bits === 'number'
|
||||
) {
|
||||
accepted.client_max_window_bits = params.client_max_window_bits;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result) throw new Error(`Doesn't support the offered configuration`);
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension response from server.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsClient (paramsList) {
|
||||
const params = paramsList[0];
|
||||
|
||||
if (this._options.clientNoContextTakeover != null) {
|
||||
if (
|
||||
this._options.clientNoContextTakeover === false &&
|
||||
params.client_no_context_takeover
|
||||
) {
|
||||
throw new Error('Invalid value for "client_no_context_takeover"');
|
||||
}
|
||||
}
|
||||
if (this._options.clientMaxWindowBits != null) {
|
||||
if (
|
||||
this._options.clientMaxWindowBits === false &&
|
||||
params.client_max_window_bits
|
||||
) {
|
||||
throw new Error('Invalid value for "client_max_window_bits"');
|
||||
}
|
||||
if (
|
||||
typeof this._options.clientMaxWindowBits === 'number' && (
|
||||
!params.client_max_window_bits ||
|
||||
params.client_max_window_bits > this._options.clientMaxWindowBits
|
||||
)) {
|
||||
throw new Error('Invalid value for "client_max_window_bits"');
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize extensions parameters.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Array} Normalized extensions parameters
|
||||
* @private
|
||||
*/
|
||||
normalizeParams (paramsList) {
|
||||
return paramsList.map((params) => {
|
||||
Object.keys(params).forEach((key) => {
|
||||
var value = params[key];
|
||||
if (value.length > 1) {
|
||||
throw new Error(`Multiple extension parameters for ${key}`);
|
||||
}
|
||||
|
||||
value = value[0];
|
||||
|
||||
switch (key) {
|
||||
case 'server_no_context_takeover':
|
||||
case 'client_no_context_takeover':
|
||||
if (value !== true) {
|
||||
throw new Error(`invalid extension parameter value for ${key} (${value})`);
|
||||
}
|
||||
params[key] = true;
|
||||
break;
|
||||
case 'server_max_window_bits':
|
||||
case 'client_max_window_bits':
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value, 10);
|
||||
if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) {
|
||||
throw new Error(`invalid extension parameter value for ${key} (${value})`);
|
||||
}
|
||||
}
|
||||
if (!this._isServer && value === true) {
|
||||
throw new Error(`Missing extension parameter value for ${key}`);
|
||||
}
|
||||
params[key] = value;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Not defined extension parameter (${key})`);
|
||||
}
|
||||
});
|
||||
return params;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
decompress (data, fin, callback) {
|
||||
const endpoint = this._isServer ? 'client' : 'server';
|
||||
|
||||
if (!this._inflate) {
|
||||
const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
|
||||
this._inflate = zlib.createInflateRaw({
|
||||
windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS
|
||||
});
|
||||
}
|
||||
this._inflate.writeInProgress = true;
|
||||
|
||||
var totalLength = 0;
|
||||
const buffers = [];
|
||||
var err;
|
||||
|
||||
const onData = (data) => {
|
||||
totalLength += data.length;
|
||||
if (this._maxPayload < 1 || totalLength <= this._maxPayload) {
|
||||
return buffers.push(data);
|
||||
}
|
||||
|
||||
err = new Error('max payload size exceeded');
|
||||
err.closeCode = 1009;
|
||||
this._inflate.reset();
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
cleanup();
|
||||
callback(err);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (!this._inflate) return;
|
||||
|
||||
this._inflate.removeListener('error', onError);
|
||||
this._inflate.removeListener('data', onData);
|
||||
this._inflate.writeInProgress = false;
|
||||
|
||||
if (
|
||||
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
|
||||
this._inflate.pendingClose
|
||||
) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
}
|
||||
};
|
||||
|
||||
this._inflate.on('error', onError).on('data', onData);
|
||||
this._inflate.write(data);
|
||||
if (fin) this._inflate.write(TRAILER);
|
||||
|
||||
this._inflate.flush(() => {
|
||||
cleanup();
|
||||
if (err) callback(err);
|
||||
else callback(null, bufferUtil.concat(buffers, totalLength));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data.
|
||||
*
|
||||
* @param {Buffer} data Data to compress
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
compress (data, fin, callback) {
|
||||
if (!data || data.length === 0) {
|
||||
process.nextTick(callback, null, EMPTY_BLOCK);
|
||||
return;
|
||||
}
|
||||
|
||||
const endpoint = this._isServer ? 'server' : 'client';
|
||||
|
||||
if (!this._deflate) {
|
||||
const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
|
||||
this._deflate = zlib.createDeflateRaw({
|
||||
flush: zlib.Z_SYNC_FLUSH,
|
||||
windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS,
|
||||
memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL
|
||||
});
|
||||
}
|
||||
this._deflate.writeInProgress = true;
|
||||
|
||||
var totalLength = 0;
|
||||
const buffers = [];
|
||||
|
||||
const onData = (data) => {
|
||||
totalLength += data.length;
|
||||
buffers.push(data);
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
cleanup();
|
||||
callback(err);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (!this._deflate) return;
|
||||
|
||||
this._deflate.removeListener('error', onError);
|
||||
this._deflate.removeListener('data', onData);
|
||||
this._deflate.writeInProgress = false;
|
||||
|
||||
if (
|
||||
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
|
||||
this._deflate.pendingClose
|
||||
) {
|
||||
this._deflate.close();
|
||||
this._deflate = null;
|
||||
}
|
||||
};
|
||||
|
||||
this._deflate.on('error', onError).on('data', onData);
|
||||
this._deflate.write(data);
|
||||
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
|
||||
cleanup();
|
||||
var data = bufferUtil.concat(buffers, totalLength);
|
||||
if (fin) data = data.slice(0, data.length - 4);
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PerMessageDeflate;
|
||||
555
node_modules/ws/lib/Receiver.js
generated
vendored
Normal file
555
node_modules/ws/lib/Receiver.js
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const isValidUTF8 = require('./Validation');
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
const ErrorCodes = require('./ErrorCodes');
|
||||
const constants = require('./Constants');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
const GET_INFO = 0;
|
||||
const GET_PAYLOAD_LENGTH_16 = 1;
|
||||
const GET_PAYLOAD_LENGTH_64 = 2;
|
||||
const GET_MASK = 3;
|
||||
const GET_DATA = 4;
|
||||
const INFLATING = 5;
|
||||
|
||||
/**
|
||||
* HyBi Receiver implementation.
|
||||
*/
|
||||
class Receiver {
|
||||
/**
|
||||
* Creates a Receiver instance.
|
||||
*
|
||||
* @param {Object} extensions An object containing the negotiated extensions
|
||||
* @param {Number} maxPayload The maximum allowed message length
|
||||
* @param {String} binaryType The type for binary data
|
||||
*/
|
||||
constructor (extensions, maxPayload, binaryType) {
|
||||
this.binaryType = binaryType || constants.BINARY_TYPES[0];
|
||||
this.extensions = extensions || {};
|
||||
this.maxPayload = maxPayload | 0;
|
||||
|
||||
this.bufferedBytes = 0;
|
||||
this.buffers = [];
|
||||
|
||||
this.compressed = false;
|
||||
this.payloadLength = 0;
|
||||
this.fragmented = 0;
|
||||
this.masked = false;
|
||||
this.fin = false;
|
||||
this.mask = null;
|
||||
this.opcode = 0;
|
||||
|
||||
this.totalPayloadLength = 0;
|
||||
this.messageLength = 0;
|
||||
this.fragments = [];
|
||||
|
||||
this.cleanupCallback = null;
|
||||
this.hadError = false;
|
||||
this.dead = false;
|
||||
this.loop = false;
|
||||
|
||||
this.onmessage = null;
|
||||
this.onclose = null;
|
||||
this.onerror = null;
|
||||
this.onping = null;
|
||||
this.onpong = null;
|
||||
|
||||
this.state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes bytes from the available buffered data.
|
||||
*
|
||||
* @param {Number} bytes The number of bytes to consume
|
||||
* @return {Buffer} Consumed bytes
|
||||
* @private
|
||||
*/
|
||||
readBuffer (bytes) {
|
||||
var offset = 0;
|
||||
var dst;
|
||||
var l;
|
||||
|
||||
this.bufferedBytes -= bytes;
|
||||
|
||||
if (bytes === this.buffers[0].length) return this.buffers.shift();
|
||||
|
||||
if (bytes < this.buffers[0].length) {
|
||||
dst = this.buffers[0].slice(0, bytes);
|
||||
this.buffers[0] = this.buffers[0].slice(bytes);
|
||||
return dst;
|
||||
}
|
||||
|
||||
dst = Buffer.allocUnsafe(bytes);
|
||||
|
||||
while (bytes > 0) {
|
||||
l = this.buffers[0].length;
|
||||
|
||||
if (bytes >= l) {
|
||||
this.buffers[0].copy(dst, offset);
|
||||
offset += l;
|
||||
this.buffers.shift();
|
||||
} else {
|
||||
this.buffers[0].copy(dst, offset, 0, bytes);
|
||||
this.buffers[0] = this.buffers[0].slice(bytes);
|
||||
}
|
||||
|
||||
bytes -= l;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the number of buffered bytes is bigger or equal than `n` and
|
||||
* calls `cleanup` if necessary.
|
||||
*
|
||||
* @param {Number} n The number of bytes to check against
|
||||
* @return {Boolean} `true` if `bufferedBytes >= n`, else `false`
|
||||
* @private
|
||||
*/
|
||||
hasBufferedBytes (n) {
|
||||
if (this.bufferedBytes >= n) return true;
|
||||
|
||||
this.loop = false;
|
||||
if (this.dead) this.cleanup(this.cleanupCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new data to the parser.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
add (data) {
|
||||
if (this.dead) return;
|
||||
|
||||
this.bufferedBytes += data.length;
|
||||
this.buffers.push(data);
|
||||
this.startLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the parsing loop.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
startLoop () {
|
||||
this.loop = true;
|
||||
|
||||
while (this.loop) {
|
||||
switch (this.state) {
|
||||
case GET_INFO:
|
||||
this.getInfo();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_16:
|
||||
this.getPayloadLength16();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_64:
|
||||
this.getPayloadLength64();
|
||||
break;
|
||||
case GET_MASK:
|
||||
this.getMask();
|
||||
break;
|
||||
case GET_DATA:
|
||||
this.getData();
|
||||
break;
|
||||
default: // `INFLATING`
|
||||
this.loop = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the first two bytes of a frame.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getInfo () {
|
||||
if (!this.hasBufferedBytes(2)) return;
|
||||
|
||||
const buf = this.readBuffer(2);
|
||||
|
||||
if ((buf[0] & 0x30) !== 0x00) {
|
||||
this.error(new Error('RSV2 and RSV3 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
const compressed = (buf[0] & 0x40) === 0x40;
|
||||
|
||||
if (compressed && !this.extensions[PerMessageDeflate.extensionName]) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
this.fin = (buf[0] & 0x80) === 0x80;
|
||||
this.opcode = buf[0] & 0x0f;
|
||||
this.payloadLength = buf[1] & 0x7f;
|
||||
|
||||
if (this.opcode === 0x00) {
|
||||
if (compressed) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fragmented) {
|
||||
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
|
||||
return;
|
||||
} else {
|
||||
this.opcode = this.fragmented;
|
||||
}
|
||||
} else if (this.opcode === 0x01 || this.opcode === 0x02) {
|
||||
if (this.fragmented) {
|
||||
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
this.compressed = compressed;
|
||||
} else if (this.opcode > 0x07 && this.opcode < 0x0b) {
|
||||
if (!this.fin) {
|
||||
this.error(new Error('FIN must be set'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (compressed) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.payloadLength > 0x7d) {
|
||||
this.error(new Error('invalid payload length'), 1002);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fin && !this.fragmented) this.fragmented = this.opcode;
|
||||
|
||||
this.masked = (buf[1] & 0x80) === 0x80;
|
||||
|
||||
if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16;
|
||||
else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64;
|
||||
else this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+16).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength16 () {
|
||||
if (!this.hasBufferedBytes(2)) return;
|
||||
|
||||
this.payloadLength = this.readBuffer(2).readUInt16BE(0, true);
|
||||
this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+64).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength64 () {
|
||||
if (!this.hasBufferedBytes(8)) return;
|
||||
|
||||
const buf = this.readBuffer(8);
|
||||
const num = buf.readUInt32BE(0, true);
|
||||
|
||||
//
|
||||
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
|
||||
// if payload length is greater than this number.
|
||||
//
|
||||
if (num > Math.pow(2, 53 - 32) - 1) {
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return;
|
||||
}
|
||||
|
||||
this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true);
|
||||
this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload length has been read.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
haveLength () {
|
||||
if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.masked) this.state = GET_MASK;
|
||||
else this.state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads mask bytes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getMask () {
|
||||
if (!this.hasBufferedBytes(4)) return;
|
||||
|
||||
this.mask = this.readBuffer(4);
|
||||
this.state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data bytes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getData () {
|
||||
var data = constants.EMPTY_BUFFER;
|
||||
|
||||
if (this.payloadLength) {
|
||||
if (!this.hasBufferedBytes(this.payloadLength)) return;
|
||||
|
||||
data = this.readBuffer(this.payloadLength);
|
||||
if (this.masked) bufferUtil.unmask(data, this.mask);
|
||||
}
|
||||
|
||||
if (this.opcode > 0x07) {
|
||||
this.controlMessage(data);
|
||||
} else if (this.compressed) {
|
||||
this.state = INFLATING;
|
||||
this.decompress(data);
|
||||
} else if (this.pushFragment(data)) {
|
||||
this.dataMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @private
|
||||
*/
|
||||
decompress (data) {
|
||||
const extension = this.extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
extension.decompress(data, this.fin, (err, buf) => {
|
||||
if (err) {
|
||||
this.error(err, err.closeCode === 1009 ? 1009 : 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pushFragment(buf)) this.dataMessage();
|
||||
this.startLoop();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a data message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
dataMessage () {
|
||||
if (this.fin) {
|
||||
const messageLength = this.messageLength;
|
||||
const fragments = this.fragments;
|
||||
|
||||
this.totalPayloadLength = 0;
|
||||
this.messageLength = 0;
|
||||
this.fragmented = 0;
|
||||
this.fragments = [];
|
||||
|
||||
if (this.opcode === 2) {
|
||||
var data;
|
||||
|
||||
if (this.binaryType === 'nodebuffer') {
|
||||
data = toBuffer(fragments, messageLength);
|
||||
} else if (this.binaryType === 'arraybuffer') {
|
||||
data = toArrayBuffer(toBuffer(fragments, messageLength));
|
||||
} else {
|
||||
data = fragments;
|
||||
}
|
||||
|
||||
this.onmessage(data, { masked: this.masked, binary: true });
|
||||
} else {
|
||||
const buf = toBuffer(fragments, messageLength);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
this.error(new Error('invalid utf8 sequence'), 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onmessage(buf.toString(), { masked: this.masked });
|
||||
}
|
||||
}
|
||||
|
||||
this.state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a control message.
|
||||
*
|
||||
* @param {Buffer} data Data to handle
|
||||
* @private
|
||||
*/
|
||||
controlMessage (data) {
|
||||
if (this.opcode === 0x08) {
|
||||
if (data.length === 0) {
|
||||
this.onclose(1000, '', { masked: this.masked });
|
||||
this.loop = false;
|
||||
this.cleanup(this.cleanupCallback);
|
||||
} else if (data.length === 1) {
|
||||
this.error(new Error('invalid payload length'), 1002);
|
||||
} else {
|
||||
const code = data.readUInt16BE(0, true);
|
||||
|
||||
if (!ErrorCodes.isValidErrorCode(code)) {
|
||||
this.error(new Error(`invalid status code: ${code}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
const buf = data.slice(2);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
this.error(new Error('invalid utf8 sequence'), 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onclose(code, buf.toString(), { masked: this.masked });
|
||||
this.loop = false;
|
||||
this.cleanup(this.cleanupCallback);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const flags = { masked: this.masked, binary: true };
|
||||
|
||||
if (this.opcode === 0x09) this.onping(data, flags);
|
||||
else this.onpong(data, flags);
|
||||
|
||||
this.state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Error} err The error
|
||||
* @param {Number} code Close code
|
||||
* @private
|
||||
*/
|
||||
error (err, code) {
|
||||
this.onerror(err, code);
|
||||
this.hadError = true;
|
||||
this.loop = false;
|
||||
this.cleanup(this.cleanupCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks payload size, disconnects socket when it exceeds `maxPayload`.
|
||||
*
|
||||
* @param {Number} length Payload length
|
||||
* @private
|
||||
*/
|
||||
maxPayloadExceeded (length) {
|
||||
if (length === 0 || this.maxPayload < 1) return false;
|
||||
|
||||
const fullLength = this.totalPayloadLength + length;
|
||||
|
||||
if (fullLength <= this.maxPayload) {
|
||||
this.totalPayloadLength = fullLength;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a fragment in the fragments array after checking that the sum of
|
||||
* fragment lengths does not exceed `maxPayload`.
|
||||
*
|
||||
* @param {Buffer} fragment The fragment to add
|
||||
* @return {Boolean} `true` if `maxPayload` is not exceeded, else `false`
|
||||
* @private
|
||||
*/
|
||||
pushFragment (fragment) {
|
||||
if (fragment.length === 0) return true;
|
||||
|
||||
const totalLength = this.messageLength + fragment.length;
|
||||
|
||||
if (this.maxPayload < 1 || totalLength <= this.maxPayload) {
|
||||
this.messageLength = totalLength;
|
||||
this.fragments.push(fragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases resources used by the receiver.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
cleanup (cb) {
|
||||
this.dead = true;
|
||||
|
||||
if (!this.hadError && (this.loop || this.state === INFLATING)) {
|
||||
this.cleanupCallback = cb;
|
||||
} else {
|
||||
this.extensions = null;
|
||||
this.fragments = null;
|
||||
this.buffers = null;
|
||||
this.mask = null;
|
||||
|
||||
this.cleanupCallback = null;
|
||||
this.onmessage = null;
|
||||
this.onclose = null;
|
||||
this.onerror = null;
|
||||
this.onping = null;
|
||||
this.onpong = null;
|
||||
|
||||
if (cb) cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
||||
|
||||
/**
|
||||
* Makes a buffer from a list of fragments.
|
||||
*
|
||||
* @param {Buffer[]} fragments The list of fragments composing the message
|
||||
* @param {Number} messageLength The length of the message
|
||||
* @return {Buffer}
|
||||
* @private
|
||||
*/
|
||||
function toBuffer (fragments, messageLength) {
|
||||
if (fragments.length === 1) return fragments[0];
|
||||
if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
|
||||
return constants.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a buffer to an `ArrayBuffer`.
|
||||
*
|
||||
* @param {Buffer} The buffer to convert
|
||||
* @return {ArrayBuffer} Converted buffer
|
||||
*/
|
||||
function toArrayBuffer (buf) {
|
||||
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
|
||||
return buf.buffer;
|
||||
}
|
||||
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
404
node_modules/ws/lib/Sender.js
generated
vendored
Normal file
404
node_modules/ws/lib/Sender.js
generated
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
const ErrorCodes = require('./ErrorCodes');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* HyBi Sender implementation.
|
||||
*/
|
||||
class Sender {
|
||||
/**
|
||||
* Creates a Sender instance.
|
||||
*
|
||||
* @param {net.Socket} socket The connection socket
|
||||
* @param {Object} extensions An object containing the negotiated extensions
|
||||
*/
|
||||
constructor (socket, extensions) {
|
||||
this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName];
|
||||
this._socket = socket;
|
||||
|
||||
this.firstFragment = true;
|
||||
this.compress = false;
|
||||
|
||||
this.bufferedBytes = 0;
|
||||
this.deflating = false;
|
||||
this.queue = [];
|
||||
|
||||
this.onerror = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a piece of data according to the HyBi WebSocket protocol.
|
||||
*
|
||||
* @param {Buffer} data The data to frame
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
|
||||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
|
||||
* @return {Buffer[]} The framed data as a list of `Buffer` instances
|
||||
* @public
|
||||
*/
|
||||
static frame (data, options) {
|
||||
const merge = data.length < 1024 || (options.mask && options.readOnly);
|
||||
var offset = options.mask ? 6 : 2;
|
||||
var payloadLength = data.length;
|
||||
|
||||
if (data.length >= 65536) {
|
||||
offset += 8;
|
||||
payloadLength = 127;
|
||||
} else if (data.length > 125) {
|
||||
offset += 2;
|
||||
payloadLength = 126;
|
||||
}
|
||||
|
||||
const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
|
||||
|
||||
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
|
||||
if (options.rsv1) target[0] |= 0x40;
|
||||
|
||||
if (payloadLength === 126) {
|
||||
target.writeUInt16BE(data.length, 2, true);
|
||||
} else if (payloadLength === 127) {
|
||||
target.writeUInt32BE(0, 2, true);
|
||||
target.writeUInt32BE(data.length, 6, true);
|
||||
}
|
||||
|
||||
if (!options.mask) {
|
||||
target[1] = payloadLength;
|
||||
if (merge) {
|
||||
data.copy(target, offset);
|
||||
return [target];
|
||||
}
|
||||
|
||||
return [target, data];
|
||||
}
|
||||
|
||||
const mask = crypto.randomBytes(4);
|
||||
|
||||
target[1] = payloadLength | 0x80;
|
||||
target[offset - 4] = mask[0];
|
||||
target[offset - 3] = mask[1];
|
||||
target[offset - 2] = mask[2];
|
||||
target[offset - 1] = mask[3];
|
||||
|
||||
if (merge) {
|
||||
bufferUtil.mask(data, mask, target, offset, data.length);
|
||||
return [target];
|
||||
}
|
||||
|
||||
bufferUtil.mask(data, mask, data, 0, data.length);
|
||||
return [target, data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a close message to the other peer.
|
||||
*
|
||||
* @param {(Number|undefined)} code The status code component of the body
|
||||
* @param {String} data The message component of the body
|
||||
* @param {Boolean} mask Specifies whether or not to mask the message
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
close (code, data, mask, cb) {
|
||||
if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) {
|
||||
throw new Error('first argument must be a valid error code number');
|
||||
}
|
||||
|
||||
const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0));
|
||||
|
||||
buf.writeUInt16BE(code || 1000, 0, true);
|
||||
if (buf.length > 2) buf.write(data, 2);
|
||||
|
||||
if (this.deflating) {
|
||||
this.enqueue([this.doClose, buf, mask, cb]);
|
||||
} else {
|
||||
this.doClose(buf, mask, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a close message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
doClose (data, mask, cb) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
readOnly: false,
|
||||
opcode: 0x08,
|
||||
rsv1: false,
|
||||
fin: true,
|
||||
mask
|
||||
}), cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ping message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @public
|
||||
*/
|
||||
ping (data, mask) {
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.deflating) {
|
||||
this.enqueue([this.doPing, data, mask, readOnly]);
|
||||
} else {
|
||||
this.doPing(data, mask, readOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a ping message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} readOnly Specifies whether `data` can be modified
|
||||
* @private
|
||||
*/
|
||||
doPing (data, mask, readOnly) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
opcode: 0x09,
|
||||
rsv1: false,
|
||||
fin: true,
|
||||
readOnly,
|
||||
mask
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a pong message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @public
|
||||
*/
|
||||
pong (data, mask) {
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.deflating) {
|
||||
this.enqueue([this.doPong, data, mask, readOnly]);
|
||||
} else {
|
||||
this.doPong(data, mask, readOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a pong message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} readOnly Specifies whether `data` can be modified
|
||||
* @private
|
||||
*/
|
||||
doPong (data, mask, readOnly) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
opcode: 0x0a,
|
||||
rsv1: false,
|
||||
fin: true,
|
||||
readOnly,
|
||||
mask
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a data message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Boolean} options.compress Specifies whether or not to compress `data`
|
||||
* @param {Boolean} options.binary Specifies whether `data` is binary or text
|
||||
* @param {Boolean} options.fin Specifies whether the fragment is the last one
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
send (data, options, cb) {
|
||||
var opcode = options.binary ? 2 : 1;
|
||||
var rsv1 = options.compress;
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.firstFragment) {
|
||||
this.firstFragment = false;
|
||||
if (rsv1 && this.perMessageDeflate) {
|
||||
rsv1 = data.length >= this.perMessageDeflate.threshold;
|
||||
}
|
||||
this.compress = rsv1;
|
||||
} else {
|
||||
rsv1 = false;
|
||||
opcode = 0;
|
||||
}
|
||||
|
||||
if (options.fin) this.firstFragment = true;
|
||||
|
||||
if (this.perMessageDeflate) {
|
||||
const opts = {
|
||||
compress: this.compress,
|
||||
mask: options.mask,
|
||||
fin: options.fin,
|
||||
readOnly,
|
||||
opcode,
|
||||
rsv1
|
||||
};
|
||||
|
||||
if (this.deflating) {
|
||||
this.enqueue([this.dispatch, data, opts, cb]);
|
||||
} else {
|
||||
this.dispatch(data, opts, cb);
|
||||
}
|
||||
} else {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
mask: options.mask,
|
||||
fin: options.fin,
|
||||
rsv1: false,
|
||||
readOnly,
|
||||
opcode
|
||||
}), cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a data message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
|
||||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
|
||||
* @param {Boolean} options.compress Specifies whether or not to compress `data`
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
dispatch (data, options, cb) {
|
||||
if (!options.compress) {
|
||||
this.sendFrame(Sender.frame(data, options), cb);
|
||||
return;
|
||||
}
|
||||
|
||||
this.deflating = true;
|
||||
this.perMessageDeflate.compress(data, options.fin, (err, buf) => {
|
||||
if (err) {
|
||||
if (cb) cb(err);
|
||||
else this.onerror(err);
|
||||
return;
|
||||
}
|
||||
|
||||
options.readOnly = false;
|
||||
this.sendFrame(Sender.frame(buf, options), cb);
|
||||
this.deflating = false;
|
||||
this.dequeue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes queued send operations.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
dequeue () {
|
||||
while (!this.deflating && this.queue.length) {
|
||||
const params = this.queue.shift();
|
||||
|
||||
this.bufferedBytes -= params[1].length;
|
||||
params[0].apply(this, params.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a send operation.
|
||||
*
|
||||
* @param {Array} params Send operation parameters.
|
||||
* @private
|
||||
*/
|
||||
enqueue (params) {
|
||||
this.bufferedBytes += params[1].length;
|
||||
this.queue.push(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a frame.
|
||||
*
|
||||
* @param {Buffer[]} list The frame to send
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
sendFrame (list, cb) {
|
||||
if (list.length === 2) {
|
||||
this._socket.write(list[0]);
|
||||
this._socket.write(list[1], cb);
|
||||
} else {
|
||||
this._socket.write(list[0], cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sender;
|
||||
|
||||
/**
|
||||
* Converts an `ArrayBuffer` view into a buffer.
|
||||
*
|
||||
* @param {(DataView|TypedArray)} view The view to convert
|
||||
* @return {Buffer} Converted view
|
||||
* @private
|
||||
*/
|
||||
function viewToBuffer (view) {
|
||||
const buf = Buffer.from(view.buffer);
|
||||
|
||||
if (view.byteLength !== view.buffer.byteLength) {
|
||||
return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
17
node_modules/ws/lib/Validation.js
generated
vendored
Normal file
17
node_modules/ws/lib/Validation.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
const isValidUTF8 = require('utf-8-validate');
|
||||
|
||||
module.exports = typeof isValidUTF8 === 'object'
|
||||
? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
|
||||
: isValidUTF8;
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
module.exports = () => true;
|
||||
}
|
||||
704
node_modules/ws/lib/WebSocket.js
generated
vendored
Normal file
704
node_modules/ws/lib/WebSocket.js
generated
vendored
Normal file
@@ -0,0 +1,704 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const crypto = require('crypto');
|
||||
const Ultron = require('ultron');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const EventTarget = require('./EventTarget');
|
||||
const Extensions = require('./Extensions');
|
||||
const constants = require('./Constants');
|
||||
const Receiver = require('./Receiver');
|
||||
const Sender = require('./Sender');
|
||||
|
||||
const protocolVersions = [8, 13];
|
||||
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocket extends EventEmitter {
|
||||
/**
|
||||
* Create a new `WebSocket`.
|
||||
*
|
||||
* @param {String} address The URL to which to connect
|
||||
* @param {(String|String[])} protocols The subprotocols
|
||||
* @param {Object} options Connection options
|
||||
*/
|
||||
constructor (address, protocols, options) {
|
||||
super();
|
||||
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (typeof protocols === 'string') {
|
||||
protocols = [protocols];
|
||||
} else if (!Array.isArray(protocols)) {
|
||||
options = protocols;
|
||||
protocols = [];
|
||||
}
|
||||
|
||||
this.readyState = WebSocket.CONNECTING;
|
||||
this.bytesReceived = 0;
|
||||
this.extensions = {};
|
||||
this.protocol = '';
|
||||
|
||||
this._binaryType = constants.BINARY_TYPES[0];
|
||||
this._finalize = this.finalize.bind(this);
|
||||
this._finalizeCalled = false;
|
||||
this._closeMessage = null;
|
||||
this._closeTimer = null;
|
||||
this._closeCode = null;
|
||||
this._receiver = null;
|
||||
this._sender = null;
|
||||
this._socket = null;
|
||||
this._ultron = null;
|
||||
|
||||
if (Array.isArray(address)) {
|
||||
initAsServerClient.call(this, address[0], address[1], address[2], options);
|
||||
} else {
|
||||
initAsClient.call(this, address, protocols, options);
|
||||
}
|
||||
}
|
||||
|
||||
get CONNECTING () { return WebSocket.CONNECTING; }
|
||||
get CLOSING () { return WebSocket.CLOSING; }
|
||||
get CLOSED () { return WebSocket.CLOSED; }
|
||||
get OPEN () { return WebSocket.OPEN; }
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
get bufferedAmount () {
|
||||
var amount = 0;
|
||||
|
||||
if (this._socket) {
|
||||
amount = this._socket.bufferSize + this._sender.bufferedBytes;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This deviates from the WHATWG interface since ws doesn't support the required
|
||||
* default "blob" type (instead we define a custom "nodebuffer" type).
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
get binaryType () {
|
||||
return this._binaryType;
|
||||
}
|
||||
|
||||
set binaryType (type) {
|
||||
if (constants.BINARY_TYPES.indexOf(type) < 0) return;
|
||||
|
||||
this._binaryType = type;
|
||||
|
||||
//
|
||||
// Allow to change `binaryType` on the fly.
|
||||
//
|
||||
if (this._receiver) this._receiver.binaryType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the socket and the internal resources.
|
||||
*
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @private
|
||||
*/
|
||||
setSocket (socket, head) {
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay();
|
||||
|
||||
this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType);
|
||||
this._sender = new Sender(socket, this.extensions);
|
||||
this._ultron = new Ultron(socket);
|
||||
this._socket = socket;
|
||||
|
||||
// socket cleanup handlers
|
||||
this._ultron.on('close', this._finalize);
|
||||
this._ultron.on('error', this._finalize);
|
||||
this._ultron.on('end', this._finalize);
|
||||
|
||||
// ensure that the head is added to the receiver
|
||||
if (head && head.length > 0) {
|
||||
socket.unshift(head);
|
||||
head = null;
|
||||
}
|
||||
|
||||
// subsequent packets are pushed to the receiver
|
||||
this._ultron.on('data', (data) => {
|
||||
this.bytesReceived += data.length;
|
||||
this._receiver.add(data);
|
||||
});
|
||||
|
||||
// receiver event handlers
|
||||
this._receiver.onmessage = (data, flags) => this.emit('message', data, flags);
|
||||
this._receiver.onping = (data, flags) => {
|
||||
this.pong(data, !this._isServer, true);
|
||||
this.emit('ping', data, flags);
|
||||
};
|
||||
this._receiver.onpong = (data, flags) => this.emit('pong', data, flags);
|
||||
this._receiver.onclose = (code, reason) => {
|
||||
this._closeMessage = reason;
|
||||
this._closeCode = code;
|
||||
this.close(code, reason);
|
||||
};
|
||||
this._receiver.onerror = (error, code) => {
|
||||
// close the connection when the receiver reports a HyBi error code
|
||||
this.close(code, '');
|
||||
this.emit('error', error);
|
||||
};
|
||||
|
||||
// sender event handlers
|
||||
this._sender.onerror = (error) => {
|
||||
this.close(1002, '');
|
||||
this.emit('error', error);
|
||||
};
|
||||
|
||||
this.readyState = WebSocket.OPEN;
|
||||
this.emit('open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up and release internal resources.
|
||||
*
|
||||
* @param {(Boolean|Error)} Indicates whether or not an error occurred
|
||||
* @private
|
||||
*/
|
||||
finalize (error) {
|
||||
if (this._finalizeCalled) return;
|
||||
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
this._finalizeCalled = true;
|
||||
|
||||
clearTimeout(this._closeTimer);
|
||||
this._closeTimer = null;
|
||||
|
||||
//
|
||||
// If the connection was closed abnormally (with an error), or if the close
|
||||
// control frame was malformed or not received then the close code must be
|
||||
// 1006.
|
||||
//
|
||||
if (error) this._closeCode = 1006;
|
||||
|
||||
if (this._socket) {
|
||||
this._ultron.destroy();
|
||||
this._socket.on('error', function onerror () {
|
||||
this.destroy();
|
||||
});
|
||||
|
||||
if (!error) this._socket.end();
|
||||
else this._socket.destroy();
|
||||
|
||||
this._socket = null;
|
||||
this._ultron = null;
|
||||
}
|
||||
|
||||
if (this._sender) {
|
||||
this._sender = this._sender.onerror = null;
|
||||
}
|
||||
|
||||
if (this._receiver) {
|
||||
this._receiver.cleanup(() => this.emitClose());
|
||||
this._receiver = null;
|
||||
} else {
|
||||
this.emitClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the `close` event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
emitClose () {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
this.emit('close', this._closeCode || 1006, this._closeMessage || '');
|
||||
|
||||
if (this.extensions[PerMessageDeflate.extensionName]) {
|
||||
this.extensions[PerMessageDeflate.extensionName].cleanup();
|
||||
}
|
||||
|
||||
this.extensions = null;
|
||||
|
||||
this.removeAllListeners();
|
||||
this.on('error', constants.NOOP); // Catch all errors after this.
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the socket stream.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
pause () {
|
||||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||||
|
||||
this._socket.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the socket stream
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
resume () {
|
||||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||||
|
||||
this._socket.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a closing handshake.
|
||||
*
|
||||
* @param {Number} code Status code explaining why the connection is closing
|
||||
* @param {String} data A string explaining why the connection is closing
|
||||
* @public
|
||||
*/
|
||||
close (code, data) {
|
||||
if (this.readyState === WebSocket.CLOSED) return;
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
if (this._req && !this._req.aborted) {
|
||||
this._req.abort();
|
||||
this.emit('error', new Error('closed before the connection is established'));
|
||||
this.finalize(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.readyState === WebSocket.CLOSING) {
|
||||
if (this._closeCode && this._socket) this._socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
this._sender.close(code, data, !this._isServer, (err) => {
|
||||
if (err) this.emit('error', err);
|
||||
|
||||
if (this._socket) {
|
||||
if (this._closeCode) this._socket.end();
|
||||
//
|
||||
// Ensure that the connection is cleaned up even when the closing
|
||||
// handshake fails.
|
||||
//
|
||||
clearTimeout(this._closeTimer);
|
||||
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a ping message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Indicates whether or not to mask `data`
|
||||
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
|
||||
* @public
|
||||
*/
|
||||
ping (data, mask, failSilently) {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (failSilently) return;
|
||||
throw new Error('not opened');
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
if (mask === undefined) mask = !this._isServer;
|
||||
this._sender.ping(data || constants.EMPTY_BUFFER, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a pong message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Indicates whether or not to mask `data`
|
||||
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
|
||||
* @public
|
||||
*/
|
||||
pong (data, mask, failSilently) {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (failSilently) return;
|
||||
throw new Error('not opened');
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
if (mask === undefined) mask = !this._isServer;
|
||||
this._sender.pong(data || constants.EMPTY_BUFFER, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a data message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Boolean} options.compress Specifies whether or not to compress `data`
|
||||
* @param {Boolean} options.binary Specifies whether `data` is binary or text
|
||||
* @param {Boolean} options.fin Specifies whether the fragment is the last one
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback which is executed when data is written out
|
||||
* @public
|
||||
*/
|
||||
send (data, options, cb) {
|
||||
if (typeof options === 'function') {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (cb) cb(new Error('not opened'));
|
||||
else throw new Error('not opened');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
|
||||
const opts = Object.assign({
|
||||
binary: typeof data !== 'string',
|
||||
mask: !this._isServer,
|
||||
compress: true,
|
||||
fin: true
|
||||
}, options);
|
||||
|
||||
if (!this.extensions[PerMessageDeflate.extensionName]) {
|
||||
opts.compress = false;
|
||||
}
|
||||
|
||||
this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly close the connection.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
terminate () {
|
||||
if (this.readyState === WebSocket.CLOSED) return;
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
if (this._req && !this._req.aborted) {
|
||||
this._req.abort();
|
||||
this.emit('error', new Error('closed before the connection is established'));
|
||||
this.finalize(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.finalize(true);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocket.CONNECTING = 0;
|
||||
WebSocket.OPEN = 1;
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.CLOSED = 3;
|
||||
|
||||
//
|
||||
// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
|
||||
// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
|
||||
//
|
||||
['open', 'error', 'close', 'message'].forEach((method) => {
|
||||
Object.defineProperty(WebSocket.prototype, `on${method}`, {
|
||||
/**
|
||||
* Return the listener of the event.
|
||||
*
|
||||
* @return {(Function|undefined)} The event listener or `undefined`
|
||||
* @public
|
||||
*/
|
||||
get () {
|
||||
const listeners = this.listeners(method);
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i]._listener) return listeners[i]._listener;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a listener for the event.
|
||||
*
|
||||
* @param {Function} listener The listener to add
|
||||
* @public
|
||||
*/
|
||||
set (listener) {
|
||||
const listeners = this.listeners(method);
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
//
|
||||
// Remove only the listeners added via `addEventListener`.
|
||||
//
|
||||
if (listeners[i]._listener) this.removeListener(method, listeners[i]);
|
||||
}
|
||||
this.addEventListener(method, listener);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
WebSocket.prototype.addEventListener = EventTarget.addEventListener;
|
||||
WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
|
||||
|
||||
module.exports = WebSocket;
|
||||
|
||||
/**
|
||||
* Initialize a WebSocket server client.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Object} options WebSocket attributes
|
||||
* @param {Number} options.protocolVersion The WebSocket protocol version
|
||||
* @param {Object} options.extensions The negotiated extensions
|
||||
* @param {Number} options.maxPayload The maximum allowed message size
|
||||
* @param {String} options.protocol The chosen subprotocol
|
||||
* @private
|
||||
*/
|
||||
function initAsServerClient (req, socket, head, options) {
|
||||
this.protocolVersion = options.protocolVersion;
|
||||
this.extensions = options.extensions;
|
||||
this.maxPayload = options.maxPayload;
|
||||
this.protocol = options.protocol;
|
||||
|
||||
this.upgradeReq = req;
|
||||
this._isServer = true;
|
||||
|
||||
this.setSocket(socket, head);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a WebSocket client.
|
||||
*
|
||||
* @param {String} address The URL to which to connect
|
||||
* @param {String[]} protocols The list of subprotocols
|
||||
* @param {Object} options Connection options
|
||||
* @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
|
||||
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
|
||||
* @param {String} options.localAddress Local interface to bind for network connections
|
||||
* @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
|
||||
* @param {Object} options.headers An object containing request headers
|
||||
* @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
|
||||
* @param {http.Agent} options.agent Use the specified Agent
|
||||
* @param {String} options.host Value of the `Host` header
|
||||
* @param {Number} options.family IP address family to use during hostname lookup (4 or 6).
|
||||
* @param {Function} options.checkServerIdentity A function to validate the server hostname
|
||||
* @param {Boolean} options.rejectUnauthorized Verify or not the server certificate
|
||||
* @param {String} options.passphrase The passphrase for the private key or pfx
|
||||
* @param {String} options.ciphers The ciphers to use or exclude
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.key The private key
|
||||
* @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates
|
||||
* @private
|
||||
*/
|
||||
function initAsClient (address, protocols, options) {
|
||||
options = Object.assign({
|
||||
protocolVersion: protocolVersions[1],
|
||||
protocol: protocols.join(','),
|
||||
perMessageDeflate: true,
|
||||
localAddress: null,
|
||||
headers: null,
|
||||
family: null,
|
||||
origin: null,
|
||||
agent: null,
|
||||
host: null,
|
||||
|
||||
//
|
||||
// SSL options.
|
||||
//
|
||||
checkServerIdentity: null,
|
||||
rejectUnauthorized: null,
|
||||
passphrase: null,
|
||||
ciphers: null,
|
||||
cert: null,
|
||||
key: null,
|
||||
pfx: null,
|
||||
ca: null
|
||||
}, options);
|
||||
|
||||
if (protocolVersions.indexOf(options.protocolVersion) === -1) {
|
||||
throw new Error(
|
||||
`unsupported protocol version: ${options.protocolVersion} ` +
|
||||
`(supported versions: ${protocolVersions.join(', ')})`
|
||||
);
|
||||
}
|
||||
|
||||
this.protocolVersion = options.protocolVersion;
|
||||
this._isServer = false;
|
||||
this.url = address;
|
||||
|
||||
const serverUrl = url.parse(address);
|
||||
const isUnixSocket = serverUrl.protocol === 'ws+unix:';
|
||||
|
||||
if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
|
||||
throw new Error('invalid url');
|
||||
}
|
||||
|
||||
const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
|
||||
const key = crypto.randomBytes(16).toString('base64');
|
||||
const httpObj = isSecure ? https : http;
|
||||
|
||||
//
|
||||
// Prepare extensions.
|
||||
//
|
||||
const extensionsOffer = {};
|
||||
var perMessageDeflate;
|
||||
|
||||
if (options.perMessageDeflate) {
|
||||
perMessageDeflate = new PerMessageDeflate(
|
||||
options.perMessageDeflate !== true ? options.perMessageDeflate : {},
|
||||
false
|
||||
);
|
||||
extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer();
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
port: serverUrl.port || (isSecure ? 443 : 80),
|
||||
host: serverUrl.hostname,
|
||||
path: '/',
|
||||
headers: {
|
||||
'Sec-WebSocket-Version': options.protocolVersion,
|
||||
'Sec-WebSocket-Key': key,
|
||||
'Connection': 'Upgrade',
|
||||
'Upgrade': 'websocket'
|
||||
}
|
||||
};
|
||||
|
||||
if (options.headers) Object.assign(requestOptions.headers, options.headers);
|
||||
if (Object.keys(extensionsOffer).length) {
|
||||
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer);
|
||||
}
|
||||
if (options.protocol) {
|
||||
requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol;
|
||||
}
|
||||
if (options.origin) {
|
||||
if (options.protocolVersion < 13) {
|
||||
requestOptions.headers['Sec-WebSocket-Origin'] = options.origin;
|
||||
} else {
|
||||
requestOptions.headers.Origin = options.origin;
|
||||
}
|
||||
}
|
||||
if (options.host) requestOptions.headers.Host = options.host;
|
||||
if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
|
||||
|
||||
if (options.localAddress) requestOptions.localAddress = options.localAddress;
|
||||
if (options.family) requestOptions.family = options.family;
|
||||
|
||||
if (isUnixSocket) {
|
||||
const parts = serverUrl.path.split(':');
|
||||
|
||||
requestOptions.socketPath = parts[0];
|
||||
requestOptions.path = parts[1];
|
||||
} else if (serverUrl.path) {
|
||||
//
|
||||
// Make sure that path starts with `/`.
|
||||
//
|
||||
if (serverUrl.path.charAt(0) !== '/') {
|
||||
requestOptions.path = `/${serverUrl.path}`;
|
||||
} else {
|
||||
requestOptions.path = serverUrl.path;
|
||||
}
|
||||
}
|
||||
|
||||
var agent = options.agent;
|
||||
|
||||
//
|
||||
// A custom agent is required for these options.
|
||||
//
|
||||
if (
|
||||
options.rejectUnauthorized != null ||
|
||||
options.checkServerIdentity ||
|
||||
options.passphrase ||
|
||||
options.ciphers ||
|
||||
options.cert ||
|
||||
options.key ||
|
||||
options.pfx ||
|
||||
options.ca
|
||||
) {
|
||||
if (options.passphrase) requestOptions.passphrase = options.passphrase;
|
||||
if (options.ciphers) requestOptions.ciphers = options.ciphers;
|
||||
if (options.cert) requestOptions.cert = options.cert;
|
||||
if (options.key) requestOptions.key = options.key;
|
||||
if (options.pfx) requestOptions.pfx = options.pfx;
|
||||
if (options.ca) requestOptions.ca = options.ca;
|
||||
if (options.checkServerIdentity) {
|
||||
requestOptions.checkServerIdentity = options.checkServerIdentity;
|
||||
}
|
||||
if (options.rejectUnauthorized != null) {
|
||||
requestOptions.rejectUnauthorized = options.rejectUnauthorized;
|
||||
}
|
||||
|
||||
if (!agent) agent = new httpObj.Agent(requestOptions);
|
||||
}
|
||||
|
||||
if (agent) requestOptions.agent = agent;
|
||||
|
||||
this._req = httpObj.get(requestOptions);
|
||||
|
||||
this._req.on('error', (error) => {
|
||||
if (this._req.aborted) return;
|
||||
|
||||
this._req = null;
|
||||
this.emit('error', error);
|
||||
this.finalize(true);
|
||||
});
|
||||
|
||||
this._req.on('response', (res) => {
|
||||
if (!this.emit('unexpected-response', this._req, res)) {
|
||||
this._req.abort();
|
||||
this.emit('error', new Error(`unexpected server response (${res.statusCode})`));
|
||||
this.finalize(true);
|
||||
}
|
||||
});
|
||||
|
||||
this._req.on('upgrade', (res, socket, head) => {
|
||||
this._req = null;
|
||||
|
||||
const digest = crypto.createHash('sha1')
|
||||
.update(key + constants.GUID, 'binary')
|
||||
.digest('base64');
|
||||
|
||||
if (res.headers['sec-websocket-accept'] !== digest) {
|
||||
socket.destroy();
|
||||
this.emit('error', new Error('invalid server key'));
|
||||
return this.finalize(true);
|
||||
}
|
||||
|
||||
const serverProt = res.headers['sec-websocket-protocol'];
|
||||
const protList = (options.protocol || '').split(/, */);
|
||||
var protError;
|
||||
|
||||
if (!options.protocol && serverProt) {
|
||||
protError = 'server sent a subprotocol even though none requested';
|
||||
} else if (options.protocol && !serverProt) {
|
||||
protError = 'server sent no subprotocol even though requested';
|
||||
} else if (serverProt && protList.indexOf(serverProt) === -1) {
|
||||
protError = 'server responded with an invalid protocol';
|
||||
}
|
||||
|
||||
if (protError) {
|
||||
socket.destroy();
|
||||
this.emit('error', new Error(protError));
|
||||
return this.finalize(true);
|
||||
}
|
||||
|
||||
if (serverProt) this.protocol = serverProt;
|
||||
|
||||
const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
|
||||
|
||||
if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
|
||||
try {
|
||||
perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
|
||||
} catch (err) {
|
||||
socket.destroy();
|
||||
this.emit('error', new Error('invalid extension parameter'));
|
||||
return this.finalize(true);
|
||||
}
|
||||
|
||||
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
|
||||
this.setSocket(socket, head);
|
||||
});
|
||||
}
|
||||
336
node_modules/ws/lib/WebSocketServer.js
generated
vendored
Normal file
336
node_modules/ws/lib/WebSocketServer.js
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const EventEmitter = require('events');
|
||||
const crypto = require('crypto');
|
||||
const Ultron = require('ultron');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const Extensions = require('./Extensions');
|
||||
const constants = require('./Constants');
|
||||
const WebSocket = require('./WebSocket');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket server.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocketServer extends EventEmitter {
|
||||
/**
|
||||
* Create a `WebSocketServer` instance.
|
||||
*
|
||||
* @param {Object} options Configuration options
|
||||
* @param {String} options.host The hostname where to bind the server
|
||||
* @param {Number} options.port The port where to bind the server
|
||||
* @param {http.Server} options.server A pre-created HTTP/S server to use
|
||||
* @param {Function} options.verifyClient An hook to reject connections
|
||||
* @param {Function} options.handleProtocols An hook to handle protocols
|
||||
* @param {String} options.path Accept only connections matching this path
|
||||
* @param {Boolean} options.noServer Enable no server mode
|
||||
* @param {Boolean} options.clientTracking Specifies whether or not to track clients
|
||||
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
|
||||
* @param {Number} options.maxPayload The maximum allowed message size
|
||||
* @param {Function} callback A listener for the `listening` event
|
||||
*/
|
||||
constructor (options, callback) {
|
||||
super();
|
||||
|
||||
options = Object.assign({
|
||||
maxPayload: 100 * 1024 * 1024,
|
||||
perMessageDeflate: true,
|
||||
handleProtocols: null,
|
||||
clientTracking: true,
|
||||
verifyClient: null,
|
||||
noServer: false,
|
||||
backlog: null, // use default (511 as implemented in net.js)
|
||||
server: null,
|
||||
host: null,
|
||||
path: null,
|
||||
port: null
|
||||
}, options);
|
||||
|
||||
if (options.port == null && !options.server && !options.noServer) {
|
||||
throw new TypeError('missing or invalid options');
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
this._server = http.createServer((req, res) => {
|
||||
const body = http.STATUS_CODES[426];
|
||||
|
||||
res.writeHead(426, {
|
||||
'Content-Length': body.length,
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
res.end(body);
|
||||
});
|
||||
this._server.allowHalfOpen = false;
|
||||
this._server.listen(options.port, options.host, options.backlog, callback);
|
||||
} else if (options.server) {
|
||||
this._server = options.server;
|
||||
}
|
||||
|
||||
if (this._server) {
|
||||
this._ultron = new Ultron(this._server);
|
||||
this._ultron.on('listening', () => this.emit('listening'));
|
||||
this._ultron.on('error', (err) => this.emit('error', err));
|
||||
this._ultron.on('upgrade', (req, socket, head) => {
|
||||
this.handleUpgrade(req, socket, head, (client) => {
|
||||
this.emit(`connection${req.url}`, client);
|
||||
this.emit('connection', client);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (options.clientTracking) this.clients = new Set();
|
||||
this.options = options;
|
||||
this.path = options.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
close (cb) {
|
||||
//
|
||||
// Terminate all associated clients.
|
||||
//
|
||||
if (this.clients) {
|
||||
for (const client of this.clients) client.terminate();
|
||||
}
|
||||
|
||||
const server = this._server;
|
||||
|
||||
if (server) {
|
||||
this._ultron.destroy();
|
||||
this._ultron = this._server = null;
|
||||
|
||||
//
|
||||
// Close the http server if it was internally created.
|
||||
//
|
||||
if (this.options.port != null) return server.close(cb);
|
||||
}
|
||||
|
||||
if (cb) cb();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a given request should be handled by this server instance.
|
||||
*
|
||||
* @param {http.IncomingMessage} req Request object to inspect
|
||||
* @return {Boolean} `true` if the request is valid, else `false`
|
||||
* @public
|
||||
*/
|
||||
shouldHandle (req) {
|
||||
if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a HTTP Upgrade request.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
handleUpgrade (req, socket, head, cb) {
|
||||
socket.on('error', socketError);
|
||||
|
||||
const version = +req.headers['sec-websocket-version'];
|
||||
|
||||
if (
|
||||
req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
|
||||
!req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
|
||||
!this.shouldHandle(req)
|
||||
) {
|
||||
return abortConnection(socket, 400);
|
||||
}
|
||||
|
||||
var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */);
|
||||
|
||||
//
|
||||
// Optionally call external protocol selection handler.
|
||||
//
|
||||
if (this.options.handleProtocols) {
|
||||
protocol = this.options.handleProtocols(protocol);
|
||||
if (protocol === false) return abortConnection(socket, 401);
|
||||
} else {
|
||||
protocol = protocol[0];
|
||||
}
|
||||
|
||||
//
|
||||
// Optionally call external client verification handler.
|
||||
//
|
||||
if (this.options.verifyClient) {
|
||||
const info = {
|
||||
origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
||||
secure: !!(req.connection.authorized || req.connection.encrypted),
|
||||
req
|
||||
};
|
||||
|
||||
if (this.options.verifyClient.length === 2) {
|
||||
this.options.verifyClient(info, (verified, code, message) => {
|
||||
if (!verified) return abortConnection(socket, code || 401, message);
|
||||
|
||||
this.completeUpgrade(protocol, version, req, socket, head, cb);
|
||||
});
|
||||
return;
|
||||
} else if (!this.options.verifyClient(info)) {
|
||||
return abortConnection(socket, 401);
|
||||
}
|
||||
}
|
||||
|
||||
this.completeUpgrade(protocol, version, req, socket, head, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the connection to WebSocket.
|
||||
*
|
||||
* @param {String} protocol The chosen subprotocol
|
||||
* @param {Number} version The WebSocket protocol version
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
completeUpgrade (protocol, version, req, socket, head, cb) {
|
||||
//
|
||||
// Destroy the socket if the client has already sent a FIN packet.
|
||||
//
|
||||
if (!socket.readable || !socket.writable) return socket.destroy();
|
||||
|
||||
const key = crypto.createHash('sha1')
|
||||
.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
|
||||
.digest('base64');
|
||||
|
||||
const headers = [
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Upgrade: websocket',
|
||||
'Connection: Upgrade',
|
||||
`Sec-WebSocket-Accept: ${key}`
|
||||
];
|
||||
|
||||
if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
||||
|
||||
const offer = Extensions.parse(req.headers['sec-websocket-extensions']);
|
||||
var extensions;
|
||||
|
||||
try {
|
||||
extensions = acceptExtensions(this.options, offer);
|
||||
} catch (err) {
|
||||
return abortConnection(socket, 400);
|
||||
}
|
||||
|
||||
const props = Object.keys(extensions);
|
||||
|
||||
if (props.length) {
|
||||
const serverExtensions = props.reduce((obj, key) => {
|
||||
obj[key] = [extensions[key].params];
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`);
|
||||
}
|
||||
|
||||
//
|
||||
// Allow external modification/inspection of handshake headers.
|
||||
//
|
||||
this.emit('headers', headers);
|
||||
|
||||
socket.write(headers.concat('', '').join('\r\n'));
|
||||
|
||||
const client = new WebSocket([req, socket, head], {
|
||||
maxPayload: this.options.maxPayload,
|
||||
protocolVersion: version,
|
||||
extensions,
|
||||
protocol
|
||||
});
|
||||
|
||||
if (this.clients) {
|
||||
this.clients.add(client);
|
||||
client.on('close', () => this.clients.delete(client));
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketError);
|
||||
cb(client);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketServer;
|
||||
|
||||
/**
|
||||
* Handle premature socket errors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function socketError () {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept WebSocket extensions.
|
||||
*
|
||||
* @param {Object} options The `WebSocketServer` configuration options
|
||||
* @param {Object} offer The parsed value of the `sec-websocket-extensions` header
|
||||
* @return {Object} Accepted extensions
|
||||
* @private
|
||||
*/
|
||||
function acceptExtensions (options, offer) {
|
||||
const pmd = options.perMessageDeflate;
|
||||
const extensions = {};
|
||||
|
||||
if (pmd && offer[PerMessageDeflate.extensionName]) {
|
||||
const perMessageDeflate = new PerMessageDeflate(
|
||||
pmd !== true ? pmd : {},
|
||||
true,
|
||||
options.maxPayload
|
||||
);
|
||||
|
||||
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
|
||||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when preconditions are not fulfilled.
|
||||
*
|
||||
* @param {net.Socket} socket The socket of the upgrade request
|
||||
* @param {Number} code The HTTP response status code
|
||||
* @param {String} [message] The HTTP response body
|
||||
* @private
|
||||
*/
|
||||
function abortConnection (socket, code, message) {
|
||||
if (socket.writable) {
|
||||
message = message || http.STATUS_CODES[code];
|
||||
socket.write(
|
||||
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
||||
'Connection: close\r\n' +
|
||||
'Content-type: text/html\r\n' +
|
||||
`Content-Length: ${Buffer.byteLength(message)}\r\n` +
|
||||
'\r\n' +
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketError);
|
||||
socket.destroy();
|
||||
}
|
||||
124
node_modules/ws/package.json
generated
vendored
Normal file
124
node_modules/ws/package.json
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
{
|
||||
"raw": "ws",
|
||||
"scope": null,
|
||||
"escapedName": "ws",
|
||||
"name": "ws",
|
||||
"rawSpec": "",
|
||||
"spec": "latest",
|
||||
"type": "tag"
|
||||
},
|
||||
"/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket"
|
||||
]
|
||||
],
|
||||
"_from": "ws@latest",
|
||||
"_id": "ws@2.2.3",
|
||||
"_inCache": true,
|
||||
"_installable": true,
|
||||
"_location": "/ws",
|
||||
"_nodeVersion": "7.8.0",
|
||||
"_npmOperationalInternal": {
|
||||
"host": "packages-12-west.internal.npmjs.com",
|
||||
"tmp": "tmp/ws-2.2.3.tgz_1491214217857_0.5180311135482043"
|
||||
},
|
||||
"_npmUser": {
|
||||
"name": "lpinca",
|
||||
"email": "luigipinca@gmail.com"
|
||||
},
|
||||
"_npmVersion": "4.2.0",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"raw": "ws",
|
||||
"scope": null,
|
||||
"escapedName": "ws",
|
||||
"name": "ws",
|
||||
"rawSpec": "",
|
||||
"spec": "latest",
|
||||
"type": "tag"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "http://registry.npmjs.org/ws/-/ws-2.2.3.tgz",
|
||||
"_shasum": "f36c9719a56dff813f455af912a2078145bbd940",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "ws",
|
||||
"_where": "/home/vincent/Projects/SublimeTextProjects/shadowsocks-over-websocket",
|
||||
"author": {
|
||||
"name": "Einar Otto Stangvik",
|
||||
"email": "einaros@gmail.com",
|
||||
"url": "http://2x.io"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/websockets/ws/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.0.1",
|
||||
"ultron": "~1.1.0"
|
||||
},
|
||||
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
||||
"devDependencies": {
|
||||
"benchmark": "~2.1.2",
|
||||
"bufferutil": "~3.0.0",
|
||||
"eslint": "~3.19.0",
|
||||
"eslint-config-standard": "~8.0.0-beta.1",
|
||||
"eslint-plugin-import": "~2.2.0",
|
||||
"eslint-plugin-node": "~4.2.0",
|
||||
"eslint-plugin-promise": "~3.5.0",
|
||||
"eslint-plugin-standard": "~2.1.0",
|
||||
"mocha": "~3.2.0",
|
||||
"nyc": "~10.2.0",
|
||||
"utf-8-validate": "~3.0.0"
|
||||
},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"shasum": "f36c9719a56dff813f455af912a2078145bbd940",
|
||||
"tarball": "https://registry.npmjs.org/ws/-/ws-2.2.3.tgz"
|
||||
},
|
||||
"gitHead": "212c7aab04a5f23d89111c1722371211efa2dd89",
|
||||
"homepage": "https://github.com/websockets/ws#readme",
|
||||
"keywords": [
|
||||
"HyBi",
|
||||
"Push",
|
||||
"RFC-6455",
|
||||
"WebSocket",
|
||||
"WebSockets",
|
||||
"real-time"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "3rdeden",
|
||||
"email": "npm@3rd-Eden.com"
|
||||
},
|
||||
{
|
||||
"name": "einaros",
|
||||
"email": "einaros@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "lpinca",
|
||||
"email": "luigipinca@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "v1",
|
||||
"email": "npm@3rd-Eden.com"
|
||||
}
|
||||
],
|
||||
"name": "ws",
|
||||
"optionalDependencies": {},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/websockets/ws.git"
|
||||
},
|
||||
"scripts": {
|
||||
"integration": "eslint . && mocha test/*.integration.js",
|
||||
"lint": "eslint .",
|
||||
"test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js"
|
||||
},
|
||||
"version": "2.2.3"
|
||||
}
|
||||
Reference in New Issue
Block a user