1 import { type IncomingMessage
, Server
, type ServerResponse
} from
'node:http'
2 import { createServer
, type Http2Server
} from
'node:http2'
4 import type { WebSocket
} from
'ws'
6 import { BaseError
} from
'../../exception/index.js'
8 ApplicationProtocolVersion
,
10 type ChargingStationData
,
14 type ProtocolResponse
,
18 type UIServerConfiguration
19 } from
'../../types/index.js'
20 import { logger
} from
'../../utils/index.js'
21 import type { AbstractUIService
} from
'./ui-services/AbstractUIService.js'
22 import { UIServiceFactory
} from
'./ui-services/UIServiceFactory.js'
23 import { getUsernameAndPasswordFromAuthorizationToken
} from
'./UIServerUtils.js'
25 const moduleName
= 'AbstractUIServer'
27 export abstract class AbstractUIServer
{
28 public readonly chargingStations
: Map
<string, ChargingStationData
>
29 public readonly chargingStationTemplates
: Set
<string>
30 protected readonly httpServer
: Server
| Http2Server
31 protected readonly responseHandlers
: Map
<
32 `${string}-${string}-${string}-${string}-${string}`,
33 ServerResponse
| WebSocket
36 protected readonly uiServices
: Map
<ProtocolVersion
, AbstractUIService
>
38 public constructor (protected readonly uiServerConfiguration
: UIServerConfiguration
) {
39 this.chargingStations
= new Map
<string, ChargingStationData
>()
40 this.chargingStationTemplates
= new Set
<string>()
41 switch (this.uiServerConfiguration
.version
) {
42 case ApplicationProtocolVersion
.VERSION_11
:
43 this.httpServer
= new Server()
45 case ApplicationProtocolVersion
.VERSION_20
:
46 this.httpServer
= createServer()
50 `Unsupported application protocol version ${this.uiServerConfiguration.version} in '${ConfigurationSection.uiServer}' configuration section`
53 this.responseHandlers
= new Map
<
54 `${string}-${string}-${string}-${string}-${string}`,
55 ServerResponse
| WebSocket
57 this.uiServices
= new Map
<ProtocolVersion
, AbstractUIService
>()
60 public buildProtocolRequest (
61 uuid
: `${string}-${string}-${string}-${string}-${string}`,
62 procedureName
: ProcedureName
,
63 requestPayload
: RequestPayload
65 return [uuid
, procedureName
, requestPayload
]
68 public buildProtocolResponse (
69 uuid
: `${string}-${string}-${string}-${string}-${string}`,
70 responsePayload
: ResponsePayload
72 return [uuid
, responsePayload
]
75 public stop (): void {
77 for (const uiService
of this.uiServices
.values()) {
83 public clearCaches (): void {
84 this.chargingStations
.clear()
85 this.chargingStationTemplates
.clear()
88 public async sendInternalRequest (request
: ProtocolRequest
): Promise
<ProtocolResponse
> {
89 const protocolVersion
= ProtocolVersion
['0.0.1']
90 this.registerProtocolVersionUIService(protocolVersion
)
91 return await (this.uiServices
93 ?.requestHandler(request
) as Promise
<ProtocolResponse
>)
96 public hasResponseHandler (uuid
: `${string}-${string}-${string}-${string}-${string}`): boolean {
97 return this.responseHandlers
.has(uuid
)
100 protected startHttpServer (): void {
101 this.httpServer
.on('error', error
=> {
103 `${this.logPrefix(moduleName, 'start.httpServer.on.error')} HTTP server error:`,
107 if (!this.httpServer
.listening
) {
108 this.httpServer
.listen(this.uiServerConfiguration
.options
)
112 protected registerProtocolVersionUIService (version
: ProtocolVersion
): void {
113 if (!this.uiServices
.has(version
)) {
114 this.uiServices
.set(version
, UIServiceFactory
.getUIServiceImplementation(version
, this))
118 protected authenticate (req
: IncomingMessage
, next
: (err
?: Error) => void): void {
119 const authorizationError
= new BaseError('Unauthorized')
120 if (this.isBasicAuthEnabled()) {
121 if (!this.isValidBasicAuth(req
, next
)) {
122 next(authorizationError
)
125 } else if (this.isProtocolBasicAuthEnabled()) {
126 if (!this.isValidProtocolBasicAuth(req
, next
)) {
127 next(authorizationError
)
130 } else if (this.uiServerConfiguration
.authentication
?.enabled
=== true) {
131 next(authorizationError
)
136 private stopHttpServer (): void {
137 if (this.httpServer
.listening
) {
138 this.httpServer
.close()
139 this.httpServer
.removeAllListeners()
143 private isBasicAuthEnabled (): boolean {
145 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
146 this.uiServerConfiguration
.authentication
.type === AuthenticationType
.BASIC_AUTH
150 private isProtocolBasicAuthEnabled (): boolean {
152 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
153 this.uiServerConfiguration
.authentication
.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
157 private isValidBasicAuth (req
: IncomingMessage
, next
: (err
?: Error) => void): boolean {
158 const [username
, password
] = getUsernameAndPasswordFromAuthorizationToken(
159 req
.headers
.authorization
?.split(/\s
+/).pop() ?? '',
162 return this.isValidUsernameAndPassword(username
, password
)
165 private isValidProtocolBasicAuth (req
: IncomingMessage
, next
: (err
?: Error) => void): boolean {
166 const authorizationProtocol
= req
.headers
['sec-websocket-protocol']?.split(/,\s
+/).pop()
167 const [username
, password
] = getUsernameAndPasswordFromAuthorizationToken(
168 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169 `${authorizationProtocol}${Array(((4 - (authorizationProtocol!.length % 4)) % 4) + 1).join(
176 return this.isValidUsernameAndPassword(username
, password
)
179 private isValidUsernameAndPassword (username
: string, password
: string): boolean {
181 this.uiServerConfiguration
.authentication
?.username
=== username
&&
182 this.uiServerConfiguration
.authentication
.password
=== password
186 public abstract start (): void
187 public abstract sendRequest (request
: ProtocolRequest
): void
188 public abstract sendResponse (response
: ProtocolResponse
): void
189 public abstract logPrefix (moduleName
?: string, methodName
?: string, prefixSuffix
?: string): string