UNPKG

socket-actions

Version:

Websocket implementation to simplify communication and queueing of user actions.

227 lines (222 loc) 8.82 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var ws = require('ws'); var express = require('express'); var bodyParser = require('body-parser'); var cors = require('cors'); var uuid = require('uuid'); var listenerFactory = require('../helpers/listenerFactory.cjs'); var file = require('ts-cornucopia/file'); const defaultOptions = { actionsPath: "./actions", disableAuthentication: false, }; const authenticationNotImplemented = async () => { throw new Error("Authentication not implemented. Maybe you forgot to disable it."); }; const onAuthSuccessDefault = async (socket) => { socket.send("Authenticated"); }; const onAuthFailureDefault = async (socket) => { socket.send("Failed Authentication"); }; const emptyPromiseFunction = async () => { }; class Socket { constructor(options) { this._activeClients = []; const { actions, disableAuthentication, onConnection, onAuth, onAuthSuccess, onAuthFailure, onClose, onError, onMessage, } = Object.assign(Object.assign({}, defaultOptions), options); let { serverOptions } = options; if (serverOptions === undefined) { serverOptions = {}; } this.serverOptions = serverOptions; this.Actions = actions !== null && actions !== void 0 ? actions : {}; this.initActions(options); this.onConnection = onConnection !== null && onConnection !== void 0 ? onConnection : emptyPromiseFunction; this.onAuth = onAuth !== null && onAuth !== void 0 ? onAuth : authenticationNotImplemented; this.onAuthSuccess = onAuthSuccess !== null && onAuthSuccess !== void 0 ? onAuthSuccess : onAuthSuccessDefault; this.onAuthFailure = onAuthFailure !== null && onAuthFailure !== void 0 ? onAuthFailure : onAuthFailureDefault; this.onClose = onClose !== null && onClose !== void 0 ? onClose : emptyPromiseFunction; this.onError = onError !== null && onError !== void 0 ? onError : emptyPromiseFunction; this.onMessage = onMessage !== null && onMessage !== void 0 ? onMessage : emptyPromiseFunction; this.disableAuthentication = disableAuthentication; if (disableAuthentication && onAuth !== undefined) { console.warn("onAuth event both added and supressed by disableAuthentication option."); } } async initActions({ actions, actionsPath }) { if (actions !== undefined && actionsPath !== undefined && actionsPath !== "./actions") { console.warn("Actions and ActionsPath supplied in the configuration, actionsPath ignored."); } if (actions === undefined && actionsPath !== undefined) { const actionFiles = file.executeOnFiles(actionsPath, (file) => file, { recursive: true, }); const parsedActionsPath = actionsPath.replace("./", ""); for (let file of actionFiles) { if (file.startsWith("../")) { file = "/" + file; } else { file = file.replace("./", "/"); } const fullFilePath = process.cwd() + file; const { default: Action } = await import(fullFilePath); let fileName = file .replace(parsedActionsPath, "") .replace(".ts", "") .replace(".js", "") .replace("//", ""); if (fileName.startsWith("/")) { fileName = fileName.substring(1); } this.Actions[fileName] = new Action(); } } } async start() { const { serverOptions } = this; const { server, host = "http://localhost", port = 3000, } = serverOptions; delete serverOptions.host; delete serverOptions.port; if (server === undefined) { const app = express(); app.use(bodyParser.json()); app.use(cors({ origin: host, optionsSuccessStatus: 200, })); serverOptions.server = app.listen(port); } this._server = serverOptions.server; this.wsInstance = new ws.WebSocketServer(serverOptions); this.prepareAllActions().then(() => { var _a; (_a = this.wsInstance) === null || _a === void 0 ? void 0 : _a.on("connection", listenerFactory.default(this, null, this.connecting)); }).catch((err) => { throw new Error(err); }); } async restart() { this.closeSocket(); await this.start(); } async prepareAllActions() { const promises = []; for (const key in this.Actions) { const action = this.Actions[key]; promises.push(action.prepareAction(this)); } await Promise.all(promises); } async connecting(socket, req) { try { await this.onConnection(socket, req); socket.on("error", listenerFactory.default(this, socket, this.reportingError)); if (this.disableAuthentication) { socket.on("message", listenerFactory.default(this, socket, this.receivingMessage)); socket.userData = { id: uuid.v4(), }; this._activeClients.push(socket); } else { socket.on("message", listenerFactory.default(this, socket, this.authenticating)); } socket.on("close", listenerFactory.default(this, socket, this.closing)); } catch (err) { console.error(err); socket.close(); } } async authenticating(socket, message) { try { await this.onAuth(socket, message); if (socket.userData === undefined) { socket.userData = {}; } if (socket.userData.id === undefined) { socket.userData.id = uuid.v4(); } this._activeClients.push(socket); } catch (err) { await this.onAuthFailure(socket, err, message); return; } socket.removeAllListeners("message"); socket.on("message", listenerFactory.default(this, socket, this.receivingMessage)); await this.onAuthSuccess(socket, message); } async receivingMessage(socket, message) { var _a; try { const messageObject = JSON.parse(message); await this.onMessage(socket, messageObject); const { path, data, requestId } = messageObject; const parameters = { socket, userData: socket.userData, requestId, data, }; await ((_a = this.Actions[path]) === null || _a === void 0 ? void 0 : _a.run(parameters)); } catch (err) { await this.reportingError(socket, err); } } async reportingError(socket, err) { await this.onError(socket, err); } async closing(socket) { await this.onClose(socket); const socketIndex = this._activeClients.findIndex((item) => { var _a, _b; return ((_a = item.userData) === null || _a === void 0 ? void 0 : _a.id) === ((_b = socket.userData) === null || _b === void 0 ? void 0 : _b.id); }); if (socketIndex !== -1) { this._activeClients.splice(socketIndex, 1); } } closeSocket(cb) { var _a; (_a = this.wsInstance) === null || _a === void 0 ? void 0 : _a.close(cb); } close(socketCallback, expressCallback) { var _a; this.closeSocket(socketCallback); (_a = this._server) === null || _a === void 0 ? void 0 : _a.close(expressCallback); } get activeClients() { return this._activeClients.map((item) => item.userData); } get server() { return this._server; } sendMessage(socket, data) { if (typeof data !== "string") { data = JSON.stringify(data); } socket.send(data); } sendMessageById(id, data) { const socket = this._activeClients.find((item) => item.userData.id === id); if (socket !== undefined) { this.sendMessage(socket, data); } } sendMessageToAll(data, { exceptions = [] }) { if (typeof exceptions[0] === "object") { exceptions = exceptions.map((item) => { return item.userData.id; }); } for (const client of this._activeClients) { if (exceptions.includes(client.userData.id)) { continue; } this.sendMessage(client, data); } } } exports.default = Socket;