1 import { useToast
} from
'vue-toast-notification'
6 type ChargingStationOptions
,
12 type UIServerConfigurationSection
15 import { randomUUID
} 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
<string, ResponseHandler
>
29 private constructor(private uiServerConfiguration
: UIServerConfigurationSection
) {
31 this.responseHandlers
= new Map
<string, ResponseHandler
>()
34 public static getInstance(uiServerConfiguration
?: UIServerConfigurationSection
): UIClient
{
35 if (UIClient
.instance
=== null) {
36 if (uiServerConfiguration
== null) {
37 throw new Error('Cannot initialize UIClient if no configuration is provided')
39 UIClient
.instance
= new UIClient(uiServerConfiguration
)
41 return UIClient
.instance
44 public setConfiguration(uiServerConfiguration
: UIServerConfigurationSection
): void {
45 if (this.ws
?.readyState
=== WebSocket
.OPEN
) {
49 this.uiServerConfiguration
= uiServerConfiguration
53 public registerWSEventListener
<K
extends keyof WebSocketEventMap
>(
55 listener
: (event
: WebSocketEventMap
[K
]) => void,
56 options
?: boolean | AddEventListenerOptions
58 this.ws
?.addEventListener(event
, listener
, options
)
61 public unregisterWSEventListener
<K
extends keyof WebSocketEventMap
>(
63 listener
: (event
: WebSocketEventMap
[K
]) => void,
64 options
?: boolean | AddEventListenerOptions
66 this.ws
?.removeEventListener(event
, listener
, options
)
69 public async simulatorState(): Promise
<ResponsePayload
> {
70 return this.sendRequest(ProcedureName
.SIMULATOR_STATE
, {})
73 public async startSimulator(): Promise
<ResponsePayload
> {
74 return this.sendRequest(ProcedureName
.START_SIMULATOR
, {})
77 public async stopSimulator(): Promise
<ResponsePayload
> {
78 return this.sendRequest(ProcedureName
.STOP_SIMULATOR
, {})
81 public async listTemplates(): Promise
<ResponsePayload
> {
82 return this.sendRequest(ProcedureName
.LIST_TEMPLATES
, {})
85 public async listChargingStations(): Promise
<ResponsePayload
> {
86 return this.sendRequest(ProcedureName
.LIST_CHARGING_STATIONS
, {})
89 public async addChargingStations(
91 numberOfStations
: number,
92 options
?: ChargingStationOptions
93 ): Promise
<ResponsePayload
> {
94 return this.sendRequest(ProcedureName
.ADD_CHARGING_STATIONS
, {
101 public async deleteChargingStation(hashId
: string): Promise
<ResponsePayload
> {
102 return this.sendRequest(ProcedureName
.DELETE_CHARGING_STATIONS
, { hashIds
: [hashId
] })
105 public async setSupervisionUrl(hashId
: string, supervisionUrl
: string): Promise
<ResponsePayload
> {
106 return this.sendRequest(ProcedureName
.SET_SUPERVISION_URL
, {
112 public async startChargingStation(hashId
: string): Promise
<ResponsePayload
> {
113 return this.sendRequest(ProcedureName
.START_CHARGING_STATION
, { hashIds
: [hashId
] })
116 public async stopChargingStation(hashId
: string): Promise
<ResponsePayload
> {
117 return this.sendRequest(ProcedureName
.STOP_CHARGING_STATION
, { hashIds
: [hashId
] })
120 public async openConnection(hashId
: string): Promise
<ResponsePayload
> {
121 return this.sendRequest(ProcedureName
.OPEN_CONNECTION
, {
126 public async closeConnection(hashId
: string): Promise
<ResponsePayload
> {
127 return this.sendRequest(ProcedureName
.CLOSE_CONNECTION
, {
132 public async startTransaction(
135 idTag
: string | undefined
136 ): Promise
<ResponsePayload
> {
137 return this.sendRequest(ProcedureName
.START_TRANSACTION
, {
144 public async stopTransaction(
146 transactionId
: number | undefined
147 ): Promise
<ResponsePayload
> {
148 return this.sendRequest(ProcedureName
.STOP_TRANSACTION
, {
154 public async startAutomaticTransactionGenerator(
157 ): Promise
<ResponsePayload
> {
158 return this.sendRequest(ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
, {
160 connectorIds
: [connectorId
]
164 public async stopAutomaticTransactionGenerator(
167 ): Promise
<ResponsePayload
> {
168 return this.sendRequest(ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
, {
170 connectorIds
: [connectorId
]
174 private openWS(): void {
176 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
177 this.uiServerConfiguration
.authentication
?.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
179 `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
180 `authorization.basic.${btoa(`${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`).replace(/={1,2}$
/, '')}`
182 : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}
`
183 this.ws = new WebSocket(
184 `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}
://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
187 this.ws
.onopen
= () => {
189 `WebSocket to UI server '${this.uiServerConfiguration.host}' successfully opened`
192 this.ws
.onmessage
= this.responseHandler
.bind(this)
193 this.ws
.onerror
= errorEvent
=> {
194 useToast().error(`Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`)
196 `Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`,
200 this.ws
.onclose
= () => {
201 useToast().info(`WebSocket to UI server closed`)
205 private async sendRequest(
206 procedureName
: ProcedureName
,
207 payload
: RequestPayload
208 ): Promise
<ResponsePayload
> {
209 return new Promise
<ResponsePayload
>((resolve
, reject
) => {
210 if (this.ws
?.readyState
=== WebSocket
.OPEN
) {
211 const uuid
= randomUUID()
212 const msg
= JSON
.stringify([uuid
, procedureName
, payload
])
213 const sendTimeout
= setTimeout(() => {
214 this.responseHandlers
.delete(uuid
)
215 return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
219 this.responseHandlers
.set(uuid
, { procedureName
, resolve
, reject
})
221 this.responseHandlers
.delete(uuid
)
224 clearTimeout(sendTimeout
)
227 reject(new Error(`Send request '${procedureName}' message: connection closed`))
232 private responseHandler(messageEvent
: MessageEvent
<string>): void {
233 const response
= JSON
.parse(messageEvent
.data
) as ProtocolResponse
235 if (Array.isArray(response
) === false) {
236 throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`)
239 const [uuid
, responsePayload
] = response
241 if (this.responseHandlers
.has(uuid
) === true) {
242 const { procedureName
, resolve
, reject
} = this.responseHandlers
.get(uuid
)!
243 switch (responsePayload
.status) {
244 case ResponseStatus
.SUCCESS
:
245 resolve(responsePayload
)
247 case ResponseStatus
.FAILURE
:
248 reject(responsePayload
)
253 `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
257 this.responseHandlers
.delete(uuid
)
259 throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)