refactor: consolidate default values handling
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / AbstractUIServer.ts
CommitLineData
66a7748d
JB
1import { type IncomingMessage, Server, type ServerResponse } from 'node:http'
2import { type Http2Server, createServer } from 'node:http2'
daa6505e 3
66a7748d 4import type { WebSocket } from 'ws'
fe94fce0 5
66a7748d
JB
6import type { AbstractUIService } from './ui-services/AbstractUIService.js'
7import { UIServiceFactory } from './ui-services/UIServiceFactory.js'
8import { BaseError } from '../../exception/index.js'
eb3abc4f 9import {
a6080904 10 ApplicationProtocolVersion,
eb3abc4f 11 AuthenticationType,
268a74bb 12 type ChargingStationData,
e0b0ee21
JB
13 type ProcedureName,
14 type ProtocolRequest,
15 type ProtocolResponse,
f130b8e6 16 ProtocolVersion,
e0b0ee21
JB
17 type RequestPayload,
18 type ResponsePayload,
66a7748d
JB
19 type UIServerConfiguration
20} from '../../types/index.js'
8114d10e 21
fe94fce0 22export abstract class AbstractUIServer {
66a7748d 23 public readonly chargingStations: Map<string, ChargingStationData>
2f989136 24 public readonly chargingStationTemplates: Set<string>
66a7748d
JB
25 protected readonly httpServer: Server | Http2Server
26 protected readonly responseHandlers: Map<string, ServerResponse | WebSocket>
27 protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
fe94fce0 28
66a7748d
JB
29 public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
30 this.chargingStations = new Map<string, ChargingStationData>()
2f989136 31 this.chargingStationTemplates = new Set<string>()
a6080904
JB
32 switch (this.uiServerConfiguration.version) {
33 case ApplicationProtocolVersion.VERSION_11:
66a7748d
JB
34 this.httpServer = new Server()
35 break
a6080904 36 case ApplicationProtocolVersion.VERSION_20:
66a7748d
JB
37 this.httpServer = createServer()
38 break
a6080904
JB
39 default:
40 throw new BaseError(
66a7748d
JB
41 `Unsupported application protocol version ${this.uiServerConfiguration.version}`
42 )
a6080904 43 }
66a7748d
JB
44 this.responseHandlers = new Map<string, ServerResponse | WebSocket>()
45 this.uiServices = new Map<ProtocolVersion, AbstractUIService>()
fe94fce0
JB
46 }
47
66a7748d 48 public buildProtocolRequest (
852a4c5f
JB
49 id: string,
50 procedureName: ProcedureName,
66a7748d 51 requestPayload: RequestPayload
5e3cb728 52 ): ProtocolRequest {
66a7748d 53 return [id, procedureName, requestPayload]
852a4c5f
JB
54 }
55
66a7748d
JB
56 public buildProtocolResponse (id: string, responsePayload: ResponsePayload): ProtocolResponse {
57 return [id, responsePayload]
852a4c5f
JB
58 }
59
66a7748d
JB
60 public stop (): void {
61 this.stopHttpServer()
62 this.chargingStations.clear()
2f989136 63 this.chargingStationTemplates.clear()
daa6505e
JB
64 }
65
66a7748d
JB
66 public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
67 const protocolVersion = ProtocolVersion['0.0.1']
68 this.registerProtocolVersionUIService(protocolVersion)
69 return await (this.uiServices
e1d9a0f4 70 .get(protocolVersion)
66a7748d 71 ?.requestHandler(request) as Promise<ProtocolResponse>)
6bd808fd
JB
72 }
73
66a7748d
JB
74 public hasResponseHandler (id: string): boolean {
75 return this.responseHandlers.has(id)
1ca4a038
JB
76 }
77
66a7748d
JB
78 protected startHttpServer (): void {
79 if (!this.httpServer.listening) {
80 this.httpServer.listen(this.uiServerConfiguration.options)
a307349b
JB
81 }
82 }
83
66a7748d
JB
84 protected registerProtocolVersionUIService (version: ProtocolVersion): void {
85 if (!this.uiServices.has(version)) {
86 this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this))
143498c8
JB
87 }
88 }
89
66a7748d
JB
90 protected authenticate (req: IncomingMessage, next: (err?: Error) => void): void {
91 if (this.isBasicAuthEnabled()) {
92 if (!this.isValidBasicAuth(req)) {
93 next(new BaseError('Unauthorized'))
976d11ec 94 }
66a7748d 95 next()
976d11ec 96 }
66a7748d 97 next()
976d11ec
JB
98 }
99
66a7748d
JB
100 private stopHttpServer (): void {
101 if (this.httpServer.listening) {
102 this.httpServer.close()
36adaf06
JB
103 }
104 }
105
66a7748d 106 private isBasicAuthEnabled (): boolean {
eb3abc4f
JB
107 return (
108 this.uiServerConfiguration.authentication?.enabled === true &&
5199f9fd
JB
109 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
110 this.uiServerConfiguration.authentication.type === AuthenticationType.BASIC_AUTH
66a7748d 111 )
eb3abc4f
JB
112 }
113
66a7748d
JB
114 private isValidBasicAuth (req: IncomingMessage): boolean {
115 const authorizationHeader = req.headers.authorization ?? ''
116 const authorizationToken = authorizationHeader.split(/\s+/).pop() ?? ''
117 const authentication = Buffer.from(authorizationToken, 'base64').toString()
118 const authenticationParts = authentication.split(/:/)
119 const username = authenticationParts.shift()
120 const password = authenticationParts.join(':')
eb3abc4f
JB
121 return (
122 this.uiServerConfiguration.authentication?.username === username &&
123 this.uiServerConfiguration.authentication?.password === password
66a7748d 124 )
eb3abc4f
JB
125 }
126
66a7748d
JB
127 public abstract start (): void
128 public abstract sendRequest (request: ProtocolRequest): void
129 public abstract sendResponse (response: ProtocolResponse): void
130 public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
fe94fce0 131}