Vue UI + UI server
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / ui-services / AbstractUIService.ts
1 import { RawData } from 'ws';
2
3 import BaseError from '../../../exception/BaseError';
4 import { JsonType } from '../../../types/JsonType';
5 import {
6 ProcedureName,
7 ProtocolRequest,
8 ProtocolRequestHandler,
9 ProtocolResponse,
10 ProtocolVersion,
11 ResponsePayload,
12 ResponseStatus,
13 } from '../../../types/UIProtocol';
14 import logger from '../../../utils/Logger';
15 import Utils from '../../../utils/Utils';
16 import Bootstrap from '../../Bootstrap';
17 import WorkerBroadcastChannel from '../../WorkerBroadcastChannel';
18 import { AbstractUIServer } from '../AbstractUIServer';
19
20 const moduleName = 'AbstractUIService';
21
22 export default abstract class AbstractUIService {
23 protected readonly version: ProtocolVersion;
24 protected readonly uiServer: AbstractUIServer;
25 protected readonly messageHandlers: Map<ProcedureName, ProtocolRequestHandler>;
26 protected workerBroadcastChannel: WorkerBroadcastChannel;
27
28 constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
29 this.version = version;
30 this.uiServer = uiServer;
31 this.messageHandlers = new Map<ProcedureName, ProtocolRequestHandler>([
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)],
35 ]);
36 this.workerBroadcastChannel = new WorkerBroadcastChannel();
37 }
38
39 public async messageHandler(request: RawData): Promise<void> {
40 let messageId: string;
41 let command: ProcedureName;
42 let requestPayload: JsonType;
43 let responsePayload: ResponsePayload;
44 try {
45 [messageId, command, requestPayload] = this.dataValidation(request);
46
47 if (this.messageHandlers.has(command) === false) {
48 // Throw exception
49 throw new BaseError(
50 `${command} is not implemented to handle message payload ${JSON.stringify(
51 requestPayload,
52 null,
53 2
54 )}`
55 );
56 }
57 // Call the message handler to build the response payload
58 responsePayload = (await this.messageHandlers.get(command)(
59 requestPayload
60 )) as ResponsePayload;
61 } catch (error) {
62 // Log
63 logger.error(
64 `${this.uiServer.logPrefix(moduleName, 'messageHandler')} Handle message error:`,
65 error
66 );
67 // Send the message response failure
68 this.uiServer.sendResponse(
69 this.buildProtocolResponse(messageId ?? 'error', {
70 status: ResponseStatus.FAILURE,
71 command,
72 requestPayload,
73 errorMessage: (error as Error).message,
74 errorStack: (error as Error).stack,
75 })
76 );
77 throw error;
78 }
79
80 // Send the message response success
81 this.uiServer.sendResponse(this.buildProtocolResponse(messageId, responsePayload));
82 }
83
84 protected buildProtocolResponse(messageId: string, payload: ResponsePayload): string {
85 return JSON.stringify([messageId, payload] as ProtocolResponse);
86 }
87
88 // Validate the raw data received from the WebSocket
89 // TODO: should probably be moved to the ws verify clients callback
90 private dataValidation(rawData: RawData): ProtocolRequest {
91 logger.debug(
92 `${this.uiServer.logPrefix(
93 moduleName,
94 'dataValidation'
95 )} Raw data received: ${rawData.toString()}`
96 );
97 const data = JSON.parse(rawData.toString()) as JsonType[];
98
99 if (Utils.isIterable(data) === false) {
100 throw new BaseError('UI protocol request is not iterable');
101 }
102
103 if (data.length !== 3) {
104 throw new BaseError('UI protocol request is malformed');
105 }
106
107 return data as ProtocolRequest;
108 }
109
110 private handleListChargingStations(): JsonType {
111 return {
112 status: ResponseStatus.SUCCESS,
113 ...Array.from(this.uiServer.chargingStations.values()),
114 } as JsonType;
115 }
116
117 private async handleStartSimulator(): Promise<ResponsePayload> {
118 await Bootstrap.getInstance().start();
119 return { status: ResponseStatus.SUCCESS };
120 }
121
122 private async handleStopSimulator(): Promise<ResponsePayload> {
123 await Bootstrap.getInstance().stop();
124 return { status: ResponseStatus.SUCCESS };
125 }
126 }