Working WebSocket connections - not yet complete, but working well
This commit is contained in:
parent
3a41a6b2e1
commit
2e18833014
|
@ -43,7 +43,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
client.session.serverName = modInfo.name;
|
client.session.serverName = modInfo.name;
|
||||||
client.session.isSecure = modInfo.isSecure || false;
|
client.session.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false);
|
||||||
|
|
||||||
clientConns.addNewClient(client, clientSock);
|
clientConns.addNewClient(client, clientSock);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ const ModuleInfo = exports.moduleInfo = {
|
||||||
packageName : 'codes.l33t.enigma.telnet.server',
|
packageName : 'codes.l33t.enigma.telnet.server',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.TelnetClient = TelnetClient;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Telnet Protocol Resources
|
// Telnet Protocol Resources
|
||||||
// * http://pcmicro.com/netfoss/telnet.html
|
// * http://pcmicro.com/netfoss/telnet.html
|
||||||
|
@ -498,54 +500,6 @@ function TelnetClient(input, output) {
|
||||||
|
|
||||||
this.input.on('data', this.dataHandler);
|
this.input.on('data', this.dataHandler);
|
||||||
|
|
||||||
/*
|
|
||||||
this.input.on('data', b => {
|
|
||||||
bufs.push(b);
|
|
||||||
|
|
||||||
let i;
|
|
||||||
while((i = bufs.indexOf(IAC_BUF)) >= 0) {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Some clients will send even IAC separate from data
|
|
||||||
//
|
|
||||||
if(bufs.length <= (i + 1)) {
|
|
||||||
i = MORE_DATA_REQUIRED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(bufs.length > (i + 1));
|
|
||||||
|
|
||||||
if(i > 0) {
|
|
||||||
self.emit('data', bufs.splice(0, i).toBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
i = parseBufs(bufs);
|
|
||||||
|
|
||||||
if(MORE_DATA_REQUIRED === i) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if(i.option) {
|
|
||||||
self.emit(i.option, i); // "transmit binary", "echo", ...
|
|
||||||
}
|
|
||||||
|
|
||||||
self.handleTelnetEvent(i);
|
|
||||||
|
|
||||||
if(i.data) {
|
|
||||||
self.emit('data', i.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(MORE_DATA_REQUIRED !== i && bufs.length > 0) {
|
|
||||||
//
|
|
||||||
// Standard data payload. This can still be "non-user" data
|
|
||||||
// such as ANSI control, but we don't handle that here.
|
|
||||||
//
|
|
||||||
self.emit('data', bufs.splice(0).toBuffer());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.input.on('end', () => {
|
this.input.on('end', () => {
|
||||||
self.emit('end');
|
self.emit('end');
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ENiGMA½
|
||||||
|
const Config = require('../../config.js').config;
|
||||||
|
const TelnetClient = require('./telnet.js').TelnetClient;
|
||||||
|
const Log = require('../../logger.js').log;
|
||||||
|
const LoginServerModule = require('../../login_server_module.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const _ = require('lodash');
|
||||||
|
const WebSocketServer = require('ws').Server;
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
|
const ModuleInfo = exports.moduleInfo = {
|
||||||
|
name : 'WebSocket',
|
||||||
|
desc : 'WebSocket Server',
|
||||||
|
author : 'NuSkooler',
|
||||||
|
packageName : 'codes.l33t.enigma.websocket.server',
|
||||||
|
};
|
||||||
|
|
||||||
|
function WebSocketClient(ws, req, serverType) {
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'isSecure', {
|
||||||
|
get : () => 'secure' === serverType ? true : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socketBridge = new class SocketBridge extends EventEmitter {
|
||||||
|
constructor(ws) {
|
||||||
|
super();
|
||||||
|
this.ws = ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
return ws.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
write(data, cb) {
|
||||||
|
return this.ws.send(data, { binary : true }, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
get remoteAddress() {
|
||||||
|
return req.connection.remoteAddress;
|
||||||
|
}
|
||||||
|
}(ws);
|
||||||
|
|
||||||
|
ws.on('message', data => {
|
||||||
|
this.socketBridge.emit('data', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
this.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Montior connection status with ping/pong
|
||||||
|
//
|
||||||
|
ws.on('pong', () => {
|
||||||
|
Log.trace(`Pong from ${this.socketBridge.remoteAddress}`);
|
||||||
|
ws.isConnectionAlive = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
TelnetClient.call(this, this.socketBridge, this.socketBridge);
|
||||||
|
|
||||||
|
// start handshake process
|
||||||
|
this.banner();
|
||||||
|
}
|
||||||
|
|
||||||
|
require('util').inherits(WebSocketClient, TelnetClient);
|
||||||
|
|
||||||
|
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
|
||||||
|
|
||||||
|
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
createServer() {
|
||||||
|
//
|
||||||
|
// We will actually create up to two servers:
|
||||||
|
// * insecure websocket (ws://)
|
||||||
|
// * secure (tls) websocket (wss://)
|
||||||
|
//
|
||||||
|
const insecureConf = _.get(Config, 'loginServers.webSocket') || { enabled : false };
|
||||||
|
const secureConf = _.get(Config, 'loginServers.secureWebSocket') || { enabled : false };
|
||||||
|
|
||||||
|
if(insecureConf.enabled) {
|
||||||
|
const httpServer = http.createServer( (req, resp) => {
|
||||||
|
// dummy handler
|
||||||
|
resp.writeHead(200);
|
||||||
|
return resp.end('ENiGMA½ BBS WebSocket Server!');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.insecure = {
|
||||||
|
httpServer : httpServer,
|
||||||
|
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(secureConf.enabled) {
|
||||||
|
const httpServer = https.createServer({
|
||||||
|
key : fs.readFileSync(Config.loginServers.secureWebSocket.keyPem),
|
||||||
|
cert : fs.readFileSync(Config.loginServers.secureWebSocket.certPem),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.secure = {
|
||||||
|
httpServer : httpServer,
|
||||||
|
wsServer : new WebSocketServer( { server : httpServer } ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configName(serverType) {
|
||||||
|
return 'secure' === serverType ? 'secureWebSocket' : 'webSocket';
|
||||||
|
}
|
||||||
|
|
||||||
|
listen() {
|
||||||
|
WSS_SERVER_TYPES.forEach(serverType => {
|
||||||
|
const server = this[serverType];
|
||||||
|
if(!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverName = `${ModuleInfo.name} (${serverType})`;
|
||||||
|
const port = parseInt( _.get( Config, [ 'loginServers', this.configName(serverType), 'port' ] ) );
|
||||||
|
|
||||||
|
if(isNaN(port)) {
|
||||||
|
Log.error( { server : serverName, port : port }, 'Cannot load server (invalid port)' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.httpServer.listen(port);
|
||||||
|
|
||||||
|
server.wsServer.on('connection', (ws, req) => {
|
||||||
|
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||||
|
this.handleNewClient(webSocketClient, webSocketClient.socketBridge, ModuleInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Send pings every 30s
|
||||||
|
//
|
||||||
|
setInterval( () => {
|
||||||
|
WSS_SERVER_TYPES.forEach(serverType => {
|
||||||
|
if(this[serverType]) {
|
||||||
|
this[serverType].wsServer.clients.forEach(ws => {
|
||||||
|
if(false === ws.isConnectionAlive) {
|
||||||
|
Log.debug('WebSocket connection seems inactive. Terminating.');
|
||||||
|
return ws.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.isConnectionAlive = false; // pong will reset this
|
||||||
|
|
||||||
|
Log.trace('Ping to remote WebSocket client');
|
||||||
|
return ws.ping('', false, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocketConnection(conn) {
|
||||||
|
const webSocketClient = new WebSocketClient(conn);
|
||||||
|
this.handleNewClient(webSocketClient, webSocketClient.socketShim, ModuleInfo);
|
||||||
|
}
|
||||||
|
};
|
|
@ -43,7 +43,7 @@
|
||||||
"temptmp": "^1.0.0",
|
"temptmp": "^1.0.0",
|
||||||
"uuid": "^3.0.1",
|
"uuid": "^3.0.1",
|
||||||
"uuid-parse": "^1.0.0",
|
"uuid-parse": "^1.0.0",
|
||||||
"ws" : "^2.3.1",
|
"ws" : "^3.0.0",
|
||||||
"graceful-fs" : "^4.1.11",
|
"graceful-fs" : "^4.1.11",
|
||||||
"exiftool" : "^0.0.3"
|
"exiftool" : "^0.0.3"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue