socket-actions
Version:
Websocket implementation to simplify communication and queueing of user actions.
227 lines (222 loc) • 8.82 kB
JavaScript
'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;