import Heartbeat from "./Heartbeat.js" import Timeout from "./Timeout.js" import InitNetworkTimeoutError from "./error/InitNetworkTimeoutError.js" import {reporter} from "./Reporter.js" import util from "./util.js" import InternalError from "./error/InternalError.js" import Logger from "./Logger.js" const logger = new Logger('ws') export default class Socket extends EventEmitter { constructor(e) { super(); E(this, "_ws"); E(this, "_openTimer"); E(this, "connected", !1); E(this, "_hasTimeout", !1); E(this, "heartbeat"); E(this, "latency", (e,t)=>this.send({ id: "checkLatency", data: JSON.stringify(e), packet_id: t })); E(this, "send", e=>{ if (this.wsNoReady()) { return; } const t = JSON.stringify(e); e.id !== "heartbeat" && logger.info("send ws frame", t); this._ws.send(t) //console.log('socket发送数据:'+t) } ); E(this, "startGame", ()=>{ const {roomId: e, userId: t, avatarId: r, skinId: n, role: o, avatarComponents: a, versionId: s, rotationRenderType: l, isAllSync: u, nickname: c, avatarScale: h, appId: f, camera: d, player: _, firends: g, syncByEvent: m, areaName: v, attitude: y, pathName: b, person: T, roomTypeId: C="", syncToOthers: A, hasAvatar: S, prioritySync: P, extra: R={}, removeWhenDisconnected: M} = this.network.room.currentNetworkOptions; R.removeWhenDisconnected = M; const x = { id: "start", room_id: e, user_id: t, trace_id: util.uuid(), data: JSON.stringify({ avatar_components: JSON.stringify(a), avatar_id: r, skin_id: n, is_host: o ? o == "host" : !0, skin_data_version: n !== void 0 && s !== void 0 ? n + s : void 0, rotation_render_type: l, is_all_sync: u, nick_name: encodeURIComponent(c || ""), app_id: f, camera: d, player: _, person: T, firends: JSON.stringify(g), sync_by_event: m, area_name: v, path_name: b, attitude: y, room_type_id: C, syncToOthers: A, hasAvatar: S, avatarSize: h, prioritySync: P, extra: JSON.stringify(R) }) }; this.send(x); const mt = JSON.parse(x.data); delete mt.token; logger.infoAndReportMeasurement({ metric: "startGame", extra: mt, startTime: Date.now() }) } ); this.network = e, this.heartbeat = new Heartbeat({ ping: t=>{ var r; if (!this.connected) { this.heartbeat.stop(), (r = e.room.stats) == null || r.assign({ rtt: 0 }); return } this.send({ id: "heartbeat", data: t }) } , pong(t) { var r; (r = e.room.stats) == null || r.assign({ rtt: t }) } }) } get connection() { return this._ws } start() { this._hasTimeout = !1; const e = this.getAddress(); logger.info(`connecting to ${e}`); const t = Date.now(); this._ws = new WebSocket(e), this._openTimer = new Timeout(()=>{ const r = `Failed to open websocket in ${DEFAULT_OPEN_TIMEOUT_MS} ms`; this._hasTimeout = !0, this.emit("socketClosed", new InitNetworkTimeoutError(r)) } ,DEFAULT_OPEN_TIMEOUT_MS), this._ws.onopen = ()=>{ var r; (r = this._openTimer) == null || r.clear(), this.connected = !0, this.heartbeat.start(), this.network.room.currentNetworkOptions.reconnect || (logger.infoAndReportMeasurement({ metric: "wsOpenedAt", group: "joinRoom", startTime: this.network.room._startTime }), logger.infoAndReportMeasurement({ metric: "wsOpenedCost", group: "joinRoom", startTime: t })) } , this.handleWSEvent() } getAddress() { const {wsServerUrl: e, reconnect: t, sessionId: r, token: n, roomId: o, userId: a, pageSession: s} = this.network.room.currentNetworkOptions , l = this.network.room.skinId; let u = e; t && (u = u + `?reconnect=true&lastSessionID=${r}`); const c = `userId=${a}&roomId=${o}&pageSession=${s}` + (this.network.room.isHost ? `&skinId=${l}` : "") + (n ? `&token=${n}` : ""); return u = u.indexOf("?") > -1 ? u + "&" + c : u + "?" + c, u } handleWSEvent() { const e = this._ws; e.addEventListener("error", t=>{ this.connected = !1, logger.error("webscoket error", t), this.emit("socketClosed", new InternalError("connect to address error: " + this.network.room.currentNetworkOptions.wsServerUrl)) } ), e.addEventListener("close", t=>{ this.connected = !1, this._onClose(t) } ), //接收数据 e.addEventListener("message", t=>{ if (!t || this._hasTimeout || !this.connected) return; let r = null; try { r = JSON.parse(t.data) //console.log('接收socket数据:'+t.data) } catch (o) { logger.error(o); return } if (!r) return; const n = r.id; if (!!n) switch (n !== "heartbeat" && logger.info(`receive ws frame: ${t.data}`), n) { case "fail": break; case "init": try { const o = r.data.slice(-37, -1); reporter.updateBody({ serverSession: o }) } catch (o) { console.error(o) } this.network.rtcp.start(); break; case "heartbeat": this.heartbeat.pong(r.data); break; case "offer": this.network.rtcp.setRemoteDescription(r.data, this.network.stream.el); break; case "ice_candidate": this.network.rtcp.addCandidate(r.data); break; case "start": this.emit("gameRoomAvailable", r); break; case "error": try { const {Code: o, Msg: a} = JSON.parse(r.data); if (o) { if (o == 3003) return this.emit("socketClosed", new TokenExpiredError); if (authenticationErrorCodes.indexOf(o) > -1) return this.emit("socketClosed", new AuthenticationError("\u9274\u6743\u9519\u8BEF:" + a)); { const s = util.getErrorByCode(o); this.emit("socketClosed", new s(a)) } } } catch (d) { const _ = new InternalError("JSON.parse websocket data error: " + r.data); logger.error(d, _); this.emit("socketClosed", _) } break; case "checkLatency": { const o = r.packet_id , a = r.data.split(","); this.onLatencyCheck({ packetId: o, addresses: a }); break } default: logger.warn("unkown ws message type", n, r) } } ) } onLatencyCheck(e) { const t = [...new Set(e.addresses || [])]; Promise.all(t.map(r=>({ [r]: 9999 }))).then(r=>{ const n = Object.assign({}, ...r); this.latency(n, e.packetId) } ) } wsNoReady() { return this._ws.readyState == WebSocket.CLOSED || this._ws.readyState == WebSocket.CLOSING || this._ws.readyState == WebSocket.CONNECTING } prepareReconnect() { this._close({ code: WS_CLOSE_RECONNECT, reason: "reconnect" }) } _onClose({code: e, reason: t}) { this._openTimer && this._openTimer.clear(), logger.warn(`ws closed: ${e} ` + t), [WS_CLOSE_RECONNECT, WS_CLOSE_NORMAL].includes(e) || this.emit("socketClosed", new InternalError("Websocket error")) } _close({code: e, reason: t}) { var r; (r = this._ws) == null || r.close(e, t) } quit() { this._close({ code: WS_CLOSE_NORMAL, reason: "quit" }) } }