1 import { type FSWatcher
, readFileSync
, watch
} from
'node:fs'
2 import { dirname
, join
} from
'node:path'
3 import { env
} from
'node:process'
4 import { fileURLToPath
} from
'node:url'
6 import chalk from
'chalk'
7 import merge from
'just-merge'
10 buildPerformanceUriFilePath
,
11 checkWorkerElementsPerWorker
,
12 checkWorkerProcessType
,
13 getDefaultPerformanceStorageUri
,
16 } from
'./ConfigurationUtils.js'
17 import { Constants
} from
'./Constants.js'
18 import { hasOwnProp
, isCFEnvironment
, once
} from
'./Utils.js'
21 type ConfigurationData
,
24 type LogConfiguration
,
25 type StationTemplateUrl
,
26 type StorageConfiguration
,
28 SupervisionUrlDistribution
,
29 type UIServerConfiguration
,
30 type WorkerConfiguration
31 } from
'../types/index.js'
33 DEFAULT_ELEMENT_START_DELAY
,
34 DEFAULT_POOL_MAX_SIZE
,
35 DEFAULT_POOL_MIN_SIZE
,
36 DEFAULT_WORKER_START_DELAY
,
38 } from
'../worker/index.js'
40 type ConfigurationSectionType
=
42 | StorageConfiguration
44 | UIServerConfiguration
46 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
47 export class Configuration
{
48 public static configurationChangeCallback
?: () => Promise
<void>
50 private static readonly configurationFile
= join(
51 dirname(fileURLToPath(import.meta
.url
)),
56 private static configurationFileReloading
= false
57 private static configurationData
?: ConfigurationData
58 private static configurationFileWatcher
?: FSWatcher
59 private static readonly configurationSectionCache
= new Map
<
61 ConfigurationSectionType
63 [ConfigurationSection
.log
, Configuration
.buildLogSection()],
64 [ConfigurationSection
.performanceStorage
, Configuration
.buildPerformanceStorageSection()],
65 [ConfigurationSection
.worker
, Configuration
.buildWorkerSection()],
66 [ConfigurationSection
.uiServer
, Configuration
.buildUIServerSection()]
69 private constructor () {
70 // This is intentional
73 public static getConfigurationSection
<T
extends ConfigurationSectionType
>(
74 sectionName
: ConfigurationSection
76 if (!Configuration
.isConfigurationSectionCached(sectionName
)) {
77 Configuration
.cacheConfigurationSection(sectionName
)
79 return Configuration
.configurationSectionCache
.get(sectionName
) as T
82 public static getStationTemplateUrls (): StationTemplateUrl
[] | undefined {
83 const checkDeprecatedConfigurationKeysOnce
= once(
84 Configuration
.checkDeprecatedConfigurationKeys
.bind(Configuration
),
87 checkDeprecatedConfigurationKeysOnce()
88 return Configuration
.getConfigurationData()?.stationTemplateUrls
91 public static getSupervisionUrls (): string | string[] | undefined {
93 Configuration
.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData
] !==
96 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97 Configuration
.getConfigurationData()!.supervisionUrls
= Configuration
.getConfigurationData()![
98 'supervisionURLs' as keyof ConfigurationData
99 ] as string | string[]
101 return Configuration
.getConfigurationData()?.supervisionUrls
104 public static getSupervisionUrlDistribution (): SupervisionUrlDistribution
| undefined {
105 return hasOwnProp(Configuration
.getConfigurationData(), 'supervisionUrlDistribution')
106 ? Configuration
.getConfigurationData()?.supervisionUrlDistribution
107 : SupervisionUrlDistribution
.ROUND_ROBIN
110 public static workerPoolInUse (): boolean {
111 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.fixedPool
].includes(
112 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
118 public static workerDynamicPoolInUse (): boolean {
120 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
121 .processType
=== WorkerProcessType
.dynamicPool
125 private static isConfigurationSectionCached (sectionName
: ConfigurationSection
): boolean {
126 return Configuration
.configurationSectionCache
.has(sectionName
)
129 private static cacheConfigurationSection (sectionName
: ConfigurationSection
): void {
130 switch (sectionName
) {
131 case ConfigurationSection
.log
:
132 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildLogSection())
134 case ConfigurationSection
.performanceStorage
:
135 Configuration
.configurationSectionCache
.set(
137 Configuration
.buildPerformanceStorageSection()
140 case ConfigurationSection
.worker
:
141 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildWorkerSection())
143 case ConfigurationSection
.uiServer
:
144 Configuration
.configurationSectionCache
.set(
146 Configuration
.buildUIServerSection()
150 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
151 throw new Error(`Unknown configuration section '${sectionName}'`)
155 private static buildUIServerSection (): UIServerConfiguration
{
156 let uiServerConfiguration
: UIServerConfiguration
= {
158 type: ApplicationProtocol
.WS
,
160 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
161 port
: Constants
.DEFAULT_UI_SERVER_PORT
164 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.uiServer
)) {
165 uiServerConfiguration
= merge
<UIServerConfiguration
>(
166 uiServerConfiguration
,
167 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
168 Configuration
.getConfigurationData()!.uiServer
!
171 if (isCFEnvironment()) {
172 delete uiServerConfiguration
.options
?.host
173 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
174 uiServerConfiguration
.options
!.port
= parseInt(env
.PORT
!)
176 return uiServerConfiguration
179 private static buildPerformanceStorageSection (): StorageConfiguration
{
180 let storageConfiguration
: StorageConfiguration
= {
182 type: StorageType
.JSON_FILE
,
183 uri
: getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
)
185 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.performanceStorage
)) {
186 storageConfiguration
= {
187 ...storageConfiguration
,
188 ...Configuration
.getConfigurationData()?.performanceStorage
,
189 ...(Configuration
.getConfigurationData()?.performanceStorage
?.type ===
190 StorageType
.JSON_FILE
&&
191 Configuration
.getConfigurationData()?.performanceStorage
?.uri
!= null && {
192 uri
: buildPerformanceUriFilePath(
193 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
194 new URL(Configuration
.getConfigurationData()!.performanceStorage
!.uri
!).pathname
199 return storageConfiguration
202 private static buildLogSection (): LogConfiguration
{
203 const defaultLogConfiguration
: LogConfiguration
= {
205 file
: 'logs/combined.log',
206 errorFile
: 'logs/error.log',
207 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
212 const deprecatedLogConfiguration
: LogConfiguration
= {
213 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logEnabled') && {
214 enabled
: Configuration
.getConfigurationData()?.logEnabled
216 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFile') && {
217 file
: Configuration
.getConfigurationData()?.logFile
219 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logErrorFile') && {
220 errorFile
: Configuration
.getConfigurationData()?.logErrorFile
222 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logStatisticsInterval') && {
223 statisticsInterval
: Configuration
.getConfigurationData()?.logStatisticsInterval
225 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logLevel') && {
226 level
: Configuration
.getConfigurationData()?.logLevel
228 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logConsole') && {
229 console
: Configuration
.getConfigurationData()?.logConsole
231 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFormat') && {
232 format
: Configuration
.getConfigurationData()?.logFormat
234 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logRotate') && {
235 rotate
: Configuration
.getConfigurationData()?.logRotate
237 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxFiles') && {
238 maxFiles
: Configuration
.getConfigurationData()?.logMaxFiles
240 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxSize') && {
241 maxSize
: Configuration
.getConfigurationData()?.logMaxSize
244 const logConfiguration
: LogConfiguration
= {
245 ...defaultLogConfiguration
,
246 ...deprecatedLogConfiguration
,
247 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.log
) &&
248 Configuration
.getConfigurationData()?.log
)
250 return logConfiguration
253 private static buildWorkerSection (): WorkerConfiguration
{
254 const defaultWorkerConfiguration
: WorkerConfiguration
= {
255 processType
: WorkerProcessType
.workerSet
,
256 startDelay
: DEFAULT_WORKER_START_DELAY
,
257 elementsPerWorker
: 'auto',
258 elementStartDelay
: DEFAULT_ELEMENT_START_DELAY
,
259 poolMinSize
: DEFAULT_POOL_MIN_SIZE
,
260 poolMaxSize
: DEFAULT_POOL_MAX_SIZE
262 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
263 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerProcess') && {
264 processType
: Configuration
.getConfigurationData()?.workerProcess
266 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerStartDelay') && {
267 startDelay
: Configuration
.getConfigurationData()?.workerStartDelay
269 ...(hasOwnProp(Configuration
.getConfigurationData(), 'chargingStationsPerWorker') && {
270 elementsPerWorker
: Configuration
.getConfigurationData()?.chargingStationsPerWorker
272 ...(hasOwnProp(Configuration
.getConfigurationData(), 'elementStartDelay') && {
273 elementStartDelay
: Configuration
.getConfigurationData()?.elementStartDelay
275 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMinSize') && {
276 poolMinSize
: Configuration
.getConfigurationData()?.workerPoolMinSize
278 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMaxSize') && {
279 poolMaxSize
: Configuration
.getConfigurationData()?.workerPoolMaxSize
282 hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolStrategy') &&
283 delete Configuration
.getConfigurationData()?.workerPoolStrategy
284 const workerConfiguration
: WorkerConfiguration
= {
285 ...defaultWorkerConfiguration
,
286 ...deprecatedWorkerConfiguration
,
287 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.worker
) &&
288 Configuration
.getConfigurationData()?.worker
)
290 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
291 checkWorkerProcessType(workerConfiguration
.processType
!)
292 checkWorkerElementsPerWorker(workerConfiguration
.elementsPerWorker
)
293 return workerConfiguration
296 private static checkDeprecatedConfigurationKeys (): void {
297 // connection timeout
298 Configuration
.warnDeprecatedConfigurationKey(
299 'autoReconnectTimeout',
301 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
303 Configuration
.warnDeprecatedConfigurationKey(
306 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
308 // connection retries
309 Configuration
.warnDeprecatedConfigurationKey(
310 'autoReconnectMaxRetries',
312 'Use it in charging station template instead'
314 // station template url(s)
315 Configuration
.warnDeprecatedConfigurationKey(
316 'stationTemplateURLs',
318 "Use 'stationTemplateUrls' instead"
320 Configuration
.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData
] !==
322 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
323 (Configuration
.getConfigurationData()!.stationTemplateUrls
=
324 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
325 Configuration
.getConfigurationData()![
326 'stationTemplateURLs' as keyof ConfigurationData
327 ] as StationTemplateUrl
[])
328 Configuration
.getConfigurationData()?.stationTemplateUrls
.forEach(
329 (stationTemplateUrl
: StationTemplateUrl
) => {
330 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
331 if (stationTemplateUrl
['numberOfStation' as keyof StationTemplateUrl
] !== undefined) {
333 `${chalk.green(logPrefix())} ${chalk.red(
334 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
340 // supervision url(s)
341 Configuration
.warnDeprecatedConfigurationKey(
344 "Use 'supervisionUrls' instead"
346 // supervision urls distribution
347 Configuration
.warnDeprecatedConfigurationKey(
348 'distributeStationToTenantEqually',
350 "Use 'supervisionUrlDistribution' instead"
352 Configuration
.warnDeprecatedConfigurationKey(
353 'distributeStationsToTenantsEqually',
355 "Use 'supervisionUrlDistribution' instead"
358 Configuration
.warnDeprecatedConfigurationKey(
361 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
363 Configuration
.warnDeprecatedConfigurationKey(
366 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
368 Configuration
.warnDeprecatedConfigurationKey(
371 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
373 Configuration
.warnDeprecatedConfigurationKey(
374 'chargingStationsPerWorker',
376 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
378 Configuration
.warnDeprecatedConfigurationKey(
381 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
383 Configuration
.warnDeprecatedConfigurationKey(
386 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
388 Configuration
.warnDeprecatedConfigurationKey(
391 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
393 Configuration
.warnDeprecatedConfigurationKey(
396 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
398 Configuration
.warnDeprecatedConfigurationKey(
399 'workerPoolStrategy',
401 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
403 Configuration
.warnDeprecatedConfigurationKey(
405 ConfigurationSection
.worker
,
406 'Not publicly exposed to end users'
409 Configuration
.getConfigurationData()?.worker
?.processType
===
410 ('staticPool' as WorkerProcessType
)
413 `${chalk.green(logPrefix())} ${chalk.red(
414 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
419 Configuration
.warnDeprecatedConfigurationKey(
422 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
424 Configuration
.warnDeprecatedConfigurationKey(
427 `Use '${ConfigurationSection.log}' section to define the log file instead`
429 Configuration
.warnDeprecatedConfigurationKey(
432 `Use '${ConfigurationSection.log}' section to define the log error file instead`
434 Configuration
.warnDeprecatedConfigurationKey(
437 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
439 Configuration
.warnDeprecatedConfigurationKey(
440 'logStatisticsInterval',
442 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
444 Configuration
.warnDeprecatedConfigurationKey(
447 `Use '${ConfigurationSection.log}' section to define the log level instead`
449 Configuration
.warnDeprecatedConfigurationKey(
452 `Use '${ConfigurationSection.log}' section to define the log format instead`
454 Configuration
.warnDeprecatedConfigurationKey(
457 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
459 Configuration
.warnDeprecatedConfigurationKey(
462 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
464 Configuration
.warnDeprecatedConfigurationKey(
467 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
469 // performanceStorage section
470 Configuration
.warnDeprecatedConfigurationKey(
472 ConfigurationSection
.performanceStorage
,
476 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
478 `${chalk.green(logPrefix())} ${chalk.red(
479 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
485 private static warnDeprecatedConfigurationKey (
487 sectionName
?: string,
491 sectionName
!= null &&
492 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] !==
495 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
502 `${chalk.green(logPrefix())} ${chalk.red(
503 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
504 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
509 Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
] !== undefined
512 `${chalk.green(logPrefix())} ${chalk.red(
513 `Deprecated configuration key
'${key}' usage$
{
514 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
521 private static getConfigurationData (): ConfigurationData
| undefined {
522 if (Configuration
.configurationData
== null) {
524 Configuration
.configurationData
= JSON
.parse(
525 readFileSync(Configuration
.configurationFile
, 'utf8')
526 ) as ConfigurationData
527 if (Configuration
.configurationFileWatcher
== null) {
528 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
532 Configuration
.configurationFile
,
533 FileType
.Configuration
,
534 error
as NodeJS
.ErrnoException
,
539 return Configuration
.configurationData
542 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
544 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
546 !Configuration
.configurationFileReloading
&&
547 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
548 filename
!.trim()!.length
> 0 &&
551 Configuration
.configurationFileReloading
= true
552 const consoleWarnOnce
= once(console
.warn
, this)
554 `${chalk.green(logPrefix())} ${chalk.yellow(
555 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
558 delete Configuration
.configurationData
559 Configuration
.configurationSectionCache
.clear()
560 if (Configuration
.configurationChangeCallback
!== undefined) {
561 Configuration
.configurationChangeCallback()
563 throw typeof error
=== 'string' ? new Error(error
) : error
566 Configuration
.configurationFileReloading
= false
569 Configuration
.configurationFileReloading
= false
575 Configuration
.configurationFile
,
576 FileType
.Configuration
,
577 error
as NodeJS
.ErrnoException
,