]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
fix(ocpp): use per-subclass singleton map to prevent version collision
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 15 Mar 2026 22:37:22 +0000 (23:37 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 15 Mar 2026 22:37:22 +0000 (23:37 +0100)
The singleton instance was stored as a single static field on the base
class. In mixed OCPP 1.6/2.0 workers, the first getInstance() call
wins — subsequent calls for a different version silently return the
wrong instance cast via 'as T'.

Replace with Map<Constructor, Instance> keyed by the concrete subclass
constructor. Each version now gets its own singleton entry. Zero
changes needed in subclasses or calling code.

src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts

index 351aef63095b3db4a3e0213e0617d010bfc44331..c73a03ce91395ee3c8dfd21ee531d1d2c08faa60 100644 (file)
@@ -17,7 +17,11 @@ const ajvFormats = _ajvFormats.default
 const moduleName = 'OCPPIncomingRequestService'
 
 export abstract class OCPPIncomingRequestService extends EventEmitter {
-  private static instance: null | OCPPIncomingRequestService = null
+  private static readonly instances = new Map<
+    new () => OCPPIncomingRequestService,
+    OCPPIncomingRequestService
+  >()
+
   protected readonly ajv: Ajv
   protected abstract payloadValidatorFunctions: Map<
     IncomingRequestCommand,
@@ -39,8 +43,10 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
   }
 
   public static getInstance<T extends OCPPIncomingRequestService>(this: new () => T): T {
-    OCPPIncomingRequestService.instance ??= new this()
-    return OCPPIncomingRequestService.instance as T
+    if (!OCPPIncomingRequestService.instances.has(this)) {
+      OCPPIncomingRequestService.instances.set(this, new this())
+    }
+    return OCPPIncomingRequestService.instances.get(this) as T
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unnecessary-type-parameters
index ce31ba90b210df0ecdf380d85f5e90cd273b3edc..19942fab48a17aa8c6085067254a2b3de4959c7e 100644 (file)
@@ -49,7 +49,11 @@ const defaultRequestParams: RequestParams = {
 }
 
 export abstract class OCPPRequestService {
-  private static instance: null | OCPPRequestService = null
+  private static readonly instances = new Map<
+    new (ocppResponseService: OCPPResponseService) => OCPPRequestService,
+    OCPPRequestService
+  >()
+
   protected readonly ajv: Ajv
   protected abstract payloadValidatorFunctions: Map<RequestCommand, ValidateFunction<JsonType>>
   private readonly ocppResponseService: OCPPResponseService
@@ -78,8 +82,10 @@ export abstract class OCPPRequestService {
     this: new (ocppResponseService: OCPPResponseService) => T,
     ocppResponseService: OCPPResponseService
   ): T {
-    OCPPRequestService.instance ??= new this(ocppResponseService)
-    return OCPPRequestService.instance as T
+    if (!OCPPRequestService.instances.has(this)) {
+      OCPPRequestService.instances.set(this, new this(ocppResponseService))
+    }
+    return OCPPRequestService.instances.get(this) as T
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
index d6f122e161b858fb952e12ca6a2d46c0c4ae7050..baab610ff72b95152b983169e7b689b5a7d6ee7e 100644 (file)
@@ -21,7 +21,7 @@ const ajvFormats = _ajvFormats.default
 const moduleName = 'OCPPResponseService'
 
 export abstract class OCPPResponseService {
-  private static instance: null | OCPPResponseService = null
+  private static readonly instances = new Map<new () => OCPPResponseService, OCPPResponseService>()
   public abstract incomingRequestResponsePayloadValidateFunctions: Map<
     IncomingRequestCommand,
     ValidateFunction<JsonType>
@@ -50,8 +50,10 @@ export abstract class OCPPResponseService {
   }
 
   public static getInstance<T extends OCPPResponseService>(this: new () => T): T {
-    OCPPResponseService.instance ??= new this()
-    return OCPPResponseService.instance as T
+    if (!OCPPResponseService.instances.has(this)) {
+      OCPPResponseService.instances.set(this, new this())
+    }
+    return OCPPResponseService.instances.get(this) as T
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters