Commit | Line | Data |
---|---|---|
6c1761d4 | 1 | import type { IncomingMessage } from 'http'; |
8114d10e | 2 | |
5e3cb728 | 3 | import WebSocket, { RawData } from 'ws'; |
8114d10e | 4 | |
5e3cb728 | 5 | import BaseError from '../../exception/BaseError'; |
6c1761d4 | 6 | import type { ServerOptions } from '../../types/ConfigurationData'; |
5e3cb728 | 7 | import type { ProtocolRequest, ProtocolResponse } from '../../types/UIProtocol'; |
8b0088bb | 8 | import { WebSocketCloseEventStatusCode } from '../../types/WebSocket'; |
8114d10e | 9 | import Configuration from '../../utils/Configuration'; |
675fa8e3 | 10 | import logger from '../../utils/Logger'; |
8114d10e JB |
11 | import Utils from '../../utils/Utils'; |
12 | import { AbstractUIServer } from './AbstractUIServer'; | |
13 | import UIServiceFactory from './ui-services/UIServiceFactory'; | |
a92929f1 | 14 | import { UIServiceUtils } from './ui-services/UIServiceUtils'; |
4198ad5c | 15 | |
32de5a57 LM |
16 | const moduleName = 'UIWebSocketServer'; |
17 | ||
fe94fce0 | 18 | export default class UIWebSocketServer extends AbstractUIServer { |
b153c0fd | 19 | public constructor(options?: ServerOptions) { |
fe94fce0 | 20 | super(); |
0d8140bd | 21 | this.server = new WebSocket.Server(options ?? Configuration.getUIServer().options); |
4198ad5c JB |
22 | } |
23 | ||
24 | public start(): void { | |
5e3cb728 JB |
25 | this.server.on('connection', (ws: WebSocket, request: IncomingMessage): void => { |
26 | const [protocol, version] = UIServiceUtils.getProtocolAndVersion(ws.protocol); | |
a92929f1 JB |
27 | if (UIServiceUtils.isProtocolAndVersionSupported(protocol, version) === false) { |
28 | logger.error( | |
29 | `${this.logPrefix( | |
30 | moduleName, | |
31 | 'start.server.onconnection' | |
32 | )} Unsupported UI protocol version: '${protocol}${version}'` | |
33 | ); | |
5e3cb728 | 34 | ws.close(WebSocketCloseEventStatusCode.CLOSE_PROTOCOL_ERROR); |
a92929f1 | 35 | } |
de9136ae | 36 | if (!this.uiServices.has(version)) { |
178ac666 | 37 | this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this)); |
4198ad5c | 38 | } |
5e3cb728 JB |
39 | ws.on('message', (rawData) => { |
40 | const [messageId, procedureName, payload] = this.validateRawDataRequest(rawData); | |
e7aeea18 JB |
41 | this.uiServices |
42 | .get(version) | |
5e3cb728 | 43 | .requestHandler(this.buildProtocolRequest(messageId, procedureName, payload)) |
6c8f5d90 JB |
44 | .catch(() => { |
45 | /* Error caught by AbstractUIService */ | |
e7aeea18 | 46 | }); |
4198ad5c | 47 | }); |
5e3cb728 JB |
48 | ws.on('error', (error) => { |
49 | logger.error(`${this.logPrefix(moduleName, 'start.ws.onerror')} WebSocket error:`, error); | |
50 | }); | |
51 | ws.on('close', (code, reason) => { | |
52 | logger.debug( | |
53 | `${this.logPrefix( | |
54 | moduleName, | |
55 | 'start.ws.onclose' | |
56 | )} WebSocket closed: '${Utils.getWebSocketCloseEventStatusString( | |
57 | code | |
58 | )}' - '${reason.toString()}'` | |
32de5a57 | 59 | ); |
4198ad5c JB |
60 | }); |
61 | }); | |
62 | } | |
63 | ||
64 | public stop(): void { | |
5a010bf0 | 65 | this.chargingStations.clear(); |
4198ad5c JB |
66 | } |
67 | ||
5e3cb728 JB |
68 | public sendRequest(request: ProtocolRequest): void { |
69 | this.broadcastToClients(JSON.stringify(request)); | |
02a6943a JB |
70 | } |
71 | ||
5e3cb728 | 72 | public sendResponse(response: ProtocolResponse): void { |
db2336d9 | 73 | // TODO: send response only to the client that sent the request |
5e3cb728 | 74 | this.broadcastToClients(JSON.stringify(response)); |
178ac666 JB |
75 | } |
76 | ||
0d2cec76 JB |
77 | public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string { |
78 | const logMsgPrefix = prefixSuffix | |
79 | ? `UI WebSocket Server ${prefixSuffix}` | |
80 | : 'UI WebSocket Server'; | |
32de5a57 | 81 | const logMsg = |
0d2cec76 | 82 | modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`; |
32de5a57 | 83 | return Utils.logPrefix(logMsg); |
4198ad5c | 84 | } |
178ac666 JB |
85 | |
86 | private broadcastToClients(message: string): void { | |
0d8140bd JB |
87 | for (const client of (this.server as WebSocket.Server).clients) { |
88 | if (client?.readyState === WebSocket.OPEN) { | |
178ac666 JB |
89 | client.send(message); |
90 | } | |
91 | } | |
92 | } | |
5e3cb728 JB |
93 | |
94 | private validateRawDataRequest(rawData: RawData): ProtocolRequest { | |
95 | // logger.debug( | |
96 | // `${this.logPrefix( | |
97 | // moduleName, | |
98 | // 'validateRawDataRequest' | |
99 | // )} Raw data received in string format: ${rawData.toString()}` | |
100 | // ); | |
101 | ||
102 | const request = JSON.parse(rawData.toString()) as ProtocolRequest; | |
103 | ||
104 | if (Array.isArray(request) === false) { | |
105 | throw new BaseError('UI protocol request is not an array'); | |
106 | } | |
107 | ||
108 | if (request.length !== 3) { | |
109 | throw new BaseError('UI protocol request is malformed'); | |
110 | } | |
111 | ||
112 | return request; | |
113 | } | |
4198ad5c | 114 | } |