ps2census
Version:
Client to connect to the PS2 Event Stream websocket.
223 lines • 9.05 kB
JavaScript
"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