9 type UIServerConfigurationSection
12 type ResponseHandler
= {
13 procedureName
: ProcedureName
14 resolve
: (value
: ResponsePayload
| PromiseLike
<ResponsePayload
>) => void
15 reject
: (reason
?: unknown
) => void
18 export class UIClient
{
19 private static readonly instances
: Map
<number, UIClient
> = new Map
<number, UIClient
>()
21 private ws
!: WebSocket
22 private responseHandlers
: Map
<string, ResponseHandler
>
24 private constructor(private uiServerConfiguration
: UIServerConfigurationSection
) {
26 this.responseHandlers
= new Map
<string, ResponseHandler
>()
29 public static getInstance(
31 uiServerConfiguration
?: UIServerConfigurationSection
33 if (!UIClient
.instances
.has(serverId
) && uiServerConfiguration
!= null) {
34 UIClient
.instances
.set(serverId
, new UIClient(uiServerConfiguration
))
35 } else if (!UIClient
.instances
.has(serverId
)) {
36 throw new Error(`UI client instance not found for server id: ${serverId}`)
38 return UIClient
.instances
.get(serverId
)!
41 public registerWSEventListener
<K
extends keyof WebSocketEventMap
>(
43 listener
: (event
: WebSocketEventMap
[K
]) => void
45 this.ws
.addEventListener(event
, listener
)
48 public async startSimulator(): Promise
<ResponsePayload
> {
49 return this.sendRequest(ProcedureName
.START_SIMULATOR
, {})
52 public async stopSimulator(): Promise
<ResponsePayload
> {
53 return this.sendRequest(ProcedureName
.STOP_SIMULATOR
, {})
56 public async listTemplates(): Promise
<ResponsePayload
> {
57 return this.sendRequest(ProcedureName
.LIST_TEMPLATES
, {})
60 public async listChargingStations(): Promise
<ResponsePayload
> {
61 return this.sendRequest(ProcedureName
.LIST_CHARGING_STATIONS
, {})
64 public async addChargingStations(
66 numberOfStations
: number
67 ): Promise
<ResponsePayload
> {
68 return this.sendRequest(ProcedureName
.ADD_CHARGING_STATIONS
, { template
, numberOfStations
})
71 public async deleteChargingStation(hashId
: string): Promise
<ResponsePayload
> {
72 return this.sendRequest(ProcedureName
.DELETE_CHARGING_STATIONS
, { hashIds
: [hashId
] })
75 public async setSupervisionUrl(hashId
: string, supervisionUrl
: string): Promise
<ResponsePayload
> {
76 return this.sendRequest(ProcedureName
.SET_SUPERVISION_URL
, {
82 public async startChargingStation(hashId
: string): Promise
<ResponsePayload
> {
83 return this.sendRequest(ProcedureName
.START_CHARGING_STATION
, { hashIds
: [hashId
] })
86 public async stopChargingStation(hashId
: string): Promise
<ResponsePayload
> {
87 return this.sendRequest(ProcedureName
.STOP_CHARGING_STATION
, { hashIds
: [hashId
] })
90 public async openConnection(hashId
: string): Promise
<ResponsePayload
> {
91 return this.sendRequest(ProcedureName
.OPEN_CONNECTION
, {
96 public async closeConnection(hashId
: string): Promise
<ResponsePayload
> {
97 return this.sendRequest(ProcedureName
.CLOSE_CONNECTION
, {
102 public async startTransaction(
105 idTag
: string | undefined
106 ): Promise
<ResponsePayload
> {
107 return this.sendRequest(ProcedureName
.START_TRANSACTION
, {
114 public async stopTransaction(
116 transactionId
: number | undefined
117 ): Promise
<ResponsePayload
> {
118 return this.sendRequest(ProcedureName
.STOP_TRANSACTION
, {
124 public async startAutomaticTransactionGenerator(
127 ): Promise
<ResponsePayload
> {
128 return this.sendRequest(ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
, {
130 connectorIds
: [connectorId
]
134 public async stopAutomaticTransactionGenerator(
137 ): Promise
<ResponsePayload
> {
138 return this.sendRequest(ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
, {
140 connectorIds
: [connectorId
]
144 private openWS(): void {
146 this.uiServerConfiguration
.authentication
?.enabled
=== true &&
147 this.uiServerConfiguration
.authentication
?.type === AuthenticationType
.PROTOCOL_BASIC_AUTH
149 `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
150 `authorization.basic.${btoa(`${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`).replace(/={1,2}$
/, '')}`
152 : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}
`
153 this.ws = new WebSocket(
154 `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}
://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
157 this.ws
.onopen
= openEvent
=> {
158 console
.info('WebSocket opened', openEvent
)
160 this.ws
.onmessage
= this.responseHandler
.bind(this)
161 this.ws
.onerror
= errorEvent
=> {
162 console
.error('WebSocket error: ', errorEvent
)
164 this.ws
.onclose
= closeEvent
=> {
165 console
.info('WebSocket closed: ', closeEvent
)
169 private async sendRequest(
170 procedureName
: ProcedureName
,
171 payload
: RequestPayload
172 ): Promise
<ResponsePayload
> {
173 return new Promise
<ResponsePayload
>((resolve
, reject
) => {
174 if (this.ws
.readyState
=== WebSocket
.OPEN
) {
175 const uuid
= crypto
.randomUUID()
176 const msg
= JSON
.stringify([uuid
, procedureName
, payload
])
177 const sendTimeout
= setTimeout(() => {
178 this.responseHandlers
.delete(uuid
)
179 return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
183 this.responseHandlers
.set(uuid
, { procedureName
, resolve
, reject
})
185 this.responseHandlers
.delete(uuid
)
188 clearTimeout(sendTimeout
)
191 reject(new Error(`Send request '${procedureName}' message: connection closed`))
196 private responseHandler(messageEvent
: MessageEvent
<string>): void {
197 const response
= JSON
.parse(messageEvent
.data
) as ProtocolResponse
199 if (Array.isArray(response
) === false) {
200 throw new Error(`Response not an array: ${JSON.stringify(response, undefined, 2)}`)
203 const [uuid
, responsePayload
] = response
205 if (this.responseHandlers
.has(uuid
) === true) {
206 const { procedureName
, resolve
, reject
} = this.responseHandlers
.get(uuid
)!
207 switch (responsePayload
.status) {
208 case ResponseStatus
.SUCCESS
:
209 resolve(responsePayload
)
211 case ResponseStatus
.FAILURE
:
212 reject(responsePayload
)
217 `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
221 this.responseHandlers
.delete(uuid
)
223 throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)