diff --git a/src/plugins/converse-rai.js b/src/plugins/converse-rai.js index c11090646e0a5ae692b4128828ddab8832e8d841..42773a47002c23e2de10755d41a4df664967a296 100644 --- a/src/plugins/converse-rai.js +++ b/src/plugins/converse-rai.js @@ -1,195 +1,172 @@ -(function() { - let Strophe, $iq, $msg, $pres, $build, b64_sha1, _, Backbone, dayjs, _converse; - let interestingServers = new Set(); - let subscribedServers = new Set(); - - converse.plugins.add('converse-rai', { - 'dependencies': [], - - 'initialize': function() { - _converse = this._converse; - - Strophe = converse.env.Strophe; - $iq = converse.env.$iq; - $msg = converse.env.$msg; - $pres = converse.env.$pres; - $build = converse.env.$build; - b64_sha1 = converse.env.b64_sha1; - _ = converse.env._; - Backbone = converse.env.Backbone; - dayjs = converse.env.dayjs; - - _converse.api.settings.extend({ - rai_notification: true, - rai_notification_label: 'Room Activity Indicator', - }); - - _converse.api.listen.on('connected', function() { - setupRoomActivityIndicators(); - }); - - _converse.api.listen.on('chatRoomViewInitialized', function(view) { - const jid = view.model.get('jid'); - - if (view.model.get('num_unread') > 0 || view.model.get('num_unread_general') > 0) { - emitNotification(jid); - } - }); - - _converse.api.listen.on('raiRoomsUpdated', function(rooms) { - interestingServers = new Set(rooms.filter(room => room).map(Strophe.getDomainFromJid)); - if (_converse.api.connection.connected()) { - updateSubscriptions(); - } - }); - - _converse.api.listen.on('chatBoxScrolledDown', function(view) { - const jid = view.chatbox.get('jid'); - const id_attr = 'stanza_id ' + jid; - const messages = view.chatbox.messages; - - if (!getUnreadStatus(jid)) { - return; +/** + * Add RAI support to MUCs. + * @see https://xmpp.org/extensions/inbox/room-activity-indicators.html + */ +converse.plugins.add('converse-rai', { + dependencies: [ + 'converse-chatboxes', + ], + overrides: { + ChatRoom: { + async initialize() { + await this.__super__.initialize.apply(this, arguments); + this.listenTo(this, 'change:hidden', this.onHiddenChange); + }, + + /** + * Subscribe to RAI if connected to the room, but it's hidden. + * @private + * @method _converse.ChatRoom#onConnectionStatusChanged + */ + async onConnectionStatusChanged() { + console.log('ChatRoom#onConnectionStatusChanged') + const { api } = this.__super__._converse; + if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) { + if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') { + await this.leave(); + this.enableRAI(); + } else { + await this.__super__.onConnectionStatusChanged.apply(this, arguments); + } } - - for (let i = messages.length - 1; i >= 0; i--) { - const message = messages.at(i); - - if (message.has(id_attr)) { - let id = message.get(id_attr); - if (id != sessionStorage.getItem('rai_displayed.' + jid)) { - sessionStorage.setItem('rai_displayed.' + jid, id); - setUnreadStatus(jid, false); - setTimeout(() => sendMarker(jid, id, 'displayed'), 0); + }, + + /** + * Handler that gets called when the 'hidden' flag is toggled. + * @private + * @method _converse.ChatRoom#onHiddenChange + */ + async onHiddenChange() { + console.log('ChatRoomView#onHiddenChange') + const conn_status = this.session.get('connection_status'); + const { api } = this.__super__._converse; + + if (this.get('hidden') && conn_status === converse.ROOMSTATUS.ENTERED) { + if (api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') { + if (conn_status !== converse.ROOMSTATUS.DISCONNECTED) { + this.sendMarkerForLastMessage('received', true); + await this.leave(); } - break; + this.enableRAI(); } + } else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) { + this.rejoin(); } - }); - - _converse.api.listen.on('chatBoxInsertedIntoDOM', function(view) { - const jid = view.model.get('jid'); - - if (view.model.get('num_unread') > 0) { - emitNotification(jid); + }, + + /** + * Ensures that the user is subscribed to XEP-0437 Room Activity Indicators + * if `muc_subscribe_to_rai` is set to `true`. + * Only affiliated users can subscribe to RAI, but this method doesn't + * check whether the current user is affiliated because it's intended to be + * called after the MUC has been left and we don't have that information + * anymore. + * @private + * @method _converse.ChatRoom#enableRAI + */ + enableRAI() { + console.log('ChatRoom#enableRAI') + const { api } = this.__super__._converse; + if (api.settings.get('muc_subscribe_to_rai')) { + api.rooms.subscribe(this.get('jid')); } + }, + }, + ChatBox: { + /** + * Finds the last eligible message and then sends a XEP-0333 chat marker for it. + * @param { ('received'|'displayed'|'acknowledged') } [type='displayed'] + * @param { Boolean } force - Whether a marker should be sent for the + * message, even if it didn't include a `markable` element. + */ + sendMarkerForLastMessage(type = 'displayed', force = false) { + const msgs = Array.from(this.messages.models); + msgs.reverse(); + const msg = msgs.find(m => m.get('sender') === 'them' && (force || m.get('is_markable'))); + msg && this.sendMarkerForMessage(msg, type, force); + }, + } + }, + initialize() { + const { Strophe, _, $pres, u } = converse.env; + const _converse = this._converse; - }); + // Register namespace + Strophe.addNamespace('RAI', 'urn:xmpp:rai:0'); - _converse.api.listen.on('message', function(data) { - var chatbox = data.chatbox; - var history = data.attrs.is_archived; - var sender = data.attrs.sender; - var body = data.attrs.body; + _converse.api.listen.on('addClientFeatures', () => { + _converse.api.disco.own.features.add(Strophe.NS.RAI); + }); - if (!history && body && chatbox && sender !== 'me') { - const alert = chatbox.get('num_unread') > 0; - const notify = chatbox.get('num_unread_general') > 0; + // Register settings + _converse.api.settings.extend({ + muc_subscribe_to_rai: true, + }); - if (alert || notify) { - emitNotification(chatbox.get('jid'), alert); - } + Object.assign(_converse.api.rooms, { + /** + * Send an RAI stanza for the given jids. + * The presence stanza is sent for a whole muc domain. + * + * @param {string|Array} jids + * @returns {void} + */ + subscribe(jids) { + if (typeof jids === 'string') { + jids = [jids]; } - }); - }, - }); - - function subscribeServer(server_name) { - const id = Math.random().toString(36).substr(2, 9); - _converse.connection.send(converse.env.$pres({ - to: server_name, - id: id, - }).c('rai', { - 'xmlns': 'xmpp:prosody.im/protocol/rai', - })); - } - - function unsubscribeServer(server_name) { - _converse.connection.send(converse.env.$pres({ - to: server_name, type: 'unavailable', - }).c('rai', { - 'xmlns': 'xmpp:prosody.im/protocol/rai', - })); - } + const muc_domains = jids.map(jid => Strophe.getDomainFromJid(jid)); - function updateSubscriptions() { - var new_servers = new Set([...interestingServers].filter(server => !subscribedServers.has(server))); - var obsolete_servers = new Set([...subscribedServers].filter(server => !interestingServers.has(server))); - for (let server of obsolete_servers) { - unsubscribeServer(server); - } - for (let server of new_servers) { - subscribeServer(server); - } - } - - function setupRoomActivityIndicators() { - updateSubscriptions(); - // If we already have unread notifications stored for this session, emit them now - for (var i = 0; i < sessionStorage.length; i++) { - if (sessionStorage.key(i).indexOf('rai_notify.') == 0) { - const jid = sessionStorage.key(i).substring(11); - emitNotification(jid); + _.uniq(muc_domains).forEach(muc_domain => { + const rai = $pres({ to: muc_domain, id: u.getUniqueId() }).c('rai', { + 'xmlns': Strophe.NS.RAI + }); + _converse.api.send(rai); + console.log('Sent RAI stanza for muc_domain', muc_domain, rai.toString()); + }); } - } + }); - // Listen for incoming RAI from the server - _converse.connection.addHandler(function(message) { - const from_jid = message.attributes.from?.nodevalue; - const room_jid = from_jid?.split('/')[0]; - const room = ''; + function mucActivityHandler(message) { + console.log('mucActivityHandler', message) + const from_jid = message.attributes.from?.nodevalue + const room_jid = from_jid?.split("/")[0] + let room = ''; let ignore = false; + for (let i = 0; i < _converse.chatboxes.models.length; i++) { if (_converse.chatboxes.models[i].id === room_jid) { room = _converse.chatboxes.models[i].id; break; } } + if (room && from_jid && room_jid) { if (from_jid === room_jid + '/' + room.get('nick')) { ignore = true; } } - if (message && !ignore) - message.querySelectorAll('activity').forEach(function(activity) { - if (activity && activity.namespaceURI == 'xmpp:prosody.im/protocol/rai') { + + if (message && !ignore) { + message.querySelectorAll('activity').forEach(activity => { + if (activity && activity.namespaceURI === Strophe.NS.RAI) { const jid = activity.textContent; - setUnreadStatus(jid, true); - emitNotification(jid); + // TODO: Give the chatroom object instead of the jid + _converse.api.trigger('chatRoomHasActivity', jid); } }); - return true; - }, null, 'message'); - } + } - function setUnreadStatus(jid, flag) { - if (flag) { - sessionStorage.setItem('rai_notify.' + jid, 'true'); - } else { - sessionStorage.removeItem('rai_notify.' + jid); + return true; } - } - - function getUnreadStatus(jid) { - return sessionStorage.getItem('rai_notify.' + jid) == 'true'; - } - - function emitNotification(jid, alert) { - _converse.api.trigger('chatRoomActivityIndicators', jid); - } - function sendMarker(to_jid, id, type) { - const stanza = converse.env.$msg({ - 'from': _converse.connection.jid, - 'id': Math.random().toString(36).substr(2, 9), - 'to': to_jid, - 'type': 'groupchat', - }).c(type, { - 'xmlns': converse.env.Strophe.NS.MARKERS, - 'id': id, + // TODO: On loading subscribe to all the ChatViews in navigation (into another plugin) + _converse.api.listen.on('connected', () => { + _converse.connection.addHandler(mucActivityHandler, null, 'message', 'groupchat'); }); - _converse.api.send(stanza); + // TODO: Remove + // _converse.api.listen.on('send', stanza => console.log('send', stanza)); } -})(); +}); diff --git a/src/plugins/sib-subscribe-to-rai.js b/src/plugins/sib-subscribe-to-rai.js index 51dbb91174b448768f923173549ac9555694063b..58bc2d3a03020fd0f0db9d1aa0df604a1ead0db5 100644 --- a/src/plugins/sib-subscribe-to-rai.js +++ b/src/plugins/sib-subscribe-to-rai.js @@ -57,9 +57,9 @@ converse.plugins.add('sib-subscribe-to-rai', { ])).flat(); // @MattJ Here userRooms is an array of each jabberID the user is on. - api.trigger('raiRoomsUpdated', userRooms); + api.rooms.subscribe(userRooms); - api.listen.on('chatRoomActivityIndicators', jid => { + api.listen.on('chatRoomHasActivity', jid => { window.dispatchEvent(new CustomEvent('newMessage', { detail: { jid,