Converse converse.js

Source: headless/converse-ping.js

/**
 * @module converse-ping
 * @description
 * Converse.js plugin which add support for application-level pings
 * as specified in XEP-0199 XMPP Ping.
 * @copyright 2020, the Converse.js contributors
 * @license Mozilla Public License (MPLv2)
 */
import { _converse, api, converse } from "./converse-core";
import log from "./log";

const { Strophe, $iq } = converse.env;
const u = converse.env.utils;

Strophe.addNamespace('PING', "urn:xmpp:ping");


converse.plugins.add('converse-ping', {

    initialize () {
        /* The initialize function gets called as soon as the plugin is
         * loaded by converse.js's plugin machinery.
         */
        let lastStanzaDate;

        api.settings.extend({
            ping_interval: 60 //in seconds
        });

        function pong (ping) {
            lastStanzaDate = new Date();
            const from = ping.getAttribute('from');
            const id = ping.getAttribute('id');
            const iq = $iq({type: 'result', to: from,id: id});
            _converse.connection.sendIQ(iq);
            return true;
        }

        function registerPongHandler () {
            if (_converse.connection.disco !== undefined) {
                api.disco.own.features.add(Strophe.NS.PING);
            }
            return _converse.connection.addHandler(pong, Strophe.NS.PING, "iq", "get");
        }

        function registerPingHandler () {
            _converse.connection.addHandler(() => {
                if (api.settings.get('ping_interval') > 0) {
                    // Handler on each stanza, saves the received date
                    // in order to ping only when needed.
                    lastStanzaDate = new Date();
                    return true;
                }
            });
        }

        setTimeout(() => {
            if (api.settings.get('ping_interval') > 0) {
                const now = new Date();
                if (!lastStanzaDate) {
                    lastStanzaDate = now;
                }
                if ((now - lastStanzaDate)/1000 > api.settings.get('ping_interval')) {
                    return api.ping();
                }
                return true;
            }
        }, 1000);


        /************************ BEGIN Event Handlers ************************/
        const onConnected = function () {
            // Wrapper so that we can spy on registerPingHandler in tests
            registerPongHandler();
            registerPingHandler();
        };
        api.listen.on('connected', onConnected);
        api.listen.on('reconnected', onConnected);


        function onWindowStateChanged (data) {
            if (data.state === 'visible' && api.connection.connected()) {
                api.ping(null, 5000);
            }
        }
        api.listen.on('windowStateChanged', onWindowStateChanged);
        /************************ END Event Handlers ************************/


        /************************ BEGIN API ************************/
        Object.assign(api, {
            /**
             * Pings the service represented by the passed in JID by sending an IQ stanza.
             * @private
             * @method api.ping
             * @param { String } [jid] - The JID of the service to ping
             * @param { Integer } [timeout] - The amount of time in
             *  milliseconds to wait for a response. The default is 10000;
             */
            async ping (jid, timeout) {
                // XXX: We could first check here if the server advertised that it supports PING.
                // However, some servers don't advertise while still responding to pings
                //
                // const feature = _converse.disco_entities[_converse.domain].features.findWhere({'var': Strophe.NS.PING});
                lastStanzaDate = new Date();
                jid = jid || Strophe.getDomainFromJid(_converse.bare_jid);
                if (_converse.connection) {
                    const iq = $iq({
                            'type': 'get',
                            'to': jid,
                            'id': u.getUniqueId('ping')
                        }).c('ping', {'xmlns': Strophe.NS.PING});

                    const result = await api.sendIQ(iq, timeout || 10000, false);
                    if (result === null) {
                        log.warn(`Timeout while pinging ${jid}`);
                        if (jid === Strophe.getDomainFromJid(_converse.bare_jid)) {
                            api.connection.reconnect();
                        }
                    } else if (u.isErrorStanza(result)) {
                        log.error(`Error while pinging ${jid}`);
                        log.error(result);
                    }
                    return true;
                }
                return false;
            }
        });
    }
});