• 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    public status: string = GetStatuses.UNCONNECTED;
42    private cacheInfo: Map<number, unknown> = new Map<number, unknown>();
43    private reconnect: number = -1;
44    private connectStatus: HTMLElement | null | undefined;
45
46    constructor() {
47        if (WebSocketManager.instance) {
48            return WebSocketManager.instance;
49        }
50        WebSocketManager.instance = this;
51        //连接websocket
52        this.connectWebSocket();
53    }
54    //连接WebSocket
55    connectWebSocket(): void {
56        // @ts-ignore
57        this.connectStatus = document.querySelector("body > sp-application").shadowRoot.querySelector("#main-menu").shadowRoot.querySelector("div.bottom > div.extend_connect");
58        this.websocket = new WebSocket(this.url);
59        this.websocket.binaryType = 'arraybuffer';
60        this.websocket.onopen = (): void => {
61            this.status = GetStatuses.CONNECTED;
62            // 设置心跳定时器
63            this.sendHeartbeat();
64            // 连接后登录
65            this.login();
66        };
67
68        //接受webSocket的消息
69        this.websocket.onmessage = (event): void => {
70            // 先解码
71            let decode: MessageParam = Utils.decode(event.data);
72            if (decode.type === TypeConstants.HEARTBEAT_TYPE) {
73                return;
74            }
75            this.onmessage(decode!);
76        };
77
78        this.websocket.onerror = (error): void => {
79            console.error('error:', error);
80            this.extendTips(false);
81        };
82
83        this.websocket.onclose = (event): void => {
84            this.status = GetStatuses.UNCONNECTED;
85            this.extendTips(false);
86            this.finalStatus();
87            //初始化标志位
88            this.initLoginInfo();
89            this.clearHeartbeat();
90        };
91    }
92
93
94    /**
95     * 接收webSocket返回的buffer数据
96     * 分别处理登录、其他业务的数据
97     * 其他业务数据分发
98     */
99    onmessage(decode: MessageParam): void {
100        if (decode.type === TypeConstants.LOGIN_TYPE) { // 先登录
101            this.loginMessage(decode);
102        } else if (decode.type === TypeConstants.UPDATE_TYPE) {// 升级
103            this.updateMessage(decode);
104        } else {// type其他
105            this.businessMessage(decode);
106        }
107    }
108
109    // 扩展服务连接状态提示
110    extendTips(flag: boolean): void {
111        if(flag) {
112            // @ts-ignore
113            this.connectStatus?.style.backgroundColor = 'green';
114            // @ts-ignore
115            this.connectStatus?.title = 'The extended service is connected.';
116        }else{
117            // @ts-ignore
118            this.connectStatus?.style.backgroundColor = 'red';
119            // @ts-ignore
120            this.connectStatus?.title = 'The extended service is not connected.';
121        }
122    }
123
124    // 登录
125    loginMessage(decode: MessageParam): void {
126        if (decode.cmd === Constants.LOGIN_CMD) {
127            this.status = GetStatuses.LOGINED;
128            this.sessionId = decode.session_id;
129            this.session = decode.session;
130            //检查版本
131            this.getVersion();
132        } else if (decode.cmd === Constants.SESSION_EXCEED) { // session满了
133            this.status = GetStatuses.LOGINFAILEDBYLACKSESSION;
134            this.finalStatus();
135        }
136    }
137
138    // 升级
139    updateMessage(decode: MessageParam): void {
140        if (decode.cmd === Constants.GET_VERSION_CMD) {
141            // 小于则升级
142            let targetVersion = '1.1.4';
143            let currentVersion = new TextDecoder().decode(decode.data);
144            let result = this.compareVersion(currentVersion, targetVersion);
145            if (result === -1) {
146                this.status = GetStatuses.UPFRADING;
147                this.updateVersion();
148                return;
149            }
150            this.status = GetStatuses.READY;
151            this.extendTips(true);
152            this.finalStatus();
153        } else if (decode.cmd === Constants.UPDATE_SUCCESS_CMD) { // 升级成功
154            this.status = GetStatuses.UPGRADESUCCESS;
155            this.finalStatus();
156        } else if (decode.cmd === Constants.UPDATE_FAIL_CMD) { // 升级失败
157            this.status = GetStatuses.UPGRADEFAILED;
158            this.finalStatus();
159        }
160    }
161
162    // 业务
163    businessMessage(decode: MessageParam): void {
164        if (this.distributeMap.has(decode.type!)) {
165            const callbackObj = this.distributeMap.get(decode.type!)!;
166            // 遍历调用所有 eventCallBacks
167            callbackObj.messageCallbacks.forEach(callback => {
168                callback(decode.cmd, decode.data);
169            });
170        }
171    }
172
173    // get版本
174    getVersion(): void {
175        // 获取扩展程序版本
176        this.send(TypeConstants.UPDATE_TYPE, Constants.GET_VERSION_CMD);
177    }
178
179    //check版本
180    compareVersion(currentVersion: string, targetVersion: string): number {
181        // 将版本字符串分割成数组
182        let parts1 = currentVersion.split('.');
183        let parts2 = targetVersion.split('.');
184
185        // 遍历数组,各部分进行比较
186        for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
187            let currentNum = i < parts1.length ? parseInt(parts1[i], 10) : 0;
188            let targetNum = i < parts2.length ? parseInt(parts2[i], 10) : 0;
189
190            // 比较
191            if (currentNum > targetNum) {// 无需更新
192                return 1;
193            } else if (currentNum < targetNum) { // 需要更新
194                return -1;
195            }
196        }
197        return 0; // 无需更新
198    }
199
200    // 更新扩展程序
201    updateVersion(): void {
202        // 扩展程序升级
203        let url = `https://${window.location.host.split(':')[0]}:${window.location.port
204            }${window.location.pathname}extend/hi-smart-perf-host-extend-update.zip`;
205        fetch(url).then(response => {
206            if (!response.ok) {
207                throw new Error('No corresponding upgrade compression package found');
208            }
209            return response.arrayBuffer();
210        }).then((arrayBuffer) => {
211            this.send(TypeConstants.UPDATE_TYPE, Constants.UPDATE_CMD, new Uint8Array(arrayBuffer));
212        }).catch((error) => {
213            this.status = GetStatuses.UPGRADEFAILED;
214            this.finalStatus();
215            console.error(error);
216        });
217    }
218
219    // 登录
220    login(): void {
221        this.websocket!.send(Utils.encode(Constants.LOGIN_PARAM));
222    }
223
224    // 模块调
225    static getInstance(): WebSocketManager | null | undefined {
226        if (!WebSocketManager.instance) {
227            new WebSocketManager();
228        }
229        return WebSocketManager.instance;
230    }
231
232    /**
233     * 消息监听器
234     * listener是不同模块传来接收数据的函数
235     * 模块调用
236    */
237    registerMessageListener(type: number, callback: Function, eventCallBack: Function, allowMultipleCallback: boolean = false): void {
238        let callbackObj = this.distributeMap.get(type);
239        if (!callbackObj) {
240            callbackObj = {
241                messageCallbacks: [callback],
242                eventCallBack: eventCallBack
243            };
244            this.distributeMap.set(type, callbackObj);
245        } else {
246            if (allowMultipleCallback) {
247                callbackObj.messageCallbacks.push(callback);
248            }
249            callbackObj.eventCallBack = eventCallBack;
250        }
251    }
252
253    // 删除回调函数
254    unregisterCallback(type: number, callback: Function): void {
255        if (!this.distributeMap.has(type)) {
256            return;
257        }
258        const callbackObj = this.distributeMap.get(type)!;
259        callbackObj.messageCallbacks = callbackObj.messageCallbacks.filter((cb) => cb !== callback);
260
261        // 如果回调数组为空,同时 eventCallBack 也为空,则可以删除整个类型
262        if (callbackObj.messageCallbacks.length === 0 && !callbackObj.eventCallBack) {
263            this.distributeMap.delete(type);
264        }
265    }
266
267    /**
268     * 传递数据信息至webSocket
269     * 模块调
270     */
271    sendMessage(type: number, cmd?: number, data?: Uint8Array): void {
272        // 初始化重连标志位
273        this.reconnect = -1;
274        // 检查状态
275        if (this.status !== GetStatuses.READY) {
276            // 缓存数据
277            this.cache(type, cmd, data);
278            this.checkStatus(type);
279        } else {
280            this.send(type, cmd, data);
281        }
282    }
283
284    send(type: number, cmd?: number, data?: Uint8Array): void {
285        let message: MessageParam = {
286            type: type,
287            cmd: cmd,
288            session_id: this.sessionId!,
289            session: this.session!,
290            data_lenght: data ? data.byteLength : undefined,
291            data: data
292        };
293        let encode = Utils.encode(message);
294        this.websocket!.send(encode!);
295    }
296
297    // 定时检查心跳
298    sendHeartbeat(): void {
299        this.heartbeatInterval = window.setInterval(() => {
300            if (this.status === GetStatuses.READY) {
301                this.send(TypeConstants.HEARTBEAT_TYPE, undefined, undefined);
302            }
303        }, Constants.INTERVAL_TIME);
304    }
305
306    /**
307     * 重连时初始化登录信息
308     * 在异常关闭时调用
309     */
310    initLoginInfo(): void {
311        this.sessionId = null;
312        this.session = null;
313    }
314
315    // 连接关闭时,清除心跳
316    clearHeartbeat(): void {
317        if (this.heartbeatInterval) {
318            clearInterval(this.heartbeatInterval);
319            this.heartbeatInterval = null;
320        }
321    }
322
323    // 缓存数据
324    cache(type: number, cmd?: number, data?: Uint8Array): void {
325        if (!this.cacheInfo.has(type)) {
326            this.cacheInfo.set(type, { type, cmd, data });
327        } else {
328            let obj = this.cacheInfo.get(type);
329            // @ts-ignore
330            obj.cmd = cmd;
331            //@ts-ignore
332            obj.data = data;
333        }
334    }
335
336    // 检查状态 中间状态,最终失败状态,最终成功状态
337    checkStatus(type: number): void {
338        // @ts-ignore
339        let statuses = this.getStatusesPrompt()[this.status];
340        const distributeEntry = this.distributeMap.get(type);
341
342        if (distributeEntry && typeof distributeEntry.eventCallBack === 'function') {
343            if (statuses.type === INTERMEDIATE_STATE) {
344                distributeEntry.eventCallBack(this.status);
345            } else if (statuses.type === FAILED_STATE) {
346                this.reconnect = type;
347                this.connectWebSocket();
348            }
349        }
350    }
351
352    // 重连后确认最终状态
353    finalStatus(): void {
354        if (this.reconnect !== -1) {
355            if (this.status === GetStatuses.READY) {
356                // @ts-ignore
357                this.sendMessage(this.reconnect, this.cacheInfo.get(this.reconnect)!.cmd, this.cacheInfo.get(this.reconnect)!.data);
358                return;
359            }
360            this.distributeMap.get(this.reconnect)!.eventCallBack(this.status);
361        }
362        this.reconnect = -1;
363    }
364
365    getStatusesPrompt(): unknown {
366        return {
367            unconnected: {
368                type: FAILED_STATE
369            }, // 重连
370            connected: {
371                type: INTERMEDIATE_STATE
372            }, // 中间
373            logined: {
374                type: INTERMEDIATE_STATE
375            }, // 中间
376            loginFailedByLackSession: {
377                type: FAILED_STATE
378            }, // 重连
379            upgrading: {
380                type: INTERMEDIATE_STATE
381            }, // 中间
382            upgradeSuccess: {
383                type: FAILED_STATE,
384            }, // 重连
385            upgradeFailed: {
386                type: FAILED_STATE,
387            }, // 重连
388        };
389    }
390}
391