import { getLogger } from 'jitsi-meet-logger';
import { $iq, Strophe } from 'strophe.js';
import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
import ConnectionPlugin from './ConnectionPlugin';
const logger = getLogger(__filename);
/**
* Ping every 10 sec
*/
const PING_INTERVAL = 10000;
/**
* Ping timeout error after 15 sec of waiting.
*/
const PING_TIMEOUT = 15000;
/**
* Will close the connection after 3 consecutive ping errors.
*/
const PING_THRESHOLD = 3;
/**
* The number of timestamps of send pings to keep.
* The current value is 2 minutes.
* @type {number} number of timestamps.
*/
const PING_TIMESTAMPS_TO_KEEP = 120000 / PING_INTERVAL;
/**
* XEP-0199 ping plugin.
*
* Registers "urn:xmpp:ping" namespace under Strophe.NS.PING.
*/
export default class PingConnectionPlugin extends ConnectionPlugin {
/**
* Contructs new object
* @param {XMPP} xmpp the xmpp module.
* @constructor
*/
constructor(xmpp) {
super();
this.failedPings = 0;
this.xmpp = xmpp;
this.pingExecIntervals = new Array(PING_TIMESTAMPS_TO_KEEP);
}
/**
* Initializes the plugin. Method called by Strophe.
* @param connection Strophe connection instance.
*/
init(connection) {
super.init(connection);
Strophe.addNamespace('PING', 'urn:xmpp:ping');
}
/* eslint-disable max-params */
/**
* Sends "ping" to given jid
* @param jid the JID to which ping request will be sent.
* @param success callback called on success.
* @param error callback called on error.
* @param timeout ms how long are we going to wait for the response. On
* timeout error/t> callback is called with undefined error argument.
*/
ping(jid, success, error, timeout) {
this._addPingExecutionTimestamp();
const iq = $iq({
type: 'get',
to: jid
});
iq.c('ping', { xmlns: Strophe.NS.PING });
this.connection.sendIQ2(iq, { timeout })
.then(success, error);
}
/* eslint-enable max-params */
/**
* Starts to send ping in given interval to specified remote JID.
* This plugin supports only one such task and stopInterval
* must be called before starting a new one.
* @param remoteJid remote JID to which ping requests will be sent to.
* @param interval task interval in ms.
*/
startInterval(remoteJid, interval = PING_INTERVAL) {
clearInterval(this.intervalId);
this.intervalId = window.setInterval(() => {
this.ping(remoteJid, () => {
this.failedPings = 0;
}, error => {
this.failedPings += 1;
const errmsg = `Ping ${error ? 'error' : 'timeout'}`;
if (this.failedPings >= PING_THRESHOLD) {
GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
logger.error(errmsg, error);
// FIXME it doesn't help to disconnect when 3rd PING
// times out, it only stops Strophe from retrying.
// Not really sure what's the right thing to do in that
// situation, but just closing the connection makes no
// sense.
// self.connection.disconnect();
} else {
logger.warn(errmsg, error);
}
}, PING_TIMEOUT);
}, interval);
logger.info(`XMPP pings will be sent every ${interval} ms`);
}
/**
* Stops current "ping" interval task.
*/
stopInterval() {
if (this.intervalId) {
window.clearInterval(this.intervalId);
this.intervalId = null;
this.failedPings = 0;
logger.info('Ping interval cleared');
}
}
/**
* Adds the current time to the array of send ping timestamps.
* @private
*/
_addPingExecutionTimestamp() {
this.pingExecIntervals.push(new Date().getTime());
// keep array length to PING_TIMESTAMPS_TO_KEEP
if (this.pingExecIntervals.length > PING_TIMESTAMPS_TO_KEEP) {
this.pingExecIntervals.shift();
}
}
/**
* Returns the maximum time between the recent sent pings, if there is a
* big value it means the computer was inactive for some time(suspended).
* Checks the maximum gap between sending pings, considering and the
* current time. Trying to detect computer inactivity (sleep).
*
* @returns {int} the time ping was suspended, if it was not 0 is returned.
*/
getPingSuspendTime() {
const pingIntervals = this.pingExecIntervals.slice();
// we need current time, as if ping was sent now
// if computer sleeps we will get correct interval after next
// scheduled ping, bet we sometimes need that interval before waiting
// for the next ping, on closing the connection on error.
pingIntervals.push(new Date().getTime());
let maxInterval = 0;
let previousTS = pingIntervals[0];
pingIntervals.forEach(e => {
const currentInterval = e - previousTS;
if (currentInterval > maxInterval) {
maxInterval = currentInterval;
}
previousTS = e;
});
// remove the interval between the ping sent
// this way in normal execution there is no suspend and the return
// will be 0 or close to 0.
maxInterval -= PING_INTERVAL;
// make sure we do not return less than 0
return Math.max(maxInterval, 0);
}
}