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