Skip to content
Snippets Groups Projects

History, Reply to messages and RAI

Merged Emmanuel Vodor requested to merge beta into master
Files
3
+ 104
179
(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;
}
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);
/**
* 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);
},
/**
* Handler that gets called when the 'hidden' flag is toggled.
* @private
* @method _converse.ChatRoom#onHiddenChange
*/
async 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();
await this.close();
}
break;
}
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
await this.rejoin();
}
});
_converse.api.listen.on('chatBoxInsertedIntoDOM', function(view) {
const jid = view.model.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, sizzle, log } = converse.env;
const _converse = this._converse;
const { api } = _converse;
// Register namespace
Strophe.addNamespace('RAI', 'urn:xmpp:rai:0');
// Register settings
api.settings.extend({
muc_subscribe_to_rai: false,
});
if (view.model.get('num_unread') > 0) {
emitNotification(jid);
Object.assign(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 (!api.settings.get('muc_subscribe_to_rai')) {
console.error('Can\'t subscribe to RAI, this feature is not enabled');
}
});
_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;
if (!history && body && chatbox && sender !== 'me') {
const alert = chatbox.get('num_unread') > 0;
const notify = chatbox.get('num_unread_general') > 0;
if (alert || notify) {
emitNotification(chatbox.get('jid'), alert);
}
if (typeof jids === 'string') {
jids = [jids];
}
});
},
});
const muc_domains = jids.map(jid => Strophe.getDomainFromJid(jid));
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',
}));
}
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);
}
}
_.uniq(muc_domains).forEach(muc_domain => {
const rai = $pres({ to: muc_domain, id: u.getUniqueId() }).c('rai', {
'xmlns': Strophe.NS.RAI,
});
api.send(rai);
console.log('Sent RAI stanza for muc_domain', muc_domain, rai.toString());
});
},
});
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);
/**
* Triggers an event if a jid has activity.
*
* @param message
* @returns {boolean}
*/
function mucActivityHandler(message) {
const rai = sizzle(`rai[xmlns="${Strophe.NS.RAI}"]`, message).pop();
if (rai) {
rai.querySelectorAll('activity').forEach(activity => {
const jid = activity.textContent;
log.debug(`Received activity for the room: ${jid}`);
api.trigger('chatRoomHasActivity', jid);
});
}
}
// 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 = '';
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') {
const jid = activity.textContent;
setUnreadStatus(jid, true);
emitNotification(jid);
}
});
return true;
}, null, 'message');
}
function setUnreadStatus(jid, flag) {
if (flag) {
sessionStorage.setItem('rai_notify.' + jid, 'true');
} else {
sessionStorage.removeItem('rai_notify.' + jid);
}
}
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,
// Register our RAI handler
api.listen.on('connected', () => {
_converse.connection.addHandler(mucActivityHandler, null, 'message');
});
_converse.api.send(stanza);
}
})();
},
});
Loading