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 if (stationTemplateUrl
?.['numberOfStation' as keyof StationTemplateUrl
] !== undefined) {
332 `${chalk.green(logPrefix())} ${chalk.red(
333 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
339 // supervision url(s)
340 Configuration
.warnDeprecatedConfigurationKey(
343 "Use 'supervisionUrls' instead"
345 // supervision urls distribution
346 Configuration
.warnDeprecatedConfigurationKey(
347 'distributeStationToTenantEqually',
349 "Use 'supervisionUrlDistribution' instead"
351 Configuration
.warnDeprecatedConfigurationKey(
352 'distributeStationsToTenantsEqually',
354 "Use 'supervisionUrlDistribution' instead"
357 Configuration
.warnDeprecatedConfigurationKey(
360 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
362 Configuration
.warnDeprecatedConfigurationKey(
365 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
367 Configuration
.warnDeprecatedConfigurationKey(
370 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
372 Configuration
.warnDeprecatedConfigurationKey(
373 'chargingStationsPerWorker',
375 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
377 Configuration
.warnDeprecatedConfigurationKey(
380 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
382 Configuration
.warnDeprecatedConfigurationKey(
385 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
387 Configuration
.warnDeprecatedConfigurationKey(
390 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
392 Configuration
.warnDeprecatedConfigurationKey(
395 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
397 Configuration
.warnDeprecatedConfigurationKey(
398 'workerPoolStrategy',
400 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
402 Configuration
.warnDeprecatedConfigurationKey(
404 ConfigurationSection
.worker
,
405 'Not publicly exposed to end users'
408 Configuration
.getConfigurationData()?.worker
?.processType
===
409 ('staticPool' as WorkerProcessType
)
412 `${chalk.green(logPrefix())} ${chalk.red(
413 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
418 Configuration
.warnDeprecatedConfigurationKey(
421 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
423 Configuration
.warnDeprecatedConfigurationKey(
426 `Use '${ConfigurationSection.log}' section to define the log file instead`
428 Configuration
.warnDeprecatedConfigurationKey(
431 `Use '${ConfigurationSection.log}' section to define the log error file instead`
433 Configuration
.warnDeprecatedConfigurationKey(
436 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
438 Configuration
.warnDeprecatedConfigurationKey(
439 'logStatisticsInterval',
441 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
443 Configuration
.warnDeprecatedConfigurationKey(
446 `Use '${ConfigurationSection.log}' section to define the log level instead`
448 Configuration
.warnDeprecatedConfigurationKey(
451 `Use '${ConfigurationSection.log}' section to define the log format instead`
453 Configuration
.warnDeprecatedConfigurationKey(
456 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
458 Configuration
.warnDeprecatedConfigurationKey(
461 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
463 Configuration
.warnDeprecatedConfigurationKey(
466 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
468 // performanceStorage section
469 Configuration
.warnDeprecatedConfigurationKey(
471 ConfigurationSection
.performanceStorage
,
475 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
477 `${chalk.green(logPrefix())} ${chalk.red(
478 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
484 private static warnDeprecatedConfigurationKey (
486 sectionName
?: string,
490 sectionName
!= null &&
491 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] !==
494 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
498 )?.[key
] !== undefined
501 `${chalk.green(logPrefix())} ${chalk.red(
502 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
503 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
508 Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
] !== undefined
511 `${chalk.green(logPrefix())} ${chalk.red(
512 `Deprecated configuration key
'${key}' usage$
{
513 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
520 private static getConfigurationData (): ConfigurationData
| undefined {
521 if (Configuration
.configurationData
== null) {
523 Configuration
.configurationData
= JSON
.parse(
524 readFileSync(Configuration
.configurationFile
, 'utf8')
525 ) as ConfigurationData
526 if (Configuration
.configurationFileWatcher
== null) {
527 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
531 Configuration
.configurationFile
,
532 FileType
.Configuration
,
533 error
as NodeJS
.ErrnoException
,
538 return Configuration
.configurationData
541 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
543 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
545 !Configuration
.configurationFileReloading
&&
546 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
547 filename
!.trim()!.length
> 0 &&
550 Configuration
.configurationFileReloading
= true
551 const consoleWarnOnce
= once(console
.warn
, this)
553 `${chalk.green(logPrefix())} ${chalk.yellow(
554 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
557 delete Configuration
.configurationData
558 Configuration
.configurationSectionCache
.clear()
559 if (Configuration
.configurationChangeCallback
!== undefined) {
560 Configuration
.configurationChangeCallback()
562 throw typeof error
=== 'string' ? new Error(error
) : error
565 Configuration
.configurationFileReloading
= false
568 Configuration
.configurationFileReloading
= false
574 Configuration
.configurationFile
,
575 FileType
.Configuration
,
576 error
as NodeJS
.ErrnoException
,