build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / AbstractUIServer.ts
1 import { type IncomingMessage, Server, type ServerResponse } from 'node:http';
2
3 import type { WebSocket } from 'ws';
4
5 import type { AbstractUIService } from './ui-services/AbstractUIService';
6 import { UIServiceFactory } from './ui-services/UIServiceFactory';
7 import { BaseError } from '../../exception';
8 import {
9 AuthenticationType,
10 type ChargingStationData,
11 type ProcedureName,
12 type ProtocolRequest,
13 type ProtocolResponse,
14 ProtocolVersion,
15 type RequestPayload,
16 type ResponsePayload,
17 type UIServerConfiguration,
18 } from '../../types';
19
20 export abstract class AbstractUIServer {
21 public readonly chargingStations: Map<string, ChargingStationData>;
22 protected readonly httpServer: Server;
23 protected readonly responseHandlers: Map<string, ServerResponse | WebSocket>;
24 protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>;
25
26 public constructor(protected readonly uiServerConfiguration: UIServerConfiguration) {
27 this.chargingStations = new Map<string, ChargingStationData>();
28 this.httpServer = new Server();
29 this.responseHandlers = new Map<string, ServerResponse | WebSocket>();
30 this.uiServices = new Map<ProtocolVersion, AbstractUIService>();
31 }
32
33 public buildProtocolRequest(
34 id: string,
35 procedureName: ProcedureName,
36 requestPayload: RequestPayload,
37 ): ProtocolRequest {
38 return [id, procedureName, requestPayload];
39 }
40
41 public buildProtocolResponse(id: string, responsePayload: ResponsePayload): ProtocolResponse {
42 return [id, responsePayload];
43 }
44
45 public stop(): void {
46 this.chargingStations.clear();
47 }
48
49 public async sendInternalRequest(request: ProtocolRequest): Promise<ProtocolResponse> {
50 const protocolVersion = ProtocolVersion['0.0.1'];
51 this.registerProtocolVersionUIService(protocolVersion);
52 return this.uiServices
53 .get(protocolVersion)
54 ?.requestHandler(request) as Promise<ProtocolResponse>;
55 }
56
57 public hasResponseHandler(id: string): boolean {
58 return this.responseHandlers.has(id);
59 }
60
61 protected startHttpServer(): void {
62 if (this.httpServer.listening === false) {
63 this.httpServer.listen(this.uiServerConfiguration.options);
64 }
65 }
66
67 protected registerProtocolVersionUIService(version: ProtocolVersion): void {
68 if (this.uiServices.has(version) === false) {
69 this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
70 }
71 }
72
73 protected authenticate(req: IncomingMessage, next: (err?: Error) => void): void {
74 if (this.isBasicAuthEnabled() === true) {
75 if (this.isValidBasicAuth(req) === false) {
76 next(new BaseError('Unauthorized'));
77 }
78 next();
79 }
80 next();
81 }
82
83 private isBasicAuthEnabled(): boolean {
84 return (
85 this.uiServerConfiguration.authentication?.enabled === true &&
86 this.uiServerConfiguration.authentication?.type === AuthenticationType.BASIC_AUTH
87 );
88 }
89
90 private isValidBasicAuth(req: IncomingMessage): boolean {
91 const authorizationHeader = req.headers.authorization ?? '';
92 const authorizationToken = authorizationHeader.split(/\s+/).pop() ?? '';
93 const authentication = Buffer.from(authorizationToken, 'base64').toString();
94 const authenticationParts = authentication.split(/:/);
95 const username = authenticationParts.shift();
96 const password = authenticationParts.join(':');
97 return (
98 this.uiServerConfiguration.authentication?.username === username &&
99 this.uiServerConfiguration.authentication?.password === password
100 );
101 }
102
103 public abstract start(): void;
104 public abstract sendRequest(request: ProtocolRequest): void;
105 public abstract sendResponse(response: ProtocolResponse): void;
106 public abstract logPrefix(
107 moduleName?: string,
108 methodName?: string,
109 prefixSuffix?: string,
110 ): string;
111 }