Unify request and response handler naming
[e-mobility-charging-stations-simulator.git] / src / ui / web / src / composable / UIClient.ts
CommitLineData
32de5a57
LM
1import { JsonType } from '@/type/JsonType';
2import {
3 ProcedureName,
4 ProtocolResponse,
5 ResponsePayload,
6 ResponseStatus,
7} from '@/type/UIProtocol';
8import Utils from './Utils';
9import config from '@/assets/config';
10import { v4 as uuidv4 } from 'uuid';
11
12type ResponseHandler = {
13 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void;
14 reject: (reason?: any) => void;
15 procedureName: ProcedureName;
16};
17
18export default class UIClient {
19 private static _instance: UIClient | null = null;
20
21 private _ws: WebSocket;
22 private _responseHandlers: Map<string, ResponseHandler>;
23
24 private constructor() {
25 this._ws = new WebSocket(
26 `ws://${config.emobility.host}:${config.emobility.port}`,
27 config.emobility.protocol
28 );
29
30 this._responseHandlers = new Map<string, ResponseHandler>();
31
32 this._ws.onmessage = this.handleResponse.bind(this);
33 }
34
35 public static get instance() {
36 if (UIClient._instance === null) {
37 UIClient._instance = new UIClient();
38 }
39 return UIClient._instance;
40 }
41
42 public onOpen(listener: (this: WebSocket, ev: Event) => void) {
43 this._ws.addEventListener('open', listener);
44 }
45
46 public async listChargingStations(): Promise<ResponsePayload> {
47 console.debug('listChargingStations');
48
49 return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {});
50 }
51
52 public async startTransaction(
53 hashId: string,
54 connectorId: number,
55 idTag: string
56 ): Promise<ResponsePayload> {
57 console.debug('startTransaction');
58
59 return this.sendRequest(ProcedureName.START_TRANSACTION, {
60 hashId,
61 connectorId,
62 idTag,
63 });
64 }
65
66 public async stopTransaction(hashId: string, transactionId: number): Promise<ResponsePayload> {
67 console.debug('stopTransaction');
68
69 return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
70 hashId,
71 transactionId,
72 });
73 }
74
75 private setResponseHandler(
76 id: string,
77 resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void,
78 reject: (reason?: any) => void,
79 procedureName: ProcedureName
80 ): void {
81 this._responseHandlers.set(id, { resolve, reject, procedureName });
82 }
83
84 private getResponseHandler(id: string): ResponseHandler | undefined {
85 return this._responseHandlers.get(id);
86 }
87
88 private async sendRequest(command: ProcedureName, data: JsonType): Promise<ResponsePayload> {
89 let uuid: string;
90 return Utils.promiseWithTimeout(
91 new Promise((resolve, reject) => {
92 uuid = uuidv4();
93 const msg = JSON.stringify([uuid, command, data]);
94
95 if (this._ws.readyState === this._ws.OPEN) {
96 console.debug(`Send request ${command} message: `, msg);
97 this._ws.send(msg);
98 } else {
99 throw new Error(`Send request ${command} message: connection not opened`);
100 }
101
102 this.setResponseHandler(uuid, resolve, reject, command);
103 }),
104 60 * 1000,
105 Error(`Send request ${command} message timeout`),
106 () => {
107 this._responseHandlers.delete(uuid);
108 }
109 );
110 }
111
112 private handleResponse(messageEvent: MessageEvent<string>): void {
113 const data = JSON.parse(messageEvent.data) as ProtocolResponse;
114
115 if (Utils.isIterable(data) === false) {
116 throw new Error('Response not iterable: ' + JSON.stringify(data, null, 2));
117 }
118
119 const [uuid, response] = data;
120
121 if (this._responseHandlers.has(uuid) === true) {
122 switch (response.status) {
123 case ResponseStatus.SUCCESS:
124 this.getResponseHandler(uuid)?.resolve(response);
125 break;
126 case ResponseStatus.FAILURE:
127 this.getResponseHandler(uuid)?.reject(response);
128 break;
129 default:
130 throw new Error(`Response status not supported: ${response.status}`);
131 }
132 } else {
133 throw new Error('Not a response to a request: ' + JSON.stringify(data, null, 2));
134 }
135 }
136}