UNPKG

ps2census

Version:

Client to connect to the PS2 Event Stream websocket.

223 lines 9.05 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamClient = void 0; const eventemitter3_1 = require("eventemitter3"); const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const stream_destroyed_exception_1 = require("./exceptions/stream-destroyed.exception"); const stream_closed_exception_1 = require("./exceptions/stream-closed.exception"); const next_tick_1 = __importDefault(require("../utils/next-tick")); var State; (function (State) { State[State["IDLE"] = 0] = "IDLE"; State[State["CONNECTING"] = 1] = "CONNECTING"; State[State["NEARLY"] = 2] = "NEARLY"; State[State["READY"] = 3] = "READY"; State[State["DISCONNECTED"] = 4] = "DISCONNECTED"; })(State || (State = {})); class StreamClient extends eventemitter3_1.EventEmitter { constructor(serviceId, environment, { connectionTimeout = 20000, heartbeatInterval = 30000, endpoint = 'wss://push.planetside2.com/streaming', wsOptions, } = {}) { super(); this.serviceId = serviceId; this.environment = environment; this.heartbeatAcknowledged = false; this.state = State.IDLE; this.gatewayUri = `${endpoint}?environment=${this.environment}&service-id=s:${this.serviceId}`; this.heartbeatInterval = heartbeatInterval; this.connectionTimeoutTime = connectionTimeout; this.wsOptions = wsOptions; } get isReady() { return this.state === State.READY; } connect() { if (this.connection && this.connection.readyState === isomorphic_ws_1.default.OPEN && this.state === State.READY) return Promise.resolve(); return new Promise((resolve, reject) => { const ready = () => { cleanup(); resolve(); }; const destroyed = () => { cleanup(); reject(new stream_destroyed_exception_1.StreamDestroyedException()); }; const closed = (code, reason) => { cleanup(); reject(new stream_closed_exception_1.StreamClosedException(code, reason)); }; const cleanup = () => { this.removeListener('ready', ready) .removeListener('destroyed', destroyed) .removeListener('close', closed); }; this.once('ready', ready) .once('destroyed', destroyed) .once('close', closed); if (this.connection && this.state == State.READY) { (0, next_tick_1.default)(() => this.emit('debug', `Open connection found, continuing operations.`)); return; } if (this.connection) { (0, next_tick_1.default)(() => this.emit('debug', `Connection found, destroying connection.`)); this.destroy({ emit: false }); } (0, next_tick_1.default)(() => this.emit('debug', `Connecting.`)); this.state = State.CONNECTING; this.connectedAt = Date.now(); this.setConnectionTimeout(true); const ws = (this.connection = new isomorphic_ws_1.default(this.gatewayUri, this.wsOptions)); ws.onopen = this.onOpen.bind(this); ws.onmessage = this.onMessage.bind(this); ws.onclose = this.onClose.bind(this); ws.onerror = this.onError.bind(this); }); } onOpen() { (0, next_tick_1.default)(() => this.emit('debug', `Established connection.`)); this.state = State.NEARLY; } onMessage(event) { const { data } = event; const isExpectedFormat = typeof data === 'string' || (typeof Buffer !== 'undefined' && data instanceof Buffer); if (!isExpectedFormat) { (0, next_tick_1.default)(() => this.emit('warn', new TypeError(`Received data in unexpected format: ${data}`))); return; } try { const parsed = JSON.parse(data.toString()); this.onPackage(parsed); } catch (err) { (0, next_tick_1.default)(() => this.emit('warn', err)); } } onPackage(data) { if ('service' in data && data.service === 'push' && data.type == 'connectionStateChanged') { if (data.connected) { this.setConnectionTimeout(false); this.setHeartbeatTimer(this.heartbeatInterval); this.state = State.READY; (0, next_tick_1.default)(() => this.emit('ready')); } else { this.destroy(); } } else if ('service' in data && data.service === 'event' && data.type == 'heartbeat') { this.acknowledgeHeartbeat(); } (0, next_tick_1.default)(() => this.emit('message', data)); } onClose(event) { const { code, reason } = event; (0, next_tick_1.default)(() => this.emit('debug', `Connection closed. ${JSON.stringify({ code, reason: reason.toString(), })}`)); this.setHeartbeatTimer(-1); this.setConnectionTimeout(false); this.cleanupConnection(); this.state = State.DISCONNECTED; (0, next_tick_1.default)(() => this.emit('close', code, reason.length ? reason.toString() : undefined)); } onError(event) { (0, next_tick_1.default)(() => this.emit('error', event.error)); } cleanupConnection() { if (!this.connection) return; this.connection.onopen = this.connection.onclose = this.connection.onmessage = null; this.connection.onerror = () => null; } destroy({ code = 1000, emit = true } = {}) { this.setHeartbeatTimer(-1); this.setConnectionTimeout(false); if (this.connection) { if (this.connection.readyState === isomorphic_ws_1.default.OPEN) { this.connection.close(code); } else { this.cleanupConnection(); try { this.connection.close(code); } catch { } if (emit) (0, next_tick_1.default)(() => this.emit('destroyed')); } this.connection = undefined; } else if (emit) { if (emit) (0, next_tick_1.default)(() => this.emit('destroyed')); } this.state = State.DISCONNECTED; } setConnectionTimeout(toggle) { if (!toggle) { if (this.connectionTimeout) { (0, next_tick_1.default)(() => this.emit('debug', `Connection timeout cleared`)); clearTimeout(this.connectionTimeout); delete this.connectionTimeout; } return; } (0, next_tick_1.default)(() => this.emit('debug', `Connection timeout set`)); this.connectionTimeout = setTimeout(() => { (0, next_tick_1.default)(() => this.emit('debug', `Connection timed out.`)); this.destroy({ code: 1001 }); }, this.connectionTimeoutTime); } setHeartbeatTimer(interval) { if (interval < 0) { if (this.heartbeatTimer) { (0, next_tick_1.default)(() => this.emit('debug', `Clearing heartbeat interval.`)); clearInterval(this.heartbeatTimer); delete this.heartbeatTimer; } return; } (0, next_tick_1.default)(() => this.emit('debug', `Setting heartbeat interval(${interval}ms).`)); if (this.heartbeatTimer) clearInterval(this.heartbeatTimer); this.heartbeatTimer = setInterval(() => this.resetHeartbeat(), this.heartbeatInterval); } resetHeartbeat() { if (!this.heartbeatAcknowledged) { (0, next_tick_1.default)(() => this.emit('debug', `Heartbeat not been received, assume connection has gone bad.`)); this.destroy({ code: 1001 }); return; } (0, next_tick_1.default)(() => this.emit('debug', `Reset heartbeat acknowledgement.`)); this.heartbeatAcknowledged = false; } acknowledgeHeartbeat() { (0, next_tick_1.default)(() => this.emit('debug', `Heartbeat acknowledged.`)); this.heartbeatAcknowledged = true; this.lastHeartbeat = Date.now(); } send(data) { if (!this.connection) throw new Error(`Connection not available`); this.connection.send(JSON.stringify(data), err => { if (err) (0, next_tick_1.default)(() => this.emit('error', err)); }); } } exports.StreamClient = StreamClient; //# sourceMappingURL=stream.client.js.map