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