1 import { type IncomingMessage
, Server
, type ServerResponse
} from
'node:http'
2 import { type Http2Server
, createServer
} from
'node:http2'
4 import type { WebSocket
} from
'ws'
6 import type { AbstractUIService
} from
'./ui-services/AbstractUIService.js'
7 import { UIServiceFactory
} from
'./ui-services/UIServiceFactory.js'
8 import { getUsernameAndPasswordFromAuthorizationToken
} from
'./UIServerUtils.js'
9 import { BaseError
} from
'../../exception/index.js'
11 ApplicationProtocolVersion
,
13 type ChargingStationData
,
16 type ProtocolResponse
,
20 type UIServerConfiguration
21 } from
'../../types/index.js'
23 export abstract class AbstractUIServer
{
24 public readonly chargingStations
: Map
<string, ChargingStationData
>
25 public readonly chargingStationTemplates
: Set
<string>
26 protected readonly httpServer
: Server
| Http2Server
27 protected readonly responseHandlers
: Map
<string, ServerResponse
| WebSocket
>
28 protected readonly uiServices
: Map
<ProtocolVersion
, AbstractUIService
>
30 public constructor (protected readonly uiServerConfiguration
: UIServerConfiguration
) {
31 this.chargingStations
= new Map
<string, ChargingStationData
>()
32 this.chargingStationTemplates
= new Set
<string>()
33 switch (this.uiServerConfiguration
.version
) {
34 case ApplicationProtocolVersion
.VERSION_11
:
35 this.httpServer
= new Server()
37 case ApplicationProtocolVersion
.VERSION_20
:
38 this.httpServer
= createServer()
42 `Unsupported application protocol version ${this.uiServerConfiguration.version}`
45 this.responseHandlers
= new Map
<string, ServerResponse
| WebSocket
>()
46 this.uiServices
= new Map
<ProtocolVersion
, AbstractUIService
>()
49 public buildProtocolRequest (
51 procedureName
: ProcedureName
,
52 requestPayload
: RequestPayload
54 return [id
, procedureName
, requestPayload
]
57 public buildProtocolResponse (id
: string, responsePayload
: ResponsePayload
): ProtocolResponse
{
58 return [id
, responsePayload
]
61 public stop (): void {
63 for (const uiService
of this.uiServices
.values()) {
69 private clearCaches (): void {
70 this.chargingStations
.clear()
71 this.chargingStationTemplates
.clear()
74 public async sendInternalRequest (request
: ProtocolRequest
): Promise
<ProtocolResponse
> {
75 const protocolVersion
= ProtocolVersion
['0.0.1']
76 this.registerProtocolVersionUIService(protocolVersion
)
77 return await (this.uiServices
79 ?.requestHandler(request
) as Promise
<ProtocolResponse
>)
82 public hasResponseHandler (id
: string): boolean {
83 return this.responseHandlers
.has(id
)
86 protected startHttpServer (): void {
87 if (!this.httpServer
.listening
) {
88 this.httpServer
.listen(this.uiServerConfiguration
.options
)
92 protected registerProtocolVersionUIService (version
: ProtocolVersion
): void {
93 if (!this.uiServices
.has(version
)) {
94 this.uiServices
.set(version
, UIServiceFactory
.getUIServiceImplementation(version
, this))
98 protected authenticate (req
: IncomingMessage
, next
: (err
?: Error) => void): void {
99 const authorizationError
= new BaseError('Unauthorized')
100 if (this.isBasicAuthEnabled()) {
101 if (!this.isValidBasicAuth(req
, next
)) {
102 next(authorizationError
)
105 } else if (this.isProtocolBasicAuthEnabled()) {
106 if (!this.isValidProtocolBasicAuth(req
, next
)) {
107 next(authorizationError
)
110 } else if (this.uiServerConfiguration
.authentication
?.enabled
=== true) {
111 next(authorizationError
)
116 private stopHttpServer (): void {
117 if (this.httpServer
.listening
) {
118 this.httpServer
.close()
122 private isBasicAuthEnabled (): boolean {
124 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
125 this.uiServerConfiguration
.authentication
.type === AuthenticationType
.BASIC_AUTH
129 private isProtocolBasicAuthEnabled (): boolean {
131 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
132 this.uiServerConfiguration
.authentication
.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
136 private isValidBasicAuth (req
: IncomingMessage
, next
: (err
?: Error) => void): boolean {
137 const [username
, password
] = getUsernameAndPasswordFromAuthorizationToken(
138 req
.headers
.authorization
?.split(/\s
+/).pop() ?? '',
141 return this.isValidUsernameAndPassword(username
, password
)
144 private isValidProtocolBasicAuth (req
: IncomingMessage
, next
: (err
?: Error) => void): boolean {
145 const authorizationProtocol
= req
.headers
['sec-websocket-protocol']?.split(/,\s
+/).pop()
146 const [username
, password
] = getUsernameAndPasswordFromAuthorizationToken(
147 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
148 `${authorizationProtocol}${Array(((4 - (authorizationProtocol!.length % 4)) % 4) + 1).join('=')}`
153 return this.isValidUsernameAndPassword(username
, password
)
156 private isValidUsernameAndPassword (username
: string, password
: string): boolean {
158 this.uiServerConfiguration
.authentication
?.username
=== username
&&
159 this.uiServerConfiguration
.authentication
.password
=== password
163 public abstract start (): void
164 public abstract sendRequest (request
: ProtocolRequest
): void
165 public abstract sendResponse (response
: ProtocolResponse
): void
166 public abstract logPrefix (moduleName
?: string, methodName
?: string, prefixSuffix
?: string): string