/* * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Utils, MessageParam } from './Util'; import { Constants, TypeConstants } from './Constants'; // 状态枚举 enum GetStatuses { UNCONNECTED = 'unconnected', // 服务器未连接 CONNECTED = 'connected', // 服务器已连接 LOGINED = 'logined', // 已登录 LOGINFAILEDBYLACKSESSION = 'loginFailedByLackSession', // session已满 UPFRADING = 'upgrading', // 正在升级 UPGRADESUCCESS = 'upgradeSuccess', // 升级成功 UPGRADEFAILED = 'upgradeFailed', // 升级失败 READY = 'ready' // 服务与扩展程序准备就绪 } const INTERMEDIATE_STATE = 'Intermediate state'; const FAILED_STATE = 'Failed state'; export class WebSocketManager { static instance: WebSocketManager | null | undefined = null; url: string = `ws://localhost:${Constants.NODE_PORT}`; private websocket: WebSocket | null | undefined = null; private distributeMap: Map = new Map(); private sessionId: number | null | undefined; private session: bigint | null | undefined; private heartbeatInterval: number | null | undefined; private status: string = GetStatuses.UNCONNECTED; private cacheInfo: Map = new Map(); private reconnect: number = -1; constructor() { if (WebSocketManager.instance) { return WebSocketManager.instance; } WebSocketManager.instance = this; //连接websocket this.connectWebSocket(); } //连接WebSocket connectWebSocket(): void { this.websocket = new WebSocket(this.url); this.websocket.binaryType = 'arraybuffer'; this.websocket.onopen = (): void => { this.status = GetStatuses.CONNECTED; // 设置心跳定时器 this.sendHeartbeat(); // 连接后登录 this.login(); }; //接受webSocket的消息 this.websocket.onmessage = (event): void => { // 先解码 let decode: MessageParam = Utils.decode(event.data); if (decode.type === TypeConstants.HEARTBEAT_TYPE) { return; } this.onmessage(decode!); }; this.websocket.onerror = (error): void => { console.error('error:', error); }; this.websocket.onclose = (event): void => { this.status = GetStatuses.UNCONNECTED; this.finalStatus(); //初始化标志位 this.initLoginInfo(); this.clearHeartbeat(); }; } /** * 接收webSocket返回的buffer数据 * 分别处理登录、其他业务的数据 * 其他业务数据分发 */ onmessage(decode: MessageParam): void { if (decode.type === TypeConstants.LOGIN_TYPE) { // 先登录 this.loginMessage(decode); } else if (decode.type === TypeConstants.UPDATE_TYPE) {// 升级 this.updateMessage(decode); } else {// type其他 this.businessMessage(decode); } } // 登录 loginMessage(decode: MessageParam): void { if (decode.cmd === Constants.LOGIN_CMD) { this.status = GetStatuses.LOGINED; this.sessionId = decode.session_id; this.session = decode.session; //检查版本 this.getVersion(); } else if (decode.cmd === Constants.SESSION_EXCEED) { // session满了 this.status = GetStatuses.LOGINFAILEDBYLACKSESSION; this.finalStatus(); } } // 升级 updateMessage(decode: MessageParam): void { if (decode.cmd === Constants.GET_VERSION_CMD) { // 小于则升级 let targetVersion = '1.0.8'; let currentVersion = new TextDecoder().decode(decode.data); let result = this.compareVersion(currentVersion, targetVersion); if (result === -1) { this.status = GetStatuses.UPFRADING; this.updateVersion(); return; } this.status = GetStatuses.READY; this.finalStatus(); } else if (decode.cmd === Constants.UPDATE_SUCCESS_CMD) { // 升级成功 this.status = GetStatuses.UPGRADESUCCESS; this.finalStatus(); } else if (decode.cmd === Constants.UPDATE_FAIL_CMD) { // 升级失败 this.status = GetStatuses.UPGRADEFAILED; this.finalStatus(); } } // 业务 businessMessage(decode: MessageParam): void { if (this.distributeMap.has(decode.type!)) { const callbackObj = this.distributeMap.get(decode.type!)!; // 遍历调用所有 eventCallBacks callbackObj.messageCallbacks.forEach(callback => { callback(decode.cmd, decode.data); }); } } // get版本 getVersion(): void { // 获取扩展程序版本 this.send(TypeConstants.UPDATE_TYPE, Constants.GET_VERSION_CMD); } //check版本 compareVersion(currentVersion: string, targetVersion: string): number { // 将版本字符串分割成数组 let parts1 = currentVersion.split('.'); let parts2 = targetVersion.split('.'); // 遍历数组,各部分进行比较 for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { let currentNum = i < parts1.length ? parseInt(parts1[i], 10) : 0; let targetNum = i < parts2.length ? parseInt(parts2[i], 10) : 0; // 比较 if (currentNum > targetNum) {// 无需更新 return 1; } else if (currentNum < targetNum) { // 需要更新 return -1; } } return 0; // 无需更新 } // 更新扩展程序 updateVersion(): void { // 扩展程序升级 let url = `https://${window.location.host.split(':')[0]}:${window.location.port }/application/extend/hi-smart-perf-host-extend-update.zip`; fetch(url).then(response => { if (!response.ok) { throw new Error('No corresponding upgrade compression package found'); } return response.arrayBuffer(); }).then((arrayBuffer) => { this.send(TypeConstants.UPDATE_TYPE, Constants.UPDATE_CMD, new Uint8Array(arrayBuffer)); }).catch((error) => { this.status = GetStatuses.UPGRADEFAILED; this.finalStatus(); console.error(error); }); } // 登录 login(): void { this.websocket!.send(Utils.encode(Constants.LOGIN_PARAM)); } // 模块调 static getInstance(): WebSocketManager | null | undefined { if (!WebSocketManager.instance) { new WebSocketManager(); } return WebSocketManager.instance; } /** * 消息监听器 * listener是不同模块传来接收数据的函数 * 模块调用 */ registerMessageListener(type: number, callback: Function, eventCallBack: Function): void { this.register(type, callback, eventCallBack); } /** * 消息监听器 * listener是不同模块传来接收数据的函数 * 模块调用 */ registerCallback(type: number, callback: Function): void { this.register(type, callback); } private register(type: number, callback: Function, eventCallBack: Function = (): void => { }): void { let callbackObj = this.distributeMap.get(type); if (!callbackObj) { callbackObj = { messageCallbacks: [callback], eventCallBack: eventCallBack }; this.distributeMap.set(type, callbackObj); } else { if (!callbackObj.messageCallbacks.includes(callback)) { callbackObj.messageCallbacks.push(callback); } callbackObj.eventCallBack = eventCallBack; } } // 删除回调函数 unregisterCallback(type: number, callback: Function): void { if (!this.distributeMap.has(type)) { return; } const callbackObj = this.distributeMap.get(type)!; callbackObj.messageCallbacks = callbackObj.messageCallbacks.filter((cb) => cb !== callback); // 如果回调数组为空,同时 eventCallBack 也为空,则可以删除整个类型 if (callbackObj.messageCallbacks.length === 0 && !callbackObj.eventCallBack) { this.distributeMap.delete(type); } } /** * 传递数据信息至webSocket * 模块调 */ sendMessage(type: number, cmd?: number, data?: Uint8Array): void { // 初始化重连标志位 this.reconnect = -1; // 检查状态 if (this.status !== GetStatuses.READY) { // 缓存数据 this.cache(type, cmd, data); this.checkStatus(type); } else { this.send(type, cmd, data); } } send(type: number, cmd?: number, data?: Uint8Array): void { let message: MessageParam = { type: type, cmd: cmd, session_id: this.sessionId!, session: this.session!, data_lenght: data ? data.byteLength : undefined, data: data }; let encode = Utils.encode(message); this.websocket!.send(encode!); } // 定时检查心跳 sendHeartbeat(): void { this.heartbeatInterval = window.setInterval(() => { if (this.status === GetStatuses.READY) { this.send(TypeConstants.HEARTBEAT_TYPE, undefined, undefined); } }, Constants.INTERVAL_TIME); } /** * 重连时初始化登录信息 * 在异常关闭时调用 */ initLoginInfo(): void { this.sessionId = null; this.session = null; } // 连接关闭时,清除心跳 clearHeartbeat(): void { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } // 缓存数据 cache(type: number, cmd?: number, data?: Uint8Array): void { if (!this.cacheInfo.has(type)) { this.cacheInfo.set(type, { type, cmd, data }); } else { let obj = this.cacheInfo.get(type); // @ts-ignore obj.cmd = cmd; //@ts-ignore obj.data = data; } } // 检查状态 中间状态,最终失败状态,最终成功状态 checkStatus(type: number): void { // @ts-ignore let statuses = this.getStatusesPrompt()[this.status]; const distributeEntry = this.distributeMap.get(type); if (distributeEntry && typeof distributeEntry.eventCallBack === 'function') { if (statuses.type === INTERMEDIATE_STATE) { distributeEntry.eventCallBack(this.status); } else if (statuses.type === FAILED_STATE) { this.reconnect = type; this.connectWebSocket(); } } } // 重连后确认最终状态 finalStatus(): void { if (this.reconnect !== -1) { if (this.status === GetStatuses.READY) { // @ts-ignore this.sendMessage(this.reconnect, this.cacheInfo.get(this.reconnect)!.cmd, this.cacheInfo.get(this.reconnect)!.data); return; } this.distributeMap.get(this.reconnect)!.eventCallBack(this.status); } this.reconnect = -1; } getStatusesPrompt(): unknown { return { unconnected: { type: FAILED_STATE }, // 重连 connected: { type: INTERMEDIATE_STATE }, // 中间 logined: { type: INTERMEDIATE_STATE }, // 中间 loginFailedByLackSession: { type: FAILED_STATE }, // 重连 upgrading: { type: INTERMEDIATE_STATE }, // 中间 upgradeSuccess: { type: FAILED_STATE, }, // 重连 upgradeFailed: { type: FAILED_STATE, }, // 重连 }; } }