• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15import { Utils, MessageParam } from './Util';
16import { Constants, TypeConstants } from './Constants';
17
18// 状态枚举
19enum GetStatuses {
20    UNCONNECTED = 'unconnected', // 服务器未连接
21    CONNECTED = 'connected', // 服务器已连接
22    LOGINED = 'logined', // 已登录
23    LOGINFAILEDBYLACKSESSION = 'loginFailedByLackSession', // session已满
24    UPFRADING = 'upgrading', // 正在升级
25    UPGRADESUCCESS = 'upgradeSuccess', // 升级成功
26    UPGRADEFAILED = 'upgradeFailed', // 升级失败
27    READY = 'ready' // 服务与扩展程序准备就绪
28}
29const INTERMEDIATE_STATE = 'Intermediate state';
30const FAILED_STATE = 'Failed state';
31
32
33export class WebSocketManager {
34    static instance: WebSocketManager | null | undefined = null;
35    url: string = `ws://localhost:${Constants.NODE_PORT}`;
36    private websocket: WebSocket | null | undefined = null;
37    private distributeMap: Map<number, { 'messageCallbacks': Function[], 'eventCallBack': Function }> = new Map<number, { 'messageCallbacks': Function[], 'eventCallBack': Function }>();
38    private sessionId: number | null | undefined;
39    private session: bigint | null | undefined;
40    private heartbeatInterval: number | null | undefined;
41    private status: string = GetStatuses.UNCONNECTED;
42    private cacheInfo: Map<number, unknown> = new Map<number, unknown>();
43    private reconnect: number = -1;
44
45    constructor() {
46        if (WebSocketManager.instance) {
47            return WebSocketManager.instance;
48        }
49        WebSocketManager.instance = this;
50        //连接websocket
51        this.connectWebSocket();
52    }
53    //连接WebSocket
54    connectWebSocket(): void {
55        this.websocket = new WebSocket(this.url);
56        this.websocket.binaryType = 'arraybuffer';
57        this.websocket.onopen = (): void => {
58            this.status = GetStatuses.CONNECTED;
59            // 设置心跳定时器
60            this.sendHeartbeat();
61            // 连接后登录
62            this.login();
63        };
64
65        //接受webSocket的消息
66        this.websocket.onmessage = (event): void => {
67            // 先解码
68            let decode: MessageParam = Utils.decode(event.data);
69            if (decode.type === TypeConstants.HEARTBEAT_TYPE) {
70                return;
71            }
72            this.onmessage(decode!);
73        };
74
75        this.websocket.onerror = (error): void => {
76            console.error('error:', error);
77        };
78
79        this.websocket.onclose = (event): void => {
80            this.status = GetStatuses.UNCONNECTED;
81            this.finalStatus();
82            //初始化标志位
83            this.initLoginInfo();
84            this.clearHeartbeat();
85        };
86    }
87
88    /**
89     * 接收webSocket返回的buffer数据
90     * 分别处理登录、其他业务的数据
91     * 其他业务数据分发
92     */
93    onmessage(decode: MessageParam): void {
94        if (decode.type === TypeConstants.LOGIN_TYPE) { // 先登录
95            this.loginMessage(decode);
96        } else if (decode.type === TypeConstants.UPDATE_TYPE) {// 升级
97            this.updateMessage(decode);
98        } else {// type其他
99            this.businessMessage(decode);
100        }
101    }
102
103    // 登录
104    loginMessage(decode: MessageParam): void {
105        if (decode.cmd === Constants.LOGIN_CMD) {
106            this.status = GetStatuses.LOGINED;
107            this.sessionId = decode.session_id;
108            this.session = decode.session;
109            //检查版本
110            this.getVersion();
111        } else if (decode.cmd === Constants.SESSION_EXCEED) { // session满了
112            this.status = GetStatuses.LOGINFAILEDBYLACKSESSION;
113            this.finalStatus();
114        }
115    }
116
117    // 升级
118    updateMessage(decode: MessageParam): void {
119        if (decode.cmd === Constants.GET_VERSION_CMD) {
120            // 小于则升级
121            let targetVersion = '1.0.8';
122            let currentVersion = new TextDecoder().decode(decode.data);
123            let result = this.compareVersion(currentVersion, targetVersion);
124            if (result === -1) {
125                this.status = GetStatuses.UPFRADING;
126                this.updateVersion();
127                return;
128            }
129            this.status = GetStatuses.READY;
130            this.finalStatus();
131        } else if (decode.cmd === Constants.UPDATE_SUCCESS_CMD) { // 升级成功
132            this.status = GetStatuses.UPGRADESUCCESS;
133            this.finalStatus();
134        } else if (decode.cmd === Constants.UPDATE_FAIL_CMD) { // 升级失败
135            this.status = GetStatuses.UPGRADEFAILED;
136            this.finalStatus();
137        }
138    }
139
140    // 业务
141    businessMessage(decode: MessageParam): void {
142        if (this.distributeMap.has(decode.type!)) {
143            const callbackObj = this.distributeMap.get(decode.type!)!;
144            // 遍历调用所有 eventCallBacks
145            callbackObj.messageCallbacks.forEach(callback => {
146                callback(decode.cmd, decode.data);
147            });
148        }
149    }
150
151    // get版本
152    getVersion(): void {
153        // 获取扩展程序版本
154        this.send(TypeConstants.UPDATE_TYPE, Constants.GET_VERSION_CMD);
155    }
156
157    //check版本
158    compareVersion(currentVersion: string, targetVersion: string): number {
159        // 将版本字符串分割成数组
160        let parts1 = currentVersion.split('.');
161        let parts2 = targetVersion.split('.');
162
163        // 遍历数组,各部分进行比较
164        for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
165            let currentNum = i < parts1.length ? parseInt(parts1[i], 10) : 0;
166            let targetNum = i < parts2.length ? parseInt(parts2[i], 10) : 0;
167
168            // 比较
169            if (currentNum > targetNum) {// 无需更新
170                return 1;
171            } else if (currentNum < targetNum) { // 需要更新
172                return -1;
173            }
174        }
175        return 0; // 无需更新
176    }
177
178    // 更新扩展程序
179    updateVersion(): void {
180        // 扩展程序升级
181        let url = `https://${window.location.host.split(':')[0]}:${window.location.port
182            }/application/extend/hi-smart-perf-host-extend-update.zip`;
183        fetch(url).then(response => {
184            if (!response.ok) {
185                throw new Error('No corresponding upgrade compression package found');
186            }
187            return response.arrayBuffer();
188        }).then((arrayBuffer) => {
189            this.send(TypeConstants.UPDATE_TYPE, Constants.UPDATE_CMD, new Uint8Array(arrayBuffer));
190        }).catch((error) => {
191            this.status = GetStatuses.UPGRADEFAILED;
192            this.finalStatus();
193            console.error(error);
194        });
195    }
196
197    // 登录
198    login(): void {
199        this.websocket!.send(Utils.encode(Constants.LOGIN_PARAM));
200    }
201
202    // 模块调
203    static getInstance(): WebSocketManager | null | undefined {
204        if (!WebSocketManager.instance) {
205            new WebSocketManager();
206        }
207        return WebSocketManager.instance;
208    }
209
210    /**
211     * 消息监听器
212     * listener是不同模块传来接收数据的函数
213     * 模块调用
214    */
215    registerMessageListener(type: number, callback: Function, eventCallBack: Function): void {
216        this.register(type, callback, eventCallBack);
217    }
218
219    /**
220     * 消息监听器
221     * listener是不同模块传来接收数据的函数
222     * 模块调用
223     */
224    registerCallback(type: number, callback: Function): void {
225        this.register(type, callback);
226    }
227
228    private register(type: number, callback: Function, eventCallBack: Function = (): void => { }): void {
229        let callbackObj = this.distributeMap.get(type);
230        if (!callbackObj) {
231            callbackObj = {
232                messageCallbacks: [callback],
233                eventCallBack: eventCallBack
234            };
235            this.distributeMap.set(type, callbackObj);
236        } else {
237            if (!callbackObj.messageCallbacks.includes(callback)) {
238                callbackObj.messageCallbacks.push(callback);
239            }
240            callbackObj.eventCallBack = eventCallBack;
241        }
242    }
243
244    // 删除回调函数
245    unregisterCallback(type: number, callback: Function): void {
246        if (!this.distributeMap.has(type)) {
247            return;
248        }
249        const callbackObj = this.distributeMap.get(type)!;
250        callbackObj.messageCallbacks = callbackObj.messageCallbacks.filter((cb) => cb !== callback);
251
252        // 如果回调数组为空,同时 eventCallBack 也为空,则可以删除整个类型
253        if (callbackObj.messageCallbacks.length === 0 && !callbackObj.eventCallBack) {
254            this.distributeMap.delete(type);
255        }
256    }
257
258    /**
259     * 传递数据信息至webSocket
260     * 模块调
261     */
262    sendMessage(type: number, cmd?: number, data?: Uint8Array): void {
263        // 初始化重连标志位
264        this.reconnect = -1;
265        // 检查状态
266        if (this.status !== GetStatuses.READY) {
267            // 缓存数据
268            this.cache(type, cmd, data);
269            this.checkStatus(type);
270        } else {
271            this.send(type, cmd, data);
272        }
273    }
274
275    send(type: number, cmd?: number, data?: Uint8Array): void {
276        let message: MessageParam = {
277            type: type,
278            cmd: cmd,
279            session_id: this.sessionId!,
280            session: this.session!,
281            data_lenght: data ? data.byteLength : undefined,
282            data: data
283        };
284        let encode = Utils.encode(message);
285        this.websocket!.send(encode!);
286    }
287
288    // 定时检查心跳
289    sendHeartbeat(): void {
290        this.heartbeatInterval = window.setInterval(() => {
291            if (this.status === GetStatuses.READY) {
292                this.send(TypeConstants.HEARTBEAT_TYPE, undefined, undefined);
293            }
294        }, Constants.INTERVAL_TIME);
295    }
296
297    /**
298     * 重连时初始化登录信息
299     * 在异常关闭时调用
300     */
301    initLoginInfo(): void {
302        this.sessionId = null;
303        this.session = null;
304    }
305
306    // 连接关闭时,清除心跳
307    clearHeartbeat(): void {
308        if (this.heartbeatInterval) {
309            clearInterval(this.heartbeatInterval);
310            this.heartbeatInterval = null;
311        }
312    }
313
314    // 缓存数据
315    cache(type: number, cmd?: number, data?: Uint8Array): void {
316        if (!this.cacheInfo.has(type)) {
317            this.cacheInfo.set(type, { type, cmd, data });
318        } else {
319            let obj = this.cacheInfo.get(type);
320            // @ts-ignore
321            obj.cmd = cmd;
322            //@ts-ignore
323            obj.data = data;
324        }
325    }
326
327    // 检查状态 中间状态,最终失败状态,最终成功状态
328    checkStatus(type: number): void {
329        // @ts-ignore
330        let statuses = this.getStatusesPrompt()[this.status];
331        const distributeEntry = this.distributeMap.get(type);
332
333        if (distributeEntry && typeof distributeEntry.eventCallBack === 'function') {
334            if (statuses.type === INTERMEDIATE_STATE) {
335                distributeEntry.eventCallBack(this.status);
336            } else if (statuses.type === FAILED_STATE) {
337                this.reconnect = type;
338                this.connectWebSocket();
339            }
340        }
341    }
342
343    // 重连后确认最终状态
344    finalStatus(): void {
345        if (this.reconnect !== -1) {
346            if (this.status === GetStatuses.READY) {
347                // @ts-ignore
348                this.sendMessage(this.reconnect, this.cacheInfo.get(this.reconnect)!.cmd, this.cacheInfo.get(this.reconnect)!.data);
349                return;
350            }
351            this.distributeMap.get(this.reconnect)!.eventCallBack(this.status);
352        }
353        this.reconnect = -1;
354    }
355
356    getStatusesPrompt(): unknown {
357        return {
358            unconnected: {
359                type: FAILED_STATE
360            }, // 重连
361            connected: {
362                type: INTERMEDIATE_STATE
363            }, // 中间
364            logined: {
365                type: INTERMEDIATE_STATE
366            }, // 中间
367            loginFailedByLackSession: {
368                type: FAILED_STATE
369            }, // 重连
370            upgrading: {
371                type: INTERMEDIATE_STATE
372            }, // 中间
373            upgradeSuccess: {
374                type: FAILED_STATE,
375            }, // 重连
376            upgradeFailed: {
377                type: FAILED_STATE,
378            }, // 重连
379        };
380    }
381}
382