Web UI: add and fix missing commands support
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / ui-services / AbstractUIService.ts
CommitLineData
8114d10e
JB
1import { RawData } from 'ws';
2
3import BaseError from '../../../exception/BaseError';
5a010bf0 4import { Bootstrap } from '../../../internal';
8114d10e 5import { JsonType } from '../../../types/JsonType';
675fa8e3 6import {
32de5a57 7 ProcedureName,
675fa8e3
JB
8 ProtocolRequest,
9 ProtocolRequestHandler,
32de5a57 10 ProtocolResponse,
33cea517 11 ProtocolVersion,
89b7a234 12 RequestPayload,
32de5a57
LM
13 ResponsePayload,
14 ResponseStatus,
675fa8e3 15} from '../../../types/UIProtocol';
675fa8e3 16import logger from '../../../utils/Logger';
6c8f5d90 17import UIServiceWorkerBroadcastChannel from '../../UIServiceWorkerBroadcastChannel';
db2336d9 18import type { AbstractUIServer } from '../AbstractUIServer';
4198ad5c 19
32de5a57
LM
20const moduleName = 'AbstractUIService';
21
4198ad5c 22export default abstract class AbstractUIService {
33cea517 23 protected readonly version: ProtocolVersion;
fe94fce0 24 protected readonly uiServer: AbstractUIServer;
02a6943a 25 protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
6812b4e1 26 protected uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel;
4198ad5c 27
33cea517
JB
28 constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
29 this.version = version;
675fa8e3 30 this.uiServer = uiServer;
02a6943a 31 this.requestHandlers = new Map<ProcedureName, ProtocolRequestHandler>([
32de5a57
LM
32 [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
33 [ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)],
34 [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)],
4198ad5c 35 ]);
6812b4e1 36 this.uiServiceWorkerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this);
4198ad5c
JB
37 }
38
02a6943a 39 public async requestHandler(request: RawData): Promise<void> {
98a5256a 40 let messageId: string;
32de5a57 41 let command: ProcedureName;
6c8f5d90 42 let requestPayload: RequestPayload | undefined;
32de5a57
LM
43 let responsePayload: ResponsePayload;
44 try {
97f0a1a5 45 [messageId, command, requestPayload] = this.requestValidation(request);
32de5a57 46
02a6943a 47 if (this.requestHandlers.has(command) === false) {
32de5a57
LM
48 throw new BaseError(
49 `${command} is not implemented to handle message payload ${JSON.stringify(
50 requestPayload,
51 null,
52 2
53 )}`
54 );
4198ad5c 55 }
89b7a234 56
6c8f5d90 57 // Call the request handler to build the response payload
02a6943a 58 responsePayload = await this.requestHandlers.get(command)(messageId, requestPayload);
32de5a57
LM
59 } catch (error) {
60 // Log
61 logger.error(
6c8f5d90 62 `${this.uiServer.logPrefix(moduleName, 'messageHandler')} Handle request error:`,
32de5a57 63 error
e7aeea18 64 );
6c8f5d90
JB
65 responsePayload = {
66 status: ResponseStatus.FAILURE,
67 command,
68 requestPayload,
69 responsePayload,
70 errorMessage: (error as Error).message,
71 errorStack: (error as Error).stack,
72 };
73 }
74
75 if (responsePayload !== undefined) {
76 // Send the response
77 this.uiServer.sendResponse(this.buildProtocolResponse(messageId ?? 'error', responsePayload));
4198ad5c 78 }
6c8f5d90
JB
79 }
80
81 public sendRequest(
82 messageId: string,
83 procedureName: ProcedureName,
84 requestPayload: RequestPayload
85 ): void {
86 this.uiServer.sendRequest(this.buildProtocolRequest(messageId, procedureName, requestPayload));
87 }
32de5a57 88
6c8f5d90 89 public sendResponse(messageId: string, responsePayload: ResponsePayload): void {
32de5a57 90 this.uiServer.sendResponse(this.buildProtocolResponse(messageId, responsePayload));
4198ad5c
JB
91 }
92
6c8f5d90 93 public logPrefix(modName: string, methodName: string): string {
f4531dfe 94 return this.uiServer.logPrefix(modName, methodName);
6c8f5d90
JB
95 }
96
97 private buildProtocolRequest(
02a6943a
JB
98 messageId: string,
99 procedureName: ProcedureName,
6c8f5d90 100 requestPayload: RequestPayload
02a6943a 101 ): string {
6c8f5d90 102 return JSON.stringify([messageId, procedureName, requestPayload] as ProtocolRequest);
02a6943a
JB
103 }
104
6c8f5d90
JB
105 private buildProtocolResponse(messageId: string, responsePayload: ResponsePayload): string {
106 return JSON.stringify([messageId, responsePayload] as ProtocolResponse);
32de5a57
LM
107 }
108
109 // Validate the raw data received from the WebSocket
110 // TODO: should probably be moved to the ws verify clients callback
97f0a1a5 111 private requestValidation(rawData: RawData): ProtocolRequest {
4e3ff94d
JB
112 // logger.debug(
113 // `${this.uiServer.logPrefix(
114 // moduleName,
115 // 'dataValidation'
116 // )} Raw data received: ${rawData.toString()}`
117 // );
118
32de5a57
LM
119 const data = JSON.parse(rawData.toString()) as JsonType[];
120
53e5fd67
JB
121 if (Array.isArray(data) === false) {
122 throw new BaseError('UI protocol request is not an array');
32de5a57
LM
123 }
124
125 if (data.length !== 3) {
126 throw new BaseError('UI protocol request is malformed');
127 }
128
129 return data as ProtocolRequest;
4198ad5c
JB
130 }
131
89b7a234
JB
132 private handleListChargingStations(): ResponsePayload {
133 // TODO: remove cast to unknown
32de5a57
LM
134 return {
135 status: ResponseStatus.SUCCESS,
136 ...Array.from(this.uiServer.chargingStations.values()),
89b7a234 137 } as unknown as ResponsePayload;
32de5a57
LM
138 }
139
140 private async handleStartSimulator(): Promise<ResponsePayload> {
141 await Bootstrap.getInstance().start();
142 return { status: ResponseStatus.SUCCESS };
143 }
144
145 private async handleStopSimulator(): Promise<ResponsePayload> {
146 await Bootstrap.getInstance().stop();
147 return { status: ResponseStatus.SUCCESS };
4198ad5c
JB
148 }
149}