"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