refactor: strong type UUID usage in UI protocol
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 9 Mar 2024 20:50:20 +0000 (21:50 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 9 Mar 2024 20:50:20 +0000 (21:50 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/types/UIProtocol.ts
src/types/WorkerBroadcastChannel.ts
src/utils/Utils.ts
ui/web/src/composables/UIClient.ts
ui/web/src/types/UIProtocol.ts

index 9056dc0dcd8ccd68cba100ca8eddbaf13c585bee..b2a5b44e5e508a2ab8751c81edfc6d3e467a1a36 100644 (file)
@@ -27,7 +27,11 @@ export abstract class AbstractUIServer {
   public readonly chargingStations: Map<string, ChargingStationData>
   public readonly chargingStationTemplates: Set<string>
   protected readonly httpServer: Server | Http2Server
-  protected readonly responseHandlers: Map<string, ServerResponse | WebSocket>
+  protected readonly responseHandlers: Map<
+    `${string}-${string}-${string}-${string}-${string}`,
+  ServerResponse | WebSocket
+  >
+
   protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
 
   public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
@@ -45,20 +49,26 @@ export abstract class AbstractUIServer {
           `Unsupported application protocol version ${this.uiServerConfiguration.version}`
         )
     }
-    this.responseHandlers = new Map<string, ServerResponse | WebSocket>()
+    this.responseHandlers = new Map<
+      `${string}-${string}-${string}-${string}-${string}`,
+    ServerResponse | WebSocket
+    >()
     this.uiServices = new Map<ProtocolVersion, AbstractUIService>()
   }
 
   public buildProtocolRequest (
-    id: string,
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
     procedureName: ProcedureName,
     requestPayload: RequestPayload
   ): ProtocolRequest {
-    return [id, procedureName, requestPayload]
+    return [uuid, procedureName, requestPayload]
   }
 
-  public buildProtocolResponse (id: string, responsePayload: ResponsePayload): ProtocolResponse {
-    return [id, responsePayload]
+  public buildProtocolResponse (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    responsePayload: ResponsePayload
+  ): ProtocolResponse {
+    return [uuid, responsePayload]
   }
 
   public stop (): void {
@@ -82,8 +92,8 @@ export abstract class AbstractUIServer {
       ?.requestHandler(request) as Promise<ProtocolResponse>)
   }
 
-  public hasResponseHandler (id: string): boolean {
-    return this.responseHandlers.has(id)
+  public hasResponseHandler (uuid: `${string}-${string}-${string}-${string}-${string}`): boolean {
+    return this.responseHandlers.has(uuid)
   }
 
   protected startHttpServer (): void {
index 07bb2cdb150888d9623d60eb1355531a67c24e71..204b5431629e82c753bc57d52fe885753344ebfb 100644 (file)
@@ -66,7 +66,10 @@ export abstract class AbstractUIService {
   private readonly version: ProtocolVersion
   private readonly uiServer: AbstractUIServer
   private readonly uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel
-  private readonly broadcastChannelRequests: Map<string, number>
+  private readonly broadcastChannelRequests: Map<
+    `${string}-${string}-${string}-${string}-${string}`,
+  number
+  >
 
   constructor (uiServer: AbstractUIServer, version: ProtocolVersion) {
     this.uiServer = uiServer
@@ -81,7 +84,10 @@ export abstract class AbstractUIService {
       [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)]
     ])
     this.uiServiceWorkerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this)
-    this.broadcastChannelRequests = new Map<string, number>()
+    this.broadcastChannelRequests = new Map<
+      `${string}-${string}-${string}-${string}-${string}`,
+    number
+    >()
   }
 
   public stop (): void {
@@ -90,12 +96,12 @@ export abstract class AbstractUIService {
   }
 
   public async requestHandler (request: ProtocolRequest): Promise<ProtocolResponse | undefined> {
-    let messageId: string | undefined
+    let uuid: `${string}-${string}-${string}-${string}-${string}` | undefined
     let command: ProcedureName | undefined
     let requestPayload: RequestPayload | undefined
     let responsePayload: ResponsePayload | undefined
     try {
-      [messageId, command, requestPayload] = request
+      [uuid, command, requestPayload] = request
 
       if (!this.requestHandlers.has(command)) {
         throw new BaseError(
@@ -111,7 +117,7 @@ export abstract class AbstractUIService {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       const requestHandler = this.requestHandlers.get(command)!
       if (isAsyncFunction(requestHandler)) {
-        responsePayload = await requestHandler(messageId, command, requestPayload)
+        responsePayload = await requestHandler(uuid, command, requestPayload)
       } else {
         responsePayload = (
           requestHandler as (
@@ -119,7 +125,7 @@ export abstract class AbstractUIService {
             procedureName?: ProcedureName,
             payload?: RequestPayload
           ) => undefined | ResponsePayload
-        )(messageId, command, requestPayload)
+        )(uuid, command, requestPayload)
       }
     } catch (error) {
       // Log
@@ -137,23 +143,26 @@ export abstract class AbstractUIService {
     }
     if (responsePayload != null) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return this.uiServer.buildProtocolResponse(messageId!, responsePayload)
+      return this.uiServer.buildProtocolResponse(uuid!, responsePayload)
     }
   }
 
   // public sendRequest (
-  //   messageId: string,
+  //   uuid: `${string}-${string}-${string}-${string}-${string}`,
   //   procedureName: ProcedureName,
   //   requestPayload: RequestPayload
   // ): void {
   //   this.uiServer.sendRequest(
-  //     this.uiServer.buildProtocolRequest(messageId, procedureName, requestPayload)
+  //     this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
   //   )
   // }
 
-  public sendResponse (messageId: string, responsePayload: ResponsePayload): void {
-    if (this.uiServer.hasResponseHandler(messageId)) {
-      this.uiServer.sendResponse(this.uiServer.buildProtocolResponse(messageId, responsePayload))
+  public sendResponse (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    responsePayload: ResponsePayload
+  ): void {
+    if (this.uiServer.hasResponseHandler(uuid)) {
+      this.uiServer.sendResponse(this.uiServer.buildProtocolResponse(uuid, responsePayload))
     }
   }
 
@@ -161,17 +170,21 @@ export abstract class AbstractUIService {
     return this.uiServer.logPrefix(modName, methodName, this.version)
   }
 
-  public deleteBroadcastChannelRequest (uuid: string): void {
+  public deleteBroadcastChannelRequest (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): void {
     this.broadcastChannelRequests.delete(uuid)
   }
 
-  public getBroadcastChannelExpectedResponses (uuid: string): number {
+  public getBroadcastChannelExpectedResponses (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): number {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     return this.broadcastChannelRequests.get(uuid)!
   }
 
   protected handleProtocolRequest (
-    uuid: string,
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
     procedureName: ProcedureName,
     payload: RequestPayload
   ): void {
@@ -184,7 +197,7 @@ export abstract class AbstractUIService {
   }
 
   private sendBroadcastChannelRequest (
-    uuid: string,
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
     procedureName: BroadcastChannelProcedureName,
     payload: BroadcastChannelRequestPayload
   ): void {
@@ -233,7 +246,7 @@ export abstract class AbstractUIService {
   }
 
   private async handleAddChargingStations (
-    _messageId?: string,
+    _uuid?: `${string}-${string}-${string}-${string}-${string}`,
     _procedureName?: ProcedureName,
     requestPayload?: RequestPayload
   ): Promise<ResponsePayload> {
index b1b71b73fda1654c6bda98c67af43a32b85741c5..f53696dae914b02ed0c3a24a66d89939393707e4 100644 (file)
@@ -19,11 +19,18 @@ export enum ProtocolVersion {
   '0.0.1' = '0.0.1'
 }
 
-export type ProtocolRequest = [string, ProcedureName, RequestPayload]
-export type ProtocolResponse = [string, ResponsePayload]
+export type ProtocolRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ProcedureName,
+  RequestPayload
+]
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
 
 export type ProtocolRequestHandler = (
-  uuid?: string,
+  uuid?: `${string}-${string}-${string}-${string}-${string}`,
   procedureName?: ProcedureName,
   payload?: RequestPayload
 ) => undefined | Promise<undefined> | ResponsePayload | Promise<ResponsePayload>
index caa56e9ff9ae5e674c069bd98c828ced97495cd5..077c2f3750acce4fe2e6ece971e8aff3ba5d5d09 100644 (file)
@@ -1,11 +1,14 @@
 import type { RequestPayload, ResponsePayload } from './UIProtocol.js'
 
 export type BroadcastChannelRequest = [
-  string,
+  `${string}-${string}-${string}-${string}-${string}`,
   BroadcastChannelProcedureName,
   BroadcastChannelRequestPayload
 ]
-export type BroadcastChannelResponse = [string, BroadcastChannelResponsePayload]
+export type BroadcastChannelResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  BroadcastChannelResponsePayload
+]
 
 export enum BroadcastChannelProcedureName {
   START_CHARGING_STATION = 'startChargingStation',
index c021cadb4864aef9912b95dde1b09f20842fc84a..69f0cd4280f5e82df830c83f885efc2f77c5feaf 100644 (file)
@@ -25,11 +25,13 @@ export const logPrefix = (prefixString = ''): string => {
   return `${new Date().toLocaleString()}${prefixString}`
 }
 
-export const generateUUID = (): string => {
+export const generateUUID = (): `${string}-${string}-${string}-${string}-${string}` => {
   return randomUUID()
 }
 
-export const validateUUID = (uuid: string): boolean => {
+export const validateUUID = (
+  uuid: `${string}-${string}-${string}-${string}-${string}`
+): uuid is `${string}-${string}-${string}-${string}-${string}` => {
   return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(uuid)
 }
 
@@ -263,7 +265,7 @@ export const isNotEmptyString = (value: unknown): value is string => {
   return isString(value) && value.trim().length > 0
 }
 
-export const isEmptyArray = (value: unknown): value is never[] => {
+export const isEmptyArray = (value: unknown): value is [] => {
   return Array.isArray(value) && value.length === 0
 }
 
index 64f2bc69d4e5efd2549d51d471c6f1dafa039058..781854dbb759fdfc542aa535573fa7f61eb1e606 100644 (file)
@@ -24,11 +24,17 @@ export class UIClient {
   private static instance: UIClient | null = null
 
   private ws?: WebSocket
-  private responseHandlers: Map<string, ResponseHandler>
+  private responseHandlers: Map<
+    `${string}-${string}-${string}-${string}-${string}`,
+    ResponseHandler
+  >
 
   private constructor(private uiServerConfiguration: UIServerConfigurationSection) {
     this.openWS()
-    this.responseHandlers = new Map<string, ResponseHandler>()
+    this.responseHandlers = new Map<
+      `${string}-${string}-${string}-${string}-${string}`,
+      ResponseHandler
+    >()
   }
 
   public static getInstance(uiServerConfiguration?: UIServerConfigurationSection): UIClient {
@@ -230,15 +236,24 @@ export class UIClient {
   }
 
   private responseHandler(messageEvent: MessageEvent<string>): void {
-    const response = JSON.parse(messageEvent.data) as ProtocolResponse
+    let response: ProtocolResponse
+    try {
+      response = JSON.parse(messageEvent.data) as ProtocolResponse
+    } catch (error) {
+      useToast().error('Invalid response format')
+      console.error('Invalid response 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 (this.responseHandlers.has(uuid)) {
       const { procedureName, resolve, reject } = this.responseHandlers.get(uuid)!
       switch (responsePayload.status) {
         case ResponseStatus.SUCCESS:
index 5b3efcdcef1efb81cccc8eeddc5525ccda0797f1..8522aa11dcd8c4a10ac40a7e95ec94523bad1992 100644 (file)
@@ -17,8 +17,15 @@ export enum AuthenticationType {
   PROTOCOL_BASIC_AUTH = 'protocol-basic-auth'
 }
 
-export type ProtocolRequest = [string, ProcedureName, RequestPayload]
-export type ProtocolResponse = [string, ResponsePayload]
+export type ProtocolRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ProcedureName,
+  RequestPayload
+]
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
 
 export type ProtocolRequestHandler = (
   payload: RequestPayload