Comparing version 0.0.20 to 1.0.0
@@ -8,3 +8,3 @@ module.exports = { | ||
"connection" : require('./WebSocketConnection'), | ||
"version" : "0.0.20" | ||
"version" : "1.0.0" | ||
}; |
@@ -33,2 +33,4 @@ /************************************************************************ | ||
this.remoteAddress = socket.remoteAddress; | ||
this.closeReasonCode = -1; | ||
this.closeDescription = null; | ||
@@ -84,3 +86,17 @@ // We have to mask outgoing packets if we're acting as a WebSocket client. | ||
if (this.config.keepalive) { | ||
this._pingIntervalID = setInterval(this.ping.bind(this), this.config.keepaliveInterval); | ||
if (typeof(this.config.keepaliveInterval) !== 'number') { | ||
throw new Error("keepaliveInterval must be specified and numeric " + | ||
"if keepalive is true."); | ||
} | ||
this._keepaliveTimerHandler = this.handleKeepaliveTimer.bind(this); | ||
this.setKeepaliveTimer(); | ||
if (this.config.dropConnectionOnKeepaliveTimeout) { | ||
if (typeof(this.config.keepaliveGracePeriod) !== 'number') { | ||
throw new Error("keepaliveGracePeriod must be specified and " + | ||
"numeric if dropConnectionOnKeepaliveTimeout " + | ||
"is true.") | ||
} | ||
this._gracePeriodTimerHandler = this.handleGracePeriodTimer.bind(this); | ||
} | ||
} | ||
@@ -93,9 +109,73 @@ }; | ||
WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003; | ||
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE = 1004; | ||
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Reserved value, not to be used | ||
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Reserved value, not to be used | ||
WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning. | ||
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire | ||
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire | ||
WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007; | ||
WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008; | ||
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009; | ||
WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010; | ||
WebSocketConnection.CLOSE_DESCRIPTIONS = { | ||
1000: "Normal connection closure", | ||
1001: "Remote peer is going away", | ||
1002: "Protocol error", | ||
1003: "Unprocessable input", | ||
1004: "Reserved", | ||
1005: "Reason not provided", | ||
1006: "Abnormal closure, no further detail available", | ||
1007: "Invalid data received", | ||
1008: "Policy violation", | ||
1009: "Message too big", | ||
1010: "Extension requested by client is required" | ||
}; | ||
util.inherits(WebSocketConnection, EventEmitter); | ||
// set or reset the keepalive timer when data is received. | ||
WebSocketConnection.prototype.setKeepaliveTimer = function() { | ||
if (!this.config.keepalive) { return; } | ||
if (this._keepaliveTimeoutID) { | ||
clearTimeout(this._keepaliveTimeoutID); | ||
} | ||
if (this._gracePeriodTimeoutID) { | ||
clearTimeout(this._gracePeriodTimeoutID); | ||
} | ||
this._keepaliveTimeoutID = setTimeout(this._keepaliveTimerHandler, this.config.keepaliveInterval); | ||
}; | ||
// No data has been received within config.keepaliveTimeout ms. | ||
WebSocketConnection.prototype.handleKeepaliveTimer = function() { | ||
this._keepaliveTimeoutID = null; | ||
this.ping(); | ||
// If we are configured to drop connections if the client doesn't respond | ||
// then set the grace period timer. | ||
if (this.config.dropConnectionOnKeepaliveTimeout) { | ||
this.setGracePeriodTimer(); | ||
} | ||
else { | ||
// Otherwise reset the keepalive timer to send the next ping. | ||
this.setKeepaliveTimer(); | ||
} | ||
}; | ||
WebSocketConnection.prototype.setGracePeriodTimer = function() { | ||
if (this._gracePeriodTimeoutID) { | ||
clearTimeout(this._gracePeriodTimeoutID); | ||
} | ||
this._gracePeriodTimeoutID = setTimeout(this._gracePeriodTimerHandler, this.config.keepaliveGracePeriod); | ||
}; | ||
WebSocketConnection.prototype.handleGracePeriodTimer = function() { | ||
// If this is called, the client has not responded and is assumed dead. | ||
this._gracePeriodTimeoutID = null; | ||
this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL, "Peer not responding.", true); | ||
}; | ||
WebSocketConnection.prototype.handleSocketData = function(data) { | ||
// Reset the keepalive timer when receiving data of any kind. | ||
this.setKeepaliveTimer(); | ||
// Add received data to our bufferList, which efficiently holds received | ||
// data chunks in a linked list of Buffer objects. | ||
this.bufferList.write(data); | ||
@@ -115,3 +195,3 @@ | ||
else if (this.currentFrame.frameTooLarge) { | ||
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE, this.currentFrame.dropReason); | ||
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, this.currentFrame.dropReason); | ||
return; | ||
@@ -155,11 +235,17 @@ } | ||
this.connected = false; | ||
this.state = "closed"; | ||
this.state = STATE_CLOSED; | ||
// If closeReasonCode is still set to -1 at this point then we must | ||
// not have received a close frame!! | ||
if (this.closeReasonCode === -1) { | ||
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; | ||
this.closeDescription = "Connection dropped by remote peer."; | ||
} | ||
if (!this.closeEventEmitted) { | ||
this.closeEventEmitted = true; | ||
// console.log((new Date()) + " - Emitting WebSocketConnection close event"); | ||
this.emit('close', this); | ||
this.emit('close', this.closeReasonCode, this.closeDescription); | ||
} | ||
this.clearCloseTimer(); | ||
if (this.config.keepalive) { | ||
clearInterval(this._pingIntervalID); | ||
if (this._keepaliveTimeoutID) { | ||
clearTimeout(this._keepaliveTimeoutID); | ||
} | ||
@@ -176,5 +262,7 @@ }; | ||
if (this.connected) { | ||
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; | ||
this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode]; | ||
this.setCloseTimer(); | ||
this.sendCloseFrame(); | ||
this.state = "closing"; | ||
this.state = STATE_CLOSING; | ||
this.connected = false; | ||
@@ -184,17 +272,30 @@ } | ||
WebSocketConnection.prototype.drop = function(closeReason, reasonText) { | ||
if (typeof(closeReason) !== 'number') { | ||
closeReason = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; | ||
WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) { | ||
if (typeof(reasonCode) !== 'number') { | ||
reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; | ||
} | ||
var logText = "WebSocket: Dropping Connection. Code: " + closeReason.toString(10); | ||
if (reasonText) { | ||
logText += (" - " + reasonText); | ||
var logText = "WebSocket: Dropping Connection. Code: " + reasonCode.toString(10); | ||
if (typeof(description) !== 'string') { | ||
// If no description is provided, try to look one up based on the | ||
// specified reasonCode. | ||
description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; | ||
} | ||
if (description) { | ||
logText += (" - " + description); | ||
} | ||
console.error((new Date()) + " " + logText); | ||
this.closeReasonCode = reasonCode; | ||
this.closeDescription = description; | ||
this.outgoingFrameQueue = []; | ||
this.frameQueue = [] | ||
this.fragmentationSize = 0; | ||
this.sendCloseFrame(closeReason, reasonText, true); | ||
if (!skipCloseFrame) { | ||
this.sendCloseFrame(reasonCode, description, true); | ||
} | ||
this.connected = false; | ||
this.state = "closed"; | ||
this.state = STATE_CLOSED; | ||
this.closeEventEmitted = true; | ||
this.emit('close', reasonCode, description); | ||
this.socket.destroy(); | ||
@@ -285,3 +386,3 @@ }; | ||
if (this.fragmentationSize > this.maxReceivedMessageSize) { | ||
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, | ||
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, | ||
"Maximum message size exceeded."); | ||
@@ -341,3 +442,3 @@ return; | ||
this.waitingForCloseResponse = false; | ||
this.state = "closed"; | ||
this.state = STATE_CLOSED; | ||
this.socket.end(); | ||
@@ -348,31 +449,13 @@ } | ||
// Send back acknowledgement and then hang up. | ||
this.state = "closing"; | ||
if (frame.closeStatus !== WebSocketConnection.CLOSE_REASON_NORMAL) { | ||
var logCloseError; | ||
switch(frame.closeStatus) { | ||
case WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR: | ||
logCloseError = "Remote peer closed connection: Protocol Error"; | ||
break; | ||
case WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_LARGE: | ||
logCloseError = "Remote peer closed connection: Received Message Too Large"; | ||
break; | ||
case WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT: | ||
logCloseError = "Remote peer closed connection: Unprocessable Input"; | ||
break; | ||
case WebSocketConnection.CLOSE_REASON_GOING_AWAY: | ||
logCloseError = "Remote peer closed connection: Going Away"; | ||
break; | ||
default: | ||
logCloseError = "Remote peer closed connection: Status code " + frame.closeStatus.toString(10); | ||
break; | ||
} | ||
if (frame.binaryPayload) { | ||
logCloseError += (" - Description Provided: " + frame.binaryPayload.toString('utf8')); | ||
} | ||
// console.error((new Date()) + " " + logCloseError); | ||
this.state = STATE_CLOSING; | ||
this.closeReasonCode = frame.closeStatus; | ||
if (frame.binaryPayload.length > 0) { | ||
this.closeDescription = frame.binaryPayload.toString('utf8'); | ||
} | ||
else { | ||
// console.log((new Date()) + " - Remote peer " + this.remoteAddress + " requested disconnect"); | ||
this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode]; | ||
} | ||
// console.log((new Date()) + " - Sending close frame in response and closing the socket."); | ||
// console.log((new Date()) + " Remote peer " + this.remoteAddress + | ||
// " requested disconnect, code: " + this.closeReasonCode + " - " + this.closeDescription + | ||
// " - close frame payload length: " + frame.length); | ||
this.sendCloseFrame(WebSocketConnection.CLOSE_REASON_NORMAL); | ||
@@ -389,2 +472,14 @@ this.socket.end(); | ||
WebSocketConnection.prototype.send = function(data) { | ||
if (Buffer.isBuffer(data)) { | ||
this.sendBytes(data); | ||
} | ||
else if (typeof(data['toString']) === 'function') { | ||
this.sendUTF(data); | ||
} | ||
else { | ||
throw new Error("Data provided must either be a Node Buffer or implement toString()") | ||
} | ||
}; | ||
WebSocketConnection.prototype.sendUTF = function(data) { | ||
@@ -407,9 +502,19 @@ var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); | ||
WebSocketConnection.prototype.ping = function() { | ||
WebSocketConnection.prototype.ping = function(data) { | ||
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config); | ||
frame.opcode = 0x09; // WebSocketOpcode.PING | ||
frame.fin = true; | ||
if (data) { | ||
if (!Buffer.isBuffer(data)) { | ||
data = new Buffer(data.toString(), 'utf8') | ||
} | ||
if (data.length > 125) { | ||
console.warn("WebSocket: Data for ping is longer than 125 bytes. Truncating."); | ||
data = data.slice(0,124); | ||
} | ||
frame.binaryPayload = data; | ||
} | ||
this.sendFrame(frame); | ||
}; | ||
// Pong frames have to echo back the contents of the data portion of the | ||
@@ -420,2 +525,6 @@ // ping frame exactly, byte for byte. | ||
frame.opcode = 0x0A; // WebSocketOpcode.PONG | ||
if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) { | ||
console.warn("WebSocket: Data for pong is longer than 125 bytes. Truncating."); | ||
binaryPayload = binaryPayload.slice(0,124); | ||
} | ||
frame.binaryPayload = binaryPayload; | ||
@@ -487,3 +596,3 @@ frame.fin = true; | ||
WebSocketConnection.prototype.processOutgoingFrameQueue = function() { | ||
if (this.outputPaused) { return; } | ||
if (this.outputPaused || !this.connected) { return; } | ||
if (this.outgoingFrameQueue.length > 0) { | ||
@@ -495,3 +604,3 @@ var buffer = this.outgoingFrameQueue.pop(); | ||
catch(e) { | ||
console.error("Error while writing to socket: " + e.toString()); | ||
console.warn("Error while writing to socket: " + e.toString()); | ||
return; | ||
@@ -498,0 +607,0 @@ } |
@@ -131,2 +131,6 @@ /************************************************************************ | ||
this.binaryPayload = new Buffer(0); | ||
if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE | ||
// The remote peer didn't include a close status | ||
this.closeStatus = 1005; // WebSocketConnection.CLOSE_REASON_NOT_PROVIDED | ||
} | ||
this.parseState = COMPLETE; | ||
@@ -143,4 +147,10 @@ return true; | ||
if (this.opcode === 0x08) { // WebSocketOpcode.CONNECTION_CLOSE | ||
this.closeStatus = ctio.ruint16(this.binaryPayload, 'big', 0); | ||
this.binaryPayload = this.binaryPayload.slice(2); | ||
if (this.length >= 2) { | ||
this.closeStatus = ctio.ruint16(this.binaryPayload, 'big', 0); | ||
this.binaryPayload = this.binaryPayload.slice(2); | ||
} | ||
else { | ||
// The remote peer didn't include a close status | ||
this.closeStatus = 1005; // WebSocketConnection.CLOSE_REASON_NOT_PROVIDED | ||
} | ||
} | ||
@@ -147,0 +157,0 @@ |
@@ -26,3 +26,2 @@ /************************************************************************ | ||
requestAccepted: this.handleRequestAccepted.bind(this), | ||
connectionClose: this.handleConnectionClose.bind(this) | ||
}; | ||
@@ -58,8 +57,21 @@ this.connections = []; | ||
// If true, the server will automatically send a ping to all | ||
// clients every 'keepaliveInterval' milliseconds. | ||
// clients every 'keepaliveInterval' milliseconds. The timer is | ||
// reset on any received data from the client. | ||
keepalive: true, | ||
// The interval to send keepalive pings to connected clients. | ||
// The interval to send keepalive pings to connected clients if the | ||
// connection is idle. Any received data will reset the counter. | ||
keepaliveInterval: 20000, | ||
// If true, the server will consider any connection that has not | ||
// received any data within the amount of time specified by | ||
// 'keepaliveGracePeriod' after a keepalive ping has been sent. | ||
// Ignored if keepalive is false. | ||
dropConnectionOnKeepaliveTimeout: true, | ||
// The amount of time to wait after sending a keepalive ping before | ||
// closing the connection if the connected peer does not respond. | ||
// Ignored if keepalive is false. | ||
keepaliveGracePeriod: 10000, | ||
// If true, fragmented messages will be automatically assembled | ||
@@ -168,7 +180,11 @@ // and the full message will be emitted via a 'message' event. | ||
WebSocketServer.prototype.handleRequestAccepted = function(connection) { | ||
connection.once('close', this._handlers.connectionClose); | ||
var self = this; | ||
connection.once('close', function(closeReason, description) { | ||
self.handleConnectionClose(connection, closeReason, description); | ||
}); | ||
this.connections.push(connection); | ||
this.emit('connect', connection); | ||
}; | ||
WebSocketServer.prototype.handleConnectionClose = function(connection) { | ||
WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) { | ||
var index = this.connections.indexOf(connection); | ||
@@ -178,5 +194,5 @@ if (index !== -1) { | ||
} | ||
this.emit('close', connection); | ||
this.emit('close', connection, closeReason, description); | ||
}; | ||
module.exports = WebSocketServer; |
@@ -6,3 +6,3 @@ { | ||
"author": "Brian McKelvey <brian@worlize.com>", | ||
"version": "0.0.20", | ||
"version": "1.0.0", | ||
"repository": { | ||
@@ -9,0 +9,0 @@ "type": "git", |
@@ -129,3 +129,3 @@ WebSocket Client & Server Implementation for Node | ||
}); | ||
connection.on('close', function(connection) { | ||
connection.on('close', function(reasonCode, description) { | ||
console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected."); | ||
@@ -132,0 +132,0 @@ }); |
@@ -61,3 +61,4 @@ #!/usr/bin/env node | ||
wsServer.on('connect', function(connection) { | ||
console.log((new Date()) + " Connection accepted."); | ||
console.log((new Date()) + " Connection accepted" + | ||
" - Protocol Version " + connection.websocketVersion); | ||
connection.on('message', function(message) { | ||
@@ -73,5 +74,5 @@ if (message.type === 'utf8') { | ||
}); | ||
connection.on('close', function(connection) { | ||
connection.on('close', function(reasonCode, description) { | ||
console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected."); | ||
}); | ||
}); |
@@ -63,3 +63,3 @@ #!/usr/bin/env node | ||
var radius = Math.round(Math.random() * 30); | ||
connection.sendUTF("c #" + color.toString(16) + " " + x + " " + y + " " + radius + ";"); | ||
connection.send("c #" + color.toString(16) + " " + x + " " + y + " " + radius + ";"); | ||
setTimeout(spamCircles, 10); | ||
@@ -66,0 +66,0 @@ } |
@@ -106,3 +106,3 @@ #!/usr/bin/env node | ||
connection.sendUTF(historyString); | ||
connection.send(historyString); | ||
} | ||
@@ -126,3 +126,3 @@ | ||
mirrorConnections.forEach(function (outputConnection) { | ||
outputConnection.sendUTF(message.utf8Data); | ||
outputConnection.send(message.utf8Data); | ||
}); | ||
@@ -132,6 +132,6 @@ } | ||
connection.on('close', function(connection) { | ||
connection.on('close', function(closeReason, description) { | ||
var index = mirrorConnections.indexOf(connection); | ||
if (index !== -1) { | ||
console.log((new Date()) + " lws-mirror-protocol peer " + connection.remoteAddress + " disconnected."); | ||
console.log((new Date()) + " lws-mirror-protocol peer " + connection.remoteAddress + " disconnected, code: " + closeReason + "."); | ||
mirrorConnections.splice(index, 1); | ||
@@ -155,3 +155,3 @@ } | ||
connection.timerInterval = setInterval(function() { | ||
connection.sendUTF((number++).toString(10)); | ||
connection.send((number++).toString(10)); | ||
}, 50); | ||
@@ -169,4 +169,4 @@ connection.on('close', function() { | ||
}); | ||
connection.on('close', function(connection) { | ||
console.log((new Date()) + " dumb-increment-protocol peer " + connection.remoteAddress + " disconnected"); | ||
connection.on('close', function(closeReason, description) { | ||
console.log((new Date()) + " dumb-increment-protocol peer " + connection.remoteAddress + " disconnected, code: " + closeReason + "."); | ||
}); | ||
@@ -173,0 +173,0 @@ }); |
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
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
300917
7435
1
4