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