Files
docker-airsignal/WebsocketNetworkServer.js
Vatsal Ambastha 45e609df64 Add server files
2021-05-28 18:57:48 +05:30

362 lines
17 KiB
JavaScript

"use strict";
var ws = require('ws');
var inet = require('./INetwork');
var WebsocketNetworkServer = (function () {
function WebsocketNetworkServer() {
this.mPool = {};
}
WebsocketNetworkServer.log = function (msg) {
console.log("(" + new Date().toISOString() + ")" + msg);
};
WebsocketNetworkServer.prototype.onConnection = function (socket, appname) {
//it would be possible to enforce the client to send a certain introduction first
//to determine to which pool we add it -> for now only one pool is supported
this.mPool[appname].add(socket);
};
//
WebsocketNetworkServer.prototype.addSocketServer = function (websocketServer, appConfig) {
var _this = this;
if (this.mPool[appConfig.name] == null) {
this.mPool[appConfig.name] = new PeerPool(appConfig);
}
var name = appConfig.name;
websocketServer.on('connection', function (socket) { _this.onConnection(socket, name); });
};
return WebsocketNetworkServer;
}());
exports.WebsocketNetworkServer = WebsocketNetworkServer;
;
//Pool of client connects that are allowed to communicate to each other
var PeerPool = (function () {
function PeerPool(config) {
this.mConnections = new Array();
this.mServers = {};
this.mAddressSharing = false;
this.maxAddressLength = 256;
this.mAppConfig = config;
if (this.mAppConfig.address_sharing) {
this.mAddressSharing = this.mAppConfig.address_sharing;
}
}
PeerPool.prototype.hasAddressSharing = function () {
return this.mAddressSharing;
};
//add a new connection based on this websocket
PeerPool.prototype.add = function (socket) {
this.mConnections.push(new SignalingPeer(this, socket));
};
//Returns the SignalingClientConnection that opened a server using the given address
//or null if address not in use
PeerPool.prototype.getServerConnection = function (address) {
return this.mServers[address];
};
//Tests if the address is available for use.
//returns true in the following cases
//the address is longer than the maxAddressLength and the server the address is not yet in use or address sharing is active
PeerPool.prototype.isAddressAvailable = function (address) {
if (address.length <= this.maxAddressLength // only allow addresses shorter than maxAddressLength
&& (this.mServers[address] == null || this.mAddressSharing)) {
return true;
}
return false;
};
//Adds the server. No checking is performed here! logic should be solely in the connection class
PeerPool.prototype.addServer = function (client, address) {
if (this.mServers[address] == null) {
this.mServers[address] = new Array();
}
this.mServers[address].push(client);
};
//Removes an address from the server. No checks performed
PeerPool.prototype.removeServer = function (client, address) {
//supports address sharing. remove the client from the server list that share the address
var index = this.mServers[address].indexOf(client);
if (index != -1) {
this.mServers[address].splice(index, 1);
}
//delete the whole list if the last one left
if (this.mServers[address].length == 0) {
delete this.mServers[address];
}
};
//Removes a given connection from the pool
PeerPool.prototype.removeConnection = function (client) {
var index = this.mConnections.indexOf(client);
if (index != -1) {
this.mConnections.splice(index, 1);
}
else {
console.warn("Tried to remove unknown SignalingClientConnection. Bug?" + client);
}
};
PeerPool.prototype.count = function () {
return this.mConnections.length;
};
return PeerPool;
}());
var SignalingConnectionState;
(function (SignalingConnectionState) {
SignalingConnectionState[SignalingConnectionState["Uninitialized"] = 0] = "Uninitialized";
SignalingConnectionState[SignalingConnectionState["Connecting"] = 1] = "Connecting";
SignalingConnectionState[SignalingConnectionState["Connected"] = 2] = "Connected";
SignalingConnectionState[SignalingConnectionState["Disconnecting"] = 3] = "Disconnecting";
SignalingConnectionState[SignalingConnectionState["Disconnected"] = 4] = "Disconnected"; //means the instance is destroyed and unusable
})(SignalingConnectionState || (SignalingConnectionState = {}));
;
///note: all methods starting with "internal" might leave the system in an inconsistent state
///e.g. peerA is connected to peerB means peerB is connected to peerA but internalRemoveConnection
///could cause peerA being disconnected from peerB but peerB still thinking to be connected to peerA!!!
var SignalingPeer = (function () {
function SignalingPeer(pool, socket) {
var _this = this;
this.mState = SignalingConnectionState.Uninitialized;
this.mConnections = {};
//C# version uses short so 16384 is 50% of the positive numbers (maybe might make sense to change to ushort or int)
this.mNextIncomingConnectionId = new inet.ConnectionId(16384);
this.mConInfo = "[con info missing]";
this.mConnectionPool = pool;
this.mSocket = socket;
//(this.mSocket as any).maxPayload = 16;
this.mState = SignalingConnectionState.Connecting;
this.mConInfo = this.mSocket.upgradeReq.connection.remoteAddress + ":" + this.mSocket.upgradeReq.connection.remotePort;
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + " connected ");
socket.on('message', function (message, flags) {
_this.onMessage(message, flags);
});
socket.on('error', function (error) {
console.error(error);
});
socket.on('close', function (code, message) { _this.onClose(code, message); });
socket.on('pong', function (data, flags) {
WebsocketNetworkServer.log("[" + _this.mConInfo + "]" + "INC: pong ");
});
this.mState = SignalingConnectionState.Connected;
this.mPingInterval = setInterval(function () { _this.doPing(); }, 30000);
}
SignalingPeer.prototype.doPing = function () {
if (this.mState == SignalingConnectionState.Connected && this.mSocket.readyState == ws.OPEN) {
this.mSocket.ping();
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "OUT: ping");
}
};
SignalingPeer.prototype.evtToString = function (evt) {
var output = "[";
output += "NetEventType: (";
output += inet.NetEventType[evt.Type];
output += "), id: (";
output += evt.ConnectionId.id;
if (evt.Info != null) {
output += "), Data: (";
output += evt.Info;
}
else if (evt.MessageData != null) {
var chars = new Uint16Array(evt.MessageData.buffer, evt.MessageData.byteOffset, evt.MessageData.byteLength / 2);
output += "), Data: (";
var binaryString = "";
for (var i = 0; i < chars.length; i++) {
binaryString += String.fromCharCode(chars[i]);
}
output += binaryString;
}
output += ")]";
return output;
};
SignalingPeer.prototype.onMessage = function (message, flags) {
try {
//unlike browsers ws will give a Uint8Array
var evt = inet.NetworkEvent.fromByteArray(message);
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "INC: " + this.evtToString(evt));
this.handleIncomingEvent(evt);
}
catch (err) {
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "Invalid message received: " + message + " \n Error: " + err);
}
};
SignalingPeer.prototype.sendToClient = function (evt) {
//this method is also called during cleanup after a disconnect
//check first if we are still connected
//bugfix: apprently 2 sockets can be closed at exactly the same time without
//onclosed being called immediately -> socket has to be checked if open
if (this.mState == SignalingConnectionState.Connected
&& this.mSocket.readyState == this.mSocket.OPEN) {
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "OUT: " + this.evtToString(evt));
var msg = inet.NetworkEvent.toByteArray(evt);
this.mSocket.send(msg);
}
};
SignalingPeer.prototype.onClose = function (code, error) {
this.mState = SignalingConnectionState.Disconnecting;
this.Cleanup();
};
//so far only used if the socket was disconnected
SignalingPeer.prototype.Cleanup = function () {
if (this.mPingInterval != null) {
clearInterval(this.mPingInterval);
}
this.mConnectionPool.removeConnection(this);
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "disconnected "
+ " " + this.mConnectionPool.count()
+ " connections left.");
//disconnect all connections
var test = this.mConnections; //workaround for not having a proper dictionary yet...
for (var v in this.mConnections) {
if (this.mConnections.hasOwnProperty(v))
this.disconnect(new inet.ConnectionId(+v));
}
//make sure the server address is freed
if (this.mServerAddress != null) {
this.stopServer();
}
this.mState = SignalingConnectionState.Disconnected;
};
SignalingPeer.prototype.handleIncomingEvent = function (evt) {
//update internal state based on the event
if (evt.Type == inet.NetEventType.NewConnection) {
//client wants to connect to another client
var address = evt.Info;
//the id this connection should be addressed with
var newConnectionId = evt.ConnectionId;
this.connect(address, newConnectionId);
}
else if (evt.Type == inet.NetEventType.ConnectionFailed) {
}
else if (evt.Type == inet.NetEventType.Disconnected) {
//peer tries to disconnect from another peer
var otherPeerId = evt.ConnectionId;
this.disconnect(otherPeerId);
}
else if (evt.Type == inet.NetEventType.ServerInitialized) {
this.startServer(evt.Info);
}
else if (evt.Type == inet.NetEventType.ServerInitFailed) {
}
else if (evt.Type == inet.NetEventType.ServerClosed) {
//stop server request
this.stopServer();
}
else if (evt.Type == inet.NetEventType.ReliableMessageReceived) {
this.sendData(evt.ConnectionId, evt.MessageData, true);
}
else if (evt.Type == inet.NetEventType.UnreliableMessageReceived) {
this.sendData(evt.ConnectionId, evt.MessageData, false);
}
};
SignalingPeer.prototype.internalAddIncomingPeer = function (peer) {
//another peer connected to this (while allowing incoming connections)
//store the reference
var id = this.nextConnectionId();
this.mConnections[id.id] = peer;
//event to this (the other peer gets the event via addOutgoing
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.NewConnection, id, null));
};
SignalingPeer.prototype.internalAddOutgoingPeer = function (peer, id) {
//this peer successfully connected to another peer. id was generated on the
//client side
this.mConnections[id.id] = peer;
//event to this (the other peer gets the event via addOutgoing
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.NewConnection, id, null));
};
SignalingPeer.prototype.internalRemovePeer = function (id) {
delete this.mConnections[id.id];
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.Disconnected, id, null));
};
//test this. might cause problems
//the number is converted to string trough java script but we need get back the number
//for creating the connection id
SignalingPeer.prototype.findPeerConnectionId = function (otherPeer) {
for (var peer in this.mConnections) {
if (this.mConnections[peer] === otherPeer) {
return new inet.ConnectionId(+peer);
}
}
};
SignalingPeer.prototype.nextConnectionId = function () {
var result = this.mNextIncomingConnectionId;
this.mNextIncomingConnectionId = new inet.ConnectionId(this.mNextIncomingConnectionId.id + 1);
return result;
};
//public methods (not really needed but can be used for testing or server side deubgging)
//this peer initializes a connection to a certain address. The connection id is set by the client
//to allow tracking of the connection attempt
SignalingPeer.prototype.connect = function (address, newConnectionId) {
var serverConnections = this.mConnectionPool.getServerConnection(address);
//
if (serverConnections != null && serverConnections.length == 1) {
//inform the server connection about the new peer
//events will be send by these methods
//shared addresses -> connect to everyone listening
serverConnections[0].internalAddIncomingPeer(this);
this.internalAddOutgoingPeer(serverConnections[0], newConnectionId);
}
else {
//if address is not in use or it is in multi join mode -> connection fails
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ConnectionFailed, newConnectionId, null));
}
};
//join connection happens if another user joins a multi address. it will connect to every address
//listening to that room
SignalingPeer.prototype.connectJoin = function (address) {
var serverConnections = this.mConnectionPool.getServerConnection(address);
//in join mode every connection is incoming as everyone listens together
if (serverConnections != null) {
for (var _i = 0, serverConnections_1 = serverConnections; _i < serverConnections_1.length; _i++) {
var v = serverConnections_1[_i];
if (v != this) {
v.internalAddIncomingPeer(this);
this.internalAddIncomingPeer(v);
}
}
}
};
SignalingPeer.prototype.disconnect = function (connectionId) {
var otherPeer = this.mConnections[connectionId.id];
if (otherPeer != null) {
var idOfOther = otherPeer.findPeerConnectionId(this);
//find the connection id the other peer uses to talk to this one
this.internalRemovePeer(connectionId);
otherPeer.internalRemovePeer(idOfOther);
}
else {
}
};
SignalingPeer.prototype.startServer = function (address) {
//what to do if it is already a server?
if (this.mServerAddress != null)
this.stopServer();
if (this.mConnectionPool.isAddressAvailable(address)) {
this.mServerAddress = address;
this.mConnectionPool.addServer(this, address);
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerInitialized, inet.ConnectionId.INVALID, address));
if (this.mConnectionPool.hasAddressSharing()) {
//address sharing is active. connect to every endpoint already listening on this address
this.connectJoin(address);
}
}
else {
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerInitFailed, inet.ConnectionId.INVALID, address));
}
};
SignalingPeer.prototype.stopServer = function () {
if (this.mServerAddress != null) {
this.mConnectionPool.removeServer(this, this.mServerAddress);
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerClosed, inet.ConnectionId.INVALID, null));
this.mServerAddress = null;
}
//do nothing if it wasnt a server
};
//delivers the message to the local peer
SignalingPeer.prototype.forwardMessage = function (senderPeer, msg, reliable) {
var id = this.findPeerConnectionId(senderPeer);
if (reliable)
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ReliableMessageReceived, id, msg));
else
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.UnreliableMessageReceived, id, msg));
};
SignalingPeer.prototype.sendData = function (id, msg, reliable) {
var peer = this.mConnections[id.id];
if (peer != null)
peer.forwardMessage(this, msg, reliable);
};
return SignalingPeer;
}());
//# sourceMappingURL=WebsocketNetworkServer.js.map