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