README.md: update to reflect response payload format change on
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / UIWebSocketServer.ts
CommitLineData
6c1761d4 1import type { IncomingMessage } from 'http';
8114d10e 2
5e3cb728 3import WebSocket, { RawData } from 'ws';
8114d10e 4
5e3cb728 5import BaseError from '../../exception/BaseError';
6c1761d4 6import type { ServerOptions } from '../../types/ConfigurationData';
5e3cb728 7import type { ProtocolRequest, ProtocolResponse } from '../../types/UIProtocol';
8b0088bb 8import { WebSocketCloseEventStatusCode } from '../../types/WebSocket';
8114d10e 9import Configuration from '../../utils/Configuration';
675fa8e3 10import logger from '../../utils/Logger';
8114d10e
JB
11import Utils from '../../utils/Utils';
12import { AbstractUIServer } from './AbstractUIServer';
13import UIServiceFactory from './ui-services/UIServiceFactory';
a92929f1 14import { UIServiceUtils } from './ui-services/UIServiceUtils';
4198ad5c 15
32de5a57
LM
16const moduleName = 'UIWebSocketServer';
17
fe94fce0 18export default class UIWebSocketServer extends AbstractUIServer {
b153c0fd 19 public constructor(options?: ServerOptions) {
fe94fce0 20 super();
0d8140bd 21 this.server = new WebSocket.Server(options ?? Configuration.getUIServer().options);
4198ad5c
JB
22 }
23
24 public start(): void {
5e3cb728
JB
25 this.server.on('connection', (ws: WebSocket, request: IncomingMessage): void => {
26 const [protocol, version] = UIServiceUtils.getProtocolAndVersion(ws.protocol);
a92929f1
JB
27 if (UIServiceUtils.isProtocolAndVersionSupported(protocol, version) === false) {
28 logger.error(
29 `${this.logPrefix(
30 moduleName,
31 'start.server.onconnection'
32 )} Unsupported UI protocol version: '${protocol}${version}'`
33 );
5e3cb728 34 ws.close(WebSocketCloseEventStatusCode.CLOSE_PROTOCOL_ERROR);
a92929f1 35 }
de9136ae 36 if (!this.uiServices.has(version)) {
178ac666 37 this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
4198ad5c 38 }
5e3cb728
JB
39 ws.on('message', (rawData) => {
40 const [messageId, procedureName, payload] = this.validateRawDataRequest(rawData);
e7aeea18
JB
41 this.uiServices
42 .get(version)
5e3cb728 43 .requestHandler(this.buildProtocolRequest(messageId, procedureName, payload))
6c8f5d90
JB
44 .catch(() => {
45 /* Error caught by AbstractUIService */
e7aeea18 46 });
4198ad5c 47 });
5e3cb728
JB
48 ws.on('error', (error) => {
49 logger.error(`${this.logPrefix(moduleName, 'start.ws.onerror')} WebSocket error:`, error);
50 });
51 ws.on('close', (code, reason) => {
52 logger.debug(
53 `${this.logPrefix(
54 moduleName,
55 'start.ws.onclose'
56 )} WebSocket closed: '${Utils.getWebSocketCloseEventStatusString(
57 code
58 )}' - '${reason.toString()}'`
32de5a57 59 );
4198ad5c
JB
60 });
61 });
62 }
63
64 public stop(): void {
5a010bf0 65 this.chargingStations.clear();
4198ad5c
JB
66 }
67
5e3cb728
JB
68 public sendRequest(request: ProtocolRequest): void {
69 this.broadcastToClients(JSON.stringify(request));
02a6943a
JB
70 }
71
5e3cb728 72 public sendResponse(response: ProtocolResponse): void {
db2336d9 73 // TODO: send response only to the client that sent the request
5e3cb728 74 this.broadcastToClients(JSON.stringify(response));
178ac666
JB
75 }
76
0d2cec76
JB
77 public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
78 const logMsgPrefix = prefixSuffix
79 ? `UI WebSocket Server ${prefixSuffix}`
80 : 'UI WebSocket Server';
32de5a57 81 const logMsg =
0d2cec76 82 modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`;
32de5a57 83 return Utils.logPrefix(logMsg);
4198ad5c 84 }
178ac666
JB
85
86 private broadcastToClients(message: string): void {
0d8140bd
JB
87 for (const client of (this.server as WebSocket.Server).clients) {
88 if (client?.readyState === WebSocket.OPEN) {
178ac666
JB
89 client.send(message);
90 }
91 }
92 }
5e3cb728
JB
93
94 private validateRawDataRequest(rawData: RawData): ProtocolRequest {
95 // logger.debug(
96 // `${this.logPrefix(
97 // moduleName,
98 // 'validateRawDataRequest'
99 // )} Raw data received in string format: ${rawData.toString()}`
100 // );
101
102 const request = JSON.parse(rawData.toString()) as ProtocolRequest;
103
104 if (Array.isArray(request) === false) {
105 throw new BaseError('UI protocol request is not an array');
106 }
107
108 if (request.length !== 3) {
109 throw new BaseError('UI protocol request is malformed');
110 }
111
112 return request;
113 }
4198ad5c 114}