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