UI Server: factor out authentication code
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
CommitLineData
6c8f5d90 1import BaseError from '../exception/BaseError';
a9ed42b2 2import type OCPPError from '../exception/OCPPError';
10db00b2
JB
3import {
4 HeartbeatRequest,
5 RequestCommand,
6 type StatusNotificationRequest,
7} from '../types/ocpp/Requests';
8import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses';
89b7a234 9import {
6c8f5d90 10 AuthorizationStatus,
1984f194
JB
11 AuthorizeRequest,
12 AuthorizeResponse,
89b7a234
JB
13 StartTransactionRequest,
14 StartTransactionResponse,
89b7a234
JB
15 StopTransactionRequest,
16 StopTransactionResponse,
17} from '../types/ocpp/Transaction';
18import {
19 BroadcastChannelProcedureName,
20 BroadcastChannelRequest,
6c8f5d90
JB
21 BroadcastChannelRequestPayload,
22 BroadcastChannelResponsePayload,
23 MessageEvent,
89b7a234 24} from '../types/WorkerBroadcastChannel';
f27eb751 25import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
6c8f5d90 26import logger from '../utils/Logger';
a9ed42b2 27import Utils from '../utils/Utils';
db2336d9 28import type ChargingStation from './ChargingStation';
1598b27c 29import WorkerBroadcastChannel from './WorkerBroadcastChannel';
89b7a234 30
4e3ff94d
JB
31const moduleName = 'ChargingStationWorkerBroadcastChannel';
32
a9ed42b2
JB
33type CommandResponse =
34 | StartTransactionResponse
35 | StopTransactionResponse
1984f194 36 | AuthorizeResponse
10db00b2
JB
37 | StatusNotificationResponse
38 | HeartbeatResponse;
89b7a234 39
d273692c
JB
40type CommandHandler = (
41 requestPayload?: BroadcastChannelRequestPayload
42) => Promise<CommandResponse | void> | void;
43
1598b27c 44export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
d273692c 45 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
9d73266c 46
89b7a234
JB
47 private readonly chargingStation: ChargingStation;
48
49 constructor(chargingStation: ChargingStation) {
1598b27c 50 super();
d273692c 51 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
9d73266c
JB
52 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
53 [
54 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
d273692c 55 async () => this.chargingStation.stop(),
9d73266c
JB
56 ],
57 [
58 BroadcastChannelProcedureName.OPEN_CONNECTION,
59 () => this.chargingStation.openWSConnection(),
60 ],
61 [
62 BroadcastChannelProcedureName.CLOSE_CONNECTION,
63 () => this.chargingStation.closeWSConnection(),
64 ],
623b39b5
JB
65 [
66 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
67 (requestPayload?: BroadcastChannelRequestPayload) =>
68 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds),
69 ],
70 [
71 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
72 (requestPayload?: BroadcastChannelRequestPayload) =>
73 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds),
74 ],
9d73266c
JB
75 [
76 BroadcastChannelProcedureName.START_TRANSACTION,
77 async (requestPayload?: BroadcastChannelRequestPayload) =>
78 this.chargingStation.ocppRequestService.requestHandler<
79 StartTransactionRequest,
80 StartTransactionResponse
1984f194 81 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload),
9d73266c
JB
82 ],
83 [
84 BroadcastChannelProcedureName.STOP_TRANSACTION,
85 async (requestPayload?: BroadcastChannelRequestPayload) =>
86 this.chargingStation.ocppRequestService.requestHandler<
87 StopTransactionRequest,
88 StartTransactionResponse
89 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
1984f194 90 ...requestPayload,
9d73266c
JB
91 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
92 requestPayload.transactionId,
93 true
94 ),
9d73266c
JB
95 }),
96 ],
1984f194
JB
97 [
98 BroadcastChannelProcedureName.AUTHORIZE,
99 async (requestPayload?: BroadcastChannelRequestPayload) =>
100 this.chargingStation.ocppRequestService.requestHandler<
101 AuthorizeRequest,
102 AuthorizeResponse
103 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload),
104 ],
9d73266c
JB
105 [
106 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
107 async (requestPayload?: BroadcastChannelRequestPayload) =>
108 this.chargingStation.ocppRequestService.requestHandler<
109 StatusNotificationRequest,
110 StatusNotificationResponse
1984f194 111 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload),
9d73266c
JB
112 ],
113 [
114 BroadcastChannelProcedureName.HEARTBEAT,
1984f194
JB
115 async (requestPayload?: BroadcastChannelRequestPayload) =>
116 this.chargingStation.ocppRequestService.requestHandler<
9d73266c
JB
117 HeartbeatRequest,
118 HeartbeatResponse
1984f194 119 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload),
9d73266c
JB
120 ],
121 ]);
89b7a234 122 this.chargingStation = chargingStation;
02a6943a 123 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 124 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
125 }
126
02a6943a 127 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
128 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
129 if (validatedMessageEvent === false) {
6c8f5d90
JB
130 return;
131 }
5dea4c94
JB
132 if (this.isResponse(validatedMessageEvent.data) === true) {
133 return;
134 }
135 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
6c8f5d90 136
2afb4d15
JB
137 if (
138 requestPayload?.hashIds !== undefined &&
139 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
140 ) {
141 return;
142 }
143 if (requestPayload?.hashId !== undefined) {
144 logger.error(
145 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
146 );
147 return;
4eca248c 148 }
6c8f5d90 149 let responsePayload: BroadcastChannelResponsePayload;
9d73266c 150 let commandResponse: CommandResponse | void;
6c8f5d90
JB
151 try {
152 commandResponse = await this.commandHandler(command, requestPayload);
9d73266c 153 if (commandResponse === undefined || commandResponse === null) {
10d244c0 154 responsePayload = {
51c83d6f 155 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
156 status: ResponseStatus.SUCCESS,
157 };
6c8f5d90 158 } else {
5e8e29f4 159 const commandResponseStatus = this.commandResponseToResponseStatus(
1984f194
JB
160 command,
161 commandResponse as CommandResponse
162 );
5e8e29f4 163 if (commandResponseStatus === ResponseStatus.SUCCESS) {
1984f194
JB
164 responsePayload = {
165 hashId: this.chargingStation.stationInfo.hashId,
5e8e29f4 166 status: commandResponseStatus,
1984f194
JB
167 };
168 } else {
169 responsePayload = {
170 hashId: this.chargingStation.stationInfo.hashId,
5e8e29f4 171 status: commandResponseStatus,
1984f194
JB
172 command,
173 requestPayload,
174 commandResponse: commandResponse as CommandResponse,
175 };
176 }
6c8f5d90
JB
177 }
178 } catch (error) {
179 logger.error(
180 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
181 error
182 );
183 responsePayload = {
51c83d6f 184 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
185 status: ResponseStatus.FAILURE,
186 command,
187 requestPayload,
9d73266c 188 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
189 errorMessage: (error as Error).message,
190 errorStack: (error as Error).stack,
a9ed42b2 191 errorDetails: (error as OCPPError).details,
6c8f5d90 192 };
623b39b5
JB
193 } finally {
194 this.sendResponse([uuid, responsePayload]);
6c8f5d90 195 }
6c8f5d90
JB
196 }
197
198 private messageErrorHandler(messageEvent: MessageEvent): void {
199 logger.error(
200 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5e3cb728 201 { messageEvent }
6c8f5d90
JB
202 );
203 }
204
205 private async commandHandler(
206 command: BroadcastChannelProcedureName,
207 requestPayload: BroadcastChannelRequestPayload
9d73266c
JB
208 ): Promise<CommandResponse | void> {
209 if (this.commandHandlers.has(command) === true) {
1984f194 210 this.cleanRequestPayload(command, requestPayload);
9d73266c 211 return this.commandHandlers.get(command)(requestPayload);
6c8f5d90 212 }
9d73266c 213 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
214 }
215
1984f194
JB
216 private cleanRequestPayload(
217 command: BroadcastChannelProcedureName,
218 requestPayload: BroadcastChannelRequestPayload
219 ): void {
220 delete requestPayload.hashId;
221 delete requestPayload.hashIds;
222 [
223 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
224 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
225 ].includes(command) === false && delete requestPayload.connectorIds;
226 }
227
10db00b2
JB
228 private commandResponseToResponseStatus(
229 command: BroadcastChannelProcedureName,
230 commandResponse: CommandResponse
231 ): ResponseStatus {
232 switch (command) {
233 case BroadcastChannelProcedureName.START_TRANSACTION:
234 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 235 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 236 if (
1984f194
JB
237 (
238 commandResponse as
239 | StartTransactionResponse
240 | StopTransactionResponse
241 | AuthorizeResponse
242 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
243 ) {
244 return ResponseStatus.SUCCESS;
245 }
246 return ResponseStatus.FAILURE;
247 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
248 if (Utils.isEmptyObject(commandResponse) === true) {
249 return ResponseStatus.SUCCESS;
250 }
251 return ResponseStatus.FAILURE;
252 case BroadcastChannelProcedureName.HEARTBEAT:
253 if ('currentTime' in commandResponse) {
254 return ResponseStatus.SUCCESS;
255 }
256 return ResponseStatus.FAILURE;
257 default:
258 return ResponseStatus.FAILURE;
89b7a234
JB
259 }
260 }
261}