@towercg2/server
Version:
The server runtime for the TowerCG2 video graphics system.
132 lines (131 loc) • 5.85 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = __importDefault(require("lodash"));
const express_1 = __importDefault(require("express"));
const socket_io_1 = __importDefault(require("socket.io"));
const sleep_promise_1 = __importDefault(require("sleep-promise"));
const v4_1 = __importDefault(require("uuid/v4"));
const client_1 = require("@towercg2/client");
const auth_1 = require("../auth");
const utils_1 = require("../utils");
const AUTHENTICATED_CLIENTS = "AUTHENTICATED_CLIENTS";
class Server {
constructor(baseConfig) {
this._started = false;
this._stopping = false;
this.plugins = [];
this.config = baseConfig;
this.logger = this.baseLogger.child({ component: this.constructor.name });
this.logger.info({ port: this.config.port, dataDirectory: this.config.dataDirectory }, "Instantiating server.");
}
get started() { return this._started; }
get baseLogger() { return this.config.logger; }
get dataDirectory() { return this.config.dataDirectory; }
get state() {
return lodash_1.default.fromPairs(this.plugins.map((plugin) => [plugin.name, plugin.state]));
}
get authenticatedSockets() {
return Object.values(this.io.in(AUTHENTICATED_CLIENTS).sockets);
}
async start(returnImmediately = false) {
if (this._started) {
throw new Error("Server cannot be started a second time. Create a new one.");
}
this.logger.info("Starting server...");
this._started = true;
for (const pluginRecord of this.config.plugins) {
const pluginClassName = pluginRecord.type.name;
this.logger.debug({ pluginClassName }, "Instantiating plugin.");
const plugin = new pluginRecord.type(pluginRecord.config, this);
this.plugins.push(plugin);
}
this.http = express_1.default();
const localIdentifiers = {};
const pluginAuthLookup = auth_1.LookupAuth(localIdentifiers);
const localUri = `http://127.0.0.1:${this.config.port}`;
for (const plugin of this.plugins) {
this.logger.debug({ pluginName: plugin.name, pluginClassName: plugin.constructor.name }, "Initializing plugin.");
const pluginApp = express_1.default();
const clientId = `LOCAL-${plugin.name}`;
const clientSecret = v4_1.default();
localIdentifiers[clientId] = clientSecret;
plugin.doSetClientConnection(localUri, clientId, clientSecret);
await plugin.doInitialize(pluginApp);
this.http.use(`/${plugin.name}`, pluginApp);
}
this.http.get("/state", (_req, res) => res.json(this.state));
const port = this.config.port;
this.logger.info({ port }, `Starting HTTP server on port ${port}.`);
this.httpServer = this.http.listen(port);
this.io = this.initializeSocketIO(localIdentifiers);
this.logger.info("Server started.");
if (!returnImmediately) {
while (!this._stopping) {
await sleep_promise_1.default(1000);
}
}
}
async stop() {
if (!this._started) {
throw new Error("Server cannot be stopped before starting.");
}
this.logger.info("Stopping server...");
this._stopping = true;
if (this.httpServer) {
this.logger.debug("Stopping HTTP.");
this.httpServer.close();
}
for (const plugin of this.plugins) {
this.logger.debug({ pluginName: plugin.name }, "Cleaning up plugin.");
await plugin.cleanup();
}
this.logger.info("Server stopped.");
}
messageToAuthenticated(messageName, messageBody) {
if (!this.io) {
throw new Error("Attempted to emit before socket.io enabled.");
}
this.io.to(AUTHENTICATED_CLIENTS).emit(client_1.ProtocolNames.MESSAGE, { messageName, messageBody });
}
notifyClientsOfStateChange(pluginName) {
}
initializeSocketIO(localIdentifiers) {
const io = socket_io_1.default();
const pluginAuthLookup = auth_1.LookupAuth(localIdentifiers);
io.on("connect", (socket) => {
const logger = this.logger.child({ socket: socket.id });
logger.info("Connection request received.");
socket.on(client_1.ProtocolNames.CLIENT_AUTHENTICATE, async (msg) => {
const auth = (await this.config.authnFn(msg.payload.clientId, msg.payload.clientSecret)) ||
await pluginAuthLookup(msg.payload.clientId, msg.payload.clientSecret);
if (auth) {
logger.info("Socket has authenticated.");
const ev = {
type: client_1.ProtocolNames.NEW_CLIENT_CONNECTED,
payload: utils_1.socketToClientInfo(socket)
};
this.messageToAuthenticated(ev.type, ev);
socket.join(AUTHENTICATED_CLIENTS);
this.attachSocketIOHandlers(socket);
socket.emit(client_1.ProtocolNames.YOU_ARE_CONNECTED);
}
else {
logger.info("Socket failed to authenticate; closing.");
socket.disconnect();
}
});
socket.emit(client_1.ProtocolNames.PLEASE_AUTHENTICATE);
});
io.attach(this.httpServer);
return io;
}
attachSocketIOHandlers(socket) {
for (const plugin of this.plugins) {
plugin.doBindSocketIOHandlers(socket);
}
}
}
exports.Server = Server;