UNPKG

@appsensorlike/appsensorlike_websocket

Version:

Base class/types utilized by websocket client/server

201 lines (200 loc) 9.37 kB
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 };