import Codes from "./enum/Codes.js" import {logger} from "./Logger.js" import Socket from "./Socket.js" import Rtcp from "./Rtcp.js" import NetworkMonitor from "./NetworkMonitor.js" import Stream from "./Stream.js" var workerSourceCode = `onmessage = function (event) { const data = event.data if (!data) return if (data.type === 'start') { const startTime = Date.now() const request = new XMLHttpRequest() request.open('GET', data.url) try { request.send() } catch (error) { console.error(error) } request.addEventListener('readystatechange', () => { if (request.readyState == 4) { if (request.status == 200) { postMessage(Date.now() - startTime) } } }) } } `; export default class NetworkController extends EventEmitter { constructor(e) { super(); this.socket = null this.rtcp = null this.stream = null this._state = 'connecting' this._networkMonitor = null this.blockedActions = [] this.reconnectCount = 0 this.room = e, this.socket = new Socket(this); this.rtcp = new Rtcp(this); this.stream = new Stream; this._networkMonitor = new NetworkMonitor(()=>{ logger.info("network changed, online:", this._networkMonitor.isOnline), this._state === "disconnected" && this._networkMonitor.isOnline && (logger.info("network back to online, try to reconnect"), this.reconnect()) }); this.checkNetworkQuality(this.room.currentNetworkOptions.wsServerUrl); this._networkMonitor.start(); new VisibilityChangeHandler().subscribe(r=>{ var n, o; r ? ((o = this.room.stats) == null || o.disable(), logger.infoAndReportMeasurement({ metric: "pageHide", startTime: Date.now() })) : ((n = this.room.stats) == null || n.enable(), logger.infoAndReportMeasurement({ metric: "pageShow", startTime: Date.now(), extra: { state: this._state } }), this._state === "disconnected" && this.reconnect()) } ) } startGame(){ return new Promise((e,t)=>{ if (!this.rtcp.connected) return t(new InternalError("Game cannot load. Please refresh")); if (!this.rtcp.inputReady) return t(new InternalError("Game is not ready yet. Please wait")); this.socket.on("gameRoomAvailable", r=>{ this.setState("connected"), e(r), this.rtcp.heartbeat.start() } ), this.socket.on("socketClosed", r=>{ t(r) } ), this.socket.startGame() }) } addBlockedActions(e) { this.blockedActions.push(...e) } removeBlockedActions(e) { if (!e) { this.blockedActions = []; return } const t = this.blockedActions.indexOf(e); this.blockedActions.splice(t, 1) } setState(e) { this._state !== e && (logger.info("Set network state to ", e), this._state = e) } async connectAndStart(e) { return this.connect(e).then(this.startGame) } async connect(e=!1) { this.room.updateCurrentNetworkOptions({ reconnect: e }); return new Promise((t,r)=>{ this.rtcp.on("rtcConnected", ()=>{ this.setState("connected"), t() } ), this.rtcp.on("rtcDisconnected", ()=>{ logger.info("rtc disconnected"), this._state === "connecting" ? (this.setState("disconnected"), r(new InternalError("rtc connect failed"))) : (this.setState("disconnected"), logger.info("rtc disconnected, start to reconnect"), this.reconnect()) } ), this.socket.on("socketQuit", ()=>{ logger.info("socket quit success"), this.setState("closed") } ), this.socket.on("socketClosed", n=>{ this._state === "connecting" && (this.setState("disconnected"), r(n)), r(n) } ), this.socket.start() } ) } reconnect() { if (this.room.viewMode === "observer") return; const e = Date.now(); if (this.reconnectCount++, this.reconnectCount > MAX_RECONNECT_COUNT) { logger.error("reconnect failed, reached max reconnect count", MAX_RECONNECT_COUNT), this.reconnectCount = 0, this.emit("stateChanged", { state: "disconnected" }); return } return logger.info("start reconnect, count:", this.reconnectCount), this._reconnect().then(()=>{ logger.infoAndReportMeasurement({ startTime: e, metric: "reconnect" }) } ).catch(t=>{ if (logger.infoAndReportMeasurement({ startTime: e, metric: "reconnect", error: t }), t.code === Codes.RepeatLogin) { this.room.handleRepetLogin(); return } const r = 1e3; logger.info("reconnect failed, wait " + r + " ms for next reconnect"), setTimeout(()=>{ this.reconnect() } , r) } ) } _reconnect() { return this._state === "closed" ? (logger.warn("connection closed already"), Promise.reject()) : this._state === "connecting" ? (logger.warn("connection is already in connecting state"), Promise.reject()) : this._state !== "disconnected" ? Promise.reject() : (this.prepareReconnect(), this._state = "connecting", this.emit("stateChanged", { state: "reconnecting", count: this.reconnectCount }), this.socket.off("gameRoomAvailable"), this.socket.off("socketClosed"), this.rtcp.off("rtcDisconnected"), this.rtcp.off("rtcConnected"), this.connectAndStart(!0).then(({session_id: e})=>{ this.room.updateCurrentNetworkOptions({ sessionId: e }), reporter.updateBody({ serverSession: e }), logger.info("reconnect success"), this.setState("connected"), this.reconnectCount = 0, this.emit("stateChanged", { state: "reconnected" }) } )) } prepareReconnect() { this.rtcp.disconnect(), this.socket.prepareReconnect(), this.prepareReconnectOptions() } prepareReconnectOptions() { const {camera: e, player: t} = this.room.currentClickingState || {}; e && t && this.room.updateCurrentNetworkOptions({ camera: e, player: t }) } sendRtcData(e) { if (this.blockedActions.includes(e.action_type)) { logger.info(`action: ${Actions[e.action_type]} was blocked`); return } this.rtcp.sendData(e) } sendSocketData(e) { logger.debug("ws send ->", e), this.socket.send(e) } quit() { const e = uuid$1() , t = { action_type: Actions.Exit, trace_id: e, exit_action: {}, user_id: this.room.options.userId, packet_id: e }; this.setState("closed"), this.socket.quit(), this.sendRtcData(t) } checkNetworkQuality(i) { let worker = null if (!i) return; const e = Date.now(); if (this.pingOthers("https://www.baidu.com", function(t, r) { logger.infoAndReportMeasurement({ metric: "baiduRtt", group: "http", value: r, startTime: e }) }), !worker) { const t = new Blob([workerSourceCode],{ type: "application/javascript" }); worker = new Worker(URL.createObjectURL(t)), worker.onmessage = function(r) { logger.infoAndReportMeasurement({ metric: "workerRtt", group: "http", startTime: e, value: r.data }) } } } pingOthers(i, e) { let t = !1; const r = new Image; r.onload = o, r.onerror = a; const n = Date.now(); function o(l) { t = !0, s() } function a(l) {} function s() { const l = Date.now() - n; if (typeof e == "function") return t ? e(null, l) : (console.error("error loading resource"), e("error", l)) } r.src = i + "/favicon.ico?" + +new Date } }