Commit c1efb0d2 authored by JC Brand's avatar JC Brand

Add an external API for sending presences

parent edf7f6b8
...@@ -140,8 +140,9 @@ ...@@ -140,8 +140,9 @@
const view = _converse.xmppstatusview; const view = _converse.xmppstatusview;
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd" modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click(); modal.el.querySelector('[type="submit"]').click();
const last_stanza = _converse.connection.sent_stanzas.pop(); const sent_stanzas = _converse.connection.sent_stanzas;
expect(Strophe.serialize(last_stanza)).toBe( const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+ `<presence xmlns="jabber:client">`+
`<show>dnd</show>`+ `<show>dnd</show>`+
`<priority>0</priority>`+ `<priority>0</priority>`+
...@@ -169,8 +170,9 @@ ...@@ -169,8 +170,9 @@
const msg = 'I am happy'; const msg = 'I am happy';
modal.el.querySelector('input[name="status_message"]').value = msg; modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click(); modal.el.querySelector('[type="submit"]').click();
const last_stanza = _converse.connection.sent_stanzas.pop(); const sent_stanzas = _converse.connection.sent_stanzas;
expect(Strophe.serialize(last_stanza)).toBe( const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+ `<presence xmlns="jabber:client">`+
`<status>I am happy</status>`+ `<status>I am happy</status>`+
`<priority>0</priority>`+ `<priority>0</priority>`+
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
], factory); ], factory);
} (this, function (jasmine, mock, test_utils) { } (this, function (jasmine, mock, test_utils) {
"use strict"; "use strict";
const Strophe = converse.env.Strophe;
const u = converse.env.utils; const u = converse.env.utils;
// See: https://xmpp.org/rfcs/rfc3921.html // See: https://xmpp.org/rfcs/rfc3921.html
...@@ -37,7 +38,7 @@ ...@@ -37,7 +38,7 @@
})); }));
it("has a given priority", mock.initConverse((done, _converse) => { it("has a given priority", mock.initConverse((done, _converse) => {
let pres = _converse.xmppstatus.constructPresence('online', 'Hello world'); let pres = _converse.xmppstatus.constructPresence('online', null, 'Hello world');
expect(pres.toLocaleString()).toBe( expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+ `<presence xmlns="jabber:client">`+
`<status>Hello world</status>`+ `<status>Hello world</status>`+
...@@ -46,7 +47,7 @@ ...@@ -46,7 +47,7 @@
`</presence>` `</presence>`
); );
_converse.priority = 2; _converse.priority = 2;
pres = _converse.xmppstatus.constructPresence('away', 'Going jogging'); pres = _converse.xmppstatus.constructPresence('away', null, 'Going jogging');
expect(pres.toLocaleString()).toBe( expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+ `<presence xmlns="jabber:client">`+
`<show>away</show>`+ `<show>away</show>`+
...@@ -57,7 +58,7 @@ ...@@ -57,7 +58,7 @@
); );
delete _converse.priority; delete _converse.priority;
pres = _converse.xmppstatus.constructPresence('dnd', 'Doing taxes'); pres = _converse.xmppstatus.constructPresence('dnd', null, 'Doing taxes');
expect(pres.toLocaleString()).toBe( expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+ `<presence xmlns="jabber:client">`+
`<show>dnd</show>`+ `<show>dnd</show>`+
...@@ -75,8 +76,6 @@ ...@@ -75,8 +76,6 @@
async (done, _converse) => { async (done, _converse) => {
test_utils.openControlBox(_converse); test_utils.openControlBox(_converse);
const view = _converse.xmppstatusview;
spyOn(view.model, 'sendPresence').and.callThrough();
spyOn(_converse.connection, 'send').and.callThrough(); spyOn(_converse.connection, 'send').and.callThrough();
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
...@@ -86,8 +85,10 @@ ...@@ -86,8 +85,10 @@
const msg = 'My custom status'; const msg = 'My custom status';
modal.el.querySelector('input[name="status_message"]').value = msg; modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click(); modal.el.querySelector('[type="submit"]').click();
expect(view.model.sendPresence).toHaveBeenCalled();
expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString()) const sent_stanzas = _converse.connection.sent_stanzas;
let sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence))
.toBe(`<presence xmlns="jabber:client">`+ .toBe(`<presence xmlns="jabber:client">`+
`<status>My custom status</status>`+ `<status>My custom status</status>`+
`<priority>0</priority>`+ `<priority>0</priority>`+
...@@ -100,10 +101,16 @@ ...@@ -100,10 +101,16 @@
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000); await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd" modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click(); modal.el.querySelector('[type="submit"]').click();
expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString()) await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).length === 2);
.toBe(`<presence xmlns="jabber:client"><show>dnd</show><status>My custom status</status><priority>0</priority>`+ sent_presence = sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop();
expect(Strophe.serialize(sent_presence))
.toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<status>My custom status</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+ `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`) `</presence>`)
done(); done();
})); }));
}); });
......
(function (root, factory) { (function (root, factory) {
define(["jasmine", "mock"], factory); define(["jasmine", "mock"], factory);
} (this, function (jasmine, mock) { } (this, function (jasmine, mock) {
const u = converse.env.utils;
return describe("The XMPPStatus model", function () { return describe("The XMPPStatus model", function () {
it("won't send <show>online</show> when setting a custom status message", mock.initConverse((done, _converse) => { it("won't send <show>online</show> when setting a custom status message",
mock.initConverse(async (done, _converse) => {
_converse.xmppstatus.save({'status': 'online'}); _converse.xmppstatus.save({'status': 'online'});
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
_converse.api.user.status.message.set("I'm also happy!"); _converse.api.user.status.message.set("I'm also happy!");
expect(_converse.connection.send).toHaveBeenCalled(); await u.waitUntil(() => _converse.connection.send.calls.count());
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree(); const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.childNodes.length).toBe(3); expect(stanza.childNodes.length).toBe(3);
expect(stanza.querySelectorAll('status').length).toBe(1); expect(stanza.querySelectorAll('status').length).toBe(1);
......
...@@ -834,7 +834,7 @@ converse.plugins.add('converse-rosterview', { ...@@ -834,7 +834,7 @@ converse.plugins.add('converse-rosterview', {
u.addClass('fa-spin', ev.target); u.addClass('fa-spin', ev.target);
_converse.roster.data.save('version', null); _converse.roster.data.save('version', null);
await _converse.roster.fetchFromServer(); await _converse.roster.fetchFromServer();
_converse.xmppstatus.sendPresence(); api.user.presence.send();
u.removeClass('fa-spin', ev.target); u.removeClass('fa-spin', ev.target);
}, },
......
...@@ -90,7 +90,6 @@ const PROMISES = [ ...@@ -90,7 +90,6 @@ const PROMISES = [
'connectionInitialized', 'connectionInitialized',
'initialized', 'initialized',
'pluginsInitialized', 'pluginsInitialized',
'statusInitialized'
]; ];
// Core plugins are whitelisted automatically // Core plugins are whitelisted automatically
...@@ -712,7 +711,6 @@ export const api = _converse.api = { ...@@ -712,7 +711,6 @@ export const api = _converse.api = {
* * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched) * * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched)
* * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched) * * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched)
* * [rosterInitialized](/docs/html/events.html#rosterInitialized) * * [rosterInitialized](/docs/html/events.html#rosterInitialized)
* * [statusInitialized](/docs/html/events.html#statusInitialized)
* * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered) * * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered)
* *
* The various plugins might also provide promises, and they do this by using the * The various plugins might also provide promises, and they do this by using the
......
...@@ -852,7 +852,7 @@ converse.plugins.add('converse-muc', { ...@@ -852,7 +852,7 @@ converse.plugins.add('converse-muc', {
} }
} }
if (api.connection.connected()) { if (api.connection.connected()) {
this.sendUnavailablePresence(exit_msg); api.user.presence.send('unavailable', this.getRoomJIDAndNick(), exit_msg);
} }
u.safeSave(this.session, {'connection_status': converse.ROOMSTATUS.DISCONNECTED}); u.safeSave(this.session, {'connection_status': converse.ROOMSTATUS.DISCONNECTED});
this.removeHandlers(); this.removeHandlers();
...@@ -877,18 +877,6 @@ converse.plugins.add('converse-muc', { ...@@ -877,18 +877,6 @@ converse.plugins.add('converse-muc', {
return self && self.isModerator() && api.disco.supports(Strophe.NS.MODERATE, this.get('jid')); return self && self.isModerator() && api.disco.supports(Strophe.NS.MODERATE, this.get('jid'));
}, },
sendUnavailablePresence (exit_msg) {
const presence = $pres({
type: "unavailable",
from: _converse.connection.jid,
to: this.getRoomJIDAndNick()
});
if (exit_msg !== null) {
presence.c("status", exit_msg);
}
_converse.connection.sendPresence(presence);
},
/** /**
* Return an array of unique nicknames based on all occupants and messages in this MUC. * Return an array of unique nicknames based on all occupants and messages in this MUC.
* @private * @private
......
...@@ -80,7 +80,7 @@ converse.plugins.add('converse-roster', { ...@@ -80,7 +80,7 @@ converse.plugins.add('converse-roster', {
_converse.sendInitialPresence = function () { _converse.sendInitialPresence = function () {
if (_converse.send_initial_presence) { if (_converse.send_initial_presence) {
_converse.xmppstatus.sendPresence(); api.user.presence.send();
} }
}; };
...@@ -771,7 +771,7 @@ converse.plugins.add('converse-roster', { ...@@ -771,7 +771,7 @@ converse.plugins.add('converse-roster', {
// //
// As a workaround for now we simply send our presence again, // As a workaround for now we simply send our presence again,
// otherwise we're treated as offline. // otherwise we're treated as offline.
_converse.xmppstatus.sendPresence(); api.user.presence.send();
} }
}, },
......
...@@ -23,6 +23,7 @@ converse.plugins.add('converse-status', { ...@@ -23,6 +23,7 @@ converse.plugins.add('converse-status', {
default_state: 'online', default_state: 'online',
priority: 0, priority: 0,
}); });
api.promises.add(['statusInitialized']);
_converse.XMPPStatus = Model.extend({ _converse.XMPPStatus = Model.extend({
defaults () { defaults () {
...@@ -35,7 +36,7 @@ converse.plugins.add('converse-status', { ...@@ -35,7 +36,7 @@ converse.plugins.add('converse-status', {
return; return;
} }
if ('status' in item.changed || 'status_message' in item.changed) { if ('status' in item.changed || 'status_message' in item.changed) {
this.sendPresence(this.get('status'), this.get('status_message')); api.user.presence.send(this.get('status'), null, this.get('status_message'));
} }
}); });
}, },
...@@ -49,12 +50,11 @@ converse.plugins.add('converse-status', { ...@@ -49,12 +50,11 @@ converse.plugins.add('converse-status', {
return ''; return '';
}, },
constructPresence (type, status_message) { constructPresence (type, to=null, status_message) {
let presence;
type = isString(type) ? type : (this.get('status') || api.settings.get("default_state")); type = isString(type) ? type : (this.get('status') || api.settings.get("default_state"));
status_message = isString(status_message) ? status_message : this.get('status_message'); status_message = isString(status_message) ? status_message : this.get('status_message');
// Most of these presence types are actually not explicitly sent, let presence;
// but I add all of them here for reference and future proofing. const attrs = {to};
if ((type === 'unavailable') || if ((type === 'unavailable') ||
(type === 'probe') || (type === 'probe') ||
(type === 'error') || (type === 'error') ||
...@@ -62,14 +62,17 @@ converse.plugins.add('converse-status', { ...@@ -62,14 +62,17 @@ converse.plugins.add('converse-status', {
(type === 'unsubscribed') || (type === 'unsubscribed') ||
(type === 'subscribe') || (type === 'subscribe') ||
(type === 'subscribed')) { (type === 'subscribed')) {
presence = $pres({'type': type}); attrs['type'] = type;
presence = $pres(attrs);
} else if (type === 'offline') { } else if (type === 'offline') {
presence = $pres({'type': 'unavailable'}); attrs['type'] = 'unavailable';
presence = $pres(attrs);
} else if (type === 'online') { } else if (type === 'online') {
presence = $pres(); presence = $pres(attrs);
} else { } else {
presence = $pres().c('show').t(type).up(); presence = $pres(attrs).c('show').t(type).up();
} }
if (status_message) { if (status_message) {
presence.c('status').t(status_message).up(); presence.c('status').t(status_message).up();
} }
...@@ -82,10 +85,6 @@ converse.plugins.add('converse-status', { ...@@ -82,10 +85,6 @@ converse.plugins.add('converse-status', {
presence.c('idle', {xmlns: Strophe.NS.IDLE, since: idle_since.toISOString()}); presence.c('idle', {xmlns: Strophe.NS.IDLE, since: idle_since.toISOString()});
} }
return presence; return presence;
},
sendPresence (type, status_message) {
api.send(this.constructPresence(type, status_message));
} }
}); });
...@@ -118,7 +117,7 @@ converse.plugins.add('converse-status', { ...@@ -118,7 +117,7 @@ converse.plugins.add('converse-status', {
} }
if (_converse.idle) { if (_converse.idle) {
_converse.idle = false; _converse.idle = false;
_converse.xmppstatus.sendPresence(); api.user.presence.send();
} }
if (_converse.auto_changed_status === true) { if (_converse.auto_changed_status === true) {
_converse.auto_changed_status = false; _converse.auto_changed_status = false;
...@@ -148,7 +147,7 @@ converse.plugins.add('converse-status', { ...@@ -148,7 +147,7 @@ converse.plugins.add('converse-status', {
_converse.idle_seconds > api.settings.get("idle_presence_timeout") && _converse.idle_seconds > api.settings.get("idle_presence_timeout") &&
!_converse.idle) { !_converse.idle) {
_converse.idle = true; _converse.idle = true;
_converse.xmppstatus.sendPresence(); api.user.presence.send();
} }
if (api.settings.get("auto_away") > 0 && if (api.settings.get("auto_away") > 0 &&
_converse.idle_seconds > api.settings.get("auto_away") && _converse.idle_seconds > api.settings.get("auto_away") &&
...@@ -245,21 +244,39 @@ converse.plugins.add('converse-status', { ...@@ -245,21 +244,39 @@ converse.plugins.add('converse-status', {
/************************ BEGIN API ************************/ /************************ BEGIN API ************************/
Object.assign(_converse.api.user, { Object.assign(_converse.api.user, {
/**
* @namespace _converse.api.user.presence
* @memberOf _converse.api.user
*/
presence: {
/**
* Send out a presence stanza
* @method _converse.api.user.presence.send
* @param { String } type
* @param { String } to
* @param { String } [status] - An optional status message
*/
async send (type, to, status) {
await api.waitUntil('statusInitialized');
api.send(_converse.xmppstatus.constructPresence(type, to, status));
}
},
/** /**
* Set and get the user's chat status, also called their *availability*. * Set and get the user's chat status, also called their *availability*.
*
* @namespace _converse.api.user.status * @namespace _converse.api.user.status
* @memberOf _converse.api.user * @memberOf _converse.api.user
*/ */
status: { status: {
/** Return the current user's availability status. /**
* * Return the current user's availability status.
* @method _converse.api.user.status.get * @method _converse.api.user.status.get
* @example _converse.api.user.status.get(); * @example _converse.api.user.status.get();
*/ */
get () { get () {
return _converse.xmppstatus.get('status'); return _converse.xmppstatus.get('status');
}, },
/** /**
* The user's status can be set to one of the following values: * The user's status can be set to one of the following values:
* *
...@@ -267,8 +284,8 @@ converse.plugins.add('converse-status', { ...@@ -267,8 +284,8 @@ converse.plugins.add('converse-status', {
* @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa') * @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
* @param {string} [message] A custom status message * @param {string} [message] A custom status message
* *
* @example this._converse.api.user.status.set('dnd'); * @example _converse.api.user.status.set('dnd');
* @example this._converse.api.user.status.set('dnd', 'In a meeting'); * @example _converse.api.user.status.set('dnd', 'In a meeting');
*/ */
set (value, message) { set (value, message) {
const data = {'status': value}; const data = {'status': value};
...@@ -280,7 +297,6 @@ converse.plugins.add('converse-status', { ...@@ -280,7 +297,6 @@ converse.plugins.add('converse-status', {
if (isString(message)) { if (isString(message)) {
data.status_message = message; data.status_message = message;
} }
_converse.xmppstatus.sendPresence(value);
_converse.xmppstatus.save(data); _converse.xmppstatus.save(data);
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment