X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=ui%2Fweb%2Fsrc%2Fcomposables%2FUIClient.ts;h=9522c59213c666a526d48927447024f3e8fc065a;hb=7445d18b9b9ac750b9dc3dc3638d4fd0e8aff818;hp=9a6920418d3adb255f64a8e97e50be35e9586640;hpb=c25a4046983ef80409cbc4546f944231e8655604;p=e-mobility-charging-stations-simulator.git diff --git a/ui/web/src/composables/UIClient.ts b/ui/web/src/composables/UIClient.ts index 9a692041..9522c592 100644 --- a/ui/web/src/composables/UIClient.ts +++ b/ui/web/src/composables/UIClient.ts @@ -1,6 +1,9 @@ +import { useToast } from 'vue-toast-notification' + import { ApplicationProtocol, AuthenticationType, + type ChargingStationOptions, ProcedureName, type ProtocolResponse, type RequestPayload, @@ -9,6 +12,8 @@ import { type UIServerConfigurationSection } from '@/types' +import { randomUUID, validateUUID } from './Utils' + type ResponseHandler = { procedureName: ProcedureName resolve: (value: ResponsePayload | PromiseLike) => void @@ -18,26 +23,57 @@ type ResponseHandler = { export class UIClient { private static instance: UIClient | null = null - private ws!: WebSocket - private responseHandlers: Map + private ws?: WebSocket + private responseHandlers: Map< + `${string}-${string}-${string}-${string}-${string}`, + ResponseHandler + > private constructor(private uiServerConfiguration: UIServerConfigurationSection) { this.openWS() - this.responseHandlers = new Map() + this.responseHandlers = new Map< + `${string}-${string}-${string}-${string}-${string}`, + ResponseHandler + >() } - public static getInstance(uiServerConfiguration: UIServerConfigurationSection): UIClient { + public static getInstance(uiServerConfiguration?: UIServerConfigurationSection): UIClient { if (UIClient.instance === null) { + if (uiServerConfiguration == null) { + throw new Error('Cannot initialize UIClient if no configuration is provided') + } UIClient.instance = new UIClient(uiServerConfiguration) } return UIClient.instance } + public setConfiguration(uiServerConfiguration: UIServerConfigurationSection): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.close() + delete this.ws + } + this.uiServerConfiguration = uiServerConfiguration + this.openWS() + } + public registerWSEventListener( event: K, - listener: (event: WebSocketEventMap[K]) => void + listener: (event: WebSocketEventMap[K]) => void, + options?: boolean | AddEventListenerOptions ) { - this.ws.addEventListener(event, listener) + this.ws?.addEventListener(event, listener, options) + } + + public unregisterWSEventListener( + event: K, + listener: (event: WebSocketEventMap[K]) => void, + options?: boolean | AddEventListenerOptions + ) { + this.ws?.removeEventListener(event, listener, options) + } + + public async simulatorState(): Promise { + return this.sendRequest(ProcedureName.SIMULATOR_STATE, {}) } public async startSimulator(): Promise { @@ -58,9 +94,14 @@ export class UIClient { public async addChargingStations( template: string, - numberOfStations: number + numberOfStations: number, + options?: ChargingStationOptions ): Promise { - return this.sendRequest(ProcedureName.ADD_CHARGING_STATIONS, { template, numberOfStations }) + return this.sendRequest(ProcedureName.ADD_CHARGING_STATIONS, { + template, + numberOfStations, + options + }) } public async deleteChargingStation(hashId: string): Promise { @@ -149,15 +190,21 @@ export class UIClient { `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`, protocols ) - this.ws.onopen = openEvent => { - console.info('WebSocket opened', openEvent) + this.ws.onopen = () => { + useToast().success( + `WebSocket to UI server '${this.uiServerConfiguration.host}' successfully opened` + ) } this.ws.onmessage = this.responseHandler.bind(this) this.ws.onerror = errorEvent => { - console.error('WebSocket error: ', errorEvent) + useToast().error(`Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`) + console.error( + `Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`, + errorEvent + ) } - this.ws.onclose = closeEvent => { - console.info('WebSocket closed: ', closeEvent) + this.ws.onclose = () => { + useToast().info(`WebSocket to UI server closed`) } } @@ -166,8 +213,8 @@ export class UIClient { payload: RequestPayload ): Promise { return new Promise((resolve, reject) => { - if (this.ws.readyState === WebSocket.OPEN) { - const uuid = crypto.randomUUID() + if (this.ws?.readyState === WebSocket.OPEN) { + const uuid = randomUUID() const msg = JSON.stringify([uuid, procedureName, payload]) const sendTimeout = setTimeout(() => { this.responseHandlers.delete(uuid) @@ -189,15 +236,30 @@ export class UIClient { } private responseHandler(messageEvent: MessageEvent): void { - const response = JSON.parse(messageEvent.data) as ProtocolResponse + let response: ProtocolResponse + try { + response = JSON.parse(messageEvent.data) + } catch (error) { + useToast().error('Invalid response JSON format') + console.error('Invalid response JSON format', error) + return + } - if (Array.isArray(response) === false) { - throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`) + if (!Array.isArray(response)) { + useToast().error('Response not an array') + console.error('Response not an array:', response) + return } const [uuid, responsePayload] = response - if (this.responseHandlers.has(uuid) === true) { + if (!validateUUID(uuid)) { + useToast().error('Response UUID field is invalid') + console.error('Response UUID field is invalid:', response) + return + } + + if (this.responseHandlers.has(uuid)) { const { procedureName, resolve, reject } = this.responseHandlers.get(uuid)! switch (responsePayload.status) { case ResponseStatus.SUCCESS: