Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
WebSocketServer
===============
A WebSocket server implementation for Node.js, various features to still implement such as support for wss:// and performance analytics centre.
Installation
============
Install using npm - https://npmjs.org/package/websocketserver
<pre>npm install websocketserver</pre>
Basic Usage
===========
In a project require the websocketserver module and create an instance of it:
<pre>var WebSocketServer = require("websocketserver");
var server = new WebSocketServer("all", 9000);</pre>
This will create a WebSocket server that runs on 9000 (default 8080) and uses a sendMethod that sends recieved connections to all server connections. Four sendMethods can be used:
1) `all` - will send received messages to all users
2) `others` - will send messages to all users apart from the sender
3) `echo` - will echo the message back to the sender
4) `none` - will not automatically send any messages (useful for when more control is needed over message sending, use API to send messages)
Events
======
The server emits events on a new connection, message and a closed connection:
1) `connection` event - use to built a list of connections
<pre>var connectionList = [];
server.on("connection", function(id) {
connectionList.push(id);
});</pre>
2) `message` event - get hold of the message
<pre>server.on("message", function(data, id) {
var mes = server.unmaskMessage(data);
var str = server.convertToString(mes.message);
console.log(str);
});</pre>
3) `connectionclosed` - log a connection has left (can also use id to remove from connectionList)
<pre>server.on("closedconnection", function(id) {
console.log("Connection " + id + " has left the server");
});</pre>
Functions
==========
1) `WebSocketServer.sendMessage(people, data, id);` - send a message to specified `people` (`all`, `others` or `one`), `data` supports strings and packaged WebSocket messages (use `packageMessage` function for packaging), `id` is relates to the connection that you would like (or not to if `others` has been selected for the `people` argument, `id` not required if all has been selected).
This example shows how new connections to the server can be welcomed with a message:
<pre>server.on('connection', function(id) {
server.sendMessage("one", "Welcome to the server!", id);
});</pre>
2) `WebSocketServer.unmaskMessage(frame);` - unmasks a WebSocket frame recieved by the server - returns a JavaScript objects with two properties: `opcode` and `message` (`message` is an array of bytes holding character codes - use `convertToString(message)` function to get the string representation)
Example shows how a message could be logged to the console:
<pre>server.on('message', function(data, id) {
var mesObj = server.unmaskMessage(data);
console.log(server.convertToString(mesObj.message));
});</pre>
3) `WebSocketServer.convertToString(bytes);` - converts an array of bytes containing character codes to a string.
4) `WebSocketServer.packageMessage(opcode, message);` - packages a recieved WebSocket message into a sendable WebSocket message - this function is only really necessary after use of `unmaskMessage()` as it is only really necessary for data that needs to pass through the server (primarily binary messages)
Example shows how we may filter binary data to send straight through the server as most of the time we have no reason to log it.
<pre>server.on('message', function(data, id) {
var mes = server.unmaskMessage(data);
if (mes.opcode == 130) {
var packagedMessage = server.packageMessage(mes.opcode, mes.message);
server.sendMessage('all', packagedMessage);
}
});</pre>
5) `WebSocketServer.closeConnection(id);` close a specific connection
+4
View File
@@ -0,0 +1,4 @@
// James Taylor - 466598
// This file is used simply for installation purposes
module.exports = require("./modules/server.js");
+283
View File
@@ -0,0 +1,283 @@
// James Taylor - 466598
// Connections Module - Used to log information of a connection
function socketConnection(socket, connectionNumber) {
this.connectionNumber = connectionNumber;
this.socket = socket;
this.waitingForFrames = false;
this.fullWaitLength = 0;
this.waitArray = [];
this.arrayIndex;
this.fragmentOpcode;
this.fragmentArray = [];
}
socketConnection.prototype = {
sendData: function(data) {
this.socket.write(data);
},
returnID: function() {
return this.connectionNumber;
},
closeSocket: function(closeFrame) {
this.socket.write(closeFrame);
this.socket.end();
this.socket.destroy();
},
returnWaitingFrames: function() {
return this.waitingForFrames;
},
changeWaitingFrames: function(state) {
this.waitingForFrames = state;
},
setWaitArray: function(data) {
this.waitArray.push(data);
},
resetWaitArray: function() {
this.waitArray = [];
},
giveWaitArray: function() {
var buf = Buffer.concat(this.waitArray)
return buf;
},
setWaitLength: function(length) {
this.fullWaitLength = length;
},
getWaitLength: function() {
return this.fullWaitLength;
},
resetWaitLength: function() {
this.fullWaitLength = 0;
},
setOpcode: function(opcode) {
this.fragmentOpcode = opcode;
},
returnOpcode: function() {
return this.fragmentOpcode;
},
setFragmentArray: function(data) {
this.fragmentArray.push(data);
},
returnFragmentArray: function() {
var buf = Buffer.concat(this.fragmentArray);
return buf;
},
resetFragmentArray: function() {
this.fragmentArray = [];
}
}
function serverConnections() {
var connectionNumber = 0;
var connectionList = [];
var sendTo = "";
var self = this;
// Sends data to all users
this.sendToAll = function(data) {
var i = 0;
while (i < connectionList.length) {
connectionList[i].sendData(data);
i++;
}
}
// Sends data to all users apart from the sender
this.sendToOthers = function(data, id) {
var i = 0;
while (i < connectionList.length) {
if (id != connectionList[i].returnID()) {
connectionList[i].sendData(data);
}
i++;
}
}
// Echo test
this.echoBack = function(data, id) {
var i = 0;
while (i < connectionList.length) {
if (id == connectionList[i].returnID()) {
connectionList[i].sendData(data);
}
i++;
}
}
this.addToConnectionList = function(connection) {
connectionList.push(connection);
}
this.closeConnection = function(connectionID, status) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
var closeFrameArray = [];
var statusBytes = statusCode(status);
closeFrameArray[0] = 136;
closeFrameArray[1] = 2;
closeFrameArray[2] = statusBytes[0];
closeFrameArray[3] = statusBytes[1];
closeFrame = new Buffer(closeFrameArray);
connectionList[connectionID].closeSocket(closeFrame);
//connectionList.splice(connectionID, 1);
}
else {
console.log("Error shutting connection.");
}
}
// Changes the state to true if the connection is waiting for more frames
this.changeWaitState = function(connectionID, state) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
connectionList[connectionID].changeWaitingFrames(state);
}
else {
console.log("Error changing Connection state");
}
}
this.returnWaitState = function(connectionID) {
var state;
connectionID = findConnection(connectionID);
if (connectionID != -1) {
state = connectionList[connectionID].returnWaitingFrames();
}
else {
state = -1;
}
return state;
}
this.addWaitArray = function(data, connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
connectionList[connectionID].setWaitArray(data);
}
else {
console.log("Error setting waitArray");
}
}
this.clearWaitArray = function(connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
connectionList[connectionID].resetWaitArray();
}
else {
console.log("Error resetting waitArray");
}
}
this.returnWaitArray = function(connectionID) {
var data = [];
connectionID = findConnection(connectionID);
if (connectionID != -1) {
data = connectionList[connectionID].giveWaitArray();
}
return data;
}
function findConnection(connectionID) {
var i = 0;
while (i < connectionList.length) {
var lookup = connectionList[i].returnID();
if (lookup == connectionID) {
connectionID = i;
}
i++;
}
return connectionID;
}
this.addFullWaitLength = function(connectionID, length) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
connectionList[connectionID].setWaitLength(length);
}
}
this.returnFullWaitLength = function(connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
return connectionList[connectionID].getWaitLength();
}
}
this.clearWaitLength = function(connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
connectionList[connectionID].resetWaitLength();
}
}
// used to remember the fragment opcode
this.fragmentOpcode = function(state, opcode, connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
if (state == "set") {
connectionList[connectionID].setOpcode(opcode);
}
else if (state == "get") {
return connectionList[connectionID].returnOpcode();
}
else {
console.log("INVALID STATE GIVEN!" + state);
}
}
}
// Used to hold recived fragments
this.fragmentArray = function(state, data, connectionID) {
connectionID = findConnection(connectionID);
if (connectionID != -1) {
if (state == "set") {
connectionList[connectionID].setFragmentArray(data);
}
else if (state == "get") {
return connectionList[connectionID].returnFragmentArray();
}
else if (state == "reset") {
connectionList[connectionID].resetFragmentArray();
}
else {
console.log("INVALID STATE GIVEN: " + state);
}
}
}
// For setting the send method
this.setSend = function(sendMethod) {
console.log("Setting server send method to: " + sendMethod);
sendTo = sendMethod;
}
// Handles which method to use to send data
this.sendFrame = function(data, connectionID) {
switch(sendTo) {
case "all": self.sendToAll(data); break;
case "others": self.sendToOthers(data, connectionID); break;
case "echo": self.echoBack(data, connectionID); break;
case "none": break;
}
}
// Returns a new id for a new connection
this.getNewID = function() {
connectionNumber++;
return connectionNumber;
}
// Returns the correct byte values for the status code
function statusCode(status) {
var first = status >> 8;
var second = status & 255;
return [first,second];
}
}
exports.socketConnection = socketConnection;
exports.serverConnections = serverConnections;
+549
View File
@@ -0,0 +1,549 @@
// James Taylor - 466598
// Framing module - Decodes and packages data
function framer() {
var connections = require("./connections");
// used for the server to emit events
var emitter = "";
var connection;
// Ensures the full length of the frame has been recieved
this.checkRecievedData = function(data, connectionID) {
// checks to see if more frames are expected
if (connection.returnWaitState(connectionID) == true) {
connection.addWaitArray(data, connectionID);
var concatenatedArray = connection.returnWaitArray(connectionID);
// Set length of expected data
if ((concatenatedArray[1] & 127) <= 125) {
var setLength = (concatenatedArray[1] & 127) + 6;
connection.addFullWaitLength(connectionID, setLength);
}
if ((concatenatedArray[1] & 127) == 126) {
if (concatenatedArray.length > 4) {
var setLength = returnLength(concatenatedArray);
connection.addFullWaitLength(connectionID, setLength);
}
}
if ((concatenatedArray[1] & 127) == 127) {
if (concatenatedArray.length > 10) {
var setLength = returnLength(concatenatedArray);
connection.addFullWaitLength(connectionID, setLength);
}
}
if (concatenatedArray.length == connection.returnFullWaitLength(connectionID)) {
// Reset wait variables
connection.changeWaitState(connectionID, false);
connection.clearWaitArray(connectionID);
connection.clearWaitLength(connectionID);
// Pass on message for decoding
decode(concatenatedArray, connectionID);
}
}
else {
if (data[0] == 129 || data[0] == 130 || data[0] == 136 || data[0] == 137 || data[0] == 138) {
if (data[0] == 138) {
function splitFrames(data) {
var checkLength = returnLength(data);
if (data.length > checkLength) {
var newFragment = data.slice(0, checkLength);
decode(newFragment, connectionID);
var unhandledFragment = data.slice(checkLength);
splitFrames(unhandledFragment);
}
else {
checkFullFrameRecieved(data, connectionID);
decode(data, connectionID);
}
}
splitFrames(data);
}
else if (data[0] == 136) {
decode(data, connectionID);
return;
}
else {
checkFullFrameRecieved(data,connectionID);
}
if (connection.returnWaitState(connectionID) == false) {
decode(data, connectionID);
}
else {
connection.addWaitArray(data, connectionID);
}
}
else if (data[0] == 1 || data[0] == 2 || data[0] == 128 || data[0] == 0) { // fragmentation
// if continuation frame is sent with no message to continue
if (data[0] == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
connection.closeConnection(connectionID, 1002);
}
// if continuation frame is sent with its FIN set but it has no message to continue
else if (data[0] == 128 && connection.fragmentArray("get", data, connectionID).length == 0) {
connection.closeConnection(connectionID, 1002);
}
else if (data.length < 6) {
connection.changeWaitState(connectionID, true);
connection.addWaitArray(data, connectionID);
}
else {
function checkFragments(data) {
//check frame is fragmented, if not divide it
var checkLength = returnLength(data);
if (data.length > checkLength) {
var newFragment = data.slice(0, checkLength);
handleFragmentation(newFragment, connectionID);
var unhandledFragment = data.slice(checkLength);
checkFragments(unhandledFragment);
}
else {
handleFragmentation(data, connectionID);
}
}
checkFragments(data);
}
}
else {
connection.closeConnection(connectionID, 1002);
}
}
}
// Handles fragmentation cases
function handleFragmentation(data, connectionID) {
var opcode = data[0];
// Pings may be sent in the middle of fragments
if (opcode == 137) {
decode(data, connectionID);
return;
}
else if (opcode == 136) {
decode(data, connectionID);
return;
}
if (opcode == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
connection.closeConnection(connectionID, 1002);
}
else if ((opcode == 1 || opcode == 2 || opcode == 129) && (connection.fragmentArray("get", data, connectionID).length != 0)) {
connection.closeConnection(connectionID, 1002);
}
// remember the opcode for sending later
if (opcode == 1) {
connection.fragmentOpcode("set", 129, connectionID);
}
else if (opcode == 2) {
connection.fragmentOpcode("set", 130, connectionID);
}
// unmask the message
var maskLocation = 2;
var dataLength = data[1] & 127;
if (dataLength == 126) {
maskLocation = 4;
}
else if (dataLength == 127) {
maskLocation = 10;
}
var unmaskedData = [];
var maskingKey = data.slice(maskLocation, maskLocation + 4);
var dataLocation = maskLocation + 4;
for (var i = 0; dataLocation < data.length; i++) {
var newData = data[dataLocation] ^ maskingKey[i % 4];
unmaskedData.push(newData);
dataLocation++;
}
var buf = new Buffer(unmaskedData);
connection.fragmentArray("set", buf, connectionID);
// Message with FIN set to 1, package fragments for sending
if (opcode == 128) {
var dataBuffer = connection.fragmentArray("get", buf, connectionID);
connection.fragmentArray("reset", buf, connectionID);
opcode = connection.fragmentOpcode("get", 0, connectionID);
// Construct the first bytes of the message
var newData = [];
newData[0] = opcode;
setPayloadLength(newData, dataBuffer.length);
var buf = new Buffer(newData);
var bufList = [];
bufList[0] = buf;
bufList[1] = dataBuffer;
var packagedMessage = Buffer.concat(bufList);
connection.sendFrame(packagedMessage, connectionID);
}
}
// Used to correctly handle the data recieved from the client
function decode(data, connectionID) {
// The type of data (Opcode)
var opcode = data[0];
// The length of the data must be greater than 5 to be a valid frame
if (data.length <= 5) {
connection.closeConnection(connectionID, 1008);
console.log("Frame Length too short!");
return;
}
// make the initial check on the RSV numbers
if (invalidRSV(opcode)) {
connection.closeConnection(connectionID, 1002);
}
if (opcode == 129) { // text message
emitter.emit("message", data, connectionID);
var decodedData = unmaskMessage(data);
var packagedData = packageData(decodedData);
var encodedBuffer = new Buffer(packagedData);
connection.sendFrame(encodedBuffer, connectionID);
}
else if (opcode == 1 || opcode == 2 || opcode == 0 || opcode == 128) { // fragmented messages
handleFragmentation(data, connectionID);
}
else if (opcode == 130) { // binary frame
emitter.emit("message", data, connectionID);
var decodedData = unmaskMessage(data);
var packagedData = packageData(decodedData);
var encodedBuffer = new Buffer(packagedData);
connection.sendFrame(encodedBuffer, connectionID);
}
else if (opcode == 136) { // close frame
// Check for valid close code and close the connection
validateCloseFrame(connectionID, data);
}
else if (opcode == 137) { // ping frame
if (data.length <= 131) {
var decodedData = unmaskMessage(data);
var packagedData = constructPongFrame(decodedData);
var encodedBuffer = new Buffer(packagedData);
connection.sendFrame(encodedBuffer, connectionID);
}
else {
connection.closeConnection(connectionID, 1002);
}
}
else if (opcode == 138) { // pong frame
console.log("Pong recieved");
}
else {
connection.closeConnection(connectionID, 1002);
}
}
// Used to package the data in the correct framing format for WebSockets
function packageData(data) {
// extract the opcode from the data array then remove it from the array
var opcode = data.pop();
var newData = [];
newData[0] = opcode;
var payloadLength = data.length;
if (payloadLength <= 125) {
var dataLocation = 2;
newData[1] = payloadLength;
}
else if (payloadLength >= 126 && payloadLength <= 65535) {
var dataLocation = 4;
newData[1] = 126;
newData[2] = payloadLength >>> 8;
newData[3] = payloadLength & 255;
}
else if (payloadLength >= 65536) {
var dataLocation = 10;
newData[1] = 127;
// The first 32 bits
newData[9] = (payloadLength) & 255;
newData[8] = (payloadLength >> 8) & 255;
newData[7] = (payloadLength >> 16) & 255;
newData[6] = (payloadLength >> 24) & 255;
// if the number is greater than 32bit
if (payloadLength >= 4294967296) {
// Get the higher 64 bit
var sixtyFourBit = payloadLength.toString(2);
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
newData[5] = (secondThirtyTwoBits) & 255;
newData[4] = (secondThirtyTwoBits >> 8) & 255;
newData[3] = (secondThirtyTwoBits >> 16) & 255;
newData[2] = (secondThirtyTwoBits >> 24) & 255;
}
else { // if number is less than 32bit
newData[5] = 0;
newData[4] = 0;
newData[3] = 0;
newData[2] = 0;
}
}
for (var i = 0; i < payloadLength; i++) {
newData[dataLocation] = data[i];
dataLocation++;
}
return newData;
}
// unmasks a message
function unmaskMessage(data) {
// The payload length
var payloadLength = data[1];
// The location of the masking key
// Defualt 2
var locationOfMask = 2;
// Find the length of the message being sent, remove the masking key using & 127
var payloadLength = payloadLength & 127;
if (payloadLength == 126) {
// If the length of the payload is 126, 2 extra bytes represent the length
locationOfMask = 4;
}
else if (payloadLength == 127) {
// If length of the payload is 127, 8 extra bytes represent the length
locationOfMask = 10;
}
// Mask is 4 bytes long
var maskingKey = data.slice(locationOfMask, locationOfMask + 4);
var dataLocation = locationOfMask + 4;
// new array to hold decodedData
var decodedData = [];
var logString = "";
for (var i = 0; dataLocation < data.length; i++) {
var newData = data[dataLocation] ^ maskingKey[i % 4];
decodedData.push(newData);
dataLocation++;
}
decodedData.push(data[0]);
return decodedData;
}
// Used to set the correct payload length for packaging data
function setPayloadLength(newData, payloadLength) {
if (payloadLength <= 125) {
var dataLocation = 2;
newData[1] = payloadLength;
}
else if (payloadLength >= 126 && payloadLength <= 65535) {
var dataLocation = 4;
newData[1] = 126;
newData[2] = payloadLength >>> 8;
newData[3] = payloadLength & 255;
}
else if (payloadLength >= 65536) {
var dataLocation = 10;
newData[1] = 127;
// The first 32 bits
newData[9] = (payloadLength) & 255;
newData[8] = (payloadLength >> 8) & 255;
newData[7] = (payloadLength >> 16) & 255;
newData[6] = (payloadLength >> 24) & 255;
// if the number is greater than 32bit
if (payloadLength >= 4294967296) {
var sixtyFourBit = payloadLength.toString(2);
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
newData[5] = (secondThirtyTwoBits) & 255;
newData[4] = (secondThirtyTwoBits >> 8) & 255;
newData[3] = (secondThirtyTwoBits >> 16) & 255;
newData[2] = (secondThirtyTwoBits >> 24) & 255;
}
else { // if number is less than 32bit
newData[5] = 0;
newData[4] = 0;
newData[3] = 0;
newData[2] = 0;
}
}
}
// Creates a Pong frame
function constructPongFrame(data) {
var pingOpcode = data.pop();
var newData = [];
newData[0] = 138;
newData[1] = data.length;
var dataLocation = 2;
var i = 0;
while (i < data.length) {
newData[dataLocation] = data[i];
dataLocation++;
i++;
}
return newData;
}
// Used to check the full frame has been recieved
function checkFullFrameRecieved(data, connectionID) {
var payloadLength = data[1] & 127; //remove mask bit
if (payloadLength <= 125) {
if ((data.length - 6) == payloadLength) {
//console.log("Correct Frame Size");
}
else {
connection.changeWaitState(connectionID, true);
}
}
else if (payloadLength == 126) {
var dataRecieved = data.slice(8, (data.length));
var payOne = data[2] << 8;
var payTwo = data[3];
var dataLength = payOne + payTwo;
if (dataLength == dataRecieved.length) {
//console.log("Correct Frame Size");
}
else {
connection.changeWaitState(connectionID, true);
}
}
else if (payloadLength == 127) {
var dataRecieved = data.slice(14, (data.length));
var dataLength = "";
for (var i = 2; i < 10; i++) {
if (data[i] == 0) {
var str ="00000000";
}
else {
var newByte = data[i];
var str = newByte.toString(2);
}
dataLength += str;
}
dataLength = parseInt(dataLength, 2);
if (dataLength == dataRecieved.length) {
//console.log("Frame Recieved");
}
else {
connection.changeWaitState(connectionID, true);
}
}
}
// Returns the expected length of the data as specified by the Payload Length byte (2nd byte);
function returnLength(data) {
if ((data[1] & 127) <= 125) {
return (data[1] & 127) + 6;
}
else if ((data[1] & 127) == 126) {
var dataLength = (data[2] << 8) + data[3];
return dataLength + 8;
}
else if ((data[1] & 127) == 127) {
var dataLength = "";
for (var i = 2; i < 10; i++) {
if (data[i] == 0) {
var str ="00000000";
}
else {
var newByte = data[i];
var str = newByte.toString(2);
}
dataLength += str;
}
dataLength = parseInt(dataLength, 2);
return dataLength + 14;
}
}
// Checks that RSV1, RSV2 and RSV3 are all set to 0
function invalidRSV(firstByte) {
var check = firstByte;
// remove opcode and FIN;
check = check >> 4;
check = check & 7;
if (check == 0) {
return false;
}
else {
return true;
}
}
// Validates a close frame
function validateCloseFrame(connectionID, data) {
if (data.length == 6) { // No close code
connection.closeConnection(connectionID, 1000);
return;
}
else if (data.length == 7) { // Only half close code
connection.closeConnection(connectionID, 1002);
return;
}
// CLOSE CODE CHECKING
var mask = data.slice(2, 6);
var maskedCode = data.slice(6, 8);
var unmaskedCode = [];
for (var i = 0; i < maskedCode.length; i++) {
unmaskedCode[i] = maskedCode[i] ^ mask[i % 4];
}
unmaskedCode[0] = unmaskedCode[0] << 8;
var statusCode = unmaskedCode[0] + unmaskedCode[1];
if (statusCode <= 999 || statusCode >= 5000) {
connection.closeConnection(connectionID, 1002);
}
else if (statusCode >= 3000 && statusCode <= 4999) {
connection.closeConnection(connectionID, 1000);
}
else {
switch(statusCode) {
case 1000: connection.closeConnection(connectionID, 1000); break;
case 1001: connection.closeConnection(connectionID, 1000); break;
case 1002: connection.closeConnection(connectionID, 1000); break;
case 1003: connection.closeConnection(connectionID, 1000); break;
case 1007: connection.closeConnection(connectionID, 1000); break;
case 1008: connection.closeConnection(connectionID, 1000); break;
case 1009: connection.closeConnection(connectionID, 1000); break;
case 1010: connection.closeConnection(connectionID, 1000); break;
case 1011: connection.closeConnection(connectionID, 1000); break;
default: connection.closeConnection(connectionID, 1002);
}
}
}
this.setEmitter = function(object) {
emitter = object;
}
this.setConnectionList = function(list) {
connection = list;
}
this.unmaskMessageCaller = function(message) {
return unmaskMessage(message);
}
this.packageDataCaller = function(data) {
return packageData(data);
}
}
module.exports = framer;
+210
View File
@@ -0,0 +1,210 @@
// WebSocket Server
// James Taylor - 466598
// Emit events
var events = require("events");
var util = require("util");
function WebSocketServer(sendMethod, port) {
// Used to create the http server that will be upgraded
var http = require("http");
// Used to create the "sec-websocket-accept" key
var crypto = require("crypto");
// Used to retrive a message sent by the client
var recievedFrame = require("./framing");
// Used to create a connection instance
var connections = require("./connections");
var connectionList = new connections.serverConnections();
var frameOperation = new recievedFrame();
frameOperation.setConnectionList(connectionList);
this.framer = frameOperation;
this.connect = connectionList;
var port = port || 8080;
events.EventEmitter.call(this);
var self = this;
frameOperation.setEmitter(this);
console.log("Creating Server...");
switch(sendMethod) {
case "all":
connectionList.setSend("all");
break;
case "others":
connectionList.setSend("others");
break;
case "echo":
connectionList.setSend("echo");
break;
case "none":
connectionList.setSend("none");
break;
default:
connectionList.setSend("all");
}
// The http server that acts as the underlying server
var newServer = http.createServer(function (request, response) {
request.on("end", function() {
response.writeHead(200, {
'Content-Type': 'text/html'
});
response.end("Connect using the ws:// prefix");
});
}).listen(port);
console.log("Server started and listening for connections on port " + port);
//listens for an upgrade
newServer.on("upgrade", function (res, socket, head) {
var key = acceptKey(res.headers["sec-websocket-key"]);
socket.write("HTTP/1.1 101 Switching Protocols\r\n" +
"Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Accept: " + key + "\r\n" +
"\r\n");
socket.setKeepAlive(true);
socket.removeAllListeners("timeout");
var newConnection = new connections.socketConnection(socket, connectionList.getNewID());
connectionList.addToConnectionList(newConnection);
var connectionID = newConnection.returnID();
self.emit("connection", connectionID);
socket.on("data", function(data) {
frameOperation.checkRecievedData(data, connectionID);
});
socket.on("close", function(e) {
self.emit("closedconnection", connectionID);
});
});
// Used to return the accept key
function acceptKey(clientKey) {
// The Globally Unique Identifier
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var key = clientKey + GUID;
var sha = crypto.createHash('sha1');
sha.update(key);
key = sha.digest(encoding="base64");
return key;
}
exports.sendFrame = connections.sendFrame;
}
util.inherits(WebSocketServer, events.EventEmitter);
// Send a message to a client
WebSocketServer.prototype.sendMessage = function(people, data, id) {
// Data type checks
if (typeof data == "string") {
var newData = [];
opcode = 129;
for (var i = 0; i < data.length; i++) {
newData.push(data.charCodeAt(i));
}
newData.push(opcode);
data = new Buffer(this.framer.packageDataCaller(newData));
}
else if (!Buffer.isBuffer(data)) {
console.log("You need to use the ws.packageMessage() first");
return;
}
// Send the data
if (people == "all") {
this.connect.sendToAll(data);
}
else if (people == "others") {
if (!id) {
console.log("The others parameter cannot be used if an ID is not specified");
}
this.connect.sendToOthers(data, id)
}
else if (people == "one") {
if (!id) {
console.log("The others parameter cannot be used if an ID is not specified");
}
this.connect.echoBack(data, id)
}
else {
console.log("Unknown Send Command: " + people);
}
}
// Unmask a message
WebSocketServer.prototype.unmaskMessage = function(message) {
if (!Buffer.isBuffer(message)) {
console.log("You must supply a valid WebSocket frame to unmask.");
return;
}
var unmasked = this.framer.unmaskMessageCaller(message);
var obj = { };
obj.opcode = unmasked.pop();
obj.message = unmasked;
return obj;
}
// Package a message
WebSocketServer.prototype.packageMessage = function(opcode, message) {
if (Array.isArray(message)) {
message.push(opcode);
return new Buffer(this.framer.packageDataCaller(message));
}
else {
console("Wrong data type fed to packageMessage");
}
}
// Close a connection
WebSocketServer.prototype.closeConnection = function(id) {
if (typeof id != "number") {
console.log("closeConnection needs a valid integer id");
return;
}
this.connect.closeConnection(id, 1000);
}
// Convert bytes to a string
WebSocketServer.prototype.convertToString = function(message) {
if (!Array.isArray(message)) {
console.log("You must enter an array of bytes to convert to a string");
return;
}
var str = "";
for (var i = 0; i < message.length; i++) {
str += String.fromCharCode(message[i]);
}
return str;
}
// Return the type of received message
WebSocketServer.prototype.messageType = function(opcode) {
// Check that the opcode is a valid whole number
if ((typeof opcode != 'number') || (opcode % 1 != 0)) {
console.log("An opcode must be an integer");
return "invalid";
}
if (opcode === 129) {
return "text";
}
else if (opcode === 130) {
return "binary";
}
else {
console.log("Cannot determine type of message");
return "invalid";
}
}
module.exports = WebSocketServer;
+47
View File
@@ -0,0 +1,47 @@
{
"_from": "websocketserver",
"_id": "websocketserver@1.0.0",
"_inBundle": false,
"_integrity": "sha1-ssuE4ndFlwqstCsXYM+LmuUdzoE=",
"_location": "/websocketserver",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "websocketserver",
"name": "websocketserver",
"escapedName": "websocketserver",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/websocketserver/-/websocketserver-1.0.0.tgz",
"_shasum": "b2cb84e27745970aacb42b1760cf8b9ae51dce81",
"_spec": "websocketserver",
"_where": "D:\\Abteilungskram\\NLY\\WSS\\SocketMap",
"author": {
"name": "James Taylor",
"email": "james.taylor5@myport.ac.uk"
},
"bin": {
"WebSocketServer": "./index.js"
},
"bugs": {
"url": "https://github.com/jamest222/WebSocketServer/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "A WebSocket Server implementation for Node.js",
"homepage": "https://github.com/jamest222/WebSocketServer#readme",
"main": "./index.js",
"name": "websocketserver",
"repository": {
"type": "git",
"url": "git+https://github.com/jamest222/WebSocketServer.git"
},
"version": "1.0.0"
}