2019-10-15 12:55:11 +02:00
|
|
|
/**
|
|
|
|
* @module converse-status
|
2020-01-26 16:21:20 +01:00
|
|
|
* @copyright The Converse.js contributors
|
2019-09-19 16:54:55 +02:00
|
|
|
* @license Mozilla Public License (MPLv2)
|
2019-10-15 12:55:11 +02:00
|
|
|
*/
|
2020-03-24 12:26:34 +01:00
|
|
|
import { isNaN, isObject, isString } from "lodash";
|
2019-09-19 16:54:55 +02:00
|
|
|
import { Model } from 'skeletor.js/src/model.js';
|
2020-04-23 13:22:31 +02:00
|
|
|
import { converse } from "@converse/headless/converse-core";
|
2019-10-15 12:55:11 +02:00
|
|
|
|
2019-09-19 16:54:55 +02:00
|
|
|
const { Strophe, $build, $pres } = converse.env;
|
2019-10-15 12:55:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
converse.plugins.add('converse-status', {
|
|
|
|
|
|
|
|
initialize () {
|
|
|
|
const { _converse } = this;
|
2020-03-31 13:15:57 +02:00
|
|
|
const { api } = _converse;
|
2019-10-15 12:55:11 +02:00
|
|
|
|
2020-03-31 13:15:57 +02:00
|
|
|
api.settings.update({
|
2020-03-30 16:29:09 +02:00
|
|
|
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,
|
|
|
|
});
|
2020-04-19 09:23:52 +02:00
|
|
|
api.promises.add(['statusInitialized']);
|
2020-03-30 16:29:09 +02:00
|
|
|
|
2019-09-19 16:54:55 +02:00
|
|
|
_converse.XMPPStatus = Model.extend({
|
2019-10-15 12:55:11 +02:00
|
|
|
defaults () {
|
2020-03-31 13:15:57 +02:00
|
|
|
return {"status": api.settings.get("default_state")}
|
2019-10-15 12:55:11 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
initialize () {
|
|
|
|
this.on('change', item => {
|
|
|
|
if (!isObject(item.changed)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ('status' in item.changed || 'status_message' in item.changed) {
|
2020-04-19 09:23:52 +02:00
|
|
|
api.user.presence.send(this.get('status'), null, this.get('status_message'));
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getNickname () {
|
|
|
|
return _converse.nickname;
|
|
|
|
},
|
|
|
|
|
|
|
|
getFullname () {
|
|
|
|
// Gets overridden in converse-vcard
|
|
|
|
return '';
|
|
|
|
},
|
|
|
|
|
2020-04-19 09:23:52 +02:00
|
|
|
constructPresence (type, to=null, status_message) {
|
2020-03-31 13:15:57 +02:00
|
|
|
type = isString(type) ? type : (this.get('status') || api.settings.get("default_state"));
|
2019-10-15 12:55:11 +02:00
|
|
|
status_message = isString(status_message) ? status_message : this.get('status_message');
|
2020-04-19 09:23:52 +02:00
|
|
|
let presence;
|
|
|
|
const attrs = {to};
|
2019-10-15 12:55:11 +02:00
|
|
|
if ((type === 'unavailable') ||
|
|
|
|
(type === 'probe') ||
|
|
|
|
(type === 'error') ||
|
|
|
|
(type === 'unsubscribe') ||
|
|
|
|
(type === 'unsubscribed') ||
|
|
|
|
(type === 'subscribe') ||
|
|
|
|
(type === 'subscribed')) {
|
2020-04-19 09:23:52 +02:00
|
|
|
attrs['type'] = type;
|
|
|
|
presence = $pres(attrs);
|
2019-10-15 12:55:11 +02:00
|
|
|
} else if (type === 'offline') {
|
2020-04-19 09:23:52 +02:00
|
|
|
attrs['type'] = 'unavailable';
|
|
|
|
presence = $pres(attrs);
|
2019-10-15 12:55:11 +02:00
|
|
|
} else if (type === 'online') {
|
2020-04-19 09:23:52 +02:00
|
|
|
presence = $pres(attrs);
|
2019-10-15 12:55:11 +02:00
|
|
|
} else {
|
2020-04-19 09:23:52 +02:00
|
|
|
presence = $pres(attrs).c('show').t(type).up();
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
2020-04-19 09:23:52 +02:00
|
|
|
|
2019-10-15 12:55:11 +02:00
|
|
|
if (status_message) {
|
|
|
|
presence.c('status').t(status_message).up();
|
|
|
|
}
|
2020-03-30 16:29:09 +02:00
|
|
|
|
2020-03-31 13:15:57 +02:00
|
|
|
const priority = api.settings.get("priority");
|
2020-03-30 16:29:09 +02:00
|
|
|
presence.c('priority').t(isNaN(Number(priority)) ? 0 : priority).up();
|
2019-10-15 12:55:11 +02:00
|
|
|
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) {
|
2020-03-31 13:15:57 +02:00
|
|
|
api.send($build(stat, {xmlns: Strophe.NS.CSI}));
|
2019-10-15 12:55:11 +02:00
|
|
|
_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;
|
|
|
|
}
|
2020-03-24 12:26:34 +01:00
|
|
|
if (!_converse.connection?.authenticated) {
|
2019-10-15 12:55:11 +02:00
|
|
|
// 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;
|
2020-04-19 09:23:52 +02:00
|
|
|
api.user.presence.send();
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
|
|
|
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...
|
2020-03-31 13:15:57 +02:00
|
|
|
_converse.xmppstatus.set('status', api.settings.get("default_state"));
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
_converse.onEverySecond = function () {
|
|
|
|
/* An interval handler running every second.
|
|
|
|
* Used for CSI and the auto_away and auto_xa features.
|
|
|
|
*/
|
2020-03-24 12:26:34 +01:00
|
|
|
if (!_converse.connection?.authenticated) {
|
2019-10-15 12:55:11 +02:00
|
|
|
// 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++;
|
2020-03-31 13:15:57 +02:00
|
|
|
if (api.settings.get("csi_waiting_time") > 0 &&
|
|
|
|
_converse.idle_seconds > api.settings.get("csi_waiting_time") &&
|
2019-10-15 12:55:11 +02:00
|
|
|
!_converse.inactive) {
|
|
|
|
_converse.sendCSI(_converse.INACTIVE);
|
|
|
|
}
|
2020-03-31 13:15:57 +02:00
|
|
|
if (api.settings.get("idle_presence_timeout") > 0 &&
|
|
|
|
_converse.idle_seconds > api.settings.get("idle_presence_timeout") &&
|
2019-10-15 12:55:11 +02:00
|
|
|
!_converse.idle) {
|
|
|
|
_converse.idle = true;
|
2020-04-19 09:23:52 +02:00
|
|
|
api.user.presence.send();
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
2020-03-31 13:15:57 +02:00
|
|
|
if (api.settings.get("auto_away") > 0 &&
|
|
|
|
_converse.idle_seconds > api.settings.get("auto_away") &&
|
2019-10-15 12:55:11 +02:00
|
|
|
stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
|
|
|
|
_converse.auto_changed_status = true;
|
|
|
|
_converse.xmppstatus.set('status', 'away');
|
2020-03-31 13:15:57 +02:00
|
|
|
} else if (api.settings.get("auto_xa") > 0 &&
|
|
|
|
_converse.idle_seconds > api.settings.get("auto_xa") &&
|
2019-10-15 12:55:11 +02:00
|
|
|
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 (
|
2020-03-31 13:15:57 +02:00
|
|
|
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
|
2019-10-15 12:55:11 +02:00
|
|
|
) {
|
|
|
|
// 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?
|
|
|
|
window.addEventListener('click', _converse.onUserActivity);
|
|
|
|
window.addEventListener('focus', _converse.onUserActivity);
|
|
|
|
window.addEventListener('keypress', _converse.onUserActivity);
|
|
|
|
window.addEventListener('mousemove', _converse.onUserActivity);
|
|
|
|
const options = {'once': true, 'passive': true};
|
|
|
|
window.addEventListener(_converse.unloadevent, _converse.onUserActivity, options);
|
|
|
|
window.addEventListener(_converse.unloadevent, () => {
|
|
|
|
if (_converse.session) {
|
|
|
|
_converse.session.save('active', false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
_converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2020-03-31 13:15:57 +02:00
|
|
|
api.listen.on('presencesInitialized', (reconnecting) => {
|
2019-10-15 12:55:11 +02:00
|
|
|
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(() => { ... });
|
|
|
|
*/
|
2020-03-31 13:15:57 +02:00
|
|
|
api.trigger('statusInitialized', reconnecting);
|
2019-10-15 12:55:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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': id});
|
|
|
|
_converse.xmppstatus.browserStorage = _converse.createStore(id, "session");
|
|
|
|
_converse.xmppstatus.fetch({
|
|
|
|
'success': () => onStatusInitialized(reconnecting),
|
|
|
|
'error': () => onStatusInitialized(reconnecting),
|
|
|
|
'silent': true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************ BEGIN Event Handlers ************************/
|
2020-03-31 13:15:57 +02:00
|
|
|
api.listen.on('clearSession', () => {
|
2019-10-15 12:55:11 +02:00
|
|
|
if (_converse.shouldClearCache() && _converse.xmppstatus) {
|
|
|
|
_converse.xmppstatus.destroy();
|
|
|
|
delete _converse.xmppstatus;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-03-31 13:15:57 +02:00
|
|
|
api.listen.on('connected', () => initStatus(false));
|
|
|
|
api.listen.on('reconnected', () => initStatus(true));
|
2019-10-15 12:55:11 +02:00
|
|
|
/************************ END Event Handlers ************************/
|
|
|
|
|
|
|
|
|
|
|
|
/************************ BEGIN API ************************/
|
|
|
|
Object.assign(_converse.api.user, {
|
2020-04-19 09:23:52 +02:00
|
|
|
/**
|
|
|
|
* @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));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-10-15 12:55:11 +02:00
|
|
|
/**
|
|
|
|
* Set and get the user's chat status, also called their *availability*.
|
|
|
|
* @namespace _converse.api.user.status
|
|
|
|
* @memberOf _converse.api.user
|
|
|
|
*/
|
|
|
|
status: {
|
2020-04-19 09:23:52 +02:00
|
|
|
/**
|
|
|
|
* Return the current user's availability status.
|
2019-10-15 12:55:11 +02:00
|
|
|
* @method _converse.api.user.status.get
|
|
|
|
* @example _converse.api.user.status.get();
|
|
|
|
*/
|
|
|
|
get () {
|
|
|
|
return _converse.xmppstatus.get('status');
|
|
|
|
},
|
2020-04-19 09:23:52 +02:00
|
|
|
|
2019-10-15 12:55:11 +02:00
|
|
|
/**
|
|
|
|
* The user's status can be set to one of the following values:
|
|
|
|
*
|
|
|
|
* @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
|
|
|
|
*
|
2020-04-19 09:23:52 +02:00
|
|
|
* @example _converse.api.user.status.set('dnd');
|
|
|
|
* @example _converse.api.user.status.set('dnd', 'In a meeting');
|
2019-10-15 12:55:11 +02:00
|
|
|
*/
|
|
|
|
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 (isString(message)) {
|
|
|
|
data.status_message = message;
|
|
|
|
}
|
|
|
|
_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: {
|
|
|
|
/**
|
|
|
|
* @method _converse.api.user.status.message.get
|
|
|
|
* @returns {string} The status message
|
|
|
|
* @example const message = _converse.api.user.status.message.get()
|
|
|
|
*/
|
|
|
|
get () {
|
|
|
|
return _converse.xmppstatus.get('status_message');
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @method _converse.api.user.status.message.set
|
|
|
|
* @param {string} status The status message
|
|
|
|
* @example _converse.api.user.status.message.set('In a meeting');
|
|
|
|
*/
|
|
|
|
set (status) {
|
|
|
|
_converse.xmppstatus.save({ status_message: status });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|