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