@appsensorlike/appsensorlike_websocket
Version:
Base class/types utilized by websocket client/server
201 lines (200 loc) • 9.37 kB
JavaScript
import { Logger } from "@appsensorlike/appsensorlike/logging/logging.js";
import { AccessDeniedError, ActionRequest, ActionResponse, UnAuthorizedActionError, UUID_QUERY_PARAM } from "../appsensor-websocket.js";
import { HttpS2Server, HttpS2ServerConfig } from "@appsensorlike/appsensorlike/http/HttpS2Server.js";
import { URL } from 'url';
import http from 'http';
import https from 'https';
import WebSocket, { WebSocketServer } from "ws";
import { ServerConfiguration } from "@appsensorlike/appsensorlike/core/configuration/server/server_configuration.js";
class WebSocketServerConfig extends HttpS2ServerConfig {
checkValidInitialize() {
if (!this.listenOptions) {
this.listenOptions = { port: WebSocketServerConfig.DEFAULT_PORT };
}
if (!this.clientApplicationIdentificationHeaderName) {
this.clientApplicationIdentificationHeaderName = ServerConfiguration.DEFAULT_HEADER_NAME;
}
}
;
}
WebSocketServerConfig.DEFAULT_PORT = 3000;
class AppSensorWebSocketServer extends HttpS2Server {
constructor(config, handleProtocols) {
super();
this.websocketServer = null;
this.config = config;
this.websocketServerOptions = {};
if (this.config.websocketServer) {
this.websocketServerOptions = this.config.websocketServer;
}
if (handleProtocols) {
this.websocketServerOptions.handleProtocols = handleProtocols;
}
}
getConfiguration() {
return this.config;
}
async attachToServer() {
if (this.server) {
if (this.server instanceof http.Server ||
this.server instanceof https.Server) {
this.websocketServerOptions.server = this.server;
}
else {
throw new Error(`WebSocketServer cannot run on http server protocol: ${this.config.protocol}`);
}
const self = this;
this.websocketServer = new WebSocketServer(this.websocketServerOptions);
const interval = setInterval(this.ping.bind(this), 30000);
const onMessageThunk = this.onMessageWrapper(this);
const isConnectionAllowedThunk = this.isConnectionAllowedWrapper(this);
this.websocketServer.on('connection', function (ws, req) {
try {
if (typeof req.headers['x-forwarded-for'] === 'string') {
ws.remoteAddress = req.headers['x-forwarded-for'].split(',')[0].trim();
}
}
catch (error) {
Logger.getServerLogger().trace('AppSensorWebSocketServer.websocketServer:', error);
}
if (!ws.remoteAddress) {
ws.remoteAddress = req.socket.remoteAddress;
}
ws.clientApplication = '';
if (self.config.clientApplicationIdentificationHeaderName) {
const headerName = self.config.clientApplicationIdentificationHeaderName.toLowerCase();
const propDescriptor = Object.getOwnPropertyDescriptor(req.headers, headerName);
if (propDescriptor) {
ws.clientApplication = propDescriptor.value;
}
}
const url = new URL(req.url, `ws://${req.headers.host}`);
// console.log(url);
ws.uuid = url.searchParams.get(UUID_QUERY_PARAM);
Logger.getServerLogger().info('AppSensorWebSocketServer.websocketServer:', 'connection:', 'Client application:', ws.clientApplication, '; IP address:', ws.remoteAddress, '; client uuid:', ws.uuid);
if (!isConnectionAllowedThunk(ws)) {
Logger.getServerLogger().warn(`AppSensorWebSocketServer.websocketServer: Closing connection to client application ${ws.clientApplication} with IP address: ${ws.remoteAddress}. Access denied! Configure server!`);
// AppSensorWebSocketServer.reportAccessDenied(ws);
ws.close(AppSensorWebSocketServer.ACCESS_DENIED_CLOSE_CODE, 'Access denied for this client application! Configure server!');
ws.terminate();
}
ws.isAlive = true;
ws.on('error', (error) => {
Logger.getServerLogger().error('AppSensorWebSocketServer.websocketServer:', 'client uuid:', ws.uuid, ': error', error);
});
ws.on('message', onMessageThunk);
ws.on('pong', function () {
Logger.getServerLogger().trace('AppSensorWebSocketServer.websocketServer:', 'client uuid:', ws.uuid, ': pong');
this.isAlive = true;
});
});
this.websocketServer.on('close', function close() {
Logger.getServerLogger().trace('AppSensorWebSocketServer.websocketServer:', 'close');
clearInterval(interval);
});
}
}
isConnectionAllowedWrapper(me) {
return function isConnectionAllowed(ws) {
return me.isConnectionAllowed(ws);
};
}
isConnectionAllowed(ws) {
//overwrite this method to controll the access
return true;
}
isActionAuthorized(ws, request) {
//overwrite this method to check the authorization
return true;
}
ping() {
if (this.websocketServer) {
this.websocketServer.clients.forEach(function each(ws) {
if (ws.isAlive === false)
return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}
}
onMessageWrapper(me) {
return function onClientRequest(data, isBinary) {
const request = JSON.parse(data.toString());
Object.setPrototypeOf(request, ActionRequest.prototype);
Logger.getServerLogger().trace('AppSensorWebSocketServer.websocketServer:', 'client uuid:', this.uuid, `: message: (action:${request.actionName})`);
if (!me.isActionAuthorized(this, request)) {
Logger.getServerLogger().warn(`AppSensorWebSocketServer.onMessageWrapper:` +
` client application with IP address: ${this.remoteAddress}` +
` is trying to perform unauthorized action: ${request.actionName}`);
AppSensorWebSocketServer.reportUnAuthorizedAction(this, request);
}
else {
me.onClientRequest(this, request);
}
};
}
onClientRequest(ws, request) {
//your code goes here
}
async closeServer() {
if (this.websocketServer) {
await new Promise((resolve, reject) => {
this.websocketServer.close((err) => {
Logger.getServerLogger().info('AppSensorWebSocketServer.closeServer');
if (err) {
Logger.getServerLogger().error('AppSensorWebSocketServer.closeServer: error', err);
reject(err);
return;
}
resolve(0);
});
});
await this.stopServer();
}
else {
await this.stopServer();
}
}
broadcast(actionName, result, resultElementClass) {
if (this.websocketServer) {
const response = new ActionResponse('', actionName, result, resultElementClass);
this.websocketServer.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(response), { binary: false });
}
});
}
}
static getParameter(request, paramName) {
let param = undefined;
if (request.parameters) {
const propDescr = Object.getOwnPropertyDescriptor(request.parameters, paramName);
if (propDescr) {
param = propDescr.value;
}
}
return param;
}
static reportMissingParameter(ws, request, paramName) {
const response = new ActionResponse(request.id, request.actionName, null, null, `Missing required parameter ${paramName}!`);
ws.send(JSON.stringify(response));
}
static reportError(ws, request, error) {
const response = new ActionResponse(request.id, request.actionName, null, null, error.toString());
ws.send(JSON.stringify(response));
}
static reportAccessDenied(ws) {
const response = new ActionResponse('', '', null, null, new AccessDeniedError().message, true);
ws.send(JSON.stringify(response));
}
static reportUnAuthorizedAction(ws, request) {
const response = new ActionResponse(request.id, request.actionName, null, null, new UnAuthorizedActionError(request.actionName).message, false, true);
ws.send(JSON.stringify(response));
}
static sendResult(ws, request, result, resultElementClass) {
const response = new ActionResponse(request.id, request.actionName, result, resultElementClass);
ws.send(JSON.stringify(response));
}
}
AppSensorWebSocketServer.ACCESS_DENIED_CLOSE_CODE = 4000;
export { AppSensorWebSocketServer, WebSocketServerConfig };