build(deps): apply updates
[e-mobility-charging-stations-simulator.git] / ui / web / src / composables / UIClient.ts
CommitLineData
0f71040c 1import {
217db058 2 ApplicationProtocol,
329eab0e 3 AuthenticationType,
0f71040c 4 ProcedureName,
82a77234 5 type ProtocolResponse,
0f71040c
JB
6 type RequestPayload,
7 type ResponsePayload,
f292861c
JB
8 ResponseStatus,
9 type UIServerConfigurationSection
66a7748d 10} from '@/types'
32de5a57
LM
11
12type ResponseHandler = {
66a7748d
JB
13 procedureName: ProcedureName
14 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void
15 reject: (reason?: unknown) => void
16}
32de5a57 17
8137295e 18export class UIClient {
c25a4046 19 private static instance: UIClient | null = null
32de5a57 20
66a7748d
JB
21 private ws!: WebSocket
22 private responseHandlers: Map<string, ResponseHandler>
32de5a57 23
f292861c 24 private constructor(private uiServerConfiguration: UIServerConfigurationSection) {
66a7748d
JB
25 this.openWS()
26 this.responseHandlers = new Map<string, ResponseHandler>()
32de5a57
LM
27 }
28
c25a4046
JB
29 public static getInstance(uiServerConfiguration: UIServerConfigurationSection): UIClient {
30 if (UIClient.instance === null) {
31 UIClient.instance = new UIClient(uiServerConfiguration)
32de5a57 32 }
c25a4046 33 return UIClient.instance
32de5a57
LM
34 }
35
a4baab63
JB
36 public setConfiguration(uiServerConfiguration: UIServerConfigurationSection): void {
37 this.ws.close()
38 this.uiServerConfiguration = uiServerConfiguration
39 this.openWS()
40 }
41
ca1e5439
JB
42 public registerWSEventListener<K extends keyof WebSocketEventMap>(
43 event: K,
44 listener: (event: WebSocketEventMap[K]) => void
45 ) {
46 this.ws.addEventListener(event, listener)
32de5a57
LM
47 }
48
5a010bf0 49 public async startSimulator(): Promise<ResponsePayload> {
66a7748d 50 return this.sendRequest(ProcedureName.START_SIMULATOR, {})
5a010bf0
JB
51 }
52
53 public async stopSimulator(): Promise<ResponsePayload> {
66a7748d 54 return this.sendRequest(ProcedureName.STOP_SIMULATOR, {})
5a010bf0 55 }
32de5a57 56
c317ae3e
JB
57 public async listTemplates(): Promise<ResponsePayload> {
58 return this.sendRequest(ProcedureName.LIST_TEMPLATES, {})
a64b9a64
JB
59 }
60
5a010bf0 61 public async listChargingStations(): Promise<ResponsePayload> {
66a7748d 62 return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {})
32de5a57
LM
63 }
64
c317ae3e
JB
65 public async addChargingStations(
66 template: string,
67 numberOfStations: number
68 ): Promise<ResponsePayload> {
69 return this.sendRequest(ProcedureName.ADD_CHARGING_STATIONS, { template, numberOfStations })
70 }
71
72 public async deleteChargingStation(hashId: string): Promise<ResponsePayload> {
73 return this.sendRequest(ProcedureName.DELETE_CHARGING_STATIONS, { hashIds: [hashId] })
74 }
75
f8696170
JB
76 public async setSupervisionUrl(hashId: string, supervisionUrl: string): Promise<ResponsePayload> {
77 return this.sendRequest(ProcedureName.SET_SUPERVISION_URL, {
78 hashIds: [hashId],
79 url: supervisionUrl
80 })
81 }
82
8fc2e5cc 83 public async startChargingStation(hashId: string): Promise<ResponsePayload> {
66a7748d 84 return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashIds: [hashId] })
8fc2e5cc
JB
85 }
86
87 public async stopChargingStation(hashId: string): Promise<ResponsePayload> {
66a7748d 88 return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashIds: [hashId] })
8fc2e5cc
JB
89 }
90
91 public async openConnection(hashId: string): Promise<ResponsePayload> {
92 return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
a974c8e4 93 hashIds: [hashId]
66a7748d 94 })
8fc2e5cc
JB
95 }
96
97 public async closeConnection(hashId: string): Promise<ResponsePayload> {
98 return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
a974c8e4 99 hashIds: [hashId]
66a7748d 100 })
8fc2e5cc
JB
101 }
102
32de5a57
LM
103 public async startTransaction(
104 hashId: string,
105 connectorId: number,
66a7748d 106 idTag: string | undefined
32de5a57 107 ): Promise<ResponsePayload> {
32de5a57 108 return this.sendRequest(ProcedureName.START_TRANSACTION, {
757b2ecf 109 hashIds: [hashId],
32de5a57 110 connectorId,
a974c8e4 111 idTag
66a7748d 112 })
32de5a57
LM
113 }
114
5a010bf0
JB
115 public async stopTransaction(
116 hashId: string,
66a7748d 117 transactionId: number | undefined
5a010bf0 118 ): Promise<ResponsePayload> {
32de5a57 119 return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
757b2ecf 120 hashIds: [hashId],
a974c8e4 121 transactionId
66a7748d 122 })
32de5a57
LM
123 }
124
757b2ecf
JB
125 public async startAutomaticTransactionGenerator(
126 hashId: string,
66a7748d 127 connectorId: number
757b2ecf
JB
128 ): Promise<ResponsePayload> {
129 return this.sendRequest(ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, {
130 hashIds: [hashId],
a974c8e4 131 connectorIds: [connectorId]
66a7748d 132 })
757b2ecf
JB
133 }
134
135 public async stopAutomaticTransactionGenerator(
136 hashId: string,
66a7748d 137 connectorId: number
757b2ecf
JB
138 ): Promise<ResponsePayload> {
139 return this.sendRequest(ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, {
140 hashIds: [hashId],
a974c8e4 141 connectorIds: [connectorId]
66a7748d 142 })
757b2ecf
JB
143 }
144
9e1d6e03 145 private openWS(): void {
329eab0e 146 const protocols =
f292861c
JB
147 this.uiServerConfiguration.authentication?.enabled === true &&
148 this.uiServerConfiguration.authentication?.type === AuthenticationType.PROTOCOL_BASIC_AUTH
329eab0e 149 ? [
f292861c
JB
150 `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
151 `authorization.basic.${btoa(`${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`).replace(/={1,2}$/, '')}`
329eab0e 152 ]
f292861c 153 : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`
12f26d4a 154 this.ws = new WebSocket(
f292861c 155 `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
329eab0e 156 protocols
66a7748d 157 )
e2372e58
JB
158 this.ws.onopen = openEvent => {
159 console.info('WebSocket opened', openEvent)
160 }
66a7748d 161 this.ws.onmessage = this.responseHandler.bind(this)
a974c8e4 162 this.ws.onerror = errorEvent => {
66a7748d
JB
163 console.error('WebSocket error: ', errorEvent)
164 }
a974c8e4 165 this.ws.onclose = closeEvent => {
66a7748d
JB
166 console.info('WebSocket closed: ', closeEvent)
167 }
5a010bf0
JB
168 }
169
757b2ecf 170 private async sendRequest(
8d6f4790
JB
171 procedureName: ProcedureName,
172 payload: RequestPayload
757b2ecf 173 ): Promise<ResponsePayload> {
5b2721db 174 return new Promise<ResponsePayload>((resolve, reject) => {
1b2acf4e 175 if (this.ws.readyState === WebSocket.OPEN) {
66a7748d 176 const uuid = crypto.randomUUID()
8d6f4790 177 const msg = JSON.stringify([uuid, procedureName, payload])
1a32c36b 178 const sendTimeout = setTimeout(() => {
8d6f4790 179 this.responseHandlers.delete(uuid)
e2372e58 180 return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
9d76f5ec 181 }, 60000)
1a32c36b 182 try {
66a7748d 183 this.ws.send(msg)
8d6f4790 184 this.responseHandlers.set(uuid, { procedureName, resolve, reject })
1a32c36b 185 } catch (error) {
8d6f4790 186 this.responseHandlers.delete(uuid)
66a7748d 187 reject(error)
1a32c36b 188 } finally {
66a7748d 189 clearTimeout(sendTimeout)
1a32c36b 190 }
1b2acf4e 191 } else {
e2372e58 192 reject(new Error(`Send request '${procedureName}' message: connection closed`))
1b2acf4e 193 }
66a7748d 194 })
32de5a57
LM
195 }
196
97f0a1a5 197 private responseHandler(messageEvent: MessageEvent<string>): void {
66a7748d 198 const response = JSON.parse(messageEvent.data) as ProtocolResponse
32de5a57 199
5e3cb728 200 if (Array.isArray(response) === false) {
66a7748d 201 throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`)
32de5a57
LM
202 }
203
66a7748d 204 const [uuid, responsePayload] = response
32de5a57 205
12f26d4a 206 if (this.responseHandlers.has(uuid) === true) {
8d6f4790 207 const { procedureName, resolve, reject } = this.responseHandlers.get(uuid)!
5e3cb728 208 switch (responsePayload.status) {
32de5a57 209 case ResponseStatus.SUCCESS:
66a7748d
JB
210 resolve(responsePayload)
211 break
32de5a57 212 case ResponseStatus.FAILURE:
66a7748d
JB
213 reject(responsePayload)
214 break
32de5a57 215 default:
7c1d037f
JB
216 reject(
217 new Error(
218 `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
219 )
66a7748d 220 )
32de5a57 221 }
8d6f4790 222 this.responseHandlers.delete(uuid)
32de5a57 223 } else {
66a7748d 224 throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)
32de5a57
LM
225 }
226 }
227}