websocket13
Advanced tools
Comparing version 1.0.0 to 1.1.0
161
lib/base.js
var WS13 = require('./index.js'); | ||
var StreamedFrame = require('./streamedframe.js'); | ||
var StreamedOutgoingMessage = require('./StreamedOutgoingMessage.js'); | ||
var StreamedIncomingMessage = require('./StreamedIncomingMessage.js'); | ||
@@ -15,5 +16,12 @@ var Crypto = require('crypto'); | ||
this.options = { | ||
"pingInterval": 10000, | ||
"pingTimeout": 10000, | ||
"pingFailures": 3 | ||
}; | ||
this._data = {}; | ||
this._outgoingFrames = []; // holds frame objects which we haven't sent yet | ||
this._dataBuffer = new Buffer(0); // holds raw TCP data that we haven't processed yet | ||
this._frameData = null; // holds the current frame for which we're still receiving payload data | ||
this._incomingStream = null; // StreamedIncomingMessage object for the current message | ||
@@ -76,3 +84,3 @@ this.on('connected', () => { | ||
WebSocketBase.prototype.createMessageStream = function(type) { | ||
var frame = new StreamedFrame(this, type); | ||
var frame = new StreamedOutgoingMessage(this, type); | ||
this._outgoingFrames.push(frame); | ||
@@ -82,2 +90,45 @@ return frame; | ||
WebSocketBase.prototype.data = function(key, value) { | ||
var val = this._data[key]; | ||
if (typeof value === 'undefined') { | ||
return val; | ||
} | ||
this._data[key] = value; | ||
return val; | ||
}; | ||
WebSocketBase.prototype._prepSocketEvents = function() { | ||
this.remoteAddress = this._socket.remoteAddress; | ||
this._socket.on('data', (data) => { | ||
if ([WS13.State.Connected, WS13.State.Closing, WS13.State.ClosingError].indexOf(this.state) != -1) { | ||
this._handleData(data); | ||
} | ||
}); | ||
this._socket.on('close', () => { | ||
if (this.state == WS13.State.ClosingError) { | ||
this.state = WS13.State.Closing; | ||
return; | ||
} | ||
if (this.state == WS13.State.Closed) { | ||
this.emit('debug', "Socket closed after successful websocket closure."); | ||
return; | ||
} | ||
var state = this.state; | ||
this.state = WS13.State.Closed; | ||
this.emit('disconnected', WS13.StatusCode.AbnormalTermination, "Socket closed", state == WS13.State.Closing); | ||
}); | ||
this._socket.on('error', (err) => { | ||
err.state = this.state; | ||
this.state = WS13.State.ClosingError; | ||
this.emit('error', err); | ||
}); | ||
}; | ||
WebSocketBase.prototype._queuePing = function() { | ||
@@ -187,2 +238,9 @@ clearTimeout(this._pingTimer); | ||
// We don't have any extensions, so all the RSV bits must be 0 or else we have to bail | ||
if (frame.RSV1 || frame.RSV2 || frame.RSV3) { | ||
var bit = (frame.RSV1 ? 'RSV1' : (frame.RSV2 ? 'RSV2' : 'RSV3')); | ||
this._terminateError(WS13.StatusCode.ProtocolError, "Unexpected reserved bit " + bit + "set"); | ||
return; | ||
} | ||
this.emit('debug', "Got frame " + frame.opcode.toString(16).toUpperCase() + ", " + (frame.FIN ? "FIN, " : "") + | ||
@@ -192,4 +250,3 @@ (frame.maskKey ? "MASK, " : "") + "payload " + frame.payload.length + " bytes"); | ||
if ( | ||
this.state != WS13.State.Connected && | ||
!( | ||
this.state != WS13.State.Connected && !( | ||
(this.state == WS13.State.ClosingError || this.state == WS13.State.Closing) && | ||
@@ -203,8 +260,14 @@ frame.opcode == WS13.FrameType.Control.Close | ||
// Unmask if applicable | ||
if (frame.maskKey !== null && frame.payload && frame.payload.length > 0) { | ||
frame.payload = maskOrUnmask(frame.payload, frame.maskKey); | ||
} | ||
var payload; | ||
// Is this a control frame? | ||
// Is this a control frame? They need to be handled before anything else as they can be interjected between | ||
// fragmented message frames. | ||
if (frame.opcode & (1 << 3)) { | ||
if (!frame.FIN) { | ||
this._terminateError(new Error("Got a fragmented control frame " + frame.opcode.toString(16))); | ||
this._terminateError(WS13.StatusCode.ProtocolError, "Got a fragmented control frame " + frame.opcode.toString(16)); | ||
return; | ||
@@ -214,10 +277,6 @@ } | ||
if (frame.payload.length > 125) { | ||
this._terminateError(new Error("Got a control frame " + frame.opcode.toString(16) + " with invalid payload length " + frame.payload.length)); | ||
this._terminateError(WS13.StatusCode.ProtocolError, "Got a control frame " + frame.opcode.toString(16) + " with invalid payload length " + frame.payload.length); | ||
return; | ||
} | ||
if (frame.maskKey !== null && frame.payload && frame.payload.length > 0) { | ||
frame.payload = maskOrUnmask(frame.payload, frame.maskKey); | ||
} | ||
switch (frame.opcode) { | ||
@@ -273,18 +332,43 @@ case WS13.FrameType.Control.Close: | ||
// Sanity checks | ||
if (!this._incomingStream && frame.opcode == WS13.FrameType.Continuation) { | ||
this._terminateError(WS13.StatusCode.ProtocolError, "Received continuation frame without initial frame."); | ||
return; | ||
} else if (this._incomingStream && frame.opcode != WS13.FrameType.Continuation) { | ||
this._terminateError(WS13.StatusCode.ProtocolError, "Received new message without finishing a fragmented one."); | ||
return; | ||
} | ||
// Is this the first frame of a fragmented message? | ||
if (!frame.FIN && !this._incomingStream) { | ||
this.emit('debug', "Got first frame of fragmented message."); | ||
var dispatch = this.listenerCount('streamedMessage') >= 1; | ||
this._incomingStream = new StreamedIncomingMessage(frame, dispatch); | ||
if (dispatch) { | ||
this.emit('streamedMessage', frame.opcode, this._incomingStream); | ||
} | ||
this._incomingStream.on('end', data => { | ||
if (!dispatch) { | ||
var frame = this._incomingStream.frameHeader; | ||
frame.payload = data; | ||
frame.payloadLength = frame.payload.length; | ||
this._dispatchDataFrame(frame); | ||
} | ||
}); | ||
return; | ||
} | ||
if (frame.opcode == WS13.FrameType.Continuation) { | ||
this.emit('debug', "Got continuation frame"); | ||
var fin = frame.FIN; | ||
payload = frame.payload; | ||
this._incomingStream._frame(frame); | ||
frame = this._frameData; | ||
if (frame.FIN) { | ||
this._incomingStream = null; | ||
} | ||
frame.FIN = fin; | ||
frame.payload = Buffer.concat([frame.payload, payload]); | ||
} | ||
if (!frame.FIN) { | ||
// There is more to come | ||
// TODO: Expose a Stream interface for these | ||
this.emit('debug', "Got non-FIN frame"); | ||
this._frameData = frame; | ||
return; | ||
@@ -295,7 +379,6 @@ } | ||
// At this time we support no extensions so don't worry about extension data. | ||
this._dispatchDataFrame(frame); | ||
}; | ||
if (frame.maskKey !== null && frame.payload && frame.payload.length > 0) { | ||
frame.payload = maskOrUnmask(frame.payload, frame.maskKey); | ||
} | ||
WebSocketBase.prototype._dispatchDataFrame = function(frame) { | ||
switch (frame.opcode) { | ||
@@ -325,3 +408,5 @@ case WS13.FrameType.Data.Text: | ||
WebSocketBase.prototype._sendFrame = function(frame, bypassQueue) { | ||
if (this.state != WS13.State.Connected) { | ||
var isControl = !!(frame.opcode & (1 << 3)); | ||
if (this.state != WS13.State.Connected && !(this.state == WS13.State.Closing && isControl)) { | ||
throw new Error("Cannot send data while not connected."); | ||
@@ -334,3 +419,3 @@ } | ||
if (frame.opcode & (1 << 3)) { | ||
if (isControl) { | ||
bypassQueue = true; // we can send control messages whenever | ||
@@ -412,5 +497,5 @@ } | ||
if (frame instanceof StreamedFrame) { | ||
if (frame instanceof StreamedOutgoingMessage) { | ||
if (!frame.started) { | ||
this.emit('debug', "Starting StreamedFrame"); | ||
this.emit('debug', "Starting StreamedOutgoingMessage"); | ||
frame._start(); | ||
@@ -433,2 +518,6 @@ } | ||
WebSocketBase.prototype._sendControl = function(opcode, payload) { | ||
if (this.state == WS13.State.Closed || !this._socket) { | ||
return; | ||
} | ||
this._sendFrame({ | ||
@@ -448,4 +537,8 @@ "opcode": opcode, | ||
this.state = WS13.State.Closed; | ||
this._socket.end(); | ||
this._socket.destroy(); | ||
if (this._socket) { | ||
this._socket.end(); | ||
this._socket.destroy(); | ||
} | ||
this.emit('error', err); | ||
@@ -452,0 +545,0 @@ }; |
@@ -5,7 +5,6 @@ var WS13 = require('./index.js'); | ||
var parseUrl = require('url').parse; | ||
var Net = require('net'); | ||
var TLS = require('tls'); | ||
var Http = require('http'); | ||
var Https = require('https'); | ||
var Crypto = require('crypto'); | ||
const HTTP_VERSION = 1.1; | ||
const WEBSOCKET_VERSION = 13; | ||
@@ -34,8 +33,2 @@ | ||
this.options = { | ||
"pingInterval": 10000, | ||
"pingTimeout": 10000, | ||
"pingFailures": 3 | ||
}; | ||
options = options || {}; | ||
@@ -48,4 +41,13 @@ for (var option in options) { | ||
this._connectOptions = options.connection || {}; | ||
for (var element in uri) { | ||
if (uri.hasOwnProperty(element) && uri[element] !== null) { | ||
this._connectOptions[element] = uri[element]; | ||
} | ||
} | ||
this._connectOptions.protocol = this.secure ? "https:" : "http:"; | ||
this.hostname = uri.hostname; | ||
this.port = parseInt(uri.port || (this.secure ? 443 : 80), 10); | ||
this.port = this._connectOptions.port = parseInt(uri.port || (this.secure ? 443 : 80), 10); | ||
this.path = uri.path || '/'; | ||
@@ -72,3 +74,5 @@ | ||
// TODO: Cookies | ||
if (this.options.cookies) { | ||
this.headers.cookie = Object.keys(this.options.cookies).map(name => name.trim() + '=' + encodeURIComponent(this.options.cookies[name])).join('; '); | ||
} | ||
@@ -88,163 +92,125 @@ this._connect(); | ||
var connectOptions = this.options.connection || {}; | ||
connectOptions.port = this.port; | ||
connectOptions.host = this.hostname; | ||
this._socket = Net.connect(connectOptions); | ||
var event = 'connect'; | ||
if (this.secure) { | ||
connectOptions.socket = this._socket; | ||
this._socket = TLS.connect(connectOptions); | ||
event = 'secureConnect'; | ||
if (this.options.handshakeBody) { | ||
this.headers['content-length'] = this.options.handshakeBody.length; | ||
} | ||
this._socket.on(event, () => { | ||
// Time to send the handshake | ||
this._socket.write("GET " + this.path + " HTTP/" + HTTP_VERSION + "\r\n"); | ||
this._connectOptions.headers = this.headers; | ||
this._connectOptions.agent = false; | ||
// Send headers | ||
for (var name in this.headers) { | ||
if (this.headers.hasOwnProperty(name)) { | ||
this._socket.write(name + ": " + this.headers[name] + "\r\n"); | ||
} | ||
} | ||
var req = (this.secure ? Https : Http).request(this._connectOptions, (res) => { | ||
var serverHttpVersion = res.httpVersion; | ||
var responseCode = res.statusCode; | ||
var responseText = res.statusMessage; | ||
this._socket.write("\r\n"); | ||
}); | ||
var err = new Error(); | ||
err.responseCode = responseCode; | ||
err.responseText = responseText; | ||
err.httpVersion = serverHttpVersion; | ||
err.headers = res.headers; | ||
var handshakeBuffer = ''; | ||
err.body = ''; | ||
this._socket.on('data', (data) => { | ||
switch (this.state) { | ||
case WS13.State.Connecting: | ||
handshakeBuffer += data.toString('ascii'); | ||
var pos = handshakeBuffer.indexOf("\r\n\r\n"); | ||
if (pos != -1) { | ||
// Anything after these characters is actual websocket data | ||
this._handleData(new Buffer(handshakeBuffer.slice(pos + 4), 'ascii')); | ||
handshakeBuffer = handshakeBuffer.substring(0, pos); | ||
res.on('data', chunk => { | ||
err.body += chunk; | ||
}); | ||
// Now we have our full headers | ||
var lines = handshakeBuffer.split("\r\n"); | ||
var match = lines[0].match(/^HTTP\/(\d+\.\d+) (\d+) (.+)$/); | ||
if (!match) { | ||
this._closeError(new Error("Malformed handshake response")); | ||
return; | ||
} | ||
res.on('end', () => { | ||
if (this.state != WS13.State.Connecting) { | ||
return; // we don't care at this point | ||
} | ||
var serverHttpVersion = match[1]; | ||
var responseCode = parseInt(match[2], 10); | ||
var responseText = match[3]; | ||
if (responseCode != 101) { | ||
err.message = "Response code " + responseCode; | ||
this._closeError(err); | ||
return; | ||
} | ||
var err = new Error(); | ||
err.responseCode = responseCode; | ||
err.responseText = responseText; | ||
err.httpVersion = serverHttpVersion; | ||
err.message = "Server not upgrading connection"; | ||
this._closeError(err); | ||
}); | ||
}); | ||
if (responseCode != 101) { | ||
err.message = "Response code " + responseCode; | ||
this._closeError(err); | ||
return; | ||
} | ||
req.on('upgrade', (res, socket, head) => { | ||
var serverHttpVersion = res.httpVersion; | ||
var responseCode = res.statusCode; | ||
var responseText = res.statusMessage; | ||
var headers = res.headers; | ||
// Parse out our headers | ||
var headers = {}; | ||
lines.slice(1).forEach(line => { | ||
match = line.match(/^([^:]+): ?(.+)$/); | ||
if (!match) { | ||
// Malformed response header, let's just ignore it | ||
return; | ||
} | ||
var err = new Error(); | ||
err.responseCode = responseCode; | ||
err.responseText = responseText; | ||
err.httpVersion = serverHttpVersion; | ||
err.headers = res.headers; | ||
headers[match[1].toLowerCase()] = match[2].trim(); | ||
}); | ||
if (!headers.upgrade || !headers.connection || !headers.upgrade.match(/websocket/i) || !headers.connection.match(/upgrade/i)) { | ||
err.message = "Invalid server upgrade response"; | ||
this._closeError(err); | ||
return; | ||
} | ||
err.headers = headers; | ||
if (!headers['sec-websocket-accept']) { | ||
err.message = "Missing Sec-WebSocket-Accept response header"; | ||
this._closeError(err); | ||
return; | ||
} | ||
if (!headers.upgrade || !headers.connection || !headers.upgrade.match(/websocket/i) || !headers.connection.match(/upgrade/i)) { | ||
err.message = "Server not upgrading connection"; | ||
this._closeError(err); | ||
return; | ||
} | ||
var hash = Crypto.createHash('sha1').update(this._nonce + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest('base64'); | ||
if (headers['sec-websocket-accept'] != hash) { | ||
err.message = "Mismatching Sec-WebSocket-Accept header"; | ||
err.expected = hash; | ||
err.actual = headers['sec-websocket-accept']; | ||
this._closeError(err); | ||
return; | ||
} | ||
if (!headers['sec-websocket-accept']) { | ||
err.message = "Missing Sec-WebSocket-Accept response header"; | ||
this._closeError(err); | ||
return; | ||
} | ||
if (headers['sec-websocket-extensions']) { | ||
var extensions = headers['sec-websocket-extensions'].split(',').map(item => item.trim().toLowerCase()); | ||
var unsupported = extensions.filter(extension => this.extensions.indexOf(extension) == -1); | ||
if (unsupported.length > 0) { | ||
err.message = "Server is using unsupported extension" + (unsupported.length > 1 ? "s" : "") + unsupported.join(', '); | ||
this._closeError(err); | ||
return; | ||
} | ||
var hash = Crypto.createHash('sha1').update(this._nonce + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest('base64'); | ||
if (headers['sec-websocket-accept'] != hash) { | ||
err.message = "Mismatching Sec-WebSocket-Accept header"; | ||
err.expected = hash; | ||
err.actual = headers['sec-websocket-accept']; | ||
this._closeError(err); | ||
return; | ||
} | ||
this.extensions = extensions; | ||
} | ||
if (headers['sec-websocket-extensions']) { | ||
var extensions = headers['sec-websocket-extensions'].split(',').map(item => item.trim().toLowerCase()); | ||
var unsupported = extensions.filter(extension => this.extensions.indexOf(extension) == -1); | ||
if (unsupported.length > 0) { | ||
err.message = "Server is using unsupported extension" + (unsupported.length > 1 ? "s" : "") + unsupported.join(', '); | ||
this._closeError(err); | ||
return; | ||
} | ||
if (headers['sec-websocket-protocol']) { | ||
var protocol = headers['sec-websocket-protocol'].toLowerCase(); | ||
if (this.options.protocols.indexOf(protocol) == -1) { | ||
err.message = "Server is using unsupported protocol " + protocol; | ||
this._closeError(err); | ||
return; | ||
} | ||
this.extensions = extensions; | ||
} | ||
this.protocol = protocol; | ||
} | ||
if (headers['sec-websocket-protocol']) { | ||
var protocol = headers['sec-websocket-protocol'].toLowerCase(); | ||
if (this.options.protocols.indexOf(protocol) == -1) { | ||
err.message = "Server is using unsupported protocol " + protocol; | ||
this._closeError(err); | ||
return; | ||
} | ||
this._socket = socket; | ||
this._prepSocketEvents(); | ||
this.protocol = protocol; | ||
} | ||
// Everything is okay! | ||
this.state = WS13.State.Connected; | ||
this.emit('connected', { | ||
"headers": headers, | ||
"httpVersion": serverHttpVersion, | ||
"responseCode": responseCode, | ||
"responseText": responseText | ||
}); | ||
// Everything is okay! | ||
this.state = WS13.State.Connected; | ||
this.emit('connected', { | ||
"headers": headers, | ||
"httpVersion": serverHttpVersion, | ||
"responseCode": responseCode, | ||
"responseText": responseText | ||
}); | ||
} | ||
break; | ||
case WS13.State.Connected: | ||
case WS13.State.Closing: | ||
case WS13.State.ClosingError: | ||
this._handleData(data); | ||
break; | ||
if (head && head.length > 0) { | ||
this._handleData(head); | ||
} | ||
}); | ||
this._socket.on('close', () => { | ||
if (this.state == WS13.State.ClosingError) { | ||
this.state = WS13.State.Closing; | ||
req.on('error', (err) => { | ||
if (this.state != WS13.State.Connecting) { | ||
return; | ||
} | ||
if (this.state == WS13.State.Closed) { | ||
this.emit('debug', "Socket closed after successful websocket closure."); | ||
return; | ||
} | ||
var state = this.state; | ||
this.state = WS13.State.Closed; | ||
this.emit('disconnected', WS13.StatusCode.AbnormalTermination, "Socket closed", state == WS13.State.Closing); | ||
}); | ||
this._socket.on('error', (err) => { | ||
err.state = this.state; | ||
this.state = WS13.State.ClosingError; | ||
this.emit('error', err); | ||
}); | ||
req.end(this.options.handshakeBody); | ||
}; | ||
@@ -251,0 +217,0 @@ |
@@ -41,1 +41,2 @@ exports.State = { | ||
require('./client.js'); | ||
require('./server.js'); |
{ | ||
"name": "websocket13", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Simple WebSocket protocol 13 client with no native or heavy dependencies", | ||
@@ -5,0 +5,0 @@ "author": "Alexander Corn <mckay@doctormckay.com>", |
314
README.md
# WebSockets for Node.js | ||
[![npm version](https://img.shields.io/npm/v/websocket13.svg)](https://www.npmjs.com/package/websocket13) | ||
[![npm downloads](https://img.shields.io/npm/dm/websocket13.svg)](https://npmjs.com/package/websocket13) | ||
[![dependencies](https://img.shields.io/david/DoctorMcKay/node-websocket13.svg)](https://david-dm.org/DoctorMcKay/node-websocket13) | ||
[![license](https://img.shields.io/npm/l/websocket13.svg)](https://github.com/DoctorMcKay/node-websocket13/blob/master/LICENSE) | ||
[![paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=N36YVAT42CZ4G&item_name=node%2dwebsocket13¤cy_code=USD) | ||
@@ -7,313 +12,10 @@ This is a pure-JavaScript implementation of [WebSockets version 13](https://tools.ietf.org/html/rfc6455). | ||
# Installation and Example | ||
**Requires node.js v4.0.0 or later.** | ||
Install it from npm: | ||
# Documentation | ||
$ npm install websocket13 | ||
Please see the [GitHub wiki](https://github.com/DoctorMcKay/node-websocket13/wiki) for documentation. | ||
Exports a `WS13` namespace, which contains a few object-enums and the `WebSocket` object. | ||
```js | ||
var WS13 = require('websocket13'); | ||
var socket = new WebSocket('wss://echo.websocket.org', { | ||
"pingInterval": 10000, // default | ||
"pingTimeout": 10000, // default | ||
"pingFailures": 3 // default | ||
"headers": { | ||
"Origin": "http://example.com" | ||
}, | ||
"protocols": ["some_subprotocol", "some_other_subprotocol"], | ||
"connection": { | ||
"servername": "echo.websocket.org", // for SNI | ||
"rejectUnauthorized": false // to not reject self-signed or otherwise invalid certificates | ||
} | ||
}); | ||
socket.on('connected', function(info) { | ||
console.log("Successfully connected to echo server with HTTP version %s, HTTP status code %d, and HTTP status text %s. Headers follow.", | ||
info.httpVersion, info.responseCode, info.responseText); | ||
console.log(info.headers); | ||
socket.send("This is a string message."); | ||
var buffer = new Buffer(8); | ||
buffer.writeUInt32BE(1337, 0); | ||
buffer.writeUInt32BE(8675309, 4); | ||
socket.send(buffer); | ||
setTimeout(() => { | ||
socket.disconnect(WS13.StatusCode.NormalClosure, "Bye bye!"); | ||
}, 5000); | ||
}); | ||
socket.on('message', function(type, data) { | ||
switch (type) { | ||
case WS13.FrameType.Data.Text: | ||
console.log("Received text: %s", data); | ||
break; | ||
case WS13.FrameType.Data.Binary: | ||
console.log("Received binary data containing two integers: %d and %d.", | ||
data.readUInt32BE(0), data.readUInt32BE(4)); | ||
break; | ||
} | ||
}); | ||
socket.on('disconnected', function(code, reason, initiatedByUs) { | ||
console.log("Disconnected from echo server with code %d and reason string '%s'. Disconnection %s initiated by us.", | ||
code, reason, initiatedByUs ? "was" : "was NOT"); | ||
}); | ||
socket.on('error', function(err) { | ||
console.log("Fatal error: %s", err.message); | ||
}); | ||
``` | ||
# Features | ||
- Very lightweight. Only one dependency (directly; two dependencies counting nested dependencies). | ||
- No native dependencies | ||
- Easy-to-use API | ||
- Supports the latest version of the WebSocket protocol | ||
- TLS support | ||
- Supports WebSocket subprotocols | ||
- Able to automatically send ping requests to the server at a customizable interval, and tear down the connection when the server stops responding | ||
- Supports both UTF-8 and binary data | ||
- Transparent framing removes the need to buffer the TCP connection yourself | ||
- `Stream` interface to which data can be written or piped, and appear on the other side as a single message | ||
# Enums | ||
There are a few enums (implemented as objects) available from the root namespace object that is returned by `websocket13`. | ||
These are: | ||
```js | ||
WS13.State = { | ||
"Closed": 0, | ||
"Connecting": 1, | ||
"Connected": 2, | ||
"Closing": 3, | ||
"ClosingError": 4 | ||
}; | ||
WS13.FrameType = { | ||
"Continuation": 0x0, | ||
"Data": { | ||
"Text": 0x1, | ||
"Binary": 0x2 | ||
}, | ||
"Control": { | ||
"Close": 0x8, | ||
"Ping": 0x9, | ||
"Pong": 0xA | ||
} | ||
}; | ||
WS13.StatusCode = { | ||
"NormalClosure": 1000, /** Graceful disconnection */ | ||
"EndpointGoingAway": 1001, /** Closing connection because either the server or the client is going down (e.g. browser navigating away) */ | ||
"ProtocolError": 1002, /** Either side is terminating the connection due to a protocol error */ | ||
"UnacceptableDataType": 1003, /** Terminating because either side received data that it can't accept or process */ | ||
"Reserved1": 1004, /** Reserved. Do not use. */ | ||
"NoStatusCode": 1005, /** MUST NOT be sent over the wire. Used internally when no status code was sent. */ | ||
"AbnormalTermination": 1006, /** MUST NOT be sent over the wire. Used internally when the connection is closed without sending/receiving a Close frame. */ | ||
"InconsistentData": 1007, /** Terminating because either side received data that wasn't consistent with the expected type */ | ||
"PolicyViolation": 1008, /** Generic. Terminating because either side received a message that violated its policy */ | ||
"MessageTooBig": 1009, /** Terminating because either side received a message that is too big to process */ | ||
"MissingExtension": 1010, /** Client is terminating because the server didn't negotiate one or more extensions that we require */ | ||
"UnexpectedCondition": 1011, /** Server is terminating because it encountered an unexpected condition that prevented it from fulfilling the request */ | ||
"TLSFailed": 1015 /** MUST NOT be sent over the wire. Used internally when TLS handshake fails. */ | ||
}; | ||
``` | ||
# Constructor | ||
To construct a new WebSocket client, use this signature: | ||
WebSocket(uri[, options]) | ||
`uri` should be a string containing the URI to which you want to connect. The protocol must be either `ws://` for | ||
insecure, or `wss://` for secure. For example: `wss://echo.websocket.org:443/?query=string`. | ||
`options` should be an object containing zero or more of the properties in the following section. You can omit it if | ||
you don't wish to pass any options. | ||
The WebSocket will immediately attempt to connect after being constructed. If you want to reconnect after being | ||
disconnected, you should construct a new one. | ||
# Options | ||
### pingInterval | ||
Defaults to `10000`. The time in milliseconds between `Ping` requests that we send to the server automatically. | ||
### pingTimeout | ||
Defaults to `10000`. The time in milliseconds that we will wait for a `Pong` in reply to a `Ping` request (controlled | ||
by the `pingInterval` option). | ||
### pingFailures | ||
Defaults to `3`. After this many `Ping` requests timeout, the WebSocket will be closed and `error` will be emitted | ||
with `message` equaling `Ping timeout`. | ||
### headers | ||
An object containing any HTTP headers which will be added to the connection handshake. The following headers are | ||
reserved for internal use and will be ignored: | ||
- Host | ||
- Upgrade | ||
- Connection | ||
- Sec-WebSocket-Version | ||
- Sec-WebSocket-Protocol | ||
- Sec-WebSocket-Key | ||
### protocols | ||
An array of strings containing acceptable subprotocols. These will be sent to the server in the connection handshake | ||
and if any are acceptable, the server will choose one. The chosen subprotocol will be assigned to the `protocol` | ||
property in the [`connected`](#connected) event. | ||
### connection | ||
An object which will be passed to [`net.connect`](https://nodejs.org/api/net.html#net_net_connect_options_connectlistener) | ||
(and [`tls.connect`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback) if secure) as `options`. Here you | ||
can bind to a specific local interface, configure TLS options, and more. | ||
# Properties | ||
### options | ||
**Read-only.** The options object which you provided to the constructor. | ||
### hostname | ||
The hostname or IP address of the server to which we're connected. | ||
### port | ||
The port to which we're connected on the server. | ||
### path | ||
The request path of the handshake. | ||
### headers | ||
An object containing the HTTP headers we sent in the handshake. | ||
### state | ||
A value from the `WS13.State` enum representing our current connection state. | ||
### extensions | ||
An array containing the WebSocket extensions which are currently in use. Currently, none are supported so this will be | ||
empty. | ||
### protocol | ||
The subprotocol chosen by the server, if any. `null` if none. | ||
# Methods | ||
### disconnect([code][, reason]) | ||
- `code` - A value from the `WS13.StatusCode` enum describing why we're disconnecting. Defaults to `NormalClosure`. | ||
- `reason` - An optional string explaining why we're disconnecting. Does not need to be human-readable. Defaults to empty. | ||
Begins our side of the disconnection process. [`disconnected`](#disconnected) will be emitted when fully disconnected. | ||
You cannot send any more data after calling `disconnect()`. | ||
### send(data) | ||
- `data` - Either a `string` or `Buffer` containing the data you want to send | ||
Sends either UTF-8 string or binary data to the server. If `data` is a `string`, it must be valid UTF-8. Otherwise, it | ||
must be a `Buffer` containing the binary data you wish to send. Since the WebSocket protocol has transparent framing, | ||
unlike plain TCP, the data will be received in one whole message. Therefore, one `send()` call maps to exactly one | ||
received message on the other side. | ||
The message will be queued and sent after every message before it is sent. If you have an ongoing | ||
[`StreamedFrame`](#streamedframe), it will be delayed until after the entire stream is sent. | ||
### createMessageStream(type) | ||
- `type` - A value from the `WS13.FrameType.Data` enum representing what kind of data is to be sent | ||
Creates and returns a new [`StreamedFrame`](#streamedframe) object. See the [`StreamedFrame` documentation](#streamedframe) | ||
for more information. | ||
# Events | ||
### connected | ||
- `details` - An object containing details about your connection | ||
- `httpVersion` - The HTTP version used by the server | ||
- `responseCode` - The HTTP response code sent by the server (always `101`) | ||
- `responseText` - The HTTP response text sent by the server (usually `Switching Protocols`) | ||
- `headers` - An object containing the HTTP headers received from the server | ||
Emitted when we successfully establish a WebSocket connection to the server. At this point, the [`state`](#state) | ||
property will equal `WS13.State.Connected`, the [`extensions`](#extensions) array will be populated with any extensions | ||
that are in use, and the [`protocol`](#protocol) property will be defined with the subprotocol selected by the server, | ||
if any. You can now safely send and receive data. | ||
### disconnected | ||
- `code` - A value from the `WS13.StatusCode` enum | ||
- `reason` - A string (possibly empty) which may or may not be human-readable describing why you disconnceted | ||
- `initiatedByUs` - `true` if we initiated this disconnection, or `false` if the server condition did | ||
Emitted when we're disconnected from the server. Not emitted if we're disconnected due to an error; see [`error`](#error) | ||
in that case. | ||
### error | ||
- `err` - An `Error` object | ||
Emitted when a fatal error causes our connection to fail (while connecting) or be disconnected (while connected). Under | ||
certain conditions, `err` may contain zero or more of these properties: | ||
- `responseCode` - The HTTP status code we received if the error occurred during the handshake | ||
- `responseText` - The HTTP status text we received if the error occurred during the handshake | ||
- `httpVersion` - The HTTP version employed by the server if the error occurred during the handshake | ||
- `headers` - An object containing the HTTP headers we received from the server if the error occurred during the handshake | ||
- `expected` - A string containing the `Sec-WebSocket-Accept` value we expected to receive, if the error occurred because we didn't | ||
- `actual` - A string containing the actual `Sec-WebSocket-Accept` value we received, if the error occurred because it didn't match what we expected | ||
- `state` - The connection state at the time of error. Always present. | ||
- `code` - A value from the `WS13.StatusCode` enum, if the error occurred after the WebSocket connection was established | ||
### message | ||
- `type` - A value from the `WS13.FrameType.Data` enum describing what type of data we received | ||
- `data` - The data we received | ||
Emitted when we receive a complete message from the server. If `type` is `Text`, then `data` is a UTF-8 string. | ||
If `type` is `Binary`, then `data` is a `Buffer`. | ||
# StreamedFrame | ||
Messages are sent over WebSockets in *frames*. Usually, a frame contains one complete message. However, the WebSocket | ||
protocol also allows messages to be split up across multiple frames. This is useful when the entire data isn't available | ||
at the time of sending, and you'd like to stream it to the other side. In this case, call | ||
[`createMessageStream`](#createmessagestreamtype), which returns a `StreamedFrame` object. | ||
`StreamedFrame` implements the [`Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) interface. | ||
To send a chunk of data to the server, just call `frame.write(chunk)`. When you're done, call `frame.end()`. | ||
Because this implements the `Writable` interface, you can `pipe()` a `ReadableStream` into it. One use-case would be to | ||
pipe a download or a file on the disk into a WebSocket. | ||
One frame can contain only one type of data. This data type is set in the `createMessageStream` method, and is fixed | ||
for the lifetime of the `StreamedFrame`. Therefore, if your data type is `Text`, you must only call `write()` with | ||
UTF-8 strings. If it's `Binary`, you must only call `write()` with `Buffer` objects. If you call `write()` with the | ||
wrong data type, the `StreamedFrame` will emit an `error`. | ||
You *can* call `send()` or create new `StreamedFrame`s while you have one ongoing, but they will be queued and cannot | ||
be sent to the server until the currently-active `StreamedFrame` is ended. Be sure to call `end()` when you're done | ||
writing data to it. | ||
# Planned Features | ||
- `StreamedFrame` support for incoming multi-frame messages. Currently they're just buffered internally and `message` is emitted when all frames are received. | ||
- Server support | ||
- Support for cookies in handshake | ||
- [Per-message compression](https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-28) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
80422
25
1125
21
3