Connection to hobu
This commit is contained in:
parent
bfae25bd33
commit
143782ce4f
@ -2,17 +2,21 @@ var Tinkerforge = require('../Tinkerforge')
|
|||||||
, Conf = require('./config.json')
|
, Conf = require('./config.json')
|
||||||
, Helper = require('../Adawim/helper')
|
, Helper = require('../Adawim/helper')
|
||||||
, Log = require('../Adawim/logging')
|
, Log = require('../Adawim/logging')
|
||||||
, http = require('http');
|
, http = require('http')
|
||||||
|
, WebSocket = require('ws');
|
||||||
|
|
||||||
|
|
||||||
|
var ws = null;
|
||||||
|
var lastTagID = '';
|
||||||
|
var timeOutForHoldingLastTagID = null;
|
||||||
|
const timeOutForHoldingLastTagIDInMillis = Conf.rfid.timeout;
|
||||||
|
var currentActionState = 0;
|
||||||
|
|
||||||
|
|
||||||
Helper.each(Conf.items, function(item) {
|
Helper.each(Conf.items, function(item) {
|
||||||
connect(item);
|
connect(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
var lastTagID = '';
|
|
||||||
var timeOutForHoldingLastTagID = null;
|
|
||||||
const timeOutForHoldingLastTagIDInMillis = Conf.rfid.timeout;
|
|
||||||
var currentActionState = 0;
|
|
||||||
|
|
||||||
/* private */
|
/* private */
|
||||||
function reconnect(item) {
|
function reconnect(item) {
|
||||||
@ -198,12 +202,56 @@ function doActionOnRFIDCallback(key) {
|
|||||||
case 0:
|
case 0:
|
||||||
// deactivate alarm system
|
// deactivate alarm system
|
||||||
Log.log( 'deactivate alarm system' );
|
Log.log( 'deactivate alarm system' );
|
||||||
|
deactivateAlarmSystem();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// activate alarm system
|
// activate alarm system
|
||||||
Log.log( 'activate alarm system' );
|
Log.log( 'activate alarm system' );
|
||||||
|
activateAlarmSystem();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* private */
|
||||||
|
function activateAlarmSystem() {
|
||||||
|
msg = '{type:"set_alarm_system_state", data:{state:"F111"}}';
|
||||||
|
send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* private */
|
||||||
|
function deactivateAlarmSystem() {
|
||||||
|
msg = '{type:"set_alarm_system_state", data:{state:"F222"}}';
|
||||||
|
send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* private */
|
||||||
|
function send(msg) {
|
||||||
|
if(Helper.isDefinedAndNotNull(ws) && Helper.isDefinedAndNotNull(msg)) {
|
||||||
|
ws.send(msg);
|
||||||
|
} else {
|
||||||
|
Log.error('ws is null');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var openWebSocket = function() {
|
||||||
|
ws = new WebSocket(Conf.hobu.url);
|
||||||
|
|
||||||
|
ws.on('open', function() {
|
||||||
|
Log.log('ws open');
|
||||||
|
ws.send(Date.now().toString(), {mask: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function() {
|
||||||
|
Log.log('ws disconnect');
|
||||||
|
openWebSocket();
|
||||||
|
});
|
||||||
|
|
||||||
|
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.
|
||||||
|
Log.inspect('data', data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
openWebSocket();
|
||||||
@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"hobu": {
|
||||||
|
"url": "ws://hobu.local:8000/"
|
||||||
|
},
|
||||||
"rfid": {
|
"rfid": {
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"host": "localhost",
|
"host": "192.168.2.123",
|
||||||
"port": 4223,
|
"port": 4223,
|
||||||
"mtouch": {
|
"mtouch": {
|
||||||
"uid": "ADk"
|
"uid": "ADk"
|
||||||
|
|||||||
22
node_modules/nodejs-websocket/.jshintrc
generated
vendored
22
node_modules/nodejs-websocket/.jshintrc
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"curly": true,
|
|
||||||
"eqeqeq": true,
|
|
||||||
"nonew": true,
|
|
||||||
"latedef": "nofunc",
|
|
||||||
"unused": true,
|
|
||||||
"noarg": true,
|
|
||||||
"asi": true,
|
|
||||||
"trailing": true,
|
|
||||||
"quotmark": true,
|
|
||||||
"strict": true,
|
|
||||||
"undef": true,
|
|
||||||
"expr": true,
|
|
||||||
"newcap": true,
|
|
||||||
"immed": true,
|
|
||||||
"node": true,
|
|
||||||
"camelcase": true,
|
|
||||||
"devel": true,
|
|
||||||
"freeze": true,
|
|
||||||
"-W033": true,
|
|
||||||
"-W058": true
|
|
||||||
}
|
|
||||||
3
node_modules/nodejs-websocket/.npmignore
generated
vendored
3
node_modules/nodejs-websocket/.npmignore
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
test
|
|
||||||
samples
|
|
||||||
5
node_modules/nodejs-websocket/.travis.yml
generated
vendored
5
node_modules/nodejs-websocket/.travis.yml
generated
vendored
@ -1,5 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "0.12"
|
|
||||||
- "4.2.1"
|
|
||||||
- "node"
|
|
||||||
616
node_modules/nodejs-websocket/Connection.js
generated
vendored
616
node_modules/nodejs-websocket/Connection.js
generated
vendored
@ -1,616 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Represents a connection (both client and server sides)
|
|
||||||
*/
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
var util = require('util'),
|
|
||||||
events = require('events'),
|
|
||||||
crypto = require('crypto'),
|
|
||||||
InStream = require('./InStream'),
|
|
||||||
OutStream = require('./OutStream'),
|
|
||||||
frame = require('./frame'),
|
|
||||||
Server = require('./Server')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @param {(net.Socket|tls.CleartextStream)} socket a net or tls socket
|
|
||||||
* @param {(Server|{path:string,host:string})} parentOrUrl parent in case of server-side connection, url object in case of client-side
|
|
||||||
* @param {Function} [callback] will be added as a listener to 'connect'
|
|
||||||
* @inherits EventEmitter
|
|
||||||
* @event close the numeric code and string reason will be passed
|
|
||||||
* @event error an error object is passed
|
|
||||||
* @event text a string is passed
|
|
||||||
* @event binary a inStream object is passed
|
|
||||||
* @event pong a string is passed
|
|
||||||
* @event connect
|
|
||||||
*/
|
|
||||||
function Connection(socket, parentOrUrl, callback) {
|
|
||||||
var that = this,
|
|
||||||
connectEvent
|
|
||||||
|
|
||||||
if (parentOrUrl instanceof Server) {
|
|
||||||
// Server-side connection
|
|
||||||
this.server = parentOrUrl
|
|
||||||
this.path = null
|
|
||||||
this.host = null
|
|
||||||
this.extraHeaders = null
|
|
||||||
} else {
|
|
||||||
// Client-side
|
|
||||||
this.server = null
|
|
||||||
this.path = parentOrUrl.path
|
|
||||||
this.host = parentOrUrl.host
|
|
||||||
this.extraHeaders = parentOrUrl.extraHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket = socket
|
|
||||||
this.readyState = this.CONNECTING
|
|
||||||
this.buffer = new Buffer(0)
|
|
||||||
this.frameBuffer = null // string for text frames and InStream for binary frames
|
|
||||||
this.outStream = null // current allocated OutStream object for sending binary frames
|
|
||||||
this.key = null // the Sec-WebSocket-Key header
|
|
||||||
this.headers = {} // read only map of header names and values. Header names are lower-cased
|
|
||||||
|
|
||||||
// Set listeners
|
|
||||||
socket.on('readable', function () {
|
|
||||||
that.doRead()
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('error', function (err) {
|
|
||||||
that.emit('error', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!this.server) {
|
|
||||||
connectEvent = socket.constructor.name === 'CleartextStream' ? 'secureConnect' : 'connect'
|
|
||||||
socket.on(connectEvent, function () {
|
|
||||||
that.startHandshake()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close listeners
|
|
||||||
var onclose = function () {
|
|
||||||
if (that.readyState === that.CONNECTING || that.readyState === that.OPEN) {
|
|
||||||
that.emit('close', 1006, '')
|
|
||||||
}
|
|
||||||
that.readyState = this.CLOSED
|
|
||||||
if (that.frameBuffer instanceof InStream) {
|
|
||||||
that.frameBuffer.end()
|
|
||||||
that.frameBuffer = null
|
|
||||||
}
|
|
||||||
if (that.outStream instanceof OutStream) {
|
|
||||||
that.outStream.end()
|
|
||||||
that.outStream = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
socket.once('close', onclose)
|
|
||||||
socket.once('finish', onclose)
|
|
||||||
|
|
||||||
// super constructor
|
|
||||||
events.EventEmitter.call(this)
|
|
||||||
if (callback) {
|
|
||||||
this.once('connect', callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Connection, events.EventEmitter)
|
|
||||||
module.exports = Connection
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum size of a pack of binary data to send in a single frame
|
|
||||||
* @property {number} binaryFragmentation
|
|
||||||
*/
|
|
||||||
Connection.binaryFragmentation = 512 * 1024 // .5 MiB
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum size the internal Buffer can grow
|
|
||||||
* If at any time it stays bigger than this, the connection will be closed with code 1009
|
|
||||||
* This is a security measure, to avoid memory attacks
|
|
||||||
* @property {number} maxBufferLength
|
|
||||||
*/
|
|
||||||
Connection.maxBufferLength = 2 * 1024 * 1024 // 2 MiB
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Possible ready states for the connection
|
|
||||||
* @constant {number} CONNECTING
|
|
||||||
* @constant {number} OPEN
|
|
||||||
* @constant {number} CLOSING
|
|
||||||
* @constant {number} CLOSED
|
|
||||||
*/
|
|
||||||
Connection.prototype.CONNECTING = 0
|
|
||||||
Connection.prototype.OPEN = 1
|
|
||||||
Connection.prototype.CLOSING = 2
|
|
||||||
Connection.prototype.CLOSED = 3
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a given string to the other side
|
|
||||||
* @param {string} str
|
|
||||||
* @param {Function} [callback] will be executed when the data is finally written out
|
|
||||||
*/
|
|
||||||
Connection.prototype.sendText = function (str, callback) {
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
if (!this.outStream) {
|
|
||||||
return this.socket.write(frame.createTextFrame(str, !this.server), callback)
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t send a text frame until you finish sending binary frames'))
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t write to a non-open connection'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for a OutStream to send binary data
|
|
||||||
* @returns {OutStream}
|
|
||||||
*/
|
|
||||||
Connection.prototype.beginBinary = function () {
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
if (!this.outStream) {
|
|
||||||
return (this.outStream = new OutStream(this, Connection.binaryFragmentation))
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames'))
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t write to a non-open connection'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a binary buffer at once
|
|
||||||
* @param {Buffer} data
|
|
||||||
* @param {Function} [callback] will be executed when the data is finally written out
|
|
||||||
*/
|
|
||||||
Connection.prototype.sendBinary = function (data, callback) {
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
if (!this.outStream) {
|
|
||||||
return this.socket.write(frame.createBinaryFrame(data, !this.server, true, true), callback)
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames'))
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t write to a non-open connection'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a text or binary frame
|
|
||||||
* @param {string|Buffer} data
|
|
||||||
* @param {Function} [callback] will be executed when the data is finally written out
|
|
||||||
*/
|
|
||||||
Connection.prototype.send = function (data, callback) {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
this.sendText(data, callback)
|
|
||||||
} else if (Buffer.isBuffer(data)) {
|
|
||||||
this.sendBinary(data, callback)
|
|
||||||
} else {
|
|
||||||
throw new TypeError('data should be either a string or a Buffer instance')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a ping to the remote
|
|
||||||
* @param {string} [data=''] - optional ping data
|
|
||||||
* @fires pong when pong reply is received
|
|
||||||
*/
|
|
||||||
Connection.prototype.sendPing = function (data) {
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
return this.socket.write(frame.createPingFrame(data || '', !this.server))
|
|
||||||
}
|
|
||||||
this.emit('error', new Error('You can\'t write to a non-open connection'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the connection, sending a close frame and waiting for response
|
|
||||||
* If the connection isn't OPEN, closes it without sending a close frame
|
|
||||||
* @param {number} [code]
|
|
||||||
* @param {string} [reason]
|
|
||||||
* @fires close
|
|
||||||
*/
|
|
||||||
Connection.prototype.close = function (code, reason) {
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
this.socket.write(frame.createCloseFrame(code, reason, !this.server))
|
|
||||||
this.readyState = this.CLOSING
|
|
||||||
} else if (this.readyState !== this.CLOSED) {
|
|
||||||
this.socket.end()
|
|
||||||
this.readyState = this.CLOSED
|
|
||||||
}
|
|
||||||
this.emit('close', code, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads contents from the socket and process it
|
|
||||||
* @fires connect
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.doRead = function () {
|
|
||||||
var buffer, temp
|
|
||||||
|
|
||||||
// Fetches the data
|
|
||||||
buffer = this.socket.read()
|
|
||||||
if (!buffer) {
|
|
||||||
// Waits for more data
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to the internal buffer
|
|
||||||
this.buffer = Buffer.concat([this.buffer, buffer], this.buffer.length + buffer.length)
|
|
||||||
|
|
||||||
if (this.readyState === this.CONNECTING) {
|
|
||||||
if (!this.readHandshake()) {
|
|
||||||
// May have failed or we're waiting for more data
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.readyState !== this.CLOSED) {
|
|
||||||
// Try to read as many frames as possible
|
|
||||||
while ((temp = this.extractFrame()) === true) {}
|
|
||||||
if (temp === false) {
|
|
||||||
// Protocol error
|
|
||||||
this.close(1002)
|
|
||||||
} else if (this.buffer.length > Connection.maxBufferLength) {
|
|
||||||
// Frame too big
|
|
||||||
this.close(1009)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and send a handshake as a client
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.startHandshake = function () {
|
|
||||||
var str, i, key, headers, header
|
|
||||||
key = new Buffer(16)
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
key[i] = Math.floor(Math.random() * 256)
|
|
||||||
}
|
|
||||||
this.key = key.toString('base64')
|
|
||||||
headers = {
|
|
||||||
Host: this.host,
|
|
||||||
Upgrade: 'websocket',
|
|
||||||
Connection: 'Upgrade',
|
|
||||||
'Sec-WebSocket-Key': this.key,
|
|
||||||
'Sec-WebSocket-Version': '13'
|
|
||||||
}
|
|
||||||
|
|
||||||
for (header in this.extraHeaders) {
|
|
||||||
headers[header] = this.extraHeaders[header]
|
|
||||||
}
|
|
||||||
|
|
||||||
str = this.buildRequest('GET ' + this.path + ' HTTP/1.1', headers)
|
|
||||||
this.socket.write(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to read the handshake from the internal buffer
|
|
||||||
* If it succeeds, the handshake data is consumed from the internal buffer
|
|
||||||
* @returns {boolean} - whether the handshake was done
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.readHandshake = function () {
|
|
||||||
var found = false,
|
|
||||||
i, data
|
|
||||||
|
|
||||||
// Do the handshake and try to connect
|
|
||||||
if (this.buffer.length > Connection.maxBufferLength) {
|
|
||||||
// Too big for a handshake
|
|
||||||
this.socket.end(this.server ? 'HTTP/1.1 400 Bad Request\r\n\r\n' : undefined)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for '\r\n\r\n'
|
|
||||||
for (i = 0; i < this.buffer.length - 3; i++) {
|
|
||||||
if (this.buffer[i] === 13 && this.buffer[i + 2] === 13 &&
|
|
||||||
this.buffer[i + 1] === 10 && this.buffer[i + 3] === 10) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
// Wait for more data
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
data = this.buffer.slice(0, i + 4).toString().split('\r\n')
|
|
||||||
if (this.server ? this.answerHandshake(data) : this.checkHandshake(data)) {
|
|
||||||
this.buffer = this.buffer.slice(i + 4)
|
|
||||||
this.readyState = this.OPEN
|
|
||||||
this.emit('connect')
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
this.socket.end(this.server ? 'HTTP/1.1 400 Bad Request\r\n\r\n' : undefined)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read headers from HTTP protocol
|
|
||||||
* Update the Connection#headers property
|
|
||||||
* @param {string[]} lines one for each '\r\n'-separated HTTP request line
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.readHeaders = function (lines) {
|
|
||||||
var i, match
|
|
||||||
|
|
||||||
// Extract all headers
|
|
||||||
// Ignore bad-formed lines and ignore the first line (HTTP header)
|
|
||||||
for (i = 1; i < lines.length; i++) {
|
|
||||||
if ((match = lines[i].match(/^([a-z-]+): (.+)$/i))) {
|
|
||||||
this.headers[match[1].toLowerCase()] = match[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process and check a handshake answered by a server
|
|
||||||
* @param {string[]} lines one for each '\r\n'-separated HTTP request line
|
|
||||||
* @returns {boolean} if the handshake was sucessful. If not, the connection must be closed
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.checkHandshake = function (lines) {
|
|
||||||
var key, sha1
|
|
||||||
|
|
||||||
// First line
|
|
||||||
if (lines.length < 4) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!lines[0].match(/^HTTP\/\d\.\d 101( .*)?$/i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all headers
|
|
||||||
this.readHeaders(lines)
|
|
||||||
|
|
||||||
// Validate necessary headers
|
|
||||||
if (!('upgrade' in this.headers) ||
|
|
||||||
!('sec-websocket-accept' in this.headers) ||
|
|
||||||
!('connection' in this.headers)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.headers.upgrade.toLowerCase() !== 'websocket' ||
|
|
||||||
this.headers.connection.toLowerCase().split(', ').indexOf('upgrade') === -1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
key = this.headers['sec-websocket-accept']
|
|
||||||
|
|
||||||
// Check the key
|
|
||||||
sha1 = crypto.createHash('sha1')
|
|
||||||
sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
||||||
if (key !== sha1.read().toString('base64')) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process and answer a handshake started by a client
|
|
||||||
* @param {string[]} lines one for each '\r\n'-separated HTTP request line
|
|
||||||
* @returns {boolean} if the handshake was sucessful. If not, the connection must be closed with error 400-Bad Request
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.answerHandshake = function (lines) {
|
|
||||||
var path, key, sha1
|
|
||||||
|
|
||||||
// First line
|
|
||||||
if (lines.length < 6) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
path = lines[0].match(/^GET (.+) HTTP\/\d\.\d$/i)
|
|
||||||
if (!path) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.path = path[1]
|
|
||||||
|
|
||||||
// Extract all headers
|
|
||||||
this.readHeaders(lines)
|
|
||||||
|
|
||||||
// Validate necessary headers
|
|
||||||
if (!('host' in this.headers) ||
|
|
||||||
!('sec-websocket-key' in this.headers) ||
|
|
||||||
!('upgrade' in this.headers) ||
|
|
||||||
!('connection' in this.headers)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.headers.upgrade.toLowerCase() !== 'websocket' ||
|
|
||||||
this.headers.connection.toLowerCase().split(', ').indexOf('upgrade') === -1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (this.headers['sec-websocket-version'] !== '13') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.key = this.headers['sec-websocket-key']
|
|
||||||
|
|
||||||
// Build and send the response
|
|
||||||
sha1 = crypto.createHash('sha1')
|
|
||||||
sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
||||||
key = sha1.read().toString('base64')
|
|
||||||
this.socket.write(this.buildRequest('HTTP/1.1 101 Switching Protocols', {
|
|
||||||
Upgrade: 'websocket',
|
|
||||||
Connection: 'Upgrade',
|
|
||||||
'Sec-WebSocket-Accept': key
|
|
||||||
}))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to extract frame contents from the buffer (and execute it)
|
|
||||||
* @returns {(boolean|undefined)} false=something went wrong (the connection must be closed); undefined=there isn't enough data to catch a frame; true=the frame was successfully fetched and executed
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.extractFrame = function () {
|
|
||||||
var fin, opcode, B, HB, mask, len, payload, start, i, hasMask
|
|
||||||
|
|
||||||
if (this.buffer.length < 2) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this the last frame in a sequence?
|
|
||||||
B = this.buffer[0]
|
|
||||||
HB = B >> 4
|
|
||||||
if (HB % 8) {
|
|
||||||
// RSV1, RSV2 and RSV3 must be clear
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fin = HB === 8
|
|
||||||
opcode = B % 16
|
|
||||||
|
|
||||||
if (opcode !== 0 && opcode !== 1 && opcode !== 2 &&
|
|
||||||
opcode !== 8 && opcode !== 9 && opcode !== 10) {
|
|
||||||
// Invalid opcode
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (opcode >= 8 && !fin) {
|
|
||||||
// Control frames must not be fragmented
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
B = this.buffer[1]
|
|
||||||
hasMask = B >> 7
|
|
||||||
if ((this.server && !hasMask) || (!this.server && hasMask)) {
|
|
||||||
// Frames sent by clients must be masked
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
len = B % 128
|
|
||||||
start = hasMask ? 6 : 2
|
|
||||||
|
|
||||||
if (this.buffer.length < start + len) {
|
|
||||||
// Not enough data in the buffer
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual payload length
|
|
||||||
if (len === 126) {
|
|
||||||
len = this.buffer.readUInt16BE(2)
|
|
||||||
start += 2
|
|
||||||
} else if (len === 127) {
|
|
||||||
// Warning: JS can only store up to 2^53 in its number format
|
|
||||||
len = this.buffer.readUInt32BE(2) * Math.pow(2, 32) + this.buffer.readUInt32BE(6)
|
|
||||||
start += 8
|
|
||||||
}
|
|
||||||
if (this.buffer.length < start + len) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the payload
|
|
||||||
payload = this.buffer.slice(start, start + len)
|
|
||||||
if (hasMask) {
|
|
||||||
// Decode with the given mask
|
|
||||||
mask = this.buffer.slice(start - 4, start)
|
|
||||||
for (i = 0; i < payload.length; i++) {
|
|
||||||
payload[i] ^= mask[i % 4]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.buffer = this.buffer.slice(start + len)
|
|
||||||
|
|
||||||
// Proceeds to frame processing
|
|
||||||
return this.processFrame(fin, opcode, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a given frame received
|
|
||||||
* @param {boolean} fin
|
|
||||||
* @param {number} opcode
|
|
||||||
* @param {Buffer} payload
|
|
||||||
* @returns {boolean} false if any error occurs, true otherwise
|
|
||||||
* @fires text
|
|
||||||
* @fires binary
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.processFrame = function (fin, opcode, payload) {
|
|
||||||
if (opcode === 8) {
|
|
||||||
// Close frame
|
|
||||||
if (this.readyState === this.CLOSING) {
|
|
||||||
this.socket.end()
|
|
||||||
} else if (this.readyState === this.OPEN) {
|
|
||||||
this.processCloseFrame(payload)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else if (opcode === 9) {
|
|
||||||
// Ping frame
|
|
||||||
if (this.readyState === this.OPEN) {
|
|
||||||
this.socket.write(frame.createPongFrame(payload.toString(), !this.server))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else if (opcode === 10) {
|
|
||||||
// Pong frame
|
|
||||||
this.emit('pong', payload.toString())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.readyState !== this.OPEN) {
|
|
||||||
// Ignores if the connection isn't opened anymore
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opcode === 0 && this.frameBuffer === null) {
|
|
||||||
// Unexpected continuation frame
|
|
||||||
return false
|
|
||||||
} else if (opcode !== 0 && this.frameBuffer !== null) {
|
|
||||||
// Last sequence didn't finished correctly
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opcode) {
|
|
||||||
// Get the current opcode for fragmented frames
|
|
||||||
opcode = typeof this.frameBuffer === 'string' ? 1 : 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opcode === 1) {
|
|
||||||
// Save text frame
|
|
||||||
payload = payload.toString()
|
|
||||||
this.frameBuffer = this.frameBuffer ? this.frameBuffer + payload : payload
|
|
||||||
|
|
||||||
if (fin) {
|
|
||||||
// Emits 'text' event
|
|
||||||
this.emit('text', this.frameBuffer)
|
|
||||||
this.frameBuffer = null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Sends the buffer for InStream object
|
|
||||||
if (!this.frameBuffer) {
|
|
||||||
// Emits the 'binary' event
|
|
||||||
this.frameBuffer = new InStream
|
|
||||||
this.emit('binary', this.frameBuffer)
|
|
||||||
}
|
|
||||||
this.frameBuffer.addData(payload)
|
|
||||||
|
|
||||||
if (fin) {
|
|
||||||
// Emits 'end' event
|
|
||||||
this.frameBuffer.end()
|
|
||||||
this.frameBuffer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a close frame, emitting the close event and sending back the frame
|
|
||||||
* @param {Buffer} payload
|
|
||||||
* @fires close
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.processCloseFrame = function (payload) {
|
|
||||||
var code, reason
|
|
||||||
if (payload.length >= 2) {
|
|
||||||
code = payload.readUInt16BE(0)
|
|
||||||
reason = payload.slice(2).toString()
|
|
||||||
} else {
|
|
||||||
code = 1005
|
|
||||||
reason = ''
|
|
||||||
}
|
|
||||||
this.socket.write(frame.createCloseFrame(code, reason, !this.server))
|
|
||||||
this.readyState = this.CLOSED
|
|
||||||
this.emit('close', code, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the header string
|
|
||||||
* @param {string} requestLine
|
|
||||||
* @param {Object<string>} headers
|
|
||||||
* @returns {string}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Connection.prototype.buildRequest = function (requestLine, headers) {
|
|
||||||
var headerString = requestLine + '\r\n',
|
|
||||||
headerName
|
|
||||||
|
|
||||||
for (headerName in headers) {
|
|
||||||
headerString += headerName + ': ' + headers[headerName] + '\r\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
return headerString + '\r\n'
|
|
||||||
}
|
|
||||||
16
node_modules/nodejs-websocket/HISTORY.md
generated
vendored
16
node_modules/nodejs-websocket/HISTORY.md
generated
vendored
@ -1,16 +0,0 @@
|
|||||||
# 1.6.0
|
|
||||||
* Added: `Server#close` as a short hand for `Server#socket.close`
|
|
||||||
|
|
||||||
# 1.5.0
|
|
||||||
* Added: `Connection#send` as a short hand for `Connection#sendText` or `Connection#sendBinary`, depending on the data type (string or Buffer)
|
|
||||||
|
|
||||||
# 1.4.1
|
|
||||||
* Added: example to README
|
|
||||||
|
|
||||||
# 1.4.0
|
|
||||||
* Added: `extraHeaders` option in `ws.connect(URL, [options], [callback])` to let one add custom headers to the HTTP handshake request
|
|
||||||
|
|
||||||
# 1.3.0
|
|
||||||
|
|
||||||
* Added: `Connection#sendPing([data=''])`
|
|
||||||
* Added: `pong(data)` event
|
|
||||||
44
node_modules/nodejs-websocket/InStream.js
generated
vendored
44
node_modules/nodejs-websocket/InStream.js
generated
vendored
@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Simple wrapper for stream.Readable, used for receiving binary data
|
|
||||||
*/
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
var util = require('util'),
|
|
||||||
stream = require('stream')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the readable stream for binary frames
|
|
||||||
* @class
|
|
||||||
* @event readable
|
|
||||||
* @event end
|
|
||||||
*/
|
|
||||||
function InStream() {
|
|
||||||
stream.Readable.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = InStream
|
|
||||||
|
|
||||||
util.inherits(InStream, stream.Readable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No logic here, the pushs are made outside _read
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
InStream.prototype._read = function () {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add more data to the stream and fires "readable" event
|
|
||||||
* @param {Buffer} data
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
InStream.prototype.addData = function (data) {
|
|
||||||
this.push(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates there is no more data to add to the stream
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
InStream.prototype.end = function () {
|
|
||||||
this.push(null)
|
|
||||||
}
|
|
||||||
59
node_modules/nodejs-websocket/OutStream.js
generated
vendored
59
node_modules/nodejs-websocket/OutStream.js
generated
vendored
@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Simple wrapper for stream.Writable, used for sending binary data
|
|
||||||
*/
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
var util = require('util'),
|
|
||||||
stream = require('stream'),
|
|
||||||
frame = require('./frame')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Represents the writable stream for binary frames
|
|
||||||
* @param {Connection} connection
|
|
||||||
* @param {number} minSize
|
|
||||||
*/
|
|
||||||
function OutStream(connection, minSize) {
|
|
||||||
var that = this
|
|
||||||
this.connection = connection
|
|
||||||
this.minSize = minSize
|
|
||||||
this.buffer = new Buffer(0)
|
|
||||||
this.hasSent = false // Indicates if any frame has been sent yet
|
|
||||||
stream.Writable.call(this)
|
|
||||||
this.on('finish', function () {
|
|
||||||
if (that.connection.readyState === that.connection.OPEN) {
|
|
||||||
// Ignore if not connected anymore
|
|
||||||
that.connection.socket.write(frame.createBinaryFrame(that.buffer, !that.connection.server, !that.hasSent, true))
|
|
||||||
}
|
|
||||||
that.connection.outStream = null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = OutStream
|
|
||||||
|
|
||||||
|
|
||||||
util.inherits(OutStream, stream.Writable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Buffer} chunk
|
|
||||||
* @param {string} encoding
|
|
||||||
* @param {Function} callback
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
OutStream.prototype._write = function (chunk, encoding, callback) {
|
|
||||||
var frameBuffer
|
|
||||||
this.buffer = Buffer.concat([this.buffer, chunk], this.buffer.length + chunk.length)
|
|
||||||
if (this.buffer.length >= this.minSize) {
|
|
||||||
if (this.connection.readyState === this.connection.OPEN) {
|
|
||||||
// Ignore if not connected anymore
|
|
||||||
frameBuffer = frame.createBinaryFrame(this.buffer, !this.connection.server, !this.hasSent, false)
|
|
||||||
this.connection.socket.write(frameBuffer, encoding, callback)
|
|
||||||
}
|
|
||||||
this.buffer = new Buffer(0)
|
|
||||||
this.hasSent = true
|
|
||||||
if (this.connection.readyState !== this.connection.OPEN) {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
192
node_modules/nodejs-websocket/README.md
generated
vendored
192
node_modules/nodejs-websocket/README.md
generated
vendored
@ -1,192 +0,0 @@
|
|||||||
# Nodejs Websocket
|
|
||||||
[](https://travis-ci.org/sitegui/nodejs-websocket)
|
|
||||||
[](https://inch-ci.org/github/sitegui/nodejs-websocket)
|
|
||||||
[](https://david-dm.org/sitegui/nodejs-websocket)
|
|
||||||
|
|
||||||
A nodejs module for websocket server and client
|
|
||||||
|
|
||||||
# How to use it
|
|
||||||
Install with `npm install nodejs-websocket` or put all files in a folder called "nodejs-websocket", and:
|
|
||||||
```javascript
|
|
||||||
var ws = require("nodejs-websocket")
|
|
||||||
|
|
||||||
// Scream server example: "hi" -> "HI!!!"
|
|
||||||
var server = ws.createServer(function (conn) {
|
|
||||||
console.log("New connection")
|
|
||||||
conn.on("text", function (str) {
|
|
||||||
console.log("Received "+str)
|
|
||||||
conn.sendText(str.toUpperCase()+"!!!")
|
|
||||||
})
|
|
||||||
conn.on("close", function (code, reason) {
|
|
||||||
console.log("Connection closed")
|
|
||||||
})
|
|
||||||
}).listen(8001)
|
|
||||||
```
|
|
||||||
|
|
||||||
Se other examples inside the folder samples
|
|
||||||
|
|
||||||
# ws
|
|
||||||
The main object, returned by `require("nodejs-websocket")`.
|
|
||||||
|
|
||||||
## ws.createServer([options], [callback])
|
|
||||||
Returns a new `Server` object.
|
|
||||||
|
|
||||||
The `options` is an optional object that will be handed to net.createServer() to create an ordinary socket.
|
|
||||||
If it has a property called "secure" with value `true`, tls.createServer() will be used instead.
|
|
||||||
|
|
||||||
The `callback` is a function which is automatically added to the `"connection"` event.
|
|
||||||
|
|
||||||
## ws.connect(URL, [options], [callback])
|
|
||||||
Returns a new `Connection` object, representing a websocket client connection
|
|
||||||
|
|
||||||
`URL` is a string with the format "ws://localhost:8000/chat" (the port can be omitted)
|
|
||||||
|
|
||||||
`options` is an object that will be passed to net.connect() (or tls.connect() if the protocol is "wss:").
|
|
||||||
The properties "host" and "port" will be read from the `URL`.
|
|
||||||
The property `extraHeaders` will be used to add more headers to the HTTP handshake request.
|
|
||||||
|
|
||||||
`callback` will be added as "connect" listener
|
|
||||||
|
|
||||||
## ws.setBinaryFragmentation(bytes)
|
|
||||||
Sets the minimum size of a pack of binary data to send in a single frame (default: 512kiB)
|
|
||||||
|
|
||||||
## ws.setMaxBufferLength(bytes)
|
|
||||||
Set the maximum size the internal Buffer can grow (default: 2MiB)
|
|
||||||
If at any time it stays bigger than this, the connection will be closed with code 1009
|
|
||||||
This is a security measure, to avoid memory attacks
|
|
||||||
|
|
||||||
# Server
|
|
||||||
The class that represents a websocket server, much like a HTTP server
|
|
||||||
|
|
||||||
## server.listen(port, [host], [callback])
|
|
||||||
Starts accepting connections on a given `port` and `host`.
|
|
||||||
|
|
||||||
If the `host` is omitted, the server will accept connections directed to any IPv4 address (INADDR_ANY).
|
|
||||||
|
|
||||||
A `port` value of zero will assign a random port.
|
|
||||||
|
|
||||||
`callback` will be added as an listener for the `'listening'` event.
|
|
||||||
|
|
||||||
## server.close([callback])
|
|
||||||
Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs.
|
|
||||||
|
|
||||||
## server.socket
|
|
||||||
The underlying socket, returned by net.createServer or tls.createServer
|
|
||||||
|
|
||||||
## server.connections
|
|
||||||
An Array with all connected clients. It's useful for broadcasting a message:
|
|
||||||
```javascript
|
|
||||||
function broadcast(server, msg) {
|
|
||||||
server.connections.forEach(function (conn) {
|
|
||||||
conn.sendText(msg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event: 'listening()'
|
|
||||||
Emitted when the server has been bound after calling server.listen
|
|
||||||
|
|
||||||
## Event: 'close()'
|
|
||||||
Emitted when the server closes. Note that if connections exist, this event is not emitted until all connections are completely ended.
|
|
||||||
|
|
||||||
## Event: 'error(errObj)'
|
|
||||||
Emitted when an error occurs. The 'close' event will be called directly following this event.
|
|
||||||
|
|
||||||
## Event: 'connection(conn)'
|
|
||||||
Emitted when a new connection is made successfully (after the handshake have been completed). conn is an instance of Connection
|
|
||||||
|
|
||||||
# Connection
|
|
||||||
The class that represents a connection, either a client-created (accepted by a nodejs ws server) or client connection.
|
|
||||||
The websocket protocol has two types of data frames: text and binary.
|
|
||||||
Text frames are implemented as simple send function and receive event.
|
|
||||||
Binary frames are implemented as streams: when you receive binary data, you get a ReadableStream; to send binary data, you must ask for a WritableStream and write into it.
|
|
||||||
The binary data will be divided into frames and be sent over the socket.
|
|
||||||
|
|
||||||
You cannot send text data while sending binary data. If you try to do so, the connection will emit an "error" event
|
|
||||||
|
|
||||||
## connection.sendText(str, [callback])
|
|
||||||
Sends a given string to the other side. You can't send text data in the middle of a binary transmission.
|
|
||||||
|
|
||||||
`callback` will be added as a listener to write operation over the socket
|
|
||||||
|
|
||||||
## connection.beginBinary()
|
|
||||||
Asks the connection to begin transmitting binary data. Returns a WritableStream.
|
|
||||||
The binary transmission will end when the WritableStream finishes (like when you call .end on it)
|
|
||||||
|
|
||||||
## connection.sendBinary(data, [callback])
|
|
||||||
Sends a single chunk of binary data (like calling connection.beginBinary().end(data))
|
|
||||||
|
|
||||||
`callback` will be added as a listener to write operation over the socket
|
|
||||||
|
|
||||||
## connection.send(data, [callback])
|
|
||||||
Sends a given string or Buffer to the other side. This is simply an alias for `sendText()` if data is a string or `sendBinary()` if the data is a Buffer.
|
|
||||||
|
|
||||||
`callback` will be added as a listener to write operation over the socket
|
|
||||||
|
|
||||||
## connection.sendPing([data=''])
|
|
||||||
Sends a [ping](http://tools.ietf.org/html/rfc6455#section-5.5.2) with optional payload
|
|
||||||
|
|
||||||
## connection.close([code, [reason]])
|
|
||||||
Starts the closing handshake (sends a close frame)
|
|
||||||
|
|
||||||
## connection.socket
|
|
||||||
The underlying net or tls socket
|
|
||||||
|
|
||||||
## connection.server
|
|
||||||
If the connection was accepted by a nodejs server, a reference to it will be saved here. null otherwise
|
|
||||||
|
|
||||||
## connection.readyState
|
|
||||||
One of these constants, representing the current state of the connection. Only an open connection can be used to send/receive data.
|
|
||||||
* connection.CONNECTING (waiting for handshake completion)
|
|
||||||
* connection.OPEN
|
|
||||||
* connection.CLOSING (waiting for the answer to a close frame)
|
|
||||||
* connection.CLOSED
|
|
||||||
|
|
||||||
## connection.outStream
|
|
||||||
Stores the OutStream object returned by connection.beginBinary(). null if there is no current binary data beeing sent.
|
|
||||||
|
|
||||||
## connection.path
|
|
||||||
For a connection accepted by a server, it is a string representing the path to which the connection was made (example: "/chat"). null otherwise
|
|
||||||
|
|
||||||
## connection.headers
|
|
||||||
Read only map of header names and values. Header names are lower-cased
|
|
||||||
|
|
||||||
## Event: 'close(code, reason)'
|
|
||||||
Emitted when the connection is closed by any side
|
|
||||||
|
|
||||||
## Event: 'error(err)'
|
|
||||||
Emitted in case of error (like trying to send text data while still sending binary data)
|
|
||||||
|
|
||||||
## Event: 'text(str)'
|
|
||||||
Emitted when a text is received. `str` is a string
|
|
||||||
|
|
||||||
## Event: 'binary(inStream)'
|
|
||||||
Emitted when the beginning of binary data is received. `inStream` is a [ReadableStream](https://nodejs.org/api/stream.html#stream_class_stream_readable):
|
|
||||||
```javascript
|
|
||||||
var server = ws.createServer(function (conn) {
|
|
||||||
console.log("New connection")
|
|
||||||
conn.on("binary", function (inStream) {
|
|
||||||
// Empty buffer for collecting binary data
|
|
||||||
var data = new Buffer(0)
|
|
||||||
// Read chunks of binary data and add to the buffer
|
|
||||||
inStream.on("readable", function () {
|
|
||||||
var newData = inStream.read()
|
|
||||||
if (newData)
|
|
||||||
data = Buffer.concat([data, newData], data.length+newData.length)
|
|
||||||
})
|
|
||||||
inStream.on("end", function () {
|
|
||||||
console.log("Received " + data.length + " bytes of binary data")
|
|
||||||
process_my_data(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
conn.on("close", function (code, reason) {
|
|
||||||
console.log("Connection closed")
|
|
||||||
})
|
|
||||||
}).listen(8001)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event: 'connect()'
|
|
||||||
Emitted when the connection is fully established (after the handshake)
|
|
||||||
|
|
||||||
## Event: 'pong(data)'
|
|
||||||
Emitted when a [pong](http://tools.ietf.org/html/rfc6455#section-5.5.3) is received, usually after a ping was sent. `data` is the pong payload, as a string
|
|
||||||
113
node_modules/nodejs-websocket/Server.js
generated
vendored
113
node_modules/nodejs-websocket/Server.js
generated
vendored
@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Represents a websocket server
|
|
||||||
*/
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
function nop() {}
|
|
||||||
|
|
||||||
var util = require('util'),
|
|
||||||
net = require('net'),
|
|
||||||
tls = require('tls'),
|
|
||||||
events = require('events'),
|
|
||||||
Connection
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ws server and starts listening for new connections
|
|
||||||
* @class
|
|
||||||
* @param {boolean} secure indicates if it should use tls
|
|
||||||
* @param {Object} [options] will be passed to net.createServer() or tls.createServer()
|
|
||||||
* @param {Function} [callback] will be added as "connection" listener
|
|
||||||
* @inherits EventEmitter
|
|
||||||
* @event listening
|
|
||||||
* @event close
|
|
||||||
* @event error an error object is passed
|
|
||||||
* @event connection a Connection object is passed
|
|
||||||
*/
|
|
||||||
function Server(secure, options, callback) {
|
|
||||||
var that = this
|
|
||||||
|
|
||||||
if (typeof options === 'function') {
|
|
||||||
callback = options
|
|
||||||
options = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
var onConnection = function (socket) {
|
|
||||||
var conn = new Connection(socket, that, function () {
|
|
||||||
that.connections.push(conn)
|
|
||||||
conn.removeListener('error', nop)
|
|
||||||
that.emit('connection', conn)
|
|
||||||
})
|
|
||||||
conn.on('close', function () {
|
|
||||||
var pos = that.connections.indexOf(conn)
|
|
||||||
if (pos !== -1) {
|
|
||||||
that.connections.splice(pos, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ignore errors before the connection is established
|
|
||||||
conn.on('error', nop)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secure) {
|
|
||||||
this.socket = tls.createServer(options, onConnection)
|
|
||||||
} else {
|
|
||||||
this.socket = net.createServer(options, onConnection)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket.on('close', function () {
|
|
||||||
that.emit('close')
|
|
||||||
})
|
|
||||||
this.socket.on('error', function (err) {
|
|
||||||
that.emit('error', err)
|
|
||||||
})
|
|
||||||
this.connections = []
|
|
||||||
|
|
||||||
// super constructor
|
|
||||||
events.EventEmitter.call(this)
|
|
||||||
if (callback) {
|
|
||||||
this.on('connection', callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Server, events.EventEmitter)
|
|
||||||
module.exports = Server
|
|
||||||
|
|
||||||
Connection = require('./Connection')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start listening for connections
|
|
||||||
* @param {number} port
|
|
||||||
* @param {string} [host]
|
|
||||||
* @param {Function} [callback] will be added as "connection" listener
|
|
||||||
*/
|
|
||||||
Server.prototype.listen = function (port, host, callback) {
|
|
||||||
var that = this
|
|
||||||
|
|
||||||
if (typeof host === 'function') {
|
|
||||||
callback = host
|
|
||||||
host = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback) {
|
|
||||||
this.on('listening', callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket.listen(port, host, function () {
|
|
||||||
that.emit('listening')
|
|
||||||
})
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the server from accepting new connections and keeps existing connections.
|
|
||||||
* This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event.
|
|
||||||
* The optional callback will be called once the 'close' event occurs.
|
|
||||||
* @param {function()} [callback]
|
|
||||||
*/
|
|
||||||
Server.prototype.close = function (callback) {
|
|
||||||
if (callback) {
|
|
||||||
this.once('close', callback)
|
|
||||||
}
|
|
||||||
this.socket.close()
|
|
||||||
}
|
|
||||||
152
node_modules/nodejs-websocket/frame.js
generated
vendored
152
node_modules/nodejs-websocket/frame.js
generated
vendored
@ -1,152 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Utility functions for creating frames
|
|
||||||
*/
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a text frame
|
|
||||||
* @param {string} data
|
|
||||||
* @param {boolean} [masked=false] if the frame should be masked
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
exports.createTextFrame = function (data, masked) {
|
|
||||||
var payload, meta
|
|
||||||
|
|
||||||
payload = new Buffer(data)
|
|
||||||
meta = generateMetaData(true, 1, masked === undefined ? false : masked, payload)
|
|
||||||
|
|
||||||
return Buffer.concat([meta, payload], meta.length + payload.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a binary frame
|
|
||||||
* @param {Buffer} data
|
|
||||||
* @param {boolean} [masked=false] if the frame should be masked
|
|
||||||
* @param {boolean} [first=true] if this is the first frame in a sequence
|
|
||||||
* @param {boolean} [fin=true] if this is the final frame in a sequence
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
exports.createBinaryFrame = function (data, masked, first, fin) {
|
|
||||||
var payload, meta
|
|
||||||
|
|
||||||
first = first === undefined ? true : first
|
|
||||||
masked = masked === undefined ? false : masked
|
|
||||||
if (masked) {
|
|
||||||
payload = new Buffer(data.length)
|
|
||||||
data.copy(payload)
|
|
||||||
} else {
|
|
||||||
payload = data
|
|
||||||
}
|
|
||||||
meta = generateMetaData(fin === undefined ? true : fin, first ? 2 : 0, masked, payload)
|
|
||||||
|
|
||||||
return Buffer.concat([meta, payload], meta.length + payload.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a close frame
|
|
||||||
* @param {number} code
|
|
||||||
* @param {string} [reason='']
|
|
||||||
* @param {boolean} [masked=false] if the frame should be masked
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
exports.createCloseFrame = function (code, reason, masked) {
|
|
||||||
var payload, meta
|
|
||||||
|
|
||||||
if (code !== undefined && code !== 1005) {
|
|
||||||
payload = new Buffer(reason === undefined ? '--' : '--' + reason)
|
|
||||||
payload.writeUInt16BE(code, 0)
|
|
||||||
} else {
|
|
||||||
payload = new Buffer(0)
|
|
||||||
}
|
|
||||||
meta = generateMetaData(true, 8, masked === undefined ? false : masked, payload)
|
|
||||||
|
|
||||||
return Buffer.concat([meta, payload], meta.length + payload.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a ping frame
|
|
||||||
* @param {string} data
|
|
||||||
* @param {boolean} [masked=false] if the frame should be masked
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
exports.createPingFrame = function (data, masked) {
|
|
||||||
var payload, meta
|
|
||||||
|
|
||||||
payload = new Buffer(data)
|
|
||||||
meta = generateMetaData(true, 9, masked === undefined ? false : masked, payload)
|
|
||||||
|
|
||||||
return Buffer.concat([meta, payload], meta.length + payload.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a pong frame
|
|
||||||
* @param {string} data
|
|
||||||
* @param {boolean} [masked=false] if the frame should be masked
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
exports.createPongFrame = function (data, masked) {
|
|
||||||
var payload, meta
|
|
||||||
|
|
||||||
payload = new Buffer(data)
|
|
||||||
meta = generateMetaData(true, 10, masked === undefined ? false : masked, payload)
|
|
||||||
|
|
||||||
return Buffer.concat([meta, payload], meta.length + payload.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the meta-data portion of the frame
|
|
||||||
* If the frame is masked, the payload is altered accordingly
|
|
||||||
* @param {boolean} fin
|
|
||||||
* @param {number} opcode
|
|
||||||
* @param {boolean} masked
|
|
||||||
* @param {Buffer} payload
|
|
||||||
* @returns {Buffer}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function generateMetaData(fin, opcode, masked, payload) {
|
|
||||||
var len, meta, start, mask, i
|
|
||||||
|
|
||||||
len = payload.length
|
|
||||||
|
|
||||||
// Creates the buffer for meta-data
|
|
||||||
meta = new Buffer(2 + (len < 126 ? 0 : (len < 65536 ? 2 : 8)) + (masked ? 4 : 0))
|
|
||||||
|
|
||||||
// Sets fin and opcode
|
|
||||||
meta[0] = (fin ? 128 : 0) + opcode
|
|
||||||
|
|
||||||
// Sets the mask and length
|
|
||||||
meta[1] = masked ? 128 : 0
|
|
||||||
start = 2
|
|
||||||
if (len < 126) {
|
|
||||||
meta[1] += len
|
|
||||||
} else if (len < 65536) {
|
|
||||||
meta[1] += 126
|
|
||||||
meta.writeUInt16BE(len, 2)
|
|
||||||
start += 2
|
|
||||||
} else {
|
|
||||||
// Warning: JS doesn't support integers greater than 2^53
|
|
||||||
meta[1] += 127
|
|
||||||
meta.writeUInt32BE(Math.floor(len / Math.pow(2, 32)), 2)
|
|
||||||
meta.writeUInt32BE(len % Math.pow(2, 32), 6)
|
|
||||||
start += 8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the mask-key
|
|
||||||
if (masked) {
|
|
||||||
mask = new Buffer(4)
|
|
||||||
for (i = 0; i < 4; i++) {
|
|
||||||
meta[start + i] = mask[i] = Math.floor(Math.random() * 256)
|
|
||||||
}
|
|
||||||
for (i = 0; i < payload.length; i++) {
|
|
||||||
payload[i] ^= mask[i % 4]
|
|
||||||
}
|
|
||||||
start += 4
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
100
node_modules/nodejs-websocket/index.js
generated
vendored
100
node_modules/nodejs-websocket/index.js
generated
vendored
@ -1,100 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
var Server = require('./Server'),
|
|
||||||
Connection = require('./Connection'),
|
|
||||||
net = require('net'),
|
|
||||||
tls = require('tls'),
|
|
||||||
url = require('url')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a WebSocket server
|
|
||||||
* @param {Object} [options] will be passed to net.createServer() or tls.createServer(), with the additional property 'secure' (a boolean)
|
|
||||||
* @param {Function} callback will be added as 'connection' listener
|
|
||||||
* @returns {Server}
|
|
||||||
*/
|
|
||||||
exports.createServer = function (options, callback) {
|
|
||||||
if (typeof options === 'function' || !arguments.length) {
|
|
||||||
return new Server(false, options)
|
|
||||||
}
|
|
||||||
return new Server(Boolean(options.secure), options, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a WebSocket client
|
|
||||||
* @param {string} URL with the format 'ws://localhost:8000/chat' (the port can be ommited)
|
|
||||||
* @param {Object} [options] will be passed to net.connect() or tls.connect()
|
|
||||||
* @param {Function} callback will be added as 'connect' listener
|
|
||||||
* @returns {Connection}
|
|
||||||
*/
|
|
||||||
exports.connect = function (URL, options, callback) {
|
|
||||||
var socket
|
|
||||||
|
|
||||||
if (typeof options === 'function') {
|
|
||||||
callback = options
|
|
||||||
options = undefined
|
|
||||||
}
|
|
||||||
options = options || {}
|
|
||||||
|
|
||||||
URL = parseWSURL(URL)
|
|
||||||
options.port = URL.port
|
|
||||||
options.host = URL.host
|
|
||||||
|
|
||||||
if (options.hasOwnProperty('extraHeaders')) {
|
|
||||||
URL.extraHeaders = options.extraHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
if (URL.secure) {
|
|
||||||
socket = tls.connect(options)
|
|
||||||
} else {
|
|
||||||
socket = net.connect(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Connection(socket, URL, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the minimum size of a pack of binary data to send in a single frame
|
|
||||||
* @param {number} bytes
|
|
||||||
*/
|
|
||||||
exports.setBinaryFragmentation = function (bytes) {
|
|
||||||
Connection.binaryFragmentation = bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the maximum size the internal Buffer can grow, to avoid memory attacks
|
|
||||||
* @param {number} bytes
|
|
||||||
*/
|
|
||||||
exports.setMaxBufferLength = function (bytes) {
|
|
||||||
Connection.maxBufferLength = bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the WebSocket URL
|
|
||||||
* @param {string} URL
|
|
||||||
* @returns {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function parseWSURL(URL) {
|
|
||||||
var parts, secure
|
|
||||||
|
|
||||||
parts = url.parse(URL)
|
|
||||||
|
|
||||||
parts.protocol = parts.protocol || 'ws:'
|
|
||||||
if (parts.protocol === 'ws:') {
|
|
||||||
secure = false
|
|
||||||
} else if (parts.protocol === 'wss:') {
|
|
||||||
secure = true
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid protocol ' + parts.protocol + '. It must be ws or wss')
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.port = parts.port || (secure ? 443 : 80)
|
|
||||||
parts.path = parts.path || '/'
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: parts.path,
|
|
||||||
port: parts.port,
|
|
||||||
secure: secure,
|
|
||||||
host: parts.hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
60
node_modules/nodejs-websocket/package.json
generated
vendored
60
node_modules/nodejs-websocket/package.json
generated
vendored
@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nodejs-websocket",
|
|
||||||
"version": "1.6.0",
|
|
||||||
"author": {
|
|
||||||
"name": "Sitegui",
|
|
||||||
"email": "sitegui@sitegui.com.br"
|
|
||||||
},
|
|
||||||
"description": "Basic server&client approach to websocket (text and binary frames)",
|
|
||||||
"main": "./index.js",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/sitegui/nodejs-websocket.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"websocket",
|
|
||||||
"websocket-server",
|
|
||||||
"websocket-client"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": "mocha -R spec -b"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"mocha": "^2.4.5",
|
|
||||||
"should": "^8.3.1"
|
|
||||||
},
|
|
||||||
"gitHead": "7398f57a6054724418ccaa0829daddc5ff62c908",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/sitegui/nodejs-websocket/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/sitegui/nodejs-websocket#readme",
|
|
||||||
"_id": "nodejs-websocket@1.6.0",
|
|
||||||
"_shasum": "e6db8d2e5af9e730bf70818bfd189783fe02797a",
|
|
||||||
"_from": "nodejs-websocket@*",
|
|
||||||
"_npmVersion": "2.14.20",
|
|
||||||
"_nodeVersion": "4.4.0",
|
|
||||||
"_npmUser": {
|
|
||||||
"name": "sitegui",
|
|
||||||
"email": "sitegui@sitegui.com.br"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"shasum": "e6db8d2e5af9e730bf70818bfd189783fe02797a",
|
|
||||||
"tarball": "https://registry.npmjs.org/nodejs-websocket/-/nodejs-websocket-1.6.0.tgz"
|
|
||||||
},
|
|
||||||
"maintainers": [
|
|
||||||
{
|
|
||||||
"name": "sitegui",
|
|
||||||
"email": "sitegui@sitegui.com.br"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"_npmOperationalInternal": {
|
|
||||||
"host": "packages-16-east.internal.npmjs.com",
|
|
||||||
"tmp": "tmp/nodejs-websocket-1.6.0.tgz_1462798103164_0.4242532418575138"
|
|
||||||
},
|
|
||||||
"directories": {},
|
|
||||||
"_resolved": "https://registry.npmjs.org/nodejs-websocket/-/nodejs-websocket-1.6.0.tgz"
|
|
||||||
}
|
|
||||||
7
node_modules/safe-buffer/.travis.yml
generated
vendored
Normal file
7
node_modules/safe-buffer/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 'node'
|
||||||
|
- '5'
|
||||||
|
- '4'
|
||||||
|
- '0.12'
|
||||||
|
- '0.10'
|
||||||
21
node_modules/safe-buffer/LICENSE
generated
vendored
Normal file
21
node_modules/safe-buffer/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Feross Aboukhadijeh
|
||||||
|
|
||||||
|
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.
|
||||||
581
node_modules/safe-buffer/README.md
generated
vendored
Normal file
581
node_modules/safe-buffer/README.md
generated
vendored
Normal file
@ -0,0 +1,581 @@
|
|||||||
|
# safe-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][npm-url]
|
||||||
|
|
||||||
|
#### Safer Node.js Buffer API
|
||||||
|
|
||||||
|
**Use the new Node.js v6 Buffer APIs (`Buffer.from`, `Buffer.alloc`,
|
||||||
|
`Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`) in Node.js v0.10, v0.12, v4.x, and v5.x.**
|
||||||
|
|
||||||
|
**Uses the built-in implementations when available.**
|
||||||
|
|
||||||
|
[travis-image]: https://img.shields.io/travis/feross/safe-buffer.svg
|
||||||
|
[travis-url]: https://travis-ci.org/feross/safe-buffer
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/safe-buffer.svg
|
||||||
|
[npm-url]: https://npmjs.org/package/safe-buffer
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/safe-buffer.svg
|
||||||
|
|
||||||
|
## install
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install safe-buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
The goal of this package is to provide a safe replacement for the node.js `Buffer`.
|
||||||
|
|
||||||
|
It's a drop-in replacement for `Buffer`. You can use it by adding one `require` line to
|
||||||
|
the top of your node.js modules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var Buffer = require('safe-buffer').Buffer
|
||||||
|
|
||||||
|
// Existing buffer code will continue to work without issues:
|
||||||
|
|
||||||
|
new Buffer('hey', 'utf8')
|
||||||
|
new Buffer([1, 2, 3], 'utf8')
|
||||||
|
new Buffer(obj)
|
||||||
|
new Buffer(16) // create an uninitialized buffer (potentially unsafe)
|
||||||
|
|
||||||
|
// But you can use these new explicit APIs to make clear what you want:
|
||||||
|
|
||||||
|
Buffer.from('hey', 'utf8') // convert from many types to a Buffer
|
||||||
|
Buffer.alloc(16) // create a zero-filled buffer (safe)
|
||||||
|
Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe)
|
||||||
|
```
|
||||||
|
|
||||||
|
## api
|
||||||
|
|
||||||
|
### Class Method: Buffer.from(array)
|
||||||
|
<!-- YAML
|
||||||
|
added: v3.0.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `array` {Array}
|
||||||
|
|
||||||
|
Allocates a new `Buffer` using an `array` of octets.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]);
|
||||||
|
// creates a new Buffer containing ASCII bytes
|
||||||
|
// ['b','u','f','f','e','r']
|
||||||
|
```
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `array` is not an `Array`.
|
||||||
|
|
||||||
|
### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]])
|
||||||
|
<!-- YAML
|
||||||
|
added: v5.10.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or
|
||||||
|
a `new ArrayBuffer()`
|
||||||
|
* `byteOffset` {Number} Default: `0`
|
||||||
|
* `length` {Number} Default: `arrayBuffer.length - byteOffset`
|
||||||
|
|
||||||
|
When passed a reference to the `.buffer` property of a `TypedArray` instance,
|
||||||
|
the newly created `Buffer` will share the same allocated memory as the
|
||||||
|
TypedArray.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const arr = new Uint16Array(2);
|
||||||
|
arr[0] = 5000;
|
||||||
|
arr[1] = 4000;
|
||||||
|
|
||||||
|
const buf = Buffer.from(arr.buffer); // shares the memory with arr;
|
||||||
|
|
||||||
|
console.log(buf);
|
||||||
|
// Prints: <Buffer 88 13 a0 0f>
|
||||||
|
|
||||||
|
// changing the TypedArray changes the Buffer also
|
||||||
|
arr[1] = 6000;
|
||||||
|
|
||||||
|
console.log(buf);
|
||||||
|
// Prints: <Buffer 88 13 70 17>
|
||||||
|
```
|
||||||
|
|
||||||
|
The optional `byteOffset` and `length` arguments specify a memory range within
|
||||||
|
the `arrayBuffer` that will be shared by the `Buffer`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ab = new ArrayBuffer(10);
|
||||||
|
const buf = Buffer.from(ab, 0, 2);
|
||||||
|
console.log(buf.length);
|
||||||
|
// Prints: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`.
|
||||||
|
|
||||||
|
### Class Method: Buffer.from(buffer)
|
||||||
|
<!-- YAML
|
||||||
|
added: v3.0.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `buffer` {Buffer}
|
||||||
|
|
||||||
|
Copies the passed `buffer` data onto a new `Buffer` instance.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf1 = Buffer.from('buffer');
|
||||||
|
const buf2 = Buffer.from(buf1);
|
||||||
|
|
||||||
|
buf1[0] = 0x61;
|
||||||
|
console.log(buf1.toString());
|
||||||
|
// 'auffer'
|
||||||
|
console.log(buf2.toString());
|
||||||
|
// 'buffer' (copy is not changed)
|
||||||
|
```
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `buffer` is not a `Buffer`.
|
||||||
|
|
||||||
|
### Class Method: Buffer.from(str[, encoding])
|
||||||
|
<!-- YAML
|
||||||
|
added: v5.10.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `str` {String} String to encode.
|
||||||
|
* `encoding` {String} Encoding to use, Default: `'utf8'`
|
||||||
|
|
||||||
|
Creates a new `Buffer` containing the given JavaScript string `str`. If
|
||||||
|
provided, the `encoding` parameter identifies the character encoding.
|
||||||
|
If not provided, `encoding` defaults to `'utf8'`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf1 = Buffer.from('this is a tést');
|
||||||
|
console.log(buf1.toString());
|
||||||
|
// prints: this is a tést
|
||||||
|
console.log(buf1.toString('ascii'));
|
||||||
|
// prints: this is a tC)st
|
||||||
|
|
||||||
|
const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
|
||||||
|
console.log(buf2.toString());
|
||||||
|
// prints: this is a tést
|
||||||
|
```
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `str` is not a string.
|
||||||
|
|
||||||
|
### Class Method: Buffer.alloc(size[, fill[, encoding]])
|
||||||
|
<!-- YAML
|
||||||
|
added: v5.10.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `size` {Number}
|
||||||
|
* `fill` {Value} Default: `undefined`
|
||||||
|
* `encoding` {String} Default: `utf8`
|
||||||
|
|
||||||
|
Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the
|
||||||
|
`Buffer` will be *zero-filled*.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf = Buffer.alloc(5);
|
||||||
|
console.log(buf);
|
||||||
|
// <Buffer 00 00 00 00 00>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `size` must be less than or equal to the value of
|
||||||
|
`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is
|
||||||
|
`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will
|
||||||
|
be created if a `size` less than or equal to 0 is specified.
|
||||||
|
|
||||||
|
If `fill` is specified, the allocated `Buffer` will be initialized by calling
|
||||||
|
`buf.fill(fill)`. See [`buf.fill()`][] for more information.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf = Buffer.alloc(5, 'a');
|
||||||
|
console.log(buf);
|
||||||
|
// <Buffer 61 61 61 61 61>
|
||||||
|
```
|
||||||
|
|
||||||
|
If both `fill` and `encoding` are specified, the allocated `Buffer` will be
|
||||||
|
initialized by calling `buf.fill(fill, encoding)`. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
|
||||||
|
console.log(buf);
|
||||||
|
// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
|
||||||
|
```
|
||||||
|
|
||||||
|
Calling `Buffer.alloc(size)` can be significantly slower than the alternative
|
||||||
|
`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance
|
||||||
|
contents will *never contain sensitive data*.
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `size` is not a number.
|
||||||
|
|
||||||
|
### Class Method: Buffer.allocUnsafe(size)
|
||||||
|
<!-- YAML
|
||||||
|
added: v5.10.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `size` {Number}
|
||||||
|
|
||||||
|
Allocates a new *non-zero-filled* `Buffer` of `size` bytes. The `size` must
|
||||||
|
be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit
|
||||||
|
architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is
|
||||||
|
thrown. A zero-length Buffer will be created if a `size` less than or equal to
|
||||||
|
0 is specified.
|
||||||
|
|
||||||
|
The underlying memory for `Buffer` instances created in this way is *not
|
||||||
|
initialized*. The contents of the newly created `Buffer` are unknown and
|
||||||
|
*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such
|
||||||
|
`Buffer` instances to zeroes.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const buf = Buffer.allocUnsafe(5);
|
||||||
|
console.log(buf);
|
||||||
|
// <Buffer 78 e0 82 02 01>
|
||||||
|
// (octets will be different, every time)
|
||||||
|
buf.fill(0);
|
||||||
|
console.log(buf);
|
||||||
|
// <Buffer 00 00 00 00 00>
|
||||||
|
```
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `size` is not a number.
|
||||||
|
|
||||||
|
Note that the `Buffer` module pre-allocates an internal `Buffer` instance of
|
||||||
|
size `Buffer.poolSize` that is used as a pool for the fast allocation of new
|
||||||
|
`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the deprecated
|
||||||
|
`new Buffer(size)` constructor) only when `size` is less than or equal to
|
||||||
|
`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default
|
||||||
|
value of `Buffer.poolSize` is `8192` but can be modified.
|
||||||
|
|
||||||
|
Use of this pre-allocated internal memory pool is a key difference between
|
||||||
|
calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`.
|
||||||
|
Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer
|
||||||
|
pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal
|
||||||
|
Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The
|
||||||
|
difference is subtle but can be important when an application requires the
|
||||||
|
additional performance that `Buffer.allocUnsafe(size)` provides.
|
||||||
|
|
||||||
|
### Class Method: Buffer.allocUnsafeSlow(size)
|
||||||
|
<!-- YAML
|
||||||
|
added: v5.10.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `size` {Number}
|
||||||
|
|
||||||
|
Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes. The
|
||||||
|
`size` must be less than or equal to the value of
|
||||||
|
`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is
|
||||||
|
`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will
|
||||||
|
be created if a `size` less than or equal to 0 is specified.
|
||||||
|
|
||||||
|
The underlying memory for `Buffer` instances created in this way is *not
|
||||||
|
initialized*. The contents of the newly created `Buffer` are unknown and
|
||||||
|
*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such
|
||||||
|
`Buffer` instances to zeroes.
|
||||||
|
|
||||||
|
When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
|
||||||
|
allocations under 4KB are, by default, sliced from a single pre-allocated
|
||||||
|
`Buffer`. This allows applications to avoid the garbage collection overhead of
|
||||||
|
creating many individually allocated Buffers. This approach improves both
|
||||||
|
performance and memory usage by eliminating the need to track and cleanup as
|
||||||
|
many `Persistent` objects.
|
||||||
|
|
||||||
|
However, in the case where a developer may need to retain a small chunk of
|
||||||
|
memory from a pool for an indeterminate amount of time, it may be appropriate
|
||||||
|
to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then
|
||||||
|
copy out the relevant bits.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// need to keep around a few small chunks of memory
|
||||||
|
const store = [];
|
||||||
|
|
||||||
|
socket.on('readable', () => {
|
||||||
|
const data = socket.read();
|
||||||
|
// allocate for retained data
|
||||||
|
const sb = Buffer.allocUnsafeSlow(10);
|
||||||
|
// copy the data into the new allocation
|
||||||
|
data.copy(sb, 0, 0, 10);
|
||||||
|
store.push(sb);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after*
|
||||||
|
a developer has observed undue memory retention in their applications.
|
||||||
|
|
||||||
|
A `TypeError` will be thrown if `size` is not a number.
|
||||||
|
|
||||||
|
### All the Rest
|
||||||
|
|
||||||
|
The rest of the `Buffer` API is exactly the same as in node.js.
|
||||||
|
[See the docs](https://nodejs.org/api/buffer.html).
|
||||||
|
|
||||||
|
|
||||||
|
## Related links
|
||||||
|
|
||||||
|
- [Node.js issue: Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660)
|
||||||
|
- [Node.js Enhancement Proposal: Buffer.from/Buffer.alloc/Buffer.zalloc/Buffer() soft-deprecate](https://github.com/nodejs/node-eps/pull/4)
|
||||||
|
|
||||||
|
## Why is `Buffer` unsafe?
|
||||||
|
|
||||||
|
Today, the node.js `Buffer` constructor is overloaded to handle many different argument
|
||||||
|
types like `String`, `Array`, `Object`, `TypedArrayView` (`Uint8Array`, etc.),
|
||||||
|
`ArrayBuffer`, and also `Number`.
|
||||||
|
|
||||||
|
The API is optimized for convenience: you can throw any type at it, and it will try to do
|
||||||
|
what you want.
|
||||||
|
|
||||||
|
Because the Buffer constructor is so powerful, you often see code like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Convert UTF-8 strings to hex
|
||||||
|
function toHex (str) {
|
||||||
|
return new Buffer(str).toString('hex')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
***But what happens if `toHex` is called with a `Number` argument?***
|
||||||
|
|
||||||
|
### Remote Memory Disclosure
|
||||||
|
|
||||||
|
If an attacker can make your program call the `Buffer` constructor with a `Number`
|
||||||
|
argument, then they can make it allocate uninitialized memory from the node.js process.
|
||||||
|
This could potentially disclose TLS private keys, user data, or database passwords.
|
||||||
|
|
||||||
|
When the `Buffer` constructor is passed a `Number` argument, it returns an
|
||||||
|
**UNINITIALIZED** block of memory of the specified `size`. When you create a `Buffer` like
|
||||||
|
this, you **MUST** overwrite the contents before returning it to the user.
|
||||||
|
|
||||||
|
From the [node.js docs](https://nodejs.org/api/buffer.html#buffer_new_buffer_size):
|
||||||
|
|
||||||
|
> `new Buffer(size)`
|
||||||
|
>
|
||||||
|
> - `size` Number
|
||||||
|
>
|
||||||
|
> The underlying memory for `Buffer` instances created in this way is not initialized.
|
||||||
|
> **The contents of a newly created `Buffer` are unknown and could contain sensitive
|
||||||
|
> data.** Use `buf.fill(0)` to initialize a Buffer to zeroes.
|
||||||
|
|
||||||
|
(Emphasis our own.)
|
||||||
|
|
||||||
|
Whenever the programmer intended to create an uninitialized `Buffer` you often see code
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var buf = new Buffer(16)
|
||||||
|
|
||||||
|
// Immediately overwrite the uninitialized buffer with data from another buffer
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
buf[i] = otherBuf[i]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Would this ever be a problem in real code?
|
||||||
|
|
||||||
|
Yes. It's surprisingly common to forget to check the type of your variables in a
|
||||||
|
dynamically-typed language like JavaScript.
|
||||||
|
|
||||||
|
Usually the consequences of assuming the wrong type is that your program crashes with an
|
||||||
|
uncaught exception. But the failure mode for forgetting to check the type of arguments to
|
||||||
|
the `Buffer` constructor is more catastrophic.
|
||||||
|
|
||||||
|
Here's an example of a vulnerable service that takes a JSON payload and converts it to
|
||||||
|
hex:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Take a JSON payload {str: "some string"} and convert it to hex
|
||||||
|
var server = http.createServer(function (req, res) {
|
||||||
|
var data = ''
|
||||||
|
req.setEncoding('utf8')
|
||||||
|
req.on('data', function (chunk) {
|
||||||
|
data += chunk
|
||||||
|
})
|
||||||
|
req.on('end', function () {
|
||||||
|
var body = JSON.parse(data)
|
||||||
|
res.end(new Buffer(body.str).toString('hex'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(8080)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, an http client just has to send:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"str": 1000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and it will get back 1,000 bytes of uninitialized memory from the server.
|
||||||
|
|
||||||
|
This is a very serious bug. It's similar in severity to the
|
||||||
|
[the Heartbleed bug](http://heartbleed.com/) that allowed disclosure of OpenSSL process
|
||||||
|
memory by remote attackers.
|
||||||
|
|
||||||
|
|
||||||
|
### Which real-world packages were vulnerable?
|
||||||
|
|
||||||
|
#### [`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht)
|
||||||
|
|
||||||
|
[Mathias Buus](https://github.com/mafintosh) and I
|
||||||
|
([Feross Aboukhadijeh](http://feross.org/)) found this issue in one of our own packages,
|
||||||
|
[`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht). The bug would allow
|
||||||
|
anyone on the internet to send a series of messages to a user of `bittorrent-dht` and get
|
||||||
|
them to reveal 20 bytes at a time of uninitialized memory from the node.js process.
|
||||||
|
|
||||||
|
Here's
|
||||||
|
[the commit](https://github.com/feross/bittorrent-dht/commit/6c7da04025d5633699800a99ec3fbadf70ad35b8)
|
||||||
|
that fixed it. We released a new fixed version, created a
|
||||||
|
[Node Security Project disclosure](https://nodesecurity.io/advisories/68), and deprecated all
|
||||||
|
vulnerable versions on npm so users will get a warning to upgrade to a newer version.
|
||||||
|
|
||||||
|
#### [`ws`](https://www.npmjs.com/package/ws)
|
||||||
|
|
||||||
|
That got us wondering if there were other vulnerable packages. Sure enough, within a short
|
||||||
|
period of time, we found the same issue in [`ws`](https://www.npmjs.com/package/ws), the
|
||||||
|
most popular WebSocket implementation in node.js.
|
||||||
|
|
||||||
|
If certain APIs were called with `Number` parameters instead of `String` or `Buffer` as
|
||||||
|
expected, then uninitialized server memory would be disclosed to the remote peer.
|
||||||
|
|
||||||
|
These were the vulnerable methods:
|
||||||
|
|
||||||
|
```js
|
||||||
|
socket.send(number)
|
||||||
|
socket.ping(number)
|
||||||
|
socket.pong(number)
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's a vulnerable socket server with some echo functionality:
|
||||||
|
|
||||||
|
```js
|
||||||
|
server.on('connection', function (socket) {
|
||||||
|
socket.on('message', function (message) {
|
||||||
|
message = JSON.parse(message)
|
||||||
|
if (message.type === 'echo') {
|
||||||
|
socket.send(message.data) // send back the user's message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`socket.send(number)` called on the server, will disclose server memory.
|
||||||
|
|
||||||
|
Here's [the release](https://github.com/websockets/ws/releases/tag/1.0.1) where the issue
|
||||||
|
was fixed, with a more detailed explanation. Props to
|
||||||
|
[Arnout Kazemier](https://github.com/3rd-Eden) for the quick fix. Here's the
|
||||||
|
[Node Security Project disclosure](https://nodesecurity.io/advisories/67).
|
||||||
|
|
||||||
|
|
||||||
|
### What's the solution?
|
||||||
|
|
||||||
|
It's important that node.js offers a fast way to get memory otherwise performance-critical
|
||||||
|
applications would needlessly get a lot slower.
|
||||||
|
|
||||||
|
But we need a better way to *signal our intent* as programmers. **When we want
|
||||||
|
uninitialized memory, we should request it explicitly.**
|
||||||
|
|
||||||
|
Sensitive functionality should not be packed into a developer-friendly API that loosely
|
||||||
|
accepts many different types. This type of API encourages the lazy practice of passing
|
||||||
|
variables in without checking the type very carefully.
|
||||||
|
|
||||||
|
#### A new API: `Buffer.allocUnsafe(number)`
|
||||||
|
|
||||||
|
The functionality of creating buffers with uninitialized memory should be part of another
|
||||||
|
API. We propose `Buffer.allocUnsafe(number)`. This way, it's not part of an API that
|
||||||
|
frequently gets user input of all sorts of different types passed into it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory!
|
||||||
|
|
||||||
|
// Immediately overwrite the uninitialized buffer with data from another buffer
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
buf[i] = otherBuf[i]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### How do we fix node.js core?
|
||||||
|
|
||||||
|
We sent [a PR to node.js core](https://github.com/nodejs/node/pull/4514) (merged as
|
||||||
|
`semver-major`) which defends against one case:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var str = 16
|
||||||
|
new Buffer(str, 'utf8')
|
||||||
|
```
|
||||||
|
|
||||||
|
In this situation, it's implied that the programmer intended the first argument to be a
|
||||||
|
string, since they passed an encoding as a second argument. Today, node.js will allocate
|
||||||
|
uninitialized memory in the case of `new Buffer(number, encoding)`, which is probably not
|
||||||
|
what the programmer intended.
|
||||||
|
|
||||||
|
But this is only a partial solution, since if the programmer does `new Buffer(variable)`
|
||||||
|
(without an `encoding` parameter) there's no way to know what they intended. If `variable`
|
||||||
|
is sometimes a number, then uninitialized memory will sometimes be returned.
|
||||||
|
|
||||||
|
### What's the real long-term fix?
|
||||||
|
|
||||||
|
We could deprecate and remove `new Buffer(number)` and use `Buffer.allocUnsafe(number)` when
|
||||||
|
we need uninitialized memory. But that would break 1000s of packages.
|
||||||
|
|
||||||
|
~~We believe the best solution is to:~~
|
||||||
|
|
||||||
|
~~1. Change `new Buffer(number)` to return safe, zeroed-out memory~~
|
||||||
|
|
||||||
|
~~2. Create a new API for creating uninitialized Buffers. We propose: `Buffer.allocUnsafe(number)`~~
|
||||||
|
|
||||||
|
#### Update
|
||||||
|
|
||||||
|
We now support adding three new APIs:
|
||||||
|
|
||||||
|
- `Buffer.from(value)` - convert from any type to a buffer
|
||||||
|
- `Buffer.alloc(size)` - create a zero-filled buffer
|
||||||
|
- `Buffer.allocUnsafe(size)` - create an uninitialized buffer with given size
|
||||||
|
|
||||||
|
This solves the core problem that affected `ws` and `bittorrent-dht` which is
|
||||||
|
`Buffer(variable)` getting tricked into taking a number argument.
|
||||||
|
|
||||||
|
This way, existing code continues working and the impact on the npm ecosystem will be
|
||||||
|
minimal. Over time, npm maintainers can migrate performance-critical code to use
|
||||||
|
`Buffer.allocUnsafe(number)` instead of `new Buffer(number)`.
|
||||||
|
|
||||||
|
|
||||||
|
### Conclusion
|
||||||
|
|
||||||
|
We think there's a serious design issue with the `Buffer` API as it exists today. It
|
||||||
|
promotes insecure software by putting high-risk functionality into a convenient API
|
||||||
|
with friendly "developer ergonomics".
|
||||||
|
|
||||||
|
This wasn't merely a theoretical exercise because we found the issue in some of the
|
||||||
|
most popular npm packages.
|
||||||
|
|
||||||
|
Fortunately, there's an easy fix that can be applied today. Use `safe-buffer` in place of
|
||||||
|
`buffer`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var Buffer = require('safe-buffer').Buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
Eventually, we hope that node.js core can switch to this new, safer behavior. We believe
|
||||||
|
the impact on the ecosystem would be minimal since it's not a breaking change.
|
||||||
|
Well-maintained, popular packages would be updated to use `Buffer.alloc` quickly, while
|
||||||
|
older, insecure packages would magically become safe from this attack vector.
|
||||||
|
|
||||||
|
|
||||||
|
## links
|
||||||
|
|
||||||
|
- [Node.js PR: buffer: throw if both length and enc are passed](https://github.com/nodejs/node/pull/4514)
|
||||||
|
- [Node Security Project disclosure for `ws`](https://nodesecurity.io/advisories/67)
|
||||||
|
- [Node Security Project disclosure for`bittorrent-dht`](https://nodesecurity.io/advisories/68)
|
||||||
|
|
||||||
|
|
||||||
|
## credit
|
||||||
|
|
||||||
|
The original issues in `bittorrent-dht`
|
||||||
|
([disclosure](https://nodesecurity.io/advisories/68)) and
|
||||||
|
`ws` ([disclosure](https://nodesecurity.io/advisories/67)) were discovered by
|
||||||
|
[Mathias Buus](https://github.com/mafintosh) and
|
||||||
|
[Feross Aboukhadijeh](http://feross.org/).
|
||||||
|
|
||||||
|
Thanks to [Adam Baldwin](https://github.com/evilpacket) for helping disclose these issues
|
||||||
|
and for his work running the [Node Security Project](https://nodesecurity.io/).
|
||||||
|
|
||||||
|
Thanks to [John Hiesey](https://github.com/jhiesey) for proofreading this README and
|
||||||
|
auditing the code.
|
||||||
|
|
||||||
|
|
||||||
|
## license
|
||||||
|
|
||||||
|
MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org)
|
||||||
1
node_modules/safe-buffer/browser.js
generated
vendored
Normal file
1
node_modules/safe-buffer/browser.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('buffer')
|
||||||
58
node_modules/safe-buffer/index.js
generated
vendored
Normal file
58
node_modules/safe-buffer/index.js
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
var buffer = require('buffer')
|
||||||
|
|
||||||
|
if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {
|
||||||
|
module.exports = buffer
|
||||||
|
} else {
|
||||||
|
// Copy properties from require('buffer')
|
||||||
|
Object.keys(buffer).forEach(function (prop) {
|
||||||
|
exports[prop] = buffer[prop]
|
||||||
|
})
|
||||||
|
exports.Buffer = SafeBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
function SafeBuffer (arg, encodingOrOffset, length) {
|
||||||
|
return Buffer(arg, encodingOrOffset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy static methods from Buffer
|
||||||
|
Object.keys(Buffer).forEach(function (prop) {
|
||||||
|
SafeBuffer[prop] = Buffer[prop]
|
||||||
|
})
|
||||||
|
|
||||||
|
SafeBuffer.from = function (arg, encodingOrOffset, length) {
|
||||||
|
if (typeof arg === 'number') {
|
||||||
|
throw new TypeError('Argument must not be a number')
|
||||||
|
}
|
||||||
|
return Buffer(arg, encodingOrOffset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer.alloc = function (size, fill, encoding) {
|
||||||
|
if (typeof size !== 'number') {
|
||||||
|
throw new TypeError('Argument must be a number')
|
||||||
|
}
|
||||||
|
var buf = Buffer(size)
|
||||||
|
if (fill !== undefined) {
|
||||||
|
if (typeof encoding === 'string') {
|
||||||
|
buf.fill(fill, encoding)
|
||||||
|
} else {
|
||||||
|
buf.fill(fill)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.fill(0)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer.allocUnsafe = function (size) {
|
||||||
|
if (typeof size !== 'number') {
|
||||||
|
throw new TypeError('Argument must be a number')
|
||||||
|
}
|
||||||
|
return Buffer(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer.allocUnsafeSlow = function (size) {
|
||||||
|
if (typeof size !== 'number') {
|
||||||
|
throw new TypeError('Argument must be a number')
|
||||||
|
}
|
||||||
|
return buffer.SlowBuffer(size)
|
||||||
|
}
|
||||||
103
node_modules/safe-buffer/package.json
generated
vendored
Normal file
103
node_modules/safe-buffer/package.json
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"_args": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"raw": "safe-buffer@~5.0.1",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "safe-buffer",
|
||||||
|
"name": "safe-buffer",
|
||||||
|
"rawSpec": "~5.0.1",
|
||||||
|
"spec": ">=5.0.1 <5.1.0",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/ws"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"_from": "safe-buffer@>=5.0.1 <5.1.0",
|
||||||
|
"_id": "safe-buffer@5.0.1",
|
||||||
|
"_inCache": true,
|
||||||
|
"_location": "/safe-buffer",
|
||||||
|
"_nodeVersion": "4.4.5",
|
||||||
|
"_npmOperationalInternal": {
|
||||||
|
"host": "packages-12-west.internal.npmjs.com",
|
||||||
|
"tmp": "tmp/safe-buffer-5.0.1.tgz_1464588482081_0.8112505874596536"
|
||||||
|
},
|
||||||
|
"_npmUser": {
|
||||||
|
"name": "feross",
|
||||||
|
"email": "feross@feross.org"
|
||||||
|
},
|
||||||
|
"_npmVersion": "2.15.5",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"raw": "safe-buffer@~5.0.1",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "safe-buffer",
|
||||||
|
"name": "safe-buffer",
|
||||||
|
"rawSpec": "~5.0.1",
|
||||||
|
"spec": ">=5.0.1 <5.1.0",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/ws"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
|
||||||
|
"_shasum": "d263ca54696cd8a306b5ca6551e92de57918fbe7",
|
||||||
|
"_shrinkwrap": null,
|
||||||
|
"_spec": "safe-buffer@~5.0.1",
|
||||||
|
"_where": "/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/ws",
|
||||||
|
"author": {
|
||||||
|
"name": "Feross Aboukhadijeh",
|
||||||
|
"email": "feross@feross.org",
|
||||||
|
"url": "http://feross.org"
|
||||||
|
},
|
||||||
|
"browser": "./browser.js",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/feross/safe-buffer/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"description": "Safer Node.js Buffer API",
|
||||||
|
"devDependencies": {
|
||||||
|
"standard": "^7.0.0",
|
||||||
|
"tape": "^4.0.0",
|
||||||
|
"zuul": "^3.0.0"
|
||||||
|
},
|
||||||
|
"directories": {},
|
||||||
|
"dist": {
|
||||||
|
"shasum": "d263ca54696cd8a306b5ca6551e92de57918fbe7",
|
||||||
|
"tarball": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz"
|
||||||
|
},
|
||||||
|
"gitHead": "1e371a367da962afae2bebc527b50271c739d28c",
|
||||||
|
"homepage": "https://github.com/feross/safe-buffer",
|
||||||
|
"keywords": [
|
||||||
|
"buffer",
|
||||||
|
"buffer allocate",
|
||||||
|
"node security",
|
||||||
|
"safe",
|
||||||
|
"safe-buffer",
|
||||||
|
"security",
|
||||||
|
"uninitialized"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"maintainers": [
|
||||||
|
{
|
||||||
|
"name": "feross",
|
||||||
|
"email": "feross@feross.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mafintosh",
|
||||||
|
"email": "mathiasbuus@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "safe-buffer",
|
||||||
|
"optionalDependencies": {},
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/feross/safe-buffer.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "standard && tape test.js"
|
||||||
|
},
|
||||||
|
"version": "5.0.1"
|
||||||
|
}
|
||||||
99
node_modules/safe-buffer/test.js
generated
vendored
Normal file
99
node_modules/safe-buffer/test.js
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
var test = require('tape')
|
||||||
|
var SafeBuffer = require('./').Buffer
|
||||||
|
|
||||||
|
test('new SafeBuffer(value) works just like Buffer', function (t) {
|
||||||
|
t.deepEqual(new SafeBuffer('hey'), new Buffer('hey'))
|
||||||
|
t.deepEqual(new SafeBuffer('hey', 'utf8'), new Buffer('hey', 'utf8'))
|
||||||
|
t.deepEqual(new SafeBuffer('686579', 'hex'), new Buffer('686579', 'hex'))
|
||||||
|
t.deepEqual(new SafeBuffer([1, 2, 3]), new Buffer([1, 2, 3]))
|
||||||
|
t.deepEqual(new SafeBuffer(new Uint8Array([1, 2, 3])), new Buffer(new Uint8Array([1, 2, 3])))
|
||||||
|
|
||||||
|
t.equal(typeof SafeBuffer.isBuffer, 'function')
|
||||||
|
t.equal(SafeBuffer.isBuffer(new SafeBuffer('hey')), true)
|
||||||
|
t.equal(Buffer.isBuffer(new SafeBuffer('hey')), true)
|
||||||
|
t.notOk(SafeBuffer.isBuffer({}))
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.from(value) converts to a Buffer', function (t) {
|
||||||
|
t.deepEqual(SafeBuffer.from('hey'), new Buffer('hey'))
|
||||||
|
t.deepEqual(SafeBuffer.from('hey', 'utf8'), new Buffer('hey', 'utf8'))
|
||||||
|
t.deepEqual(SafeBuffer.from('686579', 'hex'), new Buffer('686579', 'hex'))
|
||||||
|
t.deepEqual(SafeBuffer.from([1, 2, 3]), new Buffer([1, 2, 3]))
|
||||||
|
t.deepEqual(SafeBuffer.from(new Uint8Array([1, 2, 3])), new Buffer(new Uint8Array([1, 2, 3])))
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.alloc(number) returns zeroed-out memory', function (t) {
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
var expected1 = new Buffer(1000)
|
||||||
|
expected1.fill(0)
|
||||||
|
t.deepEqual(SafeBuffer.alloc(1000), expected1)
|
||||||
|
|
||||||
|
var expected2 = new Buffer(1000 * 1000)
|
||||||
|
expected2.fill(0)
|
||||||
|
t.deepEqual(SafeBuffer.alloc(1000 * 1000), expected2)
|
||||||
|
}
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.allocUnsafe(number)', function (t) {
|
||||||
|
var buf = SafeBuffer.allocUnsafe(100) // unitialized memory
|
||||||
|
t.equal(buf.length, 100)
|
||||||
|
t.equal(SafeBuffer.isBuffer(buf), true)
|
||||||
|
t.equal(Buffer.isBuffer(buf), true)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.from() throws with number types', function (t) {
|
||||||
|
t.plan(5)
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.from(0)
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.from(-1)
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.from(NaN)
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.from(Infinity)
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.from(99)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.allocUnsafe() throws with non-number types', function (t) {
|
||||||
|
t.plan(4)
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.allocUnsafe('hey')
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.allocUnsafe('hey', 'utf8')
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.allocUnsafe([1, 2, 3])
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.allocUnsafe({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('SafeBuffer.alloc() throws with non-number types', function (t) {
|
||||||
|
t.plan(4)
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.alloc('hey')
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.alloc('hey', 'utf8')
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.alloc([1, 2, 3])
|
||||||
|
})
|
||||||
|
t.throws(function () {
|
||||||
|
SafeBuffer.alloc({})
|
||||||
|
})
|
||||||
|
})
|
||||||
22
node_modules/ultron/LICENSE
generated
vendored
Normal file
22
node_modules/ultron/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
138
node_modules/ultron/index.js
generated
vendored
Normal file
138
node_modules/ultron/index.js
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var has = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An auto incrementing id which we can use to create "unique" Ultron instances
|
||||||
|
* so we can track the event emitters that are added through the Ultron
|
||||||
|
* interface.
|
||||||
|
*
|
||||||
|
* @type {Number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var id = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ultron is high-intelligence robot. It gathers intelligence so it can start improving
|
||||||
|
* upon his rudimentary design. It will learn from your EventEmitting patterns
|
||||||
|
* and exterminate them.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {EventEmitter} ee EventEmitter instance we need to wrap.
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
function Ultron(ee) {
|
||||||
|
if (!(this instanceof Ultron)) return new Ultron(ee);
|
||||||
|
|
||||||
|
this.id = id++;
|
||||||
|
this.ee = ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new EventListener for the given event.
|
||||||
|
*
|
||||||
|
* @param {String} event Name of the event.
|
||||||
|
* @param {Functon} fn Callback function.
|
||||||
|
* @param {Mixed} context The context of the function.
|
||||||
|
* @returns {Ultron}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
Ultron.prototype.on = function on(event, fn, context) {
|
||||||
|
fn.__ultron = this.id;
|
||||||
|
this.ee.on(event, fn, context);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Add an EventListener that's only called once.
|
||||||
|
*
|
||||||
|
* @param {String} event Name of the event.
|
||||||
|
* @param {Function} fn Callback function.
|
||||||
|
* @param {Mixed} context The context of the function.
|
||||||
|
* @returns {Ultron}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
Ultron.prototype.once = function once(event, fn, context) {
|
||||||
|
fn.__ultron = this.id;
|
||||||
|
this.ee.once(event, fn, context);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the listeners we assigned for the given event.
|
||||||
|
*
|
||||||
|
* @returns {Ultron}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
Ultron.prototype.remove = function remove() {
|
||||||
|
var args = arguments
|
||||||
|
, ee = this.ee
|
||||||
|
, event;
|
||||||
|
|
||||||
|
//
|
||||||
|
// When no event names are provided we assume that we need to clear all the
|
||||||
|
// events that were assigned through us.
|
||||||
|
//
|
||||||
|
if (args.length === 1 && 'string' === typeof args[0]) {
|
||||||
|
args = args[0].split(/[, ]+/);
|
||||||
|
} else if (!args.length) {
|
||||||
|
if (ee.eventNames) {
|
||||||
|
args = ee.eventNames();
|
||||||
|
} else if (ee._events) {
|
||||||
|
args = [];
|
||||||
|
|
||||||
|
for (event in ee._events) {
|
||||||
|
if (has.call(ee._events, event)) args.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
args = args.concat(Object.getOwnPropertySymbols(ee._events));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
var listeners = ee.listeners(args[i]);
|
||||||
|
|
||||||
|
for (var j = 0; j < listeners.length; j++) {
|
||||||
|
event = listeners[j];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Once listeners have a `listener` property that stores the real listener
|
||||||
|
// in the EventEmitter that ships with Node.js.
|
||||||
|
//
|
||||||
|
if (event.listener) {
|
||||||
|
if (event.listener.__ultron !== this.id) continue;
|
||||||
|
delete event.listener.__ultron;
|
||||||
|
} else {
|
||||||
|
if (event.__ultron !== this.id) continue;
|
||||||
|
delete event.__ultron;
|
||||||
|
}
|
||||||
|
|
||||||
|
ee.removeListener(args[i], event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the Ultron instance, remove all listeners and release all references.
|
||||||
|
*
|
||||||
|
* @returns {Boolean}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
Ultron.prototype.destroy = function destroy() {
|
||||||
|
if (!this.ee) return false;
|
||||||
|
|
||||||
|
this.remove();
|
||||||
|
this.ee = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Expose the module.
|
||||||
|
//
|
||||||
|
module.exports = Ultron;
|
||||||
112
node_modules/ultron/package.json
generated
vendored
Normal file
112
node_modules/ultron/package.json
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"_args": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"raw": "ultron@~1.1.0",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "ultron",
|
||||||
|
"name": "ultron",
|
||||||
|
"rawSpec": "~1.1.0",
|
||||||
|
"spec": ">=1.1.0 <1.2.0",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/ws"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"_from": "ultron@>=1.1.0 <1.2.0",
|
||||||
|
"_id": "ultron@1.1.0",
|
||||||
|
"_inCache": true,
|
||||||
|
"_location": "/ultron",
|
||||||
|
"_nodeVersion": "6.2.1",
|
||||||
|
"_npmOperationalInternal": {
|
||||||
|
"host": "packages-12-west.internal.npmjs.com",
|
||||||
|
"tmp": "tmp/ultron-1.1.0.tgz_1483969751660_0.8877595944795758"
|
||||||
|
},
|
||||||
|
"_npmUser": {
|
||||||
|
"name": "3rdeden",
|
||||||
|
"email": "npm@3rd-Eden.com"
|
||||||
|
},
|
||||||
|
"_npmVersion": "3.9.3",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"raw": "ultron@~1.1.0",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "ultron",
|
||||||
|
"name": "ultron",
|
||||||
|
"rawSpec": "~1.1.0",
|
||||||
|
"spec": ">=1.1.0 <1.2.0",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/ws"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
|
||||||
|
"_shasum": "b07a2e6a541a815fc6a34ccd4533baec307ca864",
|
||||||
|
"_shrinkwrap": null,
|
||||||
|
"_spec": "ultron@~1.1.0",
|
||||||
|
"_where": "/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/ws",
|
||||||
|
"author": {
|
||||||
|
"name": "Arnout Kazemier"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/unshiftio/ultron/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"description": "Ultron is high-intelligence robot. It gathers intel so it can start improving upon his rudimentary design",
|
||||||
|
"devDependencies": {
|
||||||
|
"assume": "1.4.x",
|
||||||
|
"eventemitter3": "2.0.x",
|
||||||
|
"istanbul": "0.4.x",
|
||||||
|
"mocha": "~3.2.0",
|
||||||
|
"pre-commit": "~1.2.0"
|
||||||
|
},
|
||||||
|
"directories": {},
|
||||||
|
"dist": {
|
||||||
|
"shasum": "b07a2e6a541a815fc6a34ccd4533baec307ca864",
|
||||||
|
"tarball": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz"
|
||||||
|
},
|
||||||
|
"gitHead": "6eb97b74402978aebda4a9d497cb6243ec80c9f1",
|
||||||
|
"homepage": "https://github.com/unshiftio/ultron",
|
||||||
|
"keywords": [
|
||||||
|
"Ultron",
|
||||||
|
"robot",
|
||||||
|
"gather",
|
||||||
|
"intelligence",
|
||||||
|
"event",
|
||||||
|
"events",
|
||||||
|
"eventemitter",
|
||||||
|
"emitter",
|
||||||
|
"cleanup"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"maintainers": [
|
||||||
|
{
|
||||||
|
"name": "unshift",
|
||||||
|
"email": "npm@unshift.io"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "v1",
|
||||||
|
"email": "info@3rd-Eden.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3rdeden",
|
||||||
|
"email": "npm@3rd-Eden.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "ultron",
|
||||||
|
"optionalDependencies": {},
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/unshiftio/ultron.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"100%": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
|
||||||
|
"coverage": "istanbul cover _mocha -- test.js",
|
||||||
|
"test": "mocha test.js",
|
||||||
|
"test-travis": "istanbul cover _mocha --report lcovonly -- test.js",
|
||||||
|
"watch": "mocha --watch test.js"
|
||||||
|
},
|
||||||
|
"version": "1.1.0"
|
||||||
|
}
|
||||||
2
node_modules/nodejs-websocket/LICENSE → node_modules/ws/LICENSE
generated
vendored
2
node_modules/nodejs-websocket/LICENSE → node_modules/ws/LICENSE
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Guilherme Souza
|
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
260
node_modules/ws/README.md
generated
vendored
Normal file
260
node_modules/ws/README.md
generated
vendored
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
**Note**: This module does not work in the browser. The client in the docs is a
|
||||||
|
reference to a back end with the role of a client in the WebSocket
|
||||||
|
communication. Browser clients must use the native
|
||||||
|
[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
|
||||||
|
|
||||||
|
## 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 and spec compliance
|
||||||
|
|
||||||
|
There are 2 optional modules that can be installed along side with the `ws`
|
||||||
|
module. These modules are binary addons which improve certain operations.
|
||||||
|
Prebuilt binaries are available for the most popular platforms so you don't
|
||||||
|
necessarily need to have a C++ compiler installed on your machine.
|
||||||
|
|
||||||
|
- `npm install --save-optional bufferutil`: Allows to efficiently perform
|
||||||
|
operations such as masking and unmasking the data payload of the WebSocket
|
||||||
|
frames.
|
||||||
|
- `npm install --save-optional utf-8-validate`: Allows to efficiently check
|
||||||
|
if a message contains valid UTF-8 as required by the spec.
|
||||||
|
|
||||||
|
## 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] 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
|
||||||
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);
|
||||||
|
}
|
||||||
403
node_modules/ws/lib/Sender.js
generated
vendored
Normal file
403
node_modules/ws/lib/Sender.js
generated
vendored
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
/*!
|
||||||
|
* 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, {
|
||||||
|
fin: true,
|
||||||
|
rsv1: false,
|
||||||
|
opcode: 0x08,
|
||||||
|
mask,
|
||||||
|
readOnly: false
|
||||||
|
}), 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, {
|
||||||
|
fin: true,
|
||||||
|
rsv1: false,
|
||||||
|
opcode: 0x09,
|
||||||
|
mask,
|
||||||
|
readOnly
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, {
|
||||||
|
fin: true,
|
||||||
|
rsv1: false,
|
||||||
|
opcode: 0x0a,
|
||||||
|
mask,
|
||||||
|
readOnly
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {
|
||||||
|
fin: options.fin,
|
||||||
|
rsv1,
|
||||||
|
opcode,
|
||||||
|
mask: options.mask,
|
||||||
|
readOnly
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.deflating) {
|
||||||
|
this.enqueue([this.dispatch, data, this.compress, opts, cb]);
|
||||||
|
} else {
|
||||||
|
this.dispatch(data, this.compress, opts, cb);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.sendFrame(Sender.frame(data, {
|
||||||
|
fin: options.fin,
|
||||||
|
rsv1: false,
|
||||||
|
opcode,
|
||||||
|
mask: options.mask,
|
||||||
|
readOnly
|
||||||
|
}), cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a data message.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data The message to send
|
||||||
|
* @param {Boolean} compress Specifies whether or not to compress `data`
|
||||||
|
* @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
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dispatch (data, compress, options, cb) {
|
||||||
|
if (!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;
|
||||||
|
}
|
||||||
712
node_modules/ws/lib/WebSocket.js
generated
vendored
Normal file
712
node_modules/ws/lib/WebSocket.js
generated
vendored
Normal file
@ -0,0 +1,712 @@
|
|||||||
|
/*!
|
||||||
|
* 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.emit('headers', res.headers, res);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The user may have closed the connection from a listener of the `headers`
|
||||||
|
// event.
|
||||||
|
//
|
||||||
|
if (this.readyState !== WebSocket.CONNECTING) return;
|
||||||
|
|
||||||
|
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, req);
|
||||||
|
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, req);
|
||||||
|
|
||||||
|
socket.write(headers.concat('', '').join('\r\n'));
|
||||||
|
|
||||||
|
const client = new WebSocket([req, socket, head], null, {
|
||||||
|
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();
|
||||||
|
}
|
||||||
126
node_modules/ws/package.json
generated
vendored
Normal file
126
node_modules/ws/package.json
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"_args": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"raw": "ws@*",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "ws",
|
||||||
|
"name": "ws",
|
||||||
|
"rawSpec": "*",
|
||||||
|
"spec": "*",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"/Users/gerrit/Documents/node.js-Projects/tinkerforge"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"_from": "ws@*",
|
||||||
|
"_id": "ws@2.3.1",
|
||||||
|
"_inCache": true,
|
||||||
|
"_location": "/ws",
|
||||||
|
"_nodeVersion": "7.9.0",
|
||||||
|
"_npmOperationalInternal": {
|
||||||
|
"host": "packages-12-west.internal.npmjs.com",
|
||||||
|
"tmp": "tmp/ws-2.3.1.tgz_1492711201097_0.04034068179316819"
|
||||||
|
},
|
||||||
|
"_npmUser": {
|
||||||
|
"name": "lpinca",
|
||||||
|
"email": "luigipinca@gmail.com"
|
||||||
|
},
|
||||||
|
"_npmVersion": "4.2.0",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"raw": "ws@*",
|
||||||
|
"scope": null,
|
||||||
|
"escapedName": "ws",
|
||||||
|
"name": "ws",
|
||||||
|
"rawSpec": "*",
|
||||||
|
"spec": "*",
|
||||||
|
"type": "range"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz",
|
||||||
|
"_shasum": "6b94b3e447cb6a363f785eaf94af6359e8e81c80",
|
||||||
|
"_shrinkwrap": null,
|
||||||
|
"_spec": "ws@*",
|
||||||
|
"_where": "/Users/gerrit/Documents/node.js-Projects/tinkerforge",
|
||||||
|
"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": "~10.2.0",
|
||||||
|
"eslint-plugin-import": "~2.2.0",
|
||||||
|
"eslint-plugin-node": "~4.2.0",
|
||||||
|
"eslint-plugin-promise": "~3.5.0",
|
||||||
|
"eslint-plugin-standard": "~3.0.0",
|
||||||
|
"mocha": "~3.2.0",
|
||||||
|
"nyc": "~10.2.0",
|
||||||
|
"utf-8-validate": "~3.0.0"
|
||||||
|
},
|
||||||
|
"directories": {},
|
||||||
|
"dist": {
|
||||||
|
"shasum": "6b94b3e447cb6a363f785eaf94af6359e8e81c80",
|
||||||
|
"tarball": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"gitHead": "732aaf06b76700f104eeff2740e1896be4e88199",
|
||||||
|
"homepage": "https://github.com/websockets/ws",
|
||||||
|
"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+https://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.3.1"
|
||||||
|
}
|
||||||
@ -10,6 +10,6 @@
|
|||||||
"urlencode": "*",
|
"urlencode": "*",
|
||||||
"dateformat": "*",
|
"dateformat": "*",
|
||||||
"byline": "*",
|
"byline": "*",
|
||||||
"nodejs-websocket": "*"
|
"ws": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user