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