1 import { useToast
} from
'vue-toast-notification'
6 type ChargingStationOptions
,
12 type UIServerConfigurationSection
15 import { randomUUID
, validateUUID
} from
'./Utils'
17 type ResponseHandler
= {
18 procedureName
: ProcedureName
19 resolve
: (value
: ResponsePayload
| PromiseLike
<ResponsePayload
>) => void
20 reject
: (reason
?: unknown
) => void
23 export class UIClient
{
24 private static instance
: UIClient
| null = null
26 private ws
?: WebSocket
27 private responseHandlers
: Map
<
28 `${string}-${string}-${string}-${string}-${string}`,
32 private constructor(private uiServerConfiguration
: UIServerConfigurationSection
) {
34 this.responseHandlers
= new Map
<
35 `${string}-${string}-${string}-${string}-${string}`,
40 public static getInstance(uiServerConfiguration
?: UIServerConfigurationSection
): UIClient
{
41 if (UIClient
.instance
=== null) {
42 if (uiServerConfiguration
== null) {
43 throw new Error('Cannot initialize UIClient if no configuration is provided')
45 UIClient
.instance
= new UIClient(uiServerConfiguration
)
47 return UIClient
.instance
50 public setConfiguration(uiServerConfiguration
: UIServerConfigurationSection
): void {
51 if (this.ws
?.readyState
=== WebSocket
.OPEN
) {
55 this.uiServerConfiguration
= uiServerConfiguration
59 public registerWSEventListener
<K
extends keyof WebSocketEventMap
>(
61 listener
: (event
: WebSocketEventMap
[K
]) => void,
62 options
?: boolean | AddEventListenerOptions
64 this.ws
?.addEventListener(event
, listener
, options
)
67 public unregisterWSEventListener
<K
extends keyof WebSocketEventMap
>(
69 listener
: (event
: WebSocketEventMap
[K
]) => void,
70 options
?: boolean | AddEventListenerOptions
72 this.ws
?.removeEventListener(event
, listener
, options
)
75 public async simulatorState(): Promise
<ResponsePayload
> {
76 return this.sendRequest(ProcedureName
.SIMULATOR_STATE
, {})
79 public async startSimulator(): Promise
<ResponsePayload
> {
80 return this.sendRequest(ProcedureName
.START_SIMULATOR
, {})
83 public async stopSimulator(): Promise
<ResponsePayload
> {
84 return this.sendRequest(ProcedureName
.STOP_SIMULATOR
, {})
87 public async listTemplates(): Promise
<ResponsePayload
> {
88 return this.sendRequest(ProcedureName
.LIST_TEMPLATES
, {})
91 public async listChargingStations(): Promise
<ResponsePayload
> {
92 return this.sendRequest(ProcedureName
.LIST_CHARGING_STATIONS
, {})
95 public async addChargingStations(
97 numberOfStations
: number,
98 options
?: ChargingStationOptions
99 ): Promise
<ResponsePayload
> {
100 return this.sendRequest(ProcedureName
.ADD_CHARGING_STATIONS
, {
107 public async deleteChargingStation(hashId
: string): Promise
<ResponsePayload
> {
108 return this.sendRequest(ProcedureName
.DELETE_CHARGING_STATIONS
, {
113 public async setSupervisionUrl(hashId
: string, supervisionUrl
: string): Promise
<ResponsePayload
> {
114 return this.sendRequest(ProcedureName
.SET_SUPERVISION_URL
, {
120 public async startChargingStation(hashId
: string): Promise
<ResponsePayload
> {
121 return this.sendRequest(ProcedureName
.START_CHARGING_STATION
, {
126 public async stopChargingStation(hashId
: string): Promise
<ResponsePayload
> {
127 return this.sendRequest(ProcedureName
.STOP_CHARGING_STATION
, {
132 public async openConnection(hashId
: string): Promise
<ResponsePayload
> {
133 return this.sendRequest(ProcedureName
.OPEN_CONNECTION
, {
138 public async closeConnection(hashId
: string): Promise
<ResponsePayload
> {
139 return this.sendRequest(ProcedureName
.CLOSE_CONNECTION
, {
144 public async startTransaction(
147 idTag
: string | undefined
148 ): Promise
<ResponsePayload
> {
149 return this.sendRequest(ProcedureName
.START_TRANSACTION
, {
156 public async stopTransaction(
158 transactionId
: number | undefined
159 ): Promise
<ResponsePayload
> {
160 return this.sendRequest(ProcedureName
.STOP_TRANSACTION
, {
166 public async startAutomaticTransactionGenerator(
169 ): Promise
<ResponsePayload
> {
170 return this.sendRequest(ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
, {
172 connectorIds
: [connectorId
]
176 public async stopAutomaticTransactionGenerator(
179 ): Promise
<ResponsePayload
> {
180 return this.sendRequest(ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
, {
182 connectorIds
: [connectorId
]
186 private openWS(): void {
188 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
189 this.uiServerConfiguration
.authentication
?.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
191 `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
192 `authorization.basic.${btoa(
193 `${this.uiServerConfiguration.authentication.username}
:${this.uiServerConfiguration.authentication.password}
`
194 ).replace(/={1,2}$/, '')}`
196 : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`
197 this.ws
= new WebSocket(
199 this.uiServerConfiguration.secure === true
200 ? ApplicationProtocol.WSS
201 : ApplicationProtocol.WS
202 }://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
205 this.ws
.onopen
= () => {
207 `WebSocket to UI server '${this.uiServerConfiguration.host}' successfully opened`
210 this.ws
.onmessage
= this.responseHandler
.bind(this)
211 this.ws
.onerror
= errorEvent
=> {
212 useToast().error(`Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`)
214 `Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`,
218 this.ws
.onclose
= () => {
219 useToast().info(`WebSocket to UI server closed`)
223 private async sendRequest(
224 procedureName
: ProcedureName
,
225 payload
: RequestPayload
226 ): Promise
<ResponsePayload
> {
227 return new Promise
<ResponsePayload
>((resolve
, reject
) => {
228 if (this.ws
?.readyState
=== WebSocket
.OPEN
) {
229 const uuid
= randomUUID()
230 const msg
= JSON
.stringify([uuid
, procedureName
, payload
])
231 const sendTimeout
= setTimeout(() => {
232 this.responseHandlers
.delete(uuid
)
233 return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
237 this.responseHandlers
.set(uuid
, { procedureName
, resolve
, reject
})
239 this.responseHandlers
.delete(uuid
)
242 clearTimeout(sendTimeout
)
245 reject(new Error(`Send request '${procedureName}' message: connection closed`))
250 private responseHandler(messageEvent
: MessageEvent
<string>): void {
251 let response
: ProtocolResponse
253 response
= JSON
.parse(messageEvent
.data
)
255 useToast().error('Invalid response JSON format')
256 console
.error('Invalid response JSON format', error
)
260 if (!Array.isArray(response
)) {
261 useToast().error('Response not an array')
262 console
.error('Response not an array:', response
)
266 const [uuid
, responsePayload
] = response
268 if (!validateUUID(uuid
)) {
269 useToast().error('Response UUID field is invalid')
270 console
.error('Response UUID field is invalid:', response
)
274 if (this.responseHandlers
.has(uuid
)) {
275 const { procedureName
, resolve
, reject
} = this.responseHandlers
.get(uuid
)!
276 switch (responsePayload
.status) {
277 case ResponseStatus
.SUCCESS
:
278 resolve(responsePayload
)
280 case ResponseStatus
.FAILURE
:
281 reject(responsePayload
)
286 `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
290 this.responseHandlers
.delete(uuid
)
292 throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)