From 3de193f999a0750fac951e09682e860c68d88c5a Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 15 Mar 2026 23:37:22 +0100 Subject: [PATCH] fix(ocpp): use per-subclass singleton map to prevent version collision MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 keyed by the concrete subclass constructor. Each version now gets its own singleton entry. Zero changes needed in subclasses or calling code. --- .../ocpp/OCPPIncomingRequestService.ts | 12 +++++++++--- src/charging-station/ocpp/OCPPRequestService.ts | 12 +++++++++--- src/charging-station/ocpp/OCPPResponseService.ts | 8 +++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/charging-station/ocpp/OCPPIncomingRequestService.ts b/src/charging-station/ocpp/OCPPIncomingRequestService.ts index 351aef63..c73a03ce 100644 --- a/src/charging-station/ocpp/OCPPIncomingRequestService.ts +++ b/src/charging-station/ocpp/OCPPIncomingRequestService.ts @@ -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(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 diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index ce31ba90..19942fab 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -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> 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 diff --git a/src/charging-station/ocpp/OCPPResponseService.ts b/src/charging-station/ocpp/OCPPResponseService.ts index d6f122e1..baab610f 100644 --- a/src/charging-station/ocpp/OCPPResponseService.ts +++ b/src/charging-station/ocpp/OCPPResponseService.ts @@ -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 OCPPResponseService, OCPPResponseService>() public abstract incomingRequestResponsePayloadValidateFunctions: Map< IncomingRequestCommand, ValidateFunction @@ -50,8 +50,10 @@ export abstract class OCPPResponseService { } public static getInstance(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 -- 2.53.0