import md5 from 'md5'; import * as proto from './proto'; import urls from './urls'; import { CallbackMethod, CallbackType, SmartErrorCode } from '../const/common'; import { salt, appKey, fpid, aid } from '../const/secret'; import Long from 'long'; import Frame = proto.pbbp2.Frame; import BroadcastMsg = proto.live.BroadcastMsg; export interface ReadConfig { langCode: string; domain?: string; } export class ReadClient { conf: ReadConfig; ws!: WebSocket; terminalInfo: proto.live.ITerminalInfo; lastHeartbeatTime: number; runTimer: number | null; shouldReconnect: boolean; clientCallback: CallbackMethod; constructor( conf: ReadConfig, terminalInfo: proto.live.ITerminalInfo, clientCallback: CallbackMethod ) { this.conf = conf; this.terminalInfo = terminalInfo; this.lastHeartbeatTime = Date.now(); this.runTimer = null; this.shouldReconnect = false; this.clientCallback = clientCallback; this.initWs(); } checkSocketRestart() { if (this.runTimer) { clearInterval(this.runTimer); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.runTimer = setInterval(() => { const isSocketOpened = this.ws.readyState === WebSocket.OPEN; if (isSocketOpened) { const frame = new Frame({ SeqID: Long.fromValue(1), LogID: Long.fromValue(1), service: 1, method: 666, }); this.ws.send(Frame.encode(frame).finish()); } const nowTime = Date.now(); if (this.shouldReconnect && nowTime - this.lastHeartbeatTime > 3000) { this.restartWs(); this.clientCallback(CallbackType.FAIL, { errorCode: SmartErrorCode.HEART_BEAT_ERROR, errorMessage: 'frontier heart beat timeout.', }); this.lastHeartbeatTime = nowTime; } }, 1000); } initWs() { const { deviceId, cid } = this.terminalInfo; const accessKey = this.getAccessKey(deviceId!); let wsUrl = urls.WsFrontierUrl; if (this.conf.domain) { wsUrl = this.conf.domain; } const url = `${wsUrl}?fpid=${fpid}&aid=${aid}&access_key=${accessKey}&device_id=${deviceId}&cid=${cid}&lang_code=${this.conf.langCode}`; this.ws = new WebSocket(url); this.ws.onopen = () => { this.shouldReconnect = true; this.clientCallback(CallbackType.OPEN); this.checkSocketRestart(); }; this.ws.onmessage = (msg) => { this.lastHeartbeatTime = Date.now(); if (this.shouldReconnect) { this.onRecvFrontierMsg(msg); } }; this.ws.onerror = (e) => { this.clientCallback(CallbackType.FAIL, { errorCode: SmartErrorCode.FRONTIER_SOCKET_ERROR, errorMessage: e, }); }; this.ws.onclose = () => { this.clientCallback(CallbackType.CLOSE); }; } restartWs() { if (this.ws.readyState === WebSocket.CONNECTING) { return; } this.close(); this.shouldReconnect = true; this.initWs(); } resetConfig(conf: ReadConfig) { this.conf = conf; } getAccessKey(deviceId: number) { return md5(fpid + appKey + deviceId.toString(10) + salt); } async onRecvFrontierMsg(msg: MessageEvent) { const blob: Blob = msg.data; const buf = await blob.arrayBuffer(); const frame: Frame = Frame.decode(Buffer.from(buf)); const resp: BroadcastMsg = BroadcastMsg.decode(frame.payload); this.clientCallback(CallbackType.RECEIVE_MSG, { msg: resp, }); } close() { this.ws.close(); } stop() { this.shouldReconnect = false; } continue() { this.shouldReconnect = true; } }