Commit | Line | Data |
---|---|---|
0f71040c JB |
1 | import { |
2 | ProcedureName, | |
82a77234 | 3 | type ProtocolResponse, |
0f71040c JB |
4 | type RequestPayload, |
5 | type ResponsePayload, | |
0f71040c | 6 | ResponseStatus, |
4147bb7e | 7 | } from '@/types'; |
32de5a57 | 8 | import config from '@/assets/config'; |
32de5a57 LM |
9 | |
10 | type ResponseHandler = { | |
1f7fa4de | 11 | procedureName: ProcedureName; |
32de5a57 | 12 | resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void; |
4147bb7e | 13 | reject: (reason?: unknown) => void; |
32de5a57 LM |
14 | }; |
15 | ||
8137295e | 16 | export class UIClient { |
08049dfd | 17 | private static instance: UIClient | null = null; |
32de5a57 | 18 | |
12f26d4a JB |
19 | private ws!: WebSocket; |
20 | private responseHandlers: Map<string, ResponseHandler>; | |
32de5a57 LM |
21 | |
22 | private constructor() { | |
5a010bf0 | 23 | this.openWS(); |
12f26d4a | 24 | this.responseHandlers = new Map<string, ResponseHandler>(); |
32de5a57 LM |
25 | } |
26 | ||
f27eb751 | 27 | public static getInstance() { |
08049dfd JB |
28 | if (UIClient.instance === null) { |
29 | UIClient.instance = new UIClient(); | |
32de5a57 | 30 | } |
08049dfd | 31 | return UIClient.instance; |
32de5a57 LM |
32 | } |
33 | ||
17bfa1b6 | 34 | public registerWSonOpenListener(listener: (event: Event) => void) { |
12f26d4a | 35 | this.ws.addEventListener('open', listener); |
32de5a57 LM |
36 | } |
37 | ||
5a010bf0 JB |
38 | public async startSimulator(): Promise<ResponsePayload> { |
39 | return this.sendRequest(ProcedureName.START_SIMULATOR, {}); | |
40 | } | |
41 | ||
42 | public async stopSimulator(): Promise<ResponsePayload> { | |
43 | return this.sendRequest(ProcedureName.STOP_SIMULATOR, {}); | |
44 | } | |
32de5a57 | 45 | |
5a010bf0 | 46 | public async listChargingStations(): Promise<ResponsePayload> { |
32de5a57 LM |
47 | return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {}); |
48 | } | |
49 | ||
8fc2e5cc | 50 | public async startChargingStation(hashId: string): Promise<ResponsePayload> { |
757b2ecf | 51 | return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashIds: [hashId] }); |
8fc2e5cc JB |
52 | } |
53 | ||
54 | public async stopChargingStation(hashId: string): Promise<ResponsePayload> { | |
757b2ecf | 55 | return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashIds: [hashId] }); |
8fc2e5cc JB |
56 | } |
57 | ||
58 | public async openConnection(hashId: string): Promise<ResponsePayload> { | |
59 | return this.sendRequest(ProcedureName.OPEN_CONNECTION, { | |
757b2ecf | 60 | hashIds: [hashId], |
8fc2e5cc JB |
61 | }); |
62 | } | |
63 | ||
64 | public async closeConnection(hashId: string): Promise<ResponsePayload> { | |
65 | return this.sendRequest(ProcedureName.CLOSE_CONNECTION, { | |
757b2ecf | 66 | hashIds: [hashId], |
8fc2e5cc JB |
67 | }); |
68 | } | |
69 | ||
32de5a57 LM |
70 | public async startTransaction( |
71 | hashId: string, | |
72 | connectorId: number, | |
f3095697 | 73 | idTag: string | undefined, |
32de5a57 | 74 | ): Promise<ResponsePayload> { |
32de5a57 | 75 | return this.sendRequest(ProcedureName.START_TRANSACTION, { |
757b2ecf | 76 | hashIds: [hashId], |
32de5a57 LM |
77 | connectorId, |
78 | idTag, | |
79 | }); | |
80 | } | |
81 | ||
5a010bf0 JB |
82 | public async stopTransaction( |
83 | hashId: string, | |
f3095697 | 84 | transactionId: number | undefined, |
5a010bf0 | 85 | ): Promise<ResponsePayload> { |
32de5a57 | 86 | return this.sendRequest(ProcedureName.STOP_TRANSACTION, { |
757b2ecf | 87 | hashIds: [hashId], |
32de5a57 LM |
88 | transactionId, |
89 | }); | |
90 | } | |
91 | ||
757b2ecf JB |
92 | public async startAutomaticTransactionGenerator( |
93 | hashId: string, | |
f3095697 | 94 | connectorId: number, |
757b2ecf JB |
95 | ): Promise<ResponsePayload> { |
96 | return this.sendRequest(ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, { | |
97 | hashIds: [hashId], | |
98 | connectorIds: [connectorId], | |
99 | }); | |
100 | } | |
101 | ||
102 | public async stopAutomaticTransactionGenerator( | |
103 | hashId: string, | |
f3095697 | 104 | connectorId: number, |
757b2ecf JB |
105 | ): Promise<ResponsePayload> { |
106 | return this.sendRequest(ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, { | |
107 | hashIds: [hashId], | |
108 | connectorIds: [connectorId], | |
109 | }); | |
110 | } | |
111 | ||
5a010bf0 | 112 | private openWS(): void { |
12f26d4a | 113 | this.ws = new WebSocket( |
6a619cce | 114 | `ws://${config.uiServer.host}:${config.uiServer.port}`, |
f3095697 | 115 | config.uiServer.protocol, |
5a010bf0 | 116 | ); |
12f26d4a JB |
117 | this.ws.onmessage = this.responseHandler.bind(this); |
118 | this.ws.onerror = (errorEvent) => { | |
6a619cce JB |
119 | console.error('WebSocket error: ', errorEvent); |
120 | }; | |
12f26d4a | 121 | this.ws.onclose = (closeEvent) => { |
5e3cb728 | 122 | console.info('WebSocket closed: ', closeEvent); |
a745e412 | 123 | }; |
5a010bf0 JB |
124 | } |
125 | ||
32de5a57 LM |
126 | private setResponseHandler( |
127 | id: string, | |
1f7fa4de | 128 | procedureName: ProcedureName, |
32de5a57 | 129 | resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void, |
f3095697 | 130 | reject: (reason?: unknown) => void, |
32de5a57 | 131 | ): void { |
12f26d4a | 132 | this.responseHandlers.set(id, { procedureName, resolve, reject }); |
32de5a57 LM |
133 | } |
134 | ||
135 | private getResponseHandler(id: string): ResponseHandler | undefined { | |
12f26d4a | 136 | return this.responseHandlers.get(id); |
32de5a57 LM |
137 | } |
138 | ||
1f7fa4de | 139 | private deleteResponseHandler(id: string): boolean { |
12f26d4a | 140 | return this.responseHandlers.delete(id); |
1f7fa4de JB |
141 | } |
142 | ||
757b2ecf JB |
143 | private async sendRequest( |
144 | command: ProcedureName, | |
f3095697 | 145 | data: RequestPayload, |
757b2ecf | 146 | ): Promise<ResponsePayload> { |
32de5a57 | 147 | let uuid: string; |
5b2721db | 148 | return new Promise<ResponsePayload>((resolve, reject) => { |
1b2acf4e JB |
149 | uuid = crypto.randomUUID(); |
150 | const msg = JSON.stringify([uuid, command, data]); | |
151 | ||
152 | if (this.ws.readyState !== WebSocket.OPEN) { | |
153 | this.openWS(); | |
154 | } | |
155 | if (this.ws.readyState === WebSocket.OPEN) { | |
1a32c36b | 156 | const sendTimeout = setTimeout(() => { |
1b2acf4e JB |
157 | this.deleteResponseHandler(uuid); |
158 | return reject(new Error(`Send request '${command}' message timeout`)); | |
159 | }, 60 * 1000); | |
1a32c36b JB |
160 | try { |
161 | this.ws.send(msg); | |
82fa1110 | 162 | this.setResponseHandler(uuid, command, resolve, reject); |
1a32c36b | 163 | } catch (error) { |
82fa1110 | 164 | this.deleteResponseHandler(uuid); |
1a32c36b JB |
165 | reject(error); |
166 | } finally { | |
167 | clearTimeout(sendTimeout); | |
168 | } | |
1b2acf4e JB |
169 | } else { |
170 | throw new Error(`Send request '${command}' message: connection not opened`); | |
171 | } | |
1b2acf4e | 172 | }); |
32de5a57 LM |
173 | } |
174 | ||
97f0a1a5 | 175 | private responseHandler(messageEvent: MessageEvent<string>): void { |
5e3cb728 | 176 | const response = JSON.parse(messageEvent.data) as ProtocolResponse; |
32de5a57 | 177 | |
5e3cb728 | 178 | if (Array.isArray(response) === false) { |
4ed03b6e | 179 | throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`); |
32de5a57 LM |
180 | } |
181 | ||
5e3cb728 | 182 | const [uuid, responsePayload] = response; |
32de5a57 | 183 | |
12f26d4a | 184 | if (this.responseHandlers.has(uuid) === true) { |
82fa1110 | 185 | const { procedureName, resolve, reject } = this.getResponseHandler(uuid)!; |
5e3cb728 | 186 | switch (responsePayload.status) { |
32de5a57 | 187 | case ResponseStatus.SUCCESS: |
82fa1110 | 188 | resolve(responsePayload); |
32de5a57 LM |
189 | break; |
190 | case ResponseStatus.FAILURE: | |
82fa1110 | 191 | reject(responsePayload); |
32de5a57 LM |
192 | break; |
193 | default: | |
82fa1110 JB |
194 | console.error( |
195 | `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`, | |
196 | ); | |
32de5a57 | 197 | } |
1f7fa4de | 198 | this.deleteResponseHandler(uuid); |
32de5a57 | 199 | } else { |
4ed03b6e | 200 | throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`); |
32de5a57 LM |
201 | } |
202 | } | |
203 | } |