build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / ui / web / src / composables / UIClient.ts
CommitLineData
0f71040c 1import {
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 11import configuration from '@/assets/config'
32de5a57
LM
12
13type ResponseHandler = {
66a7748d
JB
14 procedureName: ProcedureName
15 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void
16 reject: (reason?: unknown) => void
17}
32de5a57 18
8137295e 19export 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}