diff --git a/karma.conf.js b/karma.conf.js index 3751d5767..4f3e5f111 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -31,7 +31,6 @@ module.exports = function(config) { { pattern: "spec/eventemitter.js", type: 'module' }, { pattern: "spec/http-file-upload.js", type: 'module' }, { pattern: "spec/markers.js", type: 'module' }, - { pattern: "spec/ping.js", type: 'module' }, { pattern: "spec/presence.js", type: 'module' }, { pattern: "spec/protocol.js", type: 'module' }, { pattern: "spec/push.js", type: 'module' }, @@ -42,8 +41,9 @@ module.exports = function(config) { { pattern: "spec/user-details-modal.js", type: 'module' }, { pattern: "spec/utils.js", type: 'module' }, { pattern: "spec/xmppstatus.js", type: 'module' }, + { pattern: "src/headless/plugins/disco/tests/disco.js", type: 'module' }, { pattern: "src/headless/plugins/muc/tests/affiliations.js", type: 'module' }, - { pattern: "src/headless/plugins/tests/disco.js", type: 'module' }, + { pattern: "src/headless/plugins/ping/tests/ping.js", type: 'module' }, { pattern: "src/plugins/bookmark-views/tests/bookmarks.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/chatbox.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' }, diff --git a/src/headless/headless.js b/src/headless/headless.js index 9be21dd2c..ae3b98c36 100644 --- a/src/headless/headless.js +++ b/src/headless/headless.js @@ -13,7 +13,7 @@ import "./plugins/disco/index.js"; // XEP-0030 Service discovery import "./plugins/headlines.js"; // Support for headline messages import "./plugins/mam/index.js"; // XEP-0313 Message Archive Management import "./plugins/muc/index.js"; // XEP-0045 Multi-user chat -import "./plugins/ping.js"; // XEP-0199 XMPP Ping +import "./plugins/ping/index.js"; // XEP-0199 XMPP Ping import "./plugins/pubsub.js"; // XEP-0060 Pubsub import "./plugins/roster/index.js"; // RFC-6121 Contacts Roster import "./plugins/smacks.js"; // XEP-0198 Stream Management diff --git a/src/headless/plugins/ping.js b/src/headless/plugins/ping.js deleted file mode 100644 index 25eb3a4c4..000000000 --- a/src/headless/plugins/ping.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @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 "../core.js"; -import log from "../log.js"; - -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; - } - }); - } -}); diff --git a/src/headless/plugins/ping/api.js b/src/headless/plugins/ping/api.js new file mode 100644 index 000000000..e45969499 --- /dev/null +++ b/src/headless/plugins/ping/api.js @@ -0,0 +1,44 @@ +import log from '@converse/headless/log.js'; +import { _converse, api, converse } from "@converse/headless/core.js"; +import { setLastStanzaDate } from './utils.js'; + +const { Strophe, $iq, u } = converse.env; + +export default { + /** + * 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}); + setLastStanzaDate(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; + } +} diff --git a/src/headless/plugins/ping/index.js b/src/headless/plugins/ping/index.js new file mode 100644 index 000000000..414fd9dcf --- /dev/null +++ b/src/headless/plugins/ping/index.js @@ -0,0 +1,33 @@ +/** + * @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 ping_api from './api.js'; +import { api, converse } from "@converse/headless/core.js"; +import { onEverySecond, onWindowStateChanged, onConnected } from './utils.js'; + +const { Strophe } = converse.env; + + +Strophe.addNamespace('PING', "urn:xmpp:ping"); + + +converse.plugins.add('converse-ping', { + + initialize () { + api.settings.extend({ + ping_interval: 60 //in seconds + }); + + Object.assign(api, ping_api); + + setTimeout(onEverySecond, 1000); + + api.listen.on('connected', onConnected); + api.listen.on('reconnected', onConnected); + api.listen.on('windowStateChanged', onWindowStateChanged); + } +}); diff --git a/spec/ping.js b/src/headless/plugins/ping/tests/ping.js similarity index 100% rename from spec/ping.js rename to src/headless/plugins/ping/tests/ping.js diff --git a/src/headless/plugins/ping/utils.js b/src/headless/plugins/ping/utils.js new file mode 100644 index 000000000..bff5bd8ae --- /dev/null +++ b/src/headless/plugins/ping/utils.js @@ -0,0 +1,61 @@ +import { _converse, api, converse } from "@converse/headless/core.js"; + +const { Strophe, $iq } = converse.env; + +let lastStanzaDate; + +export function onWindowStateChanged (data) { + if (data.state === 'visible' && api.connection.connected()) { + api.ping(null, 5000); + } +} + +export function setLastStanzaDate (date) { + lastStanzaDate = date; +} + +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; +} + +export 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"); +} + +export 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; + } + }); +} + +export function onConnected () { + // Wrapper so that we can spy on registerPingHandler in tests + registerPongHandler(); + registerPingHandler(); +} + +export function onEverySecond () { + 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; + } +}