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
, isUndefined
, 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 {
94 Configuration
.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData
]
97 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
98 Configuration
.getConfigurationData()!.supervisionUrls
= Configuration
.getConfigurationData()![
99 'supervisionURLs' as keyof ConfigurationData
100 ] as string | string[]
102 return Configuration
.getConfigurationData()?.supervisionUrls
105 public static getSupervisionUrlDistribution (): SupervisionUrlDistribution
| undefined {
106 return hasOwnProp(Configuration
.getConfigurationData(), 'supervisionUrlDistribution')
107 ? Configuration
.getConfigurationData()?.supervisionUrlDistribution
108 : SupervisionUrlDistribution
.ROUND_ROBIN
111 public static workerPoolInUse (): boolean {
112 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.fixedPool
].includes(
113 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
114 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
119 public static workerDynamicPoolInUse (): boolean {
121 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
122 .processType
=== WorkerProcessType
.dynamicPool
126 private static isConfigurationSectionCached (sectionName
: ConfigurationSection
): boolean {
127 return Configuration
.configurationSectionCache
.has(sectionName
)
130 private static cacheConfigurationSection (sectionName
: ConfigurationSection
): void {
131 switch (sectionName
) {
132 case ConfigurationSection
.log
:
133 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildLogSection())
135 case ConfigurationSection
.performanceStorage
:
136 Configuration
.configurationSectionCache
.set(
138 Configuration
.buildPerformanceStorageSection()
141 case ConfigurationSection
.worker
:
142 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildWorkerSection())
144 case ConfigurationSection
.uiServer
:
145 Configuration
.configurationSectionCache
.set(
147 Configuration
.buildUIServerSection()
151 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
152 throw new Error(`Unknown configuration section '${sectionName}'`)
156 private static buildUIServerSection (): UIServerConfiguration
{
157 let uiServerConfiguration
: UIServerConfiguration
= {
159 type: ApplicationProtocol
.WS
,
161 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
162 port
: Constants
.DEFAULT_UI_SERVER_PORT
165 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.uiServer
)) {
166 uiServerConfiguration
= merge
<UIServerConfiguration
>(
167 uiServerConfiguration
,
168 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169 Configuration
.getConfigurationData()!.uiServer
!
172 if (isCFEnvironment()) {
173 delete uiServerConfiguration
.options
?.host
174 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
175 uiServerConfiguration
.options
!.port
= parseInt(env
.PORT
!)
177 return uiServerConfiguration
180 private static buildPerformanceStorageSection (): StorageConfiguration
{
181 let storageConfiguration
: StorageConfiguration
= {
183 type: StorageType
.JSON_FILE
,
184 uri
: getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
)
186 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.performanceStorage
)) {
187 storageConfiguration
= {
188 ...storageConfiguration
,
189 ...Configuration
.getConfigurationData()?.performanceStorage
,
190 ...(Configuration
.getConfigurationData()?.performanceStorage
?.type ===
191 StorageType
.JSON_FILE
&&
192 Configuration
.getConfigurationData()?.performanceStorage
?.uri
!= null && {
193 uri
: buildPerformanceUriFilePath(
194 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
195 new URL(Configuration
.getConfigurationData()!.performanceStorage
!.uri
!).pathname
200 return storageConfiguration
203 private static buildLogSection (): LogConfiguration
{
204 const defaultLogConfiguration
: LogConfiguration
= {
206 file
: 'logs/combined.log',
207 errorFile
: 'logs/error.log',
208 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
213 const deprecatedLogConfiguration
: LogConfiguration
= {
214 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logEnabled') && {
215 enabled
: Configuration
.getConfigurationData()?.logEnabled
217 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFile') && {
218 file
: Configuration
.getConfigurationData()?.logFile
220 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logErrorFile') && {
221 errorFile
: Configuration
.getConfigurationData()?.logErrorFile
223 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logStatisticsInterval') && {
224 statisticsInterval
: Configuration
.getConfigurationData()?.logStatisticsInterval
226 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logLevel') && {
227 level
: Configuration
.getConfigurationData()?.logLevel
229 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logConsole') && {
230 console
: Configuration
.getConfigurationData()?.logConsole
232 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFormat') && {
233 format
: Configuration
.getConfigurationData()?.logFormat
235 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logRotate') && {
236 rotate
: Configuration
.getConfigurationData()?.logRotate
238 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxFiles') && {
239 maxFiles
: Configuration
.getConfigurationData()?.logMaxFiles
241 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxSize') && {
242 maxSize
: Configuration
.getConfigurationData()?.logMaxSize
245 const logConfiguration
: LogConfiguration
= {
246 ...defaultLogConfiguration
,
247 ...deprecatedLogConfiguration
,
248 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.log
) &&
249 Configuration
.getConfigurationData()?.log
)
251 return logConfiguration
254 private static buildWorkerSection (): WorkerConfiguration
{
255 const defaultWorkerConfiguration
: WorkerConfiguration
= {
256 processType
: WorkerProcessType
.workerSet
,
257 startDelay
: DEFAULT_WORKER_START_DELAY
,
258 elementsPerWorker
: 'auto',
259 elementStartDelay
: DEFAULT_ELEMENT_START_DELAY
,
260 poolMinSize
: DEFAULT_POOL_MIN_SIZE
,
261 poolMaxSize
: DEFAULT_POOL_MAX_SIZE
263 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
264 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerProcess') && {
265 processType
: Configuration
.getConfigurationData()?.workerProcess
267 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerStartDelay') && {
268 startDelay
: Configuration
.getConfigurationData()?.workerStartDelay
270 ...(hasOwnProp(Configuration
.getConfigurationData(), 'chargingStationsPerWorker') && {
271 elementsPerWorker
: Configuration
.getConfigurationData()?.chargingStationsPerWorker
273 ...(hasOwnProp(Configuration
.getConfigurationData(), 'elementStartDelay') && {
274 elementStartDelay
: Configuration
.getConfigurationData()?.elementStartDelay
276 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMinSize') && {
277 poolMinSize
: Configuration
.getConfigurationData()?.workerPoolMinSize
279 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMaxSize') && {
280 poolMaxSize
: Configuration
.getConfigurationData()?.workerPoolMaxSize
283 hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolStrategy') &&
284 delete Configuration
.getConfigurationData()?.workerPoolStrategy
285 const workerConfiguration
: WorkerConfiguration
= {
286 ...defaultWorkerConfiguration
,
287 ...deprecatedWorkerConfiguration
,
288 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.worker
) &&
289 Configuration
.getConfigurationData()?.worker
)
291 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
292 checkWorkerProcessType(workerConfiguration
.processType
!)
293 checkWorkerElementsPerWorker(workerConfiguration
.elementsPerWorker
)
294 return workerConfiguration
297 private static checkDeprecatedConfigurationKeys (): void {
298 // connection timeout
299 Configuration
.warnDeprecatedConfigurationKey(
300 'autoReconnectTimeout',
302 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
304 Configuration
.warnDeprecatedConfigurationKey(
307 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
309 // connection retries
310 Configuration
.warnDeprecatedConfigurationKey(
311 'autoReconnectMaxRetries',
313 'Use it in charging station template instead'
315 // station template url(s)
316 Configuration
.warnDeprecatedConfigurationKey(
317 'stationTemplateURLs',
319 "Use 'stationTemplateUrls' instead"
322 Configuration
.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData
]
324 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
325 (Configuration
.getConfigurationData()!.stationTemplateUrls
=
326 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
327 Configuration
.getConfigurationData()![
328 'stationTemplateURLs' as keyof ConfigurationData
329 ] as StationTemplateUrl
[])
330 Configuration
.getConfigurationData()?.stationTemplateUrls
.forEach(
331 (stationTemplateUrl
: StationTemplateUrl
) => {
332 if (!isUndefined(stationTemplateUrl
?.['numberOfStation' as keyof StationTemplateUrl
])) {
334 `${chalk.green(logPrefix())} ${chalk.red(
335 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
341 // supervision url(s)
342 Configuration
.warnDeprecatedConfigurationKey(
345 "Use 'supervisionUrls' instead"
347 // supervision urls distribution
348 Configuration
.warnDeprecatedConfigurationKey(
349 'distributeStationToTenantEqually',
351 "Use 'supervisionUrlDistribution' instead"
353 Configuration
.warnDeprecatedConfigurationKey(
354 'distributeStationsToTenantsEqually',
356 "Use 'supervisionUrlDistribution' instead"
359 Configuration
.warnDeprecatedConfigurationKey(
362 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
364 Configuration
.warnDeprecatedConfigurationKey(
367 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
369 Configuration
.warnDeprecatedConfigurationKey(
372 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
374 Configuration
.warnDeprecatedConfigurationKey(
375 'chargingStationsPerWorker',
377 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
379 Configuration
.warnDeprecatedConfigurationKey(
382 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
384 Configuration
.warnDeprecatedConfigurationKey(
387 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
389 Configuration
.warnDeprecatedConfigurationKey(
392 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
394 Configuration
.warnDeprecatedConfigurationKey(
397 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
399 Configuration
.warnDeprecatedConfigurationKey(
400 'workerPoolStrategy',
402 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
404 Configuration
.warnDeprecatedConfigurationKey(
406 ConfigurationSection
.worker
,
407 'Not publicly exposed to end users'
410 Configuration
.getConfigurationData()?.worker
?.processType
===
411 ('staticPool' as WorkerProcessType
)
414 `${chalk.green(logPrefix())} ${chalk.red(
415 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
420 Configuration
.warnDeprecatedConfigurationKey(
423 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
425 Configuration
.warnDeprecatedConfigurationKey(
428 `Use '${ConfigurationSection.log}' section to define the log file instead`
430 Configuration
.warnDeprecatedConfigurationKey(
433 `Use '${ConfigurationSection.log}' section to define the log error file instead`
435 Configuration
.warnDeprecatedConfigurationKey(
438 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
440 Configuration
.warnDeprecatedConfigurationKey(
441 'logStatisticsInterval',
443 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
445 Configuration
.warnDeprecatedConfigurationKey(
448 `Use '${ConfigurationSection.log}' section to define the log level instead`
450 Configuration
.warnDeprecatedConfigurationKey(
453 `Use '${ConfigurationSection.log}' section to define the log format instead`
455 Configuration
.warnDeprecatedConfigurationKey(
458 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
460 Configuration
.warnDeprecatedConfigurationKey(
463 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
465 Configuration
.warnDeprecatedConfigurationKey(
468 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
470 // performanceStorage section
471 Configuration
.warnDeprecatedConfigurationKey(
473 ConfigurationSection
.performanceStorage
,
477 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
479 `${chalk.green(logPrefix())} ${chalk.red(
480 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
486 private static warnDeprecatedConfigurationKey (
488 sectionName
?: string,
492 sectionName
!= null &&
494 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
]
498 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
506 `${chalk.green(logPrefix())} ${chalk.red(
507 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
508 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
513 !isUndefined(Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
])
516 `${chalk.green(logPrefix())} ${chalk.red(
517 `Deprecated configuration key
'${key}' usage$
{
518 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
525 private static getConfigurationData (): ConfigurationData
| undefined {
526 if (Configuration
.configurationData
== null) {
528 Configuration
.configurationData
= JSON
.parse(
529 readFileSync(Configuration
.configurationFile
, 'utf8')
530 ) as ConfigurationData
531 if (Configuration
.configurationFileWatcher
== null) {
532 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
536 Configuration
.configurationFile
,
537 FileType
.Configuration
,
538 error
as NodeJS
.ErrnoException
,
543 return Configuration
.configurationData
546 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
548 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
550 !Configuration
.configurationFileReloading
&&
551 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
552 filename
!.trim()!.length
> 0 &&
555 Configuration
.configurationFileReloading
= true
556 const consoleWarnOnce
= once(console
.warn
, this)
558 `${chalk.green(logPrefix())} ${chalk.yellow(
559 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
562 delete Configuration
.configurationData
563 Configuration
.configurationSectionCache
.clear()
564 if (!isUndefined(Configuration
.configurationChangeCallback
)) {
565 Configuration
.configurationChangeCallback()
567 throw typeof error
=== 'string' ? new Error(error
) : error
570 Configuration
.configurationFileReloading
= false
573 Configuration
.configurationFileReloading
= false
579 Configuration
.configurationFile
,
580 FileType
.Configuration
,
581 error
as NodeJS
.ErrnoException
,