From 25c83d205b95c11f34c5bc5091f00ca405518f36 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 11 Jan 2026 18:51:10 +0100 Subject: [PATCH] fix: harden ChargingStation null-safety --- src/charging-station/ChargingStation.ts | 74 +++++++++++++++++-------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 1ee68a9a..c729d2ae 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -292,8 +292,14 @@ export class ChargingStation extends EventEmitter { if (reservationFound != null) { await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING) } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.getConnectorStatus(reservation.connectorId)!.reservation = reservation + const connectorStatus = this.getConnectorStatus(reservation.connectorId) + if (connectorStatus == null) { + logger.error( + `${this.logPrefix()} No connector ${reservation.connectorId.toString()} found during reservation ${reservation.reservationId.toString()} addition` + ) + return + } + connectorStatus.reservation = reservation await sendAndSetConnectorStatus( this, reservation.connectorId, @@ -309,8 +315,10 @@ export class ChargingStation extends EventEmitter { } public closeWSConnection (): void { - if (this.isWebSocketConnectionOpened()) { - this.wsConnection?.close() + if (this.wsConnection != null) { + if (this.isWebSocketConnectionOpened()) { + this.wsConnection.close() + } this.wsConnection = null } } @@ -321,13 +329,31 @@ export class ChargingStation extends EventEmitter { } AutomaticTransactionGenerator.deleteInstance(this) PerformanceStatistics.deleteInstance(this.stationInfo?.hashId) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) + if (this.stationInfo != null) { + const idTagsFile = getIdTagsFile(this.stationInfo) + if (idTagsFile != null) { + this.idTagsCache.deleteIdTags(idTagsFile) + } else { + logger.warn(`${this.logPrefix()} No ID tags file found during deletion`) + } + } else { + logger.warn(`${this.logPrefix()} No station info available during deletion`) + } this.requests.clear() this.connectors.clear() this.evses.clear() + this.messageQueue.length = 0 this.templateFileWatcher?.unref() - deleteConfiguration && rmSync(this.configurationFile, { force: true }) + if (deleteConfiguration && existsSync(this.configurationFile)) { + try { + rmSync(this.configurationFile, { force: true }) + } catch (error) { + logger.error( + `${this.logPrefix()} Failed to delete configuration file ${this.configurationFile}:`, + error + ) + } + } this.chargingStationWorkerBroadcastChannel.unref() this.emitChargingStationEvent(ChargingStationEvents.deleted) this.removeAllListeners() @@ -1786,16 +1812,16 @@ export class ChargingStation extends EventEmitter { if (templateMaxEvses > 0) { for (const evseKey in stationTemplate.Evses) { const evseId = convertToInt(evseKey) - this.evses.set(evseId, { + const evseStatus: EvseStatus = { availability: AvailabilityType.Operative, connectors: buildConnectorsMap( stationTemplate.Evses[evseKey].Connectors, this.logPrefix(), this.templateFile ), - }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix()) + } + this.evses.set(evseId, evseStatus) + initializeConnectorsMapStatus(evseStatus.connectors, this.logPrefix()) } this.saveEvsesStatus() } else { @@ -2344,8 +2370,9 @@ export class ChargingStation extends EventEmitter { beginId = PerformanceStatistics.beginMeasure(commandName) } this.wsConnection?.send(message, (error?: Error) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!) + if (isRequest && commandName != null && beginId != null) { + PerformanceStatistics.endMeasure(commandName, beginId) + } if (error == null) { logger.debug( `${this.logPrefix()} >> Buffered ${getMessageTypeString(messageType)} OCPP message sent '${message}'` @@ -2353,15 +2380,16 @@ export class ChargingStation extends EventEmitter { this.messageQueue.shift() } else { logger.error( - `${this.logPrefix()} >> Buffered ${getMessageTypeString(messageType)} OCPP message '${message}' send failed:`, + `${this.logPrefix()} Error while sending buffered ${getMessageTypeString(messageType)} OCPP message '${message}':`, error ) } // eslint-disable-next-line promise/no-promise-in-callback sleep(exponentialDelay(messageIdx)) .then(() => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ++messageIdx! + if (messageIdx != null) { + ++messageIdx + } this.sendMessageBuffer(onCompleteCallback, messageIdx) return undefined }) @@ -2419,15 +2447,17 @@ export class ChargingStation extends EventEmitter { } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0) { + const connectorStatus = this.getConnectorStatus(connectorId) + if (connectorStatus == null) { + logger.error( + `${this.logPrefix()} No connector ${connectorId.toString()} status found during message sequence start` + ) + continue + } await sendAndSetConnectorStatus( this, connectorId, - getBootConnectorStatus( - this, - connectorId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.getConnectorStatus(connectorId)! - ) + getBootConnectorStatus(this, connectorId, connectorStatus) ) } } -- 2.43.0