UI Protocol: Expose ATG status and use array for all list
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / UIWebSocketServer.ts
1 import type { IncomingMessage } from 'http';
2
3 import WebSocket, { RawData } from 'ws';
4
5 import BaseError from '../../exception/BaseError';
6 import type { ServerOptions } from '../../types/ConfigurationData';
7 import type { ProtocolRequest, ProtocolResponse } from '../../types/UIProtocol';
8 import { WebSocketCloseEventStatusCode } from '../../types/WebSocket';
9 import Configuration from '../../utils/Configuration';
10 import logger from '../../utils/Logger';
11 import Utils from '../../utils/Utils';
12 import { AbstractUIServer } from './AbstractUIServer';
13 import UIServiceFactory from './ui-services/UIServiceFactory';
14 import { UIServiceUtils } from './ui-services/UIServiceUtils';
15
16 const moduleName = 'UIWebSocketServer';
17
18 export default class UIWebSocketServer extends AbstractUIServer {
19 public constructor(options?: ServerOptions) {
20 super();
21 this.server = new WebSocket.Server(options ?? Configuration.getUIServer().options);
22 }
23
24 public start(): void {
25 this.server.on('connection', (ws: WebSocket, request: IncomingMessage): void => {
26 const [protocol, version] = UIServiceUtils.getProtocolAndVersion(ws.protocol);
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 );
34 ws.close(WebSocketCloseEventStatusCode.CLOSE_PROTOCOL_ERROR);
35 }
36 if (!this.uiServices.has(version)) {
37 this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
38 }
39 ws.on('message', (rawData) => {
40 const [messageId, procedureName, payload] = this.validateRawDataRequest(rawData);
41 this.uiServices
42 .get(version)
43 .requestHandler(this.buildProtocolRequest(messageId, procedureName, payload))
44 .catch(() => {
45 /* Error caught by AbstractUIService */
46 });
47 });
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()}'`
59 );
60 });
61 });
62 }
63
64 public stop(): void {
65 this.chargingStations.clear();
66 }
67
68 public sendRequest(request: ProtocolRequest): void {
69 this.broadcastToClients(JSON.stringify(request));
70 }
71
72 public sendResponse(response: ProtocolResponse): void {
73 // TODO: send response only to the client that sent the request
74 this.broadcastToClients(JSON.stringify(response));
75 }
76
77 public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
78 const logMsgPrefix = prefixSuffix
79 ? `UI WebSocket Server ${prefixSuffix}`
80 : 'UI WebSocket Server';
81 const logMsg =
82 modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`;
83 return Utils.logPrefix(logMsg);
84 }
85
86 private broadcastToClients(message: string): void {
87 for (const client of (this.server as WebSocket.Server).clients) {
88 if (client?.readyState === WebSocket.OPEN) {
89 client.send(message);
90 }
91 }
92 }
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 }
114 }