2017-11-13 01:23:39 -05:00
|
|
|
'use strict';
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ============================================================================
|
|
|
|
// Socket Client
|
|
|
|
// This connects to the FrankerFaceZ socket servers for PubSub and RPC.
|
|
|
|
// ============================================================================
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
import Module from 'utilities/module';
|
|
|
|
import {DEBUG, WS_CLUSTERS} from 'utilities/constants';
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
export const State = {
|
|
|
|
DISCONNECTED: 0,
|
|
|
|
CONNECTING: 1,
|
|
|
|
CONNECTED: 2
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
export default class SocketClient extends Module {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.inject('settings');
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.settings.add('socket.cluster', {
|
|
|
|
default: 'Production',
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ui: {
|
|
|
|
path: 'Debugging @{"expanded": false, "sort": 9999} > Socket >> General',
|
|
|
|
title: 'Server Cluster',
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
component: 'setting-select-box',
|
2015-08-02 02:41:03 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
data: [{
|
|
|
|
value: null,
|
|
|
|
title: 'Disabled'
|
|
|
|
}].concat(Object.keys(WS_CLUSTERS).map(x => ({
|
|
|
|
value: x,
|
|
|
|
title: x
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
});
|
2015-08-02 02:41:03 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._want_connected = false;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2018-03-14 13:58:04 -04:00
|
|
|
this._topics = new Map;
|
2017-11-13 01:23:39 -05:00
|
|
|
this._pending = [];
|
|
|
|
this._awaiting = new Map;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._socket = null;
|
|
|
|
this._state = 0;
|
|
|
|
this._last_id = 1;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._delay = 0;
|
|
|
|
this._last_ping = null;
|
|
|
|
this._time_drift = 0;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._host_idx = -1;
|
|
|
|
this._host_pool = -1;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.settings.on(':changed:socket.cluster', () => {
|
|
|
|
this._host = null;
|
|
|
|
if ( this.disconnected)
|
|
|
|
this.connect();
|
|
|
|
else
|
|
|
|
this.reconnect();
|
|
|
|
});
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.on(':command:reconnect', this.reconnect, this);
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.on(':command:do_authorize', challenge => {
|
|
|
|
this.log.warn('Unimplemented: do_authorize', challenge);
|
|
|
|
});
|
2015-11-01 17:28:19 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.enable();
|
2015-11-01 17:28:19 -05:00
|
|
|
}
|
|
|
|
|
2016-05-22 14:08:11 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
onEnable() { this.connect() }
|
|
|
|
onDisable() { this.disconnect() }
|
2016-05-22 14:08:11 -04:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ========================================================================
|
|
|
|
// Properties
|
|
|
|
// ========================================================================
|
2016-05-22 14:08:11 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
get connected() {
|
|
|
|
return this._state === State.CONNECTED;
|
|
|
|
}
|
2016-05-22 14:08:11 -04:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
get connecting() {
|
|
|
|
return this._state === State.CONNECTING;
|
|
|
|
}
|
2016-05-22 14:08:11 -04:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
get disconnected() {
|
|
|
|
return this._state === State.DISCONNECTED;
|
|
|
|
}
|
2016-05-22 14:08:11 -04:00
|
|
|
|
2015-10-24 22:44:00 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ========================================================================
|
|
|
|
// Connection Logic
|
|
|
|
// ========================================================================
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
selectHost() {
|
|
|
|
const cluster_id = this.settings.get('socket.cluster'),
|
2018-04-02 03:30:22 -04:00
|
|
|
cluster = WS_CLUSTERS[cluster_id],
|
|
|
|
l = cluster && cluster.length;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2018-04-02 03:30:22 -04:00
|
|
|
if ( ! l )
|
2017-11-13 01:23:39 -05:00
|
|
|
return null;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2018-04-02 03:30:22 -04:00
|
|
|
let total = 0, i = l;
|
2017-11-13 01:23:39 -05:00
|
|
|
while(i-- > 0)
|
|
|
|
total += cluster[i][1];
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
let val = Math.random() * total;
|
|
|
|
for(let i=0; i < l; i++) {
|
|
|
|
val -= cluster[i][1];
|
|
|
|
if ( val <= 0 )
|
|
|
|
return cluster[i][0];
|
|
|
|
}
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
return cluster[l-1][0];
|
|
|
|
}
|
2015-11-01 17:28:19 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
_reconnect() {
|
|
|
|
if ( ! this._reconnect_timer ) {
|
|
|
|
if ( this._delay < 60000 )
|
|
|
|
this._delay += (Math.floor(Math.random() * 10) + 5) * 1000;
|
|
|
|
else
|
|
|
|
this._delay = (Math.floor(Math.random() * 60) + 30) * 1000;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._reconnect_timer = setTimeout(() => {
|
|
|
|
this.connect();
|
|
|
|
}, this._delay);
|
|
|
|
}
|
2015-01-27 17:05:51 -05:00
|
|
|
}
|
|
|
|
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
reconnect() {
|
|
|
|
this.disconnect();
|
|
|
|
this._reconnect();
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
connect() {
|
|
|
|
this._want_connected = true;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( this._reconnect_timer ) {
|
|
|
|
clearTimeout(this._reconnect_timer);
|
|
|
|
this._reconnect_timer = null;
|
2015-05-17 19:02:57 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( ! this.disconnected )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const host = this._host = this._host || this.selectHost();
|
|
|
|
if ( ! host )
|
|
|
|
return;
|
2015-06-10 18:46:04 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._state = State.CONNECTING;
|
|
|
|
this._last_id = 1;
|
2015-06-10 18:46:04 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._delay = 0;
|
|
|
|
this._last_ping = null;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.log.info(`Using Server: ${host}`);
|
2015-07-04 17:06:36 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
let ws;
|
2015-07-04 17:06:36 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
try {
|
|
|
|
ws = this._socket = new WebSocket(host);
|
|
|
|
} catch(err) {
|
|
|
|
this._state = State.DISCONNECTED;
|
|
|
|
this._reconnect();
|
|
|
|
this.log.error('Unable to create WebSocket.', err);
|
|
|
|
return;
|
2015-07-04 17:06:36 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ws.onopen = () => {
|
|
|
|
this._state = State.CONNECTED;
|
|
|
|
this._sent_user = false;
|
|
|
|
|
|
|
|
this.log.info('Connected.');
|
|
|
|
|
|
|
|
// Initial HELLO. Here we get a Client-ID and initial server timestamp.
|
|
|
|
// This is handled entirely on the socket server and so should be
|
|
|
|
// fast enough to use as a ping.
|
|
|
|
this._ping_time = performance.now();
|
|
|
|
this._send(
|
|
|
|
'hello',
|
|
|
|
[`ffz_${window.FrankerFaceZ.version_info}`, this.settings.provider.get('client-id')],
|
|
|
|
(success, data) => {
|
|
|
|
if ( ! success )
|
|
|
|
return this.log.warn('Error Saying Hello', data);
|
|
|
|
|
|
|
|
this._on_pong(false, success, data[1]);
|
|
|
|
this.settings.provider.set('client-id', data[0]);
|
|
|
|
this.log.info('Client ID:', data[0]);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Grab the current user from the site.
|
|
|
|
const site = this.resolve('site'),
|
|
|
|
send_user = () => {
|
|
|
|
if ( this._sent_user || ! this.connected )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const user = site.getUser();
|
|
|
|
if ( user && user.login ) {
|
|
|
|
this._sent_user = true;
|
|
|
|
this._send('setuser', user.login);
|
|
|
|
|
|
|
|
} else if ( ! site.enabled )
|
|
|
|
this.once('site:enabled', send_user, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
send_user();
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe to Topics
|
2018-03-14 17:32:19 -04:00
|
|
|
for(const topic of this._topics.keys())
|
2017-11-13 01:23:39 -05:00
|
|
|
this._send('sub', topic);
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2015-10-26 12:13:28 -07:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// Send pending commands.
|
|
|
|
for(const [command, args, callback] of this._pending)
|
|
|
|
this._send(command, args, callback);
|
|
|
|
|
|
|
|
this._pending = [];
|
|
|
|
|
|
|
|
|
|
|
|
// We're ready.
|
|
|
|
this._send('ready', this._offline_time || 0);
|
|
|
|
this._offline_time = null;
|
|
|
|
this.emit(':connected');
|
2015-10-26 12:13:28 -07:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ws.onerror = () => {
|
|
|
|
if ( ! this._offline_time )
|
|
|
|
this._offline_time = Date.now();
|
2015-10-26 12:13:28 -07:00
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ws.onclose = event => {
|
|
|
|
const old_state = this._state;
|
|
|
|
this.log.info(`Disconnected. (${event.code}:${event.reason})`);
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._state = State.DISCONNECTED;
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
for(const [cmd_id, callback] of this._awaiting) {
|
|
|
|
const err = new Error('disconnected');
|
|
|
|
try {
|
|
|
|
if ( typeof callback === 'function' )
|
|
|
|
callback(false, err);
|
|
|
|
else
|
|
|
|
callback[1](err);
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
} catch(error) {
|
|
|
|
this.log.warn(`Callback Error #${cmd_id}`, error);
|
|
|
|
}
|
2015-02-08 02:01:09 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
this._awaiting.clear();
|
|
|
|
|
|
|
|
if ( ! this._want_connected )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( ! this._offline_time )
|
|
|
|
this._offline_time = Date.now();
|
|
|
|
|
|
|
|
// Reset the host if we didn't manage to connect.
|
|
|
|
if ( old_state !== State.CONNECTED )
|
|
|
|
this._host = null;
|
|
|
|
|
|
|
|
this._reconnect();
|
|
|
|
this.emit(':closed', event.code, event.reason);
|
2015-02-08 02:01:09 -05:00
|
|
|
}
|
2015-03-15 21:24:22 -04:00
|
|
|
|
2015-08-02 02:41:03 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ws.onmessage = event => {
|
|
|
|
// Format:
|
|
|
|
// -1 <cmd_name>[ <json_data>]
|
|
|
|
// <reply-id> <ok/err>[ <json_data>]
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const raw = event.data,
|
|
|
|
idx = raw.indexOf(' ');
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( idx === -1 )
|
|
|
|
return this.log.warn('Malformed message from server.', event.data);
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const reply = parseInt(raw.slice(0, idx), 10),
|
|
|
|
ix2 = raw.indexOf(' ', idx + 1),
|
2015-10-24 21:33:31 -07:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
cmd = raw.slice(idx+1, ix2 === -1 ? raw.length : ix2),
|
|
|
|
data = ix2 === -1 ? undefined : JSON.parse(raw.slice(ix2+1));
|
2015-02-26 00:42:11 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( reply === -1 ) {
|
|
|
|
this.log.debug(`Received Command: ${cmd}`, data);
|
|
|
|
this.emit(`:command:${cmd}`, data);
|
2015-02-26 00:42:11 -05:00
|
|
|
|
2017-02-14 00:07:55 -05:00
|
|
|
} else {
|
2017-11-13 01:23:39 -05:00
|
|
|
const success = cmd === 'ok',
|
|
|
|
callback = this._awaiting.get(reply);
|
|
|
|
|
|
|
|
if ( callback ) {
|
|
|
|
this._awaiting.delete(reply);
|
|
|
|
if ( typeof callback === 'function' )
|
|
|
|
callback(success, data);
|
|
|
|
else
|
|
|
|
callback[success ? 0 : 1](data);
|
|
|
|
|
|
|
|
} else if ( ! success || DEBUG )
|
|
|
|
this.log.info(`Received Reply #${reply}:`, success ? 'OK' : 'Error', data);
|
2015-01-20 01:53:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
disconnect() {
|
|
|
|
this._want_connected = false;
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( this._reconnect_timer ) {
|
|
|
|
clearTimeout(this._reconnect_timer);
|
|
|
|
this._reconnect_timer = null;
|
|
|
|
}
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( this.disconnected )
|
|
|
|
return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
this._socket.close();
|
|
|
|
} catch(err) { /* if this caused an exception, we don't care -- it's still closed */ }
|
2015-01-20 01:53:18 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._socket = null;
|
|
|
|
this._state = State.DISCONNECTED;
|
2015-07-13 21:52:44 -04:00
|
|
|
}
|
|
|
|
|
2015-02-24 00:33:29 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ========================================================================
|
|
|
|
// Latency
|
|
|
|
// ========================================================================
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
_on_pong(skip_log, success, data) {
|
|
|
|
const now = performance.now();
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( ! success ) {
|
|
|
|
this._ping_time = null;
|
|
|
|
if ( ! skip_log )
|
|
|
|
this.log.warn('Error Pinging Server', data);
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
} else if ( this._ping_time ) {
|
|
|
|
const d_now = Date.now(),
|
|
|
|
rtt = now - this._ping_time,
|
|
|
|
ping = this._last_ping = rtt / 2;
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._ping_time = null;
|
|
|
|
const drift = this._time_drift = d_now - (data + ping);
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( ! skip_log ) {
|
|
|
|
this.log.info('Server Time:', new Date(data).toISOString());
|
|
|
|
this.log.info(' Local Time:', new Date(d_now).toISOString());
|
|
|
|
this.log.info(` Est. Ping: ${ping.toFixed(5)}ms`);
|
|
|
|
this.log.info(`Time Offset: ${drift / 1000}`);
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( Math.abs(drift) > 300000 )
|
|
|
|
this.log.warn('Local time differs from server time by more than 5 minutes.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-19 02:45:56 -05:00
|
|
|
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ping(skip_log) {
|
|
|
|
if ( this._ping_time || ! this.connected )
|
|
|
|
return;
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._ping_time = performance.now();
|
|
|
|
this._send('ping', undefined, (s,d) => this._on_pong(skip_log, s, d));
|
|
|
|
}
|
2015-11-19 02:45:56 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ========================================================================
|
|
|
|
// Communication
|
|
|
|
// ========================================================================
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
_send(command, args, callback) {
|
|
|
|
if ( ! this.connected )
|
|
|
|
return this.log.warn(`Tried sending command "${command}" while disconnected.`);
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
const cmd_id = this._last_id++;
|
|
|
|
if ( callback )
|
|
|
|
this._awaiting.set(cmd_id, callback);
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this._socket.send(`${cmd_id} ${command}${args !== undefined ? ` ${JSON.stringify(args)}` : ''}`);
|
|
|
|
}
|
2015-06-05 03:59:28 -04:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
send(command, ...args) {
|
|
|
|
if ( args.length === 1 )
|
|
|
|
args = args[0];
|
|
|
|
|
|
|
|
if ( ! this.connected )
|
|
|
|
this._pending.push([command, args]);
|
|
|
|
else
|
|
|
|
this._send(command, args);
|
2015-11-19 02:45:56 -05:00
|
|
|
}
|
2015-06-05 03:59:28 -04:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
call(command, ...args) {
|
|
|
|
if ( args.length === 1 )
|
|
|
|
args = args[0];
|
2015-11-01 17:28:19 -05:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
if ( ! this.connected )
|
|
|
|
this._pending.push([command, args, [resolve, reject]]);
|
|
|
|
else
|
|
|
|
this._send(command, args, [resolve, reject]);
|
|
|
|
});
|
|
|
|
}
|
2015-11-01 17:28:19 -05:00
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
// ========================================================================
|
|
|
|
// Topics
|
|
|
|
// ========================================================================
|
|
|
|
|
2018-03-14 13:58:04 -04:00
|
|
|
subscribe(referrer, ...topics) {
|
2017-11-13 01:23:39 -05:00
|
|
|
const t = this._topics;
|
|
|
|
for(const topic of topics) {
|
2018-03-14 13:58:04 -04:00
|
|
|
if ( ! t.has(topic) ) {
|
|
|
|
if ( this.connected )
|
|
|
|
this._send('sub', topic);
|
|
|
|
|
|
|
|
t.set(topic, new Set);
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-03-14 13:58:04 -04:00
|
|
|
const tp = t.get(topic);
|
|
|
|
tp.add(referrer);
|
2015-11-01 17:28:19 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-05 03:59:28 -04:00
|
|
|
|
2018-03-14 13:58:04 -04:00
|
|
|
unsubscribe(referrer, ...topics) {
|
2017-11-13 01:23:39 -05:00
|
|
|
const t = this._topics;
|
|
|
|
for(const topic of topics) {
|
2018-03-14 13:58:04 -04:00
|
|
|
if ( ! t.has(topic) )
|
|
|
|
continue;
|
2015-02-24 00:33:29 -05:00
|
|
|
|
2018-03-14 13:58:04 -04:00
|
|
|
const tp = t.get(topic);
|
|
|
|
tp.delete(referrer);
|
|
|
|
|
|
|
|
if ( ! tp.size ) {
|
|
|
|
t.delete(topic);
|
|
|
|
if ( this.connected )
|
|
|
|
this._send('unsub', topic);
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
}
|
2015-02-24 00:33:29 -05:00
|
|
|
|
2016-04-11 18:57:25 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
get topics() {
|
2018-03-14 13:58:04 -04:00
|
|
|
return Array.from(this._topics.keys());
|
2015-02-24 00:33:29 -05:00
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SocketClient.State = State;
|