-import type { ResponsePayload } from '../types/UIProtocol';
-import type { BroadcastChannelResponse, MessageEvent } from '../types/WorkerBroadcastChannel';
+import { ResponsePayload, ResponseStatus } from '../types/UIProtocol';
+import type {
+ BroadcastChannelResponse,
+ BroadcastChannelResponsePayload,
+ MessageEvent,
+} from '../types/WorkerBroadcastChannel';
import logger from '../utils/Logger';
import type AbstractUIService from './ui-server/ui-services/AbstractUIService';
import WorkerBroadcastChannel from './WorkerBroadcastChannel';
const moduleName = 'UIServiceWorkerBroadcastChannel';
+type Responses = {
+ responsesExpected: number;
+ responsesReceived: number;
+ responses: BroadcastChannelResponsePayload[];
+};
+
export default class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
- private uiService: AbstractUIService;
+ private readonly uiService: AbstractUIService;
+ private responses: Map<string, Responses>;
constructor(uiService: AbstractUIService) {
super();
this.uiService = uiService;
this.onmessage = this.responseHandler.bind(this) as (message: MessageEvent) => void;
this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
+ this.responses = new Map<string, Responses>();
}
private responseHandler(messageEvent: MessageEvent): void {
}
this.validateMessageEvent(messageEvent);
const [uuid, responsePayload] = messageEvent.data as BroadcastChannelResponse;
- // TODO: handle multiple responses for the same uuid
- delete responsePayload.hashId;
+ if (this.responses.has(uuid) === false) {
+ this.responses.set(uuid, {
+ responsesExpected: this.uiService.getBroadcastChannelExpectedResponses(uuid),
+ responsesReceived: 1,
+ responses: [responsePayload],
+ });
+ } else if (
+ this.responses.get(uuid)?.responsesReceived + 1 <
+ this.responses.get(uuid)?.responsesExpected
+ ) {
+ this.responses.get(uuid).responsesReceived++;
+ this.responses.get(uuid).responses.push(responsePayload);
+ } else if (
+ this.responses.get(uuid)?.responsesReceived + 1 ===
+ this.responses.get(uuid)?.responsesExpected
+ ) {
+ this.responses.get(uuid).responses.push(responsePayload);
+ this.uiService.sendResponse(uuid, this.buildResponsePayload(uuid));
+ this.responses.delete(uuid);
+ this.uiService.deleteBroadcastChannelRequest(uuid);
+ }
+ }
- this.uiService.sendResponse(uuid, responsePayload as ResponsePayload);
+ private buildResponsePayload(uuid: string): ResponsePayload {
+ const responsesStatus = this.responses
+ .get(uuid)
+ ?.responses.every((response) => response.status === ResponseStatus.SUCCESS)
+ ? ResponseStatus.SUCCESS
+ : ResponseStatus.FAILURE;
+ return {
+ status: responsesStatus,
+ hashIdsSucceeded: this.responses
+ .get(uuid)
+ ?.responses.map(({ status, hashId }) => {
+ if (status === ResponseStatus.SUCCESS) {
+ return hashId;
+ }
+ })
+ .filter((hashId) => hashId !== undefined),
+ ...(responsesStatus === ResponseStatus.FAILURE && {
+ hashIdsFailed: this.responses
+ .get(uuid)
+ ?.responses.map(({ status, hashId }) => {
+ if (status === ResponseStatus.FAILURE) {
+ return hashId;
+ }
+ })
+ .filter((hashId) => hashId !== undefined),
+ }),
+ };
}
private messageErrorHandler(messageEvent: MessageEvent): void {
public abstract stop(): void;
public abstract sendRequest(request: string): void;
public abstract sendResponse(response: string): void;
- public abstract logPrefix(modName?: string, methodName?: string): string;
+ public abstract logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string;
}
this.responseHandlers.delete(uuid);
} else {
logger.error(
- `${this.logPrefix()} ${moduleName}.sendResponse: Response for unknown request: ${response}`
+ `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request: ${response}`
);
}
}
- public logPrefix(modName?: string, methodName?: string): string {
+ public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
+ const logMsgPrefix = prefixSuffix ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server';
const logMsg =
- modName && methodName ? ` UI HTTP Server | ${modName}.${methodName}:` : ' UI HTTP Server |';
+ modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`;
return Utils.logPrefix(logMsg);
}
this.broadcastToClients(response);
}
- public logPrefix(modName?: string, methodName?: string): string {
+ public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
+ const logMsgPrefix = prefixSuffix
+ ? `UI WebSocket Server ${prefixSuffix}`
+ : 'UI WebSocket Server';
const logMsg =
- modName && methodName
- ? ` UI WebSocket Server | ${modName}.${methodName}:`
- : ' UI WebSocket Server |';
+ modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`;
return Utils.logPrefix(logMsg);
}
ResponsePayload,
ResponseStatus,
} from '../../../types/UIProtocol';
+import type {
+ BroadcastChannelProcedureName,
+ BroadcastChannelRequestPayload,
+} from '../../../types/WorkerBroadcastChannel';
import logger from '../../../utils/Logger';
+import Utils from '../../../utils/Utils';
import UIServiceWorkerBroadcastChannel from '../../UIServiceWorkerBroadcastChannel';
import type { AbstractUIServer } from '../AbstractUIServer';
protected readonly version: ProtocolVersion;
protected readonly uiServer: AbstractUIServer;
protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
- protected uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel;
+ private uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel;
+ private readonly broadcastChannelRequests: Map<string, number>;
constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
this.version = version;
[ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)],
]);
this.uiServiceWorkerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this);
+ this.broadcastChannelRequests = new Map<string, number>();
}
public async requestHandler(request: RawData | JsonType): Promise<void> {
errorStack: (error as Error).stack,
};
}
-
+ // Send response for payload not forwarded to broadcast channel
if (responsePayload !== undefined) {
- // Send the response
this.sendResponse(messageId ?? 'error', responsePayload);
}
}
}
public logPrefix(modName: string, methodName: string): string {
- return this.uiServer.logPrefix(modName, methodName);
+ return this.uiServer.logPrefix(modName, methodName, this.version);
+ }
+
+ public deleteBroadcastChannelRequest(uuid: string): void {
+ this.broadcastChannelRequests.delete(uuid);
+ }
+
+ public getBroadcastChannelExpectedResponses(uuid: string): number {
+ return this.broadcastChannelRequests.get(uuid) ?? 0;
+ }
+
+ protected sendBroadcastChannelRequest(
+ uuid: string,
+ procedureName: BroadcastChannelProcedureName,
+ payload: BroadcastChannelRequestPayload
+ ): void {
+ if (!Utils.isEmptyArray(payload.hashIds)) {
+ payload.hashIds = payload.hashIds
+ .map((hashId) => {
+ if (this.uiServer.chargingStations.has(hashId) === true) {
+ return hashId;
+ }
+ logger.warn(
+ `${this.logPrefix(
+ moduleName,
+ 'sendBroadcastChannelRequest'
+ )} Charging station with hashId '${hashId}' not found`
+ );
+ })
+ .filter((hashId) => hashId !== undefined);
+ }
+ const expectedNumberOfResponses = !Utils.isEmptyArray(payload.hashIds)
+ ? payload.hashIds.length
+ : this.uiServer.chargingStations.size;
+ this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses);
+ this.uiServiceWorkerBroadcastChannel.sendRequest([uuid, procedureName, payload]);
}
// Validate the raw data received from the UI server
ProtocolVersion,
RequestPayload,
} from '../../../types/UIProtocol';
-import {
- BroadcastChannelProcedureName,
- BroadcastChannelRequestPayload,
-} from '../../../types/WorkerBroadcastChannel';
+import { BroadcastChannelProcedureName } from '../../../types/WorkerBroadcastChannel';
import type { AbstractUIServer } from '../AbstractUIServer';
import AbstractUIService from './AbstractUIService';
}
private handleStartChargingStation(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
+ this.sendBroadcastChannelRequest(
uuid,
BroadcastChannelProcedureName.START_CHARGING_STATION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ payload
+ );
}
private handleStopChargingStation(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
+ this.sendBroadcastChannelRequest(
uuid,
BroadcastChannelProcedureName.STOP_CHARGING_STATION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ payload
+ );
}
private handleOpenConnection(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
- uuid,
- BroadcastChannelProcedureName.OPEN_CONNECTION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ this.sendBroadcastChannelRequest(uuid, BroadcastChannelProcedureName.OPEN_CONNECTION, payload);
}
private handleCloseConnection(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
- uuid,
- BroadcastChannelProcedureName.CLOSE_CONNECTION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ this.sendBroadcastChannelRequest(uuid, BroadcastChannelProcedureName.CLOSE_CONNECTION, payload);
}
private handleStartTransaction(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
+ this.sendBroadcastChannelRequest(
uuid,
BroadcastChannelProcedureName.START_TRANSACTION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ payload
+ );
}
private handleStopTransaction(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
- uuid,
- BroadcastChannelProcedureName.STOP_TRANSACTION,
- payload as BroadcastChannelRequestPayload,
- ]);
+ this.sendBroadcastChannelRequest(uuid, BroadcastChannelProcedureName.STOP_TRANSACTION, payload);
}
private handleStartAutomaticTransactionGenerator(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
+ this.sendBroadcastChannelRequest(
uuid,
BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
- payload as BroadcastChannelRequestPayload,
- ]);
+ payload
+ );
}
private handleStopAutomaticTransactionGenerator(uuid: string, payload: RequestPayload): void {
- this.uiServiceWorkerBroadcastChannel.sendRequest([
+ this.sendBroadcastChannelRequest(
uuid,
BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
- payload as BroadcastChannelRequestPayload,
- ]);
+ payload
+ );
}
}
export interface ResponsePayload extends JsonObject {
status: ResponseStatus;
- hashIds?: string[];
+ hashIdsSucceeded?: string[];
+ hashIdsFailed?: string[];
}