docs: refine links in READMEs
[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()
09e5a7a8
JB
62 for (const uiService of this.uiServices.values()) {
63 uiService.stop()
64 }
66a7748d 65 this.chargingStations.clear()
2f989136 66 this.chargingStationTemplates.clear()
daa6505e
JB
67 }
68
66a7748d
JB
69 public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
70 const protocolVersion = ProtocolVersion['0.0.1']
71 this.registerProtocolVersionUIService(protocolVersion)
72 return await (this.uiServices
e1d9a0f4 73 .get(protocolVersion)
66a7748d 74 ?.requestHandler(request) as Promise<ProtocolResponse>)
6bd808fd
JB
75 }
76
66a7748d
JB
77 public hasResponseHandler (id: string): boolean {
78 return this.responseHandlers.has(id)
1ca4a038
JB
79 }
80
66a7748d
JB
81 protected startHttpServer (): void {
82 if (!this.httpServer.listening) {
83 this.httpServer.listen(this.uiServerConfiguration.options)
a307349b
JB
84 }
85 }
86
66a7748d
JB
87 protected registerProtocolVersionUIService (version: ProtocolVersion): void {
88 if (!this.uiServices.has(version)) {
89 this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this))
143498c8
JB
90 }
91 }
92
66a7748d
JB
93 protected authenticate (req: IncomingMessage, next: (err?: Error) => void): void {
94 if (this.isBasicAuthEnabled()) {
95 if (!this.isValidBasicAuth(req)) {
96 next(new BaseError('Unauthorized'))
976d11ec 97 }
66a7748d 98 next()
976d11ec 99 }
66a7748d 100 next()
976d11ec
JB
101 }
102
66a7748d
JB
103 private stopHttpServer (): void {
104 if (this.httpServer.listening) {
105 this.httpServer.close()
36adaf06
JB
106 }
107 }
108
66a7748d 109 private isBasicAuthEnabled (): boolean {
eb3abc4f
JB
110 return (
111 this.uiServerConfiguration.authentication?.enabled === true &&
5199f9fd
JB
112 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
113 this.uiServerConfiguration.authentication.type === AuthenticationType.BASIC_AUTH
66a7748d 114 )
eb3abc4f
JB
115 }
116
66a7748d
JB
117 private isValidBasicAuth (req: IncomingMessage): boolean {
118 const authorizationHeader = req.headers.authorization ?? ''
119 const authorizationToken = authorizationHeader.split(/\s+/).pop() ?? ''
120 const authentication = Buffer.from(authorizationToken, 'base64').toString()
121 const authenticationParts = authentication.split(/:/)
122 const username = authenticationParts.shift()
123 const password = authenticationParts.join(':')
eb3abc4f
JB
124 return (
125 this.uiServerConfiguration.authentication?.username === username &&
126 this.uiServerConfiguration.authentication?.password === password
66a7748d 127 )
eb3abc4f
JB
128 }
129
66a7748d
JB
130 public abstract start (): void
131 public abstract sendRequest (request: ProtocolRequest): void
132 public abstract sendResponse (response: ProtocolResponse): void
133 public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
fe94fce0 134}