build: switch to NodeNext module resolution
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / AbstractUIServer.ts
CommitLineData
01f4001e 1import { type IncomingMessage, Server, type ServerResponse } from 'node:http';
a6080904 2import { type Http2Server, createServer } from 'node:http2';
daa6505e
JB
3
4import type { WebSocket } from 'ws';
fe94fce0 5
a6ef1ece
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,
268a74bb 19 type UIServerConfiguration,
a6ef1ece 20} from '../../types/index.js';
8114d10e 21
fe94fce0 22export 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}