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