refactor(simulator): factor out common helpers
[e-mobility-charging-stations-simulator.git] / src / charging-station / broadcast-channel / UIServiceWorkerBroadcastChannel.ts
1 import { WorkerBroadcastChannel } from './WorkerBroadcastChannel';
2 import {
3 type BroadcastChannelResponse,
4 type BroadcastChannelResponsePayload,
5 type MessageEvent,
6 type ResponsePayload,
7 ResponseStatus,
8 } from '../../types';
9 import { Utils, logger } from '../../utils';
10 import type { AbstractUIService } from '../ui-server/ui-services/AbstractUIService';
11
12 const moduleName = 'UIServiceWorkerBroadcastChannel';
13
14 type Responses = {
15 responsesExpected: number;
16 responsesReceived: number;
17 responses: BroadcastChannelResponsePayload[];
18 };
19
20 export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
21 private readonly uiService: AbstractUIService;
22 private readonly responses: Map<string, Responses>;
23
24 constructor(uiService: AbstractUIService) {
25 super();
26 this.uiService = uiService;
27 this.onmessage = this.responseHandler.bind(this) as (message: MessageEvent) => void;
28 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
29 this.responses = new Map<string, Responses>();
30 }
31
32 private responseHandler(messageEvent: MessageEvent): void {
33 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
34 if (validatedMessageEvent === false) {
35 return;
36 }
37 if (this.isRequest(validatedMessageEvent.data) === true) {
38 return;
39 }
40 const [uuid, responsePayload] = validatedMessageEvent.data as BroadcastChannelResponse;
41 if (this.responses.has(uuid) === false) {
42 this.responses.set(uuid, {
43 responsesExpected: this.uiService.getBroadcastChannelExpectedResponses(uuid),
44 responsesReceived: 1,
45 responses: [responsePayload],
46 });
47 } else if (
48 this.responses.get(uuid)?.responsesReceived <= this.responses.get(uuid)?.responsesExpected
49 ) {
50 this.responses.get(uuid).responsesReceived++;
51 this.responses.get(uuid)?.responses.push(responsePayload);
52 }
53 if (
54 this.responses.get(uuid)?.responsesReceived === this.responses.get(uuid)?.responsesExpected
55 ) {
56 this.uiService.sendResponse(uuid, this.buildResponsePayload(uuid));
57 this.responses.delete(uuid);
58 this.uiService.deleteBroadcastChannelRequest(uuid);
59 }
60 }
61
62 private buildResponsePayload(uuid: string): ResponsePayload {
63 const responsesStatus =
64 this.responses
65 .get(uuid)
66 ?.responses.every(({ status }) => status === ResponseStatus.SUCCESS) === true
67 ? ResponseStatus.SUCCESS
68 : ResponseStatus.FAILURE;
69 return {
70 status: responsesStatus,
71 hashIdsSucceeded: this.responses
72 .get(uuid)
73 ?.responses.filter(({ hashId }) => !Utils.isNullOrUndefined(hashId))
74 .map(({ status, hashId }) => {
75 if (status === ResponseStatus.SUCCESS) {
76 return hashId;
77 }
78 }),
79 ...(responsesStatus === ResponseStatus.FAILURE && {
80 hashIdsFailed: this.responses
81 .get(uuid)
82 ?.responses.filter(({ hashId }) => !Utils.isNullOrUndefined(hashId))
83 .map(({ status, hashId }) => {
84 if (status === ResponseStatus.FAILURE) {
85 return hashId;
86 }
87 }),
88 }),
89 ...(responsesStatus === ResponseStatus.FAILURE && {
90 responsesFailed: this.responses
91 .get(uuid)
92 ?.responses.filter((response) => !Utils.isNullOrUndefined(response))
93 .map((response) => {
94 if (response.status === ResponseStatus.FAILURE) {
95 return response;
96 }
97 }),
98 }),
99 };
100 }
101
102 private messageErrorHandler(messageEvent: MessageEvent): void {
103 logger.error(
104 `${this.uiService.logPrefix(moduleName, 'messageErrorHandler')} Error at handling message:`,
105 messageEvent
106 );
107 }
108 }