1 import { useToast
} from
'vue-toast-notification'
5 type ChargingStationOptions
,
11 type UIServerConfigurationSection
14 type ResponseHandler
= {
15 procedureName
: ProcedureName
16 resolve
: (value
: ResponsePayload
| PromiseLike
<ResponsePayload
>) => void
17 reject
: (reason
?: unknown
) => void
20 export class UIClient
{
21 private static instance
: UIClient
| null = null
23 private ws
!: WebSocket
24 private responseHandlers
: Map
<string, ResponseHandler
>
26 private constructor(private uiServerConfiguration
: UIServerConfigurationSection
) {
28 this.responseHandlers
= new Map
<string, ResponseHandler
>()
31 public static getInstance(uiServerConfiguration
: UIServerConfigurationSection
): UIClient
{
32 if (UIClient
.instance
=== null) {
33 UIClient
.instance
= new UIClient(uiServerConfiguration
)
35 return UIClient
.instance
38 public setConfiguration(uiServerConfiguration
: UIServerConfigurationSection
): void {
40 this.uiServerConfiguration
= uiServerConfiguration
44 public registerWSEventListener
<K
extends keyof WebSocketEventMap
>(
46 listener
: (event
: WebSocketEventMap
[K
]) => void
48 this.ws
.addEventListener(event
, listener
)
51 public async startSimulator(): Promise
<ResponsePayload
> {
52 return this.sendRequest(ProcedureName
.START_SIMULATOR
, {})
55 public async stopSimulator(): Promise
<ResponsePayload
> {
56 return this.sendRequest(ProcedureName
.STOP_SIMULATOR
, {})
59 public async listTemplates(): Promise
<ResponsePayload
> {
60 return this.sendRequest(ProcedureName
.LIST_TEMPLATES
, {})
63 public async listChargingStations(): Promise
<ResponsePayload
> {
64 return this.sendRequest(ProcedureName
.LIST_CHARGING_STATIONS
, {})
67 public async addChargingStations(
69 numberOfStations
: number,
70 options
?: ChargingStationOptions
71 ): Promise
<ResponsePayload
> {
72 return this.sendRequest(ProcedureName
.ADD_CHARGING_STATIONS
, {
79 public async deleteChargingStation(hashId
: string): Promise
<ResponsePayload
> {
80 return this.sendRequest(ProcedureName
.DELETE_CHARGING_STATIONS
, { hashIds
: [hashId
] })
83 public async setSupervisionUrl(hashId
: string, supervisionUrl
: string): Promise
<ResponsePayload
> {
84 return this.sendRequest(ProcedureName
.SET_SUPERVISION_URL
, {
90 public async startChargingStation(hashId
: string): Promise
<ResponsePayload
> {
91 return this.sendRequest(ProcedureName
.START_CHARGING_STATION
, { hashIds
: [hashId
] })
94 public async stopChargingStation(hashId
: string): Promise
<ResponsePayload
> {
95 return this.sendRequest(ProcedureName
.STOP_CHARGING_STATION
, { hashIds
: [hashId
] })
98 public async openConnection(hashId
: string): Promise
<ResponsePayload
> {
99 return this.sendRequest(ProcedureName
.OPEN_CONNECTION
, {
104 public async closeConnection(hashId
: string): Promise
<ResponsePayload
> {
105 return this.sendRequest(ProcedureName
.CLOSE_CONNECTION
, {
110 public async startTransaction(
113 idTag
: string | undefined
114 ): Promise
<ResponsePayload
> {
115 return this.sendRequest(ProcedureName
.START_TRANSACTION
, {
122 public async stopTransaction(
124 transactionId
: number | undefined
125 ): Promise
<ResponsePayload
> {
126 return this.sendRequest(ProcedureName
.STOP_TRANSACTION
, {
132 public async startAutomaticTransactionGenerator(
135 ): Promise
<ResponsePayload
> {
136 return this.sendRequest(ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
, {
138 connectorIds
: [connectorId
]
142 public async stopAutomaticTransactionGenerator(
145 ): Promise
<ResponsePayload
> {
146 return this.sendRequest(ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
, {
148 connectorIds
: [connectorId
]
152 private openWS(): void {
154 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
155 this.uiServerConfiguration
.authentication
?.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
157 `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
158 `authorization.basic.${btoa(`${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`).replace(/={1,2}$
/, '')}`
160 : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}
`
161 this.ws = new WebSocket(
162 `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}
://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
165 this.ws
.onopen
= () => {
167 `WebSocket to UI server '${this.uiServerConfiguration.host}' successfully opened`
170 this.ws
.onmessage
= this.responseHandler
.bind(this)
171 this.ws
.onerror
= errorEvent
=> {
172 useToast().error(`Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`)
174 `Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`,
178 this.ws
.onclose
= () => {
179 useToast().info(`WebSocket to UI server closed`)
183 private async sendRequest(
184 procedureName
: ProcedureName
,
185 payload
: RequestPayload
186 ): Promise
<ResponsePayload
> {
187 return new Promise
<ResponsePayload
>((resolve
, reject
) => {
188 if (this.ws
.readyState
=== WebSocket
.OPEN
) {
189 const uuid
= crypto
.randomUUID()
190 const msg
= JSON
.stringify([uuid
, procedureName
, payload
])
191 const sendTimeout
= setTimeout(() => {
192 this.responseHandlers
.delete(uuid
)
193 return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
197 this.responseHandlers
.set(uuid
, { procedureName
, resolve
, reject
})
199 this.responseHandlers
.delete(uuid
)
202 clearTimeout(sendTimeout
)
205 reject(new Error(`Send request '${procedureName}' message: connection closed`))
210 private responseHandler(messageEvent
: MessageEvent
<string>): void {
211 const response
= JSON
.parse(messageEvent
.data
) as ProtocolResponse
213 if (Array.isArray(response
) === false) {
214 throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`)
217 const [uuid
, responsePayload
] = response
219 if (this.responseHandlers
.has(uuid
) === true) {
220 const { procedureName
, resolve
, reject
} = this.responseHandlers
.get(uuid
)!
221 switch (responsePayload
.status) {
222 case ResponseStatus
.SUCCESS
:
223 resolve(responsePayload
)
225 case ResponseStatus
.FAILURE
:
226 reject(responsePayload
)
231 `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
235 this.responseHandlers
.delete(uuid
)
237 throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)