Turn status plugin into folder
Remove the use of an override and add a hook `presenceConstructed` instead
This commit is contained in:
parent
1fc44b9d8e
commit
f40bbbf145
|
@ -14,6 +14,7 @@
|
|||
- Bugfix: `null` inserted by emoji picker and can't switch between skintones
|
||||
- New hook: [getMessageActionButtons](https://conversejs.org/docs/html/api/-_converse.html#event:getMessageActionButtons)
|
||||
- New hook: [shouldNotifyOfGroupMessage](https://conversejs.org/docs/html/api/-_converse.html#event:shouldNotifyOfGroupMessage)
|
||||
- New hook: [presenceConstructed](https://conversejs.org/docs/html/api/-_converse.html#event:presenceConstructed)
|
||||
- File structure reordering: All plugins are now in `./plugins` folders.
|
||||
- New configuration setting: [show_tab_notifications](https://conversejs.org/docs/html/configuration.html#show-tab-notifications)
|
||||
- New configuration setting: [muc_clear_messages_on_leave](https://conversejs.org/docs/html/configuration.html#muc-clear-messages-on-leave)
|
||||
|
|
|
@ -33,7 +33,6 @@ module.exports = function(config) {
|
|||
{ pattern: "spec/retractions.js", type: 'module' },
|
||||
{ pattern: "spec/user-details-modal.js", type: 'module' },
|
||||
{ pattern: "spec/utils.js", type: 'module' },
|
||||
{ pattern: "spec/xmppstatus.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/caps/tests/caps.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/chat/tests/api.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/disco/tests/disco.js", type: 'module' },
|
||||
|
@ -41,6 +40,7 @@ module.exports = function(config) {
|
|||
{ pattern: "src/headless/plugins/ping/tests/ping.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/roster/tests/presence.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/smacks/tests/smacks.js", type: 'module' },
|
||||
{ pattern: "src/headless/plugins/status/tests/status.js", type: 'module' },
|
||||
{ pattern: "src/headless/tests/converse.js", type: 'module' },
|
||||
{ pattern: "src/headless/tests/eventemitter.js", type: 'module' },
|
||||
{ pattern: "src/plugins/bookmark-views/tests/bookmarks.js", type: 'module' },
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
* --------------------
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
import "./plugins/adhoc.js"; // XEP-0050 Ad Hoc Commands
|
||||
import "./plugins/bookmarks/index.js"; // XEP-0199 XMPP Ping
|
||||
import "./plugins/bosh.js"; // XEP-0206 BOSH
|
||||
import "./plugins/caps/index.js"; // XEP-0115 Entity Capabilities
|
||||
import "./plugins/carbons.js"; // XEP-0280 Message Carbons
|
||||
import "./plugins/chat/index.js"; // RFC-6121 Instant messaging
|
||||
import "./plugins/adhoc.js"; // XEP-0050 Ad Hoc Commands
|
||||
import "./plugins/bookmarks/index.js"; // XEP-0199 XMPP Ping
|
||||
import "./plugins/bosh.js"; // XEP-0206 BOSH
|
||||
import "./plugins/caps/index.js"; // XEP-0115 Entity Capabilities
|
||||
import "./plugins/carbons.js"; // XEP-0280 Message Carbons
|
||||
import "./plugins/chat/index.js"; // RFC-6121 Instant messaging
|
||||
import "./plugins/chatboxes/index.js";
|
||||
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/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/index.js"; // XEP-0198 Stream Management
|
||||
import "./plugins/status.js"; // XEP-0199 XMPP Ping
|
||||
import "./plugins/vcard.js"; // XEP-0054 VCard-temp
|
||||
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/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/index.js"; // XEP-0198 Stream Management
|
||||
import "./plugins/status/index.js";
|
||||
import "./plugins/vcard.js"; // XEP-0054 VCard-temp
|
||||
/* END: Removable components */
|
||||
|
||||
import { converse } from "./core.js";
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* @copyright 2020, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import { _converse, converse } from '@converse/headless/core';
|
||||
import { api, converse } from '@converse/headless/core';
|
||||
import { createCapsNode } from './utils.js';
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
|
@ -12,16 +12,9 @@ Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps");
|
|||
|
||||
converse.plugins.add('converse-caps', {
|
||||
|
||||
overrides: {
|
||||
// Overrides mentioned here will be picked up by converse.js's
|
||||
// plugin architecture they will replace existing methods on the
|
||||
// relevant objects or classes.
|
||||
XMPPStatus: {
|
||||
constructPresence () {
|
||||
const presence = this.__super__.constructPresence.apply(this, arguments);
|
||||
presence.root().cnode(createCapsNode(_converse)).up();
|
||||
return presence;
|
||||
}
|
||||
}
|
||||
dependencies: ['converse-status'],
|
||||
|
||||
initialize () {
|
||||
api.listen.on('constructedPresence', p => p.root().cnode(createCapsNode()).up() && p);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ describe("A sent presence stanza", function () {
|
|||
_converse.api.disco.own.features.add("http://jabber.org/protocol/disco#items");
|
||||
_converse.api.disco.own.features.add("http://jabber.org/protocol/muc");
|
||||
|
||||
const presence = _converse.xmppstatus.constructPresence();
|
||||
const presence = await _converse.xmppstatus.constructPresence();
|
||||
expect(presence.toLocaleString()).toBe(
|
||||
`<presence xmlns="jabber:client">`+
|
||||
`<priority>0</priority>`+
|
||||
|
@ -30,9 +30,9 @@ describe("A sent presence stanza", function () {
|
|||
done();
|
||||
}));
|
||||
|
||||
it("has a given priority", mock.initConverse(['statusInitialized'], {}, (done, _converse) => {
|
||||
it("has a given priority", mock.initConverse(['statusInitialized'], {}, async (done, _converse) => {
|
||||
const { api } = _converse;
|
||||
let pres = _converse.xmppstatus.constructPresence('online', null, 'Hello world');
|
||||
let pres = await _converse.xmppstatus.constructPresence('online', null, 'Hello world');
|
||||
expect(pres.toLocaleString()).toBe(
|
||||
`<presence xmlns="jabber:client">`+
|
||||
`<status>Hello world</status>`+
|
||||
|
@ -40,8 +40,9 @@ describe("A sent presence stanza", function () {
|
|||
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+
|
||||
`</presence>`
|
||||
);
|
||||
|
||||
api.settings.set('priority', 2);
|
||||
pres = _converse.xmppstatus.constructPresence('away', null, 'Going jogging');
|
||||
pres = await _converse.xmppstatus.constructPresence('away', null, 'Going jogging');
|
||||
expect(pres.toLocaleString()).toBe(
|
||||
`<presence xmlns="jabber:client">`+
|
||||
`<show>away</show>`+
|
||||
|
@ -52,7 +53,7 @@ describe("A sent presence stanza", function () {
|
|||
);
|
||||
|
||||
api.settings.set('priority', undefined);
|
||||
pres = _converse.xmppstatus.constructPresence('dnd', null, 'Doing taxes');
|
||||
pres = await _converse.xmppstatus.constructPresence('dnd', null, 'Doing taxes');
|
||||
expect(pres.toLocaleString()).toBe(
|
||||
`<presence xmlns="jabber:client">`+
|
||||
`<show>dnd</show>`+
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import SHA1 from 'strophe.js/src/sha1';
|
||||
import { converse } from '@converse/headless/core';
|
||||
import { _converse, converse } from '@converse/headless/core';
|
||||
|
||||
const { Strophe, $build } = converse.env;
|
||||
|
||||
|
@ -7,7 +7,7 @@ function propertySort (array, property) {
|
|||
return array.sort((a, b) => { return a[property] > b[property] ? -1 : 1 });
|
||||
}
|
||||
|
||||
function generateVerificationString (_converse) {
|
||||
function generateVerificationString () {
|
||||
const identities = _converse.api.disco.own.identities.get();
|
||||
const features = _converse.api.disco.own.features.get();
|
||||
|
||||
|
@ -23,11 +23,11 @@ function generateVerificationString (_converse) {
|
|||
return SHA1.b64_sha1(S);
|
||||
}
|
||||
|
||||
export function createCapsNode (_converse) {
|
||||
export function createCapsNode () {
|
||||
return $build("c", {
|
||||
'xmlns': Strophe.NS.CAPS,
|
||||
'hash': "sha-1",
|
||||
'node': "https://conversejs.org",
|
||||
'ver': generateVerificationString(_converse)
|
||||
'ver': generateVerificationString()
|
||||
}).nodeTree;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ describe("XEP-0198 Stream Management", function () {
|
|||
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
|
||||
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`]);
|
||||
|
||||
await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'presence')).length);
|
||||
|
||||
const disco_iq = IQ_stanzas.pop();
|
||||
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
|
||||
iq = IQ_stanzas.pop();
|
||||
|
|
|
@ -1,345 +0,0 @@
|
|||
/**
|
||||
* @module converse-status
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import isNaN from "lodash-es/isNaN";
|
||||
import isObject from "lodash-es/isObject";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { initStorage } from '@converse/headless/shared/utils.js';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
||||
const { Strophe, $build, $pres } = converse.env;
|
||||
|
||||
|
||||
converse.plugins.add('converse-status', {
|
||||
|
||||
initialize () {
|
||||
|
||||
api.settings.extend({
|
||||
auto_away: 0, // Seconds after which user status is set to 'away'
|
||||
auto_xa: 0, // Seconds after which user status is set to 'xa'
|
||||
csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
|
||||
default_state: 'online',
|
||||
priority: 0,
|
||||
});
|
||||
api.promises.add(['statusInitialized']);
|
||||
|
||||
_converse.XMPPStatus = Model.extend({
|
||||
defaults () {
|
||||
return {"status": api.settings.get("default_state")}
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.on('change', item => {
|
||||
if (!isObject(item.changed)) {
|
||||
return;
|
||||
}
|
||||
if ('status' in item.changed || 'status_message' in item.changed) {
|
||||
api.user.presence.send(this.get('status'), null, this.get('status_message'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getNickname () {
|
||||
return _converse.nickname;
|
||||
},
|
||||
|
||||
getFullname () {
|
||||
// Gets overridden in converse-vcard
|
||||
return '';
|
||||
},
|
||||
|
||||
constructPresence (type, to=null, status_message) {
|
||||
type = typeof type === 'string' ? type : (this.get('status') || api.settings.get("default_state"));
|
||||
status_message = typeof status_message === 'string' ? status_message : this.get('status_message');
|
||||
let presence;
|
||||
const attrs = {to};
|
||||
if ((type === 'unavailable') ||
|
||||
(type === 'probe') ||
|
||||
(type === 'error') ||
|
||||
(type === 'unsubscribe') ||
|
||||
(type === 'unsubscribed') ||
|
||||
(type === 'subscribe') ||
|
||||
(type === 'subscribed')) {
|
||||
attrs['type'] = type;
|
||||
presence = $pres(attrs);
|
||||
} else if (type === 'offline') {
|
||||
attrs['type'] = 'unavailable';
|
||||
presence = $pres(attrs);
|
||||
} else if (type === 'online') {
|
||||
presence = $pres(attrs);
|
||||
} else {
|
||||
presence = $pres(attrs).c('show').t(type).up();
|
||||
}
|
||||
|
||||
if (status_message) {
|
||||
presence.c('status').t(status_message).up();
|
||||
}
|
||||
|
||||
const priority = api.settings.get("priority");
|
||||
presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
|
||||
if (_converse.idle) {
|
||||
const idle_since = new Date();
|
||||
idle_since.setSeconds(idle_since.getSeconds() - _converse.idle_seconds);
|
||||
presence.c('idle', {xmlns: Strophe.NS.IDLE, since: idle_since.toISOString()});
|
||||
}
|
||||
return presence;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Send out a Client State Indication (XEP-0352)
|
||||
* @private
|
||||
* @method sendCSI
|
||||
* @memberOf _converse
|
||||
* @param { String } stat - The user's chat status
|
||||
*/
|
||||
_converse.sendCSI = function (stat) {
|
||||
api.send($build(stat, {xmlns: Strophe.NS.CSI}));
|
||||
_converse.inactive = (stat === _converse.INACTIVE) ? true : false;
|
||||
};
|
||||
|
||||
|
||||
_converse.onUserActivity = function () {
|
||||
/* Resets counters and flags relating to CSI and auto_away/auto_xa */
|
||||
if (_converse.idle_seconds > 0) {
|
||||
_converse.idle_seconds = 0;
|
||||
}
|
||||
if (!_converse.connection?.authenticated) {
|
||||
// We can't send out any stanzas when there's no authenticated connection.
|
||||
// This can happen when the connection reconnects.
|
||||
return;
|
||||
}
|
||||
if (_converse.inactive) {
|
||||
_converse.sendCSI(_converse.ACTIVE);
|
||||
}
|
||||
if (_converse.idle) {
|
||||
_converse.idle = false;
|
||||
api.user.presence.send();
|
||||
}
|
||||
if (_converse.auto_changed_status === true) {
|
||||
_converse.auto_changed_status = false;
|
||||
// XXX: we should really remember the original state here, and
|
||||
// then set it back to that...
|
||||
_converse.xmppstatus.set('status', api.settings.get("default_state"));
|
||||
}
|
||||
};
|
||||
|
||||
_converse.onEverySecond = function () {
|
||||
/* An interval handler running every second.
|
||||
* Used for CSI and the auto_away and auto_xa features.
|
||||
*/
|
||||
if (!_converse.connection?.authenticated) {
|
||||
// We can't send out any stanzas when there's no authenticated connection.
|
||||
// This can happen when the connection reconnects.
|
||||
return;
|
||||
}
|
||||
const stat = _converse.xmppstatus.get('status');
|
||||
_converse.idle_seconds++;
|
||||
if (api.settings.get("csi_waiting_time") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("csi_waiting_time") &&
|
||||
!_converse.inactive) {
|
||||
_converse.sendCSI(_converse.INACTIVE);
|
||||
}
|
||||
if (api.settings.get("idle_presence_timeout") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("idle_presence_timeout") &&
|
||||
!_converse.idle) {
|
||||
_converse.idle = true;
|
||||
api.user.presence.send();
|
||||
}
|
||||
if (api.settings.get("auto_away") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("auto_away") &&
|
||||
stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
|
||||
_converse.auto_changed_status = true;
|
||||
_converse.xmppstatus.set('status', 'away');
|
||||
} else if (api.settings.get("auto_xa") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("auto_xa") &&
|
||||
stat !== 'xa' && stat !== 'dnd') {
|
||||
_converse.auto_changed_status = true;
|
||||
_converse.xmppstatus.set('status', 'xa');
|
||||
}
|
||||
};
|
||||
|
||||
_converse.registerIntervalHandler = function () {
|
||||
/* Set an interval of one second and register a handler for it.
|
||||
* Required for the auto_away, auto_xa and csi_waiting_time features.
|
||||
*/
|
||||
if (
|
||||
api.settings.get("auto_away") < 1 &&
|
||||
api.settings.get("auto_xa") < 1 &&
|
||||
api.settings.get("csi_waiting_time") < 1 &&
|
||||
api.settings.get("idle_presence_timeout") < 1
|
||||
) {
|
||||
// Waiting time of less then one second means features aren't used.
|
||||
return;
|
||||
}
|
||||
_converse.idle_seconds = 0;
|
||||
_converse.auto_changed_status = false; // Was the user's status changed by Converse?
|
||||
|
||||
const { unloadevent } = _converse;
|
||||
window.addEventListener('click', _converse.onUserActivity);
|
||||
window.addEventListener('focus', _converse.onUserActivity);
|
||||
window.addEventListener('keypress', _converse.onUserActivity);
|
||||
window.addEventListener('mousemove', _converse.onUserActivity);
|
||||
window.addEventListener(unloadevent, _converse.onUserActivity, {'once': true, 'passive': true});
|
||||
window.addEventListener(unloadevent, () => _converse.session?.save('active', false));
|
||||
_converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
|
||||
};
|
||||
|
||||
|
||||
api.listen.on('presencesInitialized', (reconnecting) => {
|
||||
if (!reconnecting) {
|
||||
_converse.registerIntervalHandler();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function onStatusInitialized (reconnecting) {
|
||||
/**
|
||||
* Triggered when the user's own chat status has been initialized.
|
||||
* @event _converse#statusInitialized
|
||||
* @example _converse.api.listen.on('statusInitialized', status => { ... });
|
||||
* @example _converse.api.waitUntil('statusInitialized').then(() => { ... });
|
||||
*/
|
||||
api.trigger('statusInitialized', reconnecting);
|
||||
}
|
||||
|
||||
|
||||
function initStatus (reconnecting) {
|
||||
// If there's no xmppstatus obj, then we were never connected to
|
||||
// begin with, so we set reconnecting to false.
|
||||
reconnecting = _converse.xmppstatus === undefined ? false : reconnecting;
|
||||
if (reconnecting) {
|
||||
onStatusInitialized(reconnecting);
|
||||
} else {
|
||||
const id = `converse.xmppstatus-${_converse.bare_jid}`;
|
||||
_converse.xmppstatus = new _converse.XMPPStatus({ id });
|
||||
initStorage(_converse.xmppstatus, id, 'session');
|
||||
_converse.xmppstatus.fetch({
|
||||
'success': () => onStatusInitialized(reconnecting),
|
||||
'error': () => onStatusInitialized(reconnecting),
|
||||
'silent': true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************ BEGIN Event Handlers ************************/
|
||||
api.listen.on('clearSession', () => {
|
||||
if (_converse.shouldClearCache() && _converse.xmppstatus) {
|
||||
_converse.xmppstatus.destroy();
|
||||
delete _converse.xmppstatus;
|
||||
api.promises.add(['statusInitialized']);
|
||||
}
|
||||
});
|
||||
|
||||
api.listen.on('connected', () => initStatus(false));
|
||||
api.listen.on('reconnected', () => initStatus(true));
|
||||
/************************ END Event Handlers ************************/
|
||||
|
||||
|
||||
/************************ BEGIN API ************************/
|
||||
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
|
||||
* @param { Element[]|Strophe.Builder[]|Element|Strophe.Builder } [child_nodes]
|
||||
* Nodes(s) to be added as child nodes of the `presence` XML element.
|
||||
*/
|
||||
async send (type, to, status, child_nodes) {
|
||||
await api.waitUntil('statusInitialized');
|
||||
const presence = _converse.xmppstatus.constructPresence(type, to, status);
|
||||
if (child_nodes) {
|
||||
if (!Array.isArray(child_nodes)) {
|
||||
child_nodes = [child_nodes];
|
||||
}
|
||||
child_nodes.map(c => c?.tree() ?? c).forEach(c => presence.cnode(c).up());
|
||||
}
|
||||
api.send(presence);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set and get the user's chat status, also called their *availability*.
|
||||
* @namespace _converse.api.user.status
|
||||
* @memberOf _converse.api.user
|
||||
*/
|
||||
status: {
|
||||
/**
|
||||
* Return the current user's availability status.
|
||||
* @async
|
||||
* @method _converse.api.user.status.get
|
||||
* @example _converse.api.user.status.get();
|
||||
*/
|
||||
async get () {
|
||||
await api.waitUntil('statusInitialized');
|
||||
return _converse.xmppstatus.get('status');
|
||||
},
|
||||
|
||||
/**
|
||||
* The user's status can be set to one of the following values:
|
||||
*
|
||||
* @async
|
||||
* @method _converse.api.user.status.set
|
||||
* @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
|
||||
* @param {string} [message] A custom status message
|
||||
*
|
||||
* @example _converse.api.user.status.set('dnd');
|
||||
* @example _converse.api.user.status.set('dnd', 'In a meeting');
|
||||
*/
|
||||
async set (value, message) {
|
||||
const data = {'status': value};
|
||||
if (!Object.keys(_converse.STATUS_WEIGHTS).includes(value)) {
|
||||
throw new Error(
|
||||
'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'
|
||||
);
|
||||
}
|
||||
if (typeof message === 'string') {
|
||||
data.status_message = message;
|
||||
}
|
||||
await api.waitUntil('statusInitialized');
|
||||
_converse.xmppstatus.save(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set and retrieve the user's custom status message.
|
||||
*
|
||||
* @namespace _converse.api.user.status.message
|
||||
* @memberOf _converse.api.user.status
|
||||
*/
|
||||
message: {
|
||||
/**
|
||||
* @async
|
||||
* @method _converse.api.user.status.message.get
|
||||
* @returns {string} The status message
|
||||
* @example const message = _converse.api.user.status.message.get()
|
||||
*/
|
||||
async get () {
|
||||
await api.waitUntil('statusInitialized');
|
||||
return _converse.xmppstatus.get('status_message');
|
||||
},
|
||||
/**
|
||||
* @async
|
||||
* @method _converse.api.user.status.message.set
|
||||
* @param {string} status The status message
|
||||
* @example _converse.api.user.status.message.set('In a meeting');
|
||||
*/
|
||||
async set (status) {
|
||||
await api.waitUntil('statusInitialized');
|
||||
_converse.xmppstatus.save({ status_message: status });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
import { _converse, api } from '@converse/headless/core';
|
||||
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @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
|
||||
* @param { Element[]|Strophe.Builder[]|Element|Strophe.Builder } [child_nodes]
|
||||
* Nodes(s) to be added as child nodes of the `presence` XML element.
|
||||
*/
|
||||
async send (type, to, status, child_nodes) {
|
||||
await api.waitUntil('statusInitialized');
|
||||
const presence = await _converse.xmppstatus.constructPresence(type, to, status);
|
||||
if (child_nodes) {
|
||||
if (!Array.isArray(child_nodes)) {
|
||||
child_nodes = [child_nodes];
|
||||
}
|
||||
child_nodes.map(c => c?.tree() ?? c).forEach(c => presence.cnode(c).up());
|
||||
}
|
||||
api.send(presence);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set and get the user's chat status, also called their *availability*.
|
||||
* @namespace _converse.api.user.status
|
||||
* @memberOf _converse.api.user
|
||||
*/
|
||||
status: {
|
||||
/**
|
||||
* Return the current user's availability status.
|
||||
* @async
|
||||
* @method _converse.api.user.status.get
|
||||
* @example _converse.api.user.status.get();
|
||||
*/
|
||||
async get () {
|
||||
await api.waitUntil('statusInitialized');
|
||||
return _converse.xmppstatus.get('status');
|
||||
},
|
||||
|
||||
/**
|
||||
* The user's status can be set to one of the following values:
|
||||
*
|
||||
* @async
|
||||
* @method _converse.api.user.status.set
|
||||
* @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa')
|
||||
* @param {string} [message] A custom status message
|
||||
*
|
||||
* @example _converse.api.user.status.set('dnd');
|
||||
* @example _converse.api.user.status.set('dnd', 'In a meeting');
|
||||
*/
|
||||
async set (value, message) {
|
||||
const data = {'status': value};
|
||||
if (!Object.keys(_converse.STATUS_WEIGHTS).includes(value)) {
|
||||
throw new Error(
|
||||
'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'
|
||||
);
|
||||
}
|
||||
if (typeof message === 'string') {
|
||||
data.status_message = message;
|
||||
}
|
||||
await api.waitUntil('statusInitialized');
|
||||
_converse.xmppstatus.save(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set and retrieve the user's custom status message.
|
||||
*
|
||||
* @namespace _converse.api.user.status.message
|
||||
* @memberOf _converse.api.user.status
|
||||
*/
|
||||
message: {
|
||||
/**
|
||||
* @async
|
||||
* @method _converse.api.user.status.message.get
|
||||
* @returns {string} The status message
|
||||
* @example const message = _converse.api.user.status.message.get()
|
||||
*/
|
||||
async get () {
|
||||
await api.waitUntil('statusInitialized');
|
||||
return _converse.xmppstatus.get('status_message');
|
||||
},
|
||||
/**
|
||||
* @async
|
||||
* @method _converse.api.user.status.message.set
|
||||
* @param {string} status The status message
|
||||
* @example _converse.api.user.status.message.set('In a meeting');
|
||||
*/
|
||||
async set (status) {
|
||||
await api.waitUntil('statusInitialized');
|
||||
_converse.xmppstatus.save({ status_message: status });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import XMPPStatus from './status.js';
|
||||
import status_api from './api.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { initStatus, onEverySecond, onUserActivity, registerIntervalHandler, sendCSI } from './utils.js';
|
||||
|
||||
|
||||
converse.plugins.add('converse-status', {
|
||||
|
||||
initialize () {
|
||||
|
||||
api.settings.extend({
|
||||
auto_away: 0, // Seconds after which user status is set to 'away'
|
||||
auto_xa: 0, // Seconds after which user status is set to 'xa'
|
||||
csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
|
||||
default_state: 'online',
|
||||
priority: 0,
|
||||
});
|
||||
api.promises.add(['statusInitialized']);
|
||||
|
||||
_converse.XMPPStatus = XMPPStatus;
|
||||
_converse.onUserActivity = onUserActivity;
|
||||
_converse.onEverySecond = onEverySecond;
|
||||
_converse.sendCSI = sendCSI;
|
||||
_converse.registerIntervalHandler = registerIntervalHandler;
|
||||
|
||||
Object.assign(_converse.api.user, status_api);
|
||||
|
||||
api.listen.on('presencesInitialized', (reconnecting) => {
|
||||
if (!reconnecting) {
|
||||
_converse.registerIntervalHandler();
|
||||
}
|
||||
});
|
||||
|
||||
api.listen.on('clearSession', () => {
|
||||
if (_converse.shouldClearCache() && _converse.xmppstatus) {
|
||||
_converse.xmppstatus.destroy();
|
||||
delete _converse.xmppstatus;
|
||||
api.promises.add(['statusInitialized']);
|
||||
}
|
||||
});
|
||||
|
||||
api.listen.on('connected', () => initStatus(false));
|
||||
api.listen.on('reconnected', () => initStatus(true));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
import isNaN from 'lodash-es/isNaN';
|
||||
import isObject from 'lodash-es/isObject';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
|
||||
const { Strophe, $pres } = converse.env;
|
||||
|
||||
const XMPPStatus = Model.extend({
|
||||
defaults () {
|
||||
return { "status": api.settings.get("default_state") }
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.on('change', item => {
|
||||
if (!isObject(item.changed)) {
|
||||
return;
|
||||
}
|
||||
if ('status' in item.changed || 'status_message' in item.changed) {
|
||||
api.user.presence.send(this.get('status'), null, this.get('status_message'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getNickname () {
|
||||
return _converse.nickname;
|
||||
},
|
||||
|
||||
getFullname () {
|
||||
// Gets overridden in converse-vcard
|
||||
return '';
|
||||
},
|
||||
|
||||
async constructPresence (type, to=null, status_message) {
|
||||
type = typeof type === 'string' ? type : (this.get('status') || api.settings.get("default_state"));
|
||||
status_message = typeof status_message === 'string' ? status_message : this.get('status_message');
|
||||
let presence;
|
||||
const attrs = {to};
|
||||
if ((type === 'unavailable') ||
|
||||
(type === 'probe') ||
|
||||
(type === 'error') ||
|
||||
(type === 'unsubscribe') ||
|
||||
(type === 'unsubscribed') ||
|
||||
(type === 'subscribe') ||
|
||||
(type === 'subscribed')) {
|
||||
attrs['type'] = type;
|
||||
presence = $pres(attrs);
|
||||
} else if (type === 'offline') {
|
||||
attrs['type'] = 'unavailable';
|
||||
presence = $pres(attrs);
|
||||
} else if (type === 'online') {
|
||||
presence = $pres(attrs);
|
||||
} else {
|
||||
presence = $pres(attrs).c('show').t(type).up();
|
||||
}
|
||||
|
||||
if (status_message) {
|
||||
presence.c('status').t(status_message).up();
|
||||
}
|
||||
|
||||
const priority = api.settings.get("priority");
|
||||
presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
|
||||
if (_converse.idle) {
|
||||
const idle_since = new Date();
|
||||
idle_since.setSeconds(idle_since.getSeconds() - _converse.idle_seconds);
|
||||
presence.c('idle', {xmlns: Strophe.NS.IDLE, since: idle_since.toISOString()});
|
||||
}
|
||||
presence = await _converse.api.hook('constructedPresence', presence);
|
||||
return presence;
|
||||
}
|
||||
});
|
||||
|
||||
export default XMPPStatus;
|
|
@ -0,0 +1,128 @@
|
|||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { initStorage } from '@converse/headless/shared/utils.js';
|
||||
|
||||
const { Strophe, $build } = converse.env;
|
||||
|
||||
function onStatusInitialized (reconnecting) {
|
||||
/**
|
||||
* Triggered when the user's own chat status has been initialized.
|
||||
* @event _converse#statusInitialized
|
||||
* @example _converse.api.listen.on('statusInitialized', status => { ... });
|
||||
* @example _converse.api.waitUntil('statusInitialized').then(() => { ... });
|
||||
*/
|
||||
api.trigger('statusInitialized', reconnecting);
|
||||
}
|
||||
|
||||
export function initStatus (reconnecting) {
|
||||
// If there's no xmppstatus obj, then we were never connected to
|
||||
// begin with, so we set reconnecting to false.
|
||||
reconnecting = _converse.xmppstatus === undefined ? false : reconnecting;
|
||||
if (reconnecting) {
|
||||
onStatusInitialized(reconnecting);
|
||||
} else {
|
||||
const id = `converse.xmppstatus-${_converse.bare_jid}`;
|
||||
_converse.xmppstatus = new _converse.XMPPStatus({ id });
|
||||
initStorage(_converse.xmppstatus, id, 'session');
|
||||
_converse.xmppstatus.fetch({
|
||||
'success': () => onStatusInitialized(reconnecting),
|
||||
'error': () => onStatusInitialized(reconnecting),
|
||||
'silent': true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function onUserActivity () {
|
||||
/* Resets counters and flags relating to CSI and auto_away/auto_xa */
|
||||
if (_converse.idle_seconds > 0) {
|
||||
_converse.idle_seconds = 0;
|
||||
}
|
||||
if (!_converse.connection?.authenticated) {
|
||||
// We can't send out any stanzas when there's no authenticated connection.
|
||||
// This can happen when the connection reconnects.
|
||||
return;
|
||||
}
|
||||
if (_converse.inactive) {
|
||||
_converse.sendCSI(_converse.ACTIVE);
|
||||
}
|
||||
if (_converse.idle) {
|
||||
_converse.idle = false;
|
||||
api.user.presence.send();
|
||||
}
|
||||
if (_converse.auto_changed_status === true) {
|
||||
_converse.auto_changed_status = false;
|
||||
// XXX: we should really remember the original state here, and
|
||||
// then set it back to that...
|
||||
_converse.xmppstatus.set('status', api.settings.get("default_state"));
|
||||
}
|
||||
}
|
||||
|
||||
export function onEverySecond () {
|
||||
/* An interval handler running every second.
|
||||
* Used for CSI and the auto_away and auto_xa features.
|
||||
*/
|
||||
if (!_converse.connection?.authenticated) {
|
||||
// We can't send out any stanzas when there's no authenticated connection.
|
||||
// This can happen when the connection reconnects.
|
||||
return;
|
||||
}
|
||||
const stat = _converse.xmppstatus.get('status');
|
||||
_converse.idle_seconds++;
|
||||
if (api.settings.get("csi_waiting_time") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("csi_waiting_time") &&
|
||||
!_converse.inactive) {
|
||||
_converse.sendCSI(_converse.INACTIVE);
|
||||
}
|
||||
if (api.settings.get("idle_presence_timeout") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("idle_presence_timeout") &&
|
||||
!_converse.idle) {
|
||||
_converse.idle = true;
|
||||
api.user.presence.send();
|
||||
}
|
||||
if (api.settings.get("auto_away") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("auto_away") &&
|
||||
stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
|
||||
_converse.auto_changed_status = true;
|
||||
_converse.xmppstatus.set('status', 'away');
|
||||
} else if (api.settings.get("auto_xa") > 0 &&
|
||||
_converse.idle_seconds > api.settings.get("auto_xa") &&
|
||||
stat !== 'xa' && stat !== 'dnd') {
|
||||
_converse.auto_changed_status = true;
|
||||
_converse.xmppstatus.set('status', 'xa');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send out a Client State Indication (XEP-0352)
|
||||
* @function sendCSI
|
||||
* @param { String } stat - The user's chat status
|
||||
*/
|
||||
export function sendCSI (stat) {
|
||||
api.send($build(stat, {xmlns: Strophe.NS.CSI}));
|
||||
_converse.inactive = (stat === _converse.INACTIVE) ? true : false;
|
||||
}
|
||||
|
||||
export function registerIntervalHandler () {
|
||||
/* Set an interval of one second and register a handler for it.
|
||||
* Required for the auto_away, auto_xa and csi_waiting_time features.
|
||||
*/
|
||||
if (
|
||||
api.settings.get("auto_away") < 1 &&
|
||||
api.settings.get("auto_xa") < 1 &&
|
||||
api.settings.get("csi_waiting_time") < 1 &&
|
||||
api.settings.get("idle_presence_timeout") < 1
|
||||
) {
|
||||
// Waiting time of less then one second means features aren't used.
|
||||
return;
|
||||
}
|
||||
_converse.idle_seconds = 0;
|
||||
_converse.auto_changed_status = false; // Was the user's status changed by Converse?
|
||||
|
||||
const { unloadevent } = _converse;
|
||||
window.addEventListener('click', _converse.onUserActivity);
|
||||
window.addEventListener('focus', _converse.onUserActivity);
|
||||
window.addEventListener('keypress', _converse.onUserActivity);
|
||||
window.addEventListener('mousemove', _converse.onUserActivity);
|
||||
window.addEventListener(unloadevent, _converse.onUserActivity, {'once': true, 'passive': true});
|
||||
window.addEventListener(unloadevent, () => _converse.session?.save('active', false));
|
||||
_converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
|
||||
}
|
|
@ -6,10 +6,10 @@ import tpl_muc_head from './templates/muc-head.js';
|
|||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { showModeratorToolsModal } from './utils.js';
|
||||
import {
|
||||
getHeadingDropdownItem,
|
||||
getHeadingStandaloneButton,
|
||||
showModeratorToolsModal
|
||||
} from 'plugins/chatview/utils.js';
|
||||
|
||||
import './styles/muc-head.scss';
|
||||
|
|
|
@ -241,7 +241,7 @@ function verifyAndSetAffiliation (muc, command, args, required_affiliations) {
|
|||
}
|
||||
|
||||
|
||||
function showModeratorToolsModal (muc, affiliation) {
|
||||
export function showModeratorToolsModal (muc, affiliation) {
|
||||
if (!muc.verifyRoles(['moderator'])) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue