|
@@ -92,6 +92,7 @@
|
|
|
'converse-profile',
|
|
|
'converse-register',
|
|
|
'converse-roomslist',
|
|
|
+ 'converse-roster',
|
|
|
'converse-rosterview',
|
|
|
'converse-singleton',
|
|
|
'converse-spoilers',
|
|
@@ -212,13 +213,8 @@
|
|
|
|
|
|
const PROMISES = [
|
|
|
'initialized',
|
|
|
- 'cachedRoster',
|
|
|
'connectionInitialized',
|
|
|
'pluginsInitialized',
|
|
|
- 'roster',
|
|
|
- 'rosterContactsFetched',
|
|
|
- 'rosterGroupsFetched',
|
|
|
- 'rosterInitialized',
|
|
|
'statusInitialized'
|
|
|
];
|
|
|
|
|
@@ -359,7 +355,6 @@
|
|
|
registration_domain: '',
|
|
|
rid: undefined,
|
|
|
root: window.document,
|
|
|
- roster_groups: true,
|
|
|
show_only_online_users: false,
|
|
|
show_send_button: false,
|
|
|
sid: undefined,
|
|
@@ -679,9 +674,6 @@
|
|
|
};
|
|
|
|
|
|
this.clearSession = function () {
|
|
|
- if (!_.isUndefined(this.roster)) {
|
|
|
- this.roster.browserStorage._clear();
|
|
|
- }
|
|
|
if (!_.isUndefined(this.session) && this.session.browserStorage) {
|
|
|
this.session.browserStorage._clear();
|
|
|
}
|
|
@@ -779,51 +771,6 @@
|
|
|
this.connection.send(carbons_iq);
|
|
|
};
|
|
|
|
|
|
- this.initRoster = function () {
|
|
|
- /* Initialize the Bakcbone collections that represent the contats
|
|
|
- * roster and the roster groups.
|
|
|
- */
|
|
|
- _converse.roster = new _converse.RosterContacts();
|
|
|
- _converse.roster.browserStorage = new Backbone.BrowserStorage.session(
|
|
|
- b64_sha1(`converse.contacts-${_converse.bare_jid}`));
|
|
|
- _converse.rostergroups = new _converse.RosterGroups();
|
|
|
- _converse.rostergroups.browserStorage = new Backbone.BrowserStorage.session(
|
|
|
- b64_sha1(`converse.roster.groups${_converse.bare_jid}`));
|
|
|
- _converse.emit('rosterInitialized');
|
|
|
- };
|
|
|
-
|
|
|
- this.populateRoster = function (ignore_cache=false) {
|
|
|
- /* Fetch all the roster groups, and then the roster contacts.
|
|
|
- * Emit an event after fetching is done in each case.
|
|
|
- *
|
|
|
- * Parameters:
|
|
|
- * (Bool) ignore_cache - If set to to true, the local cache
|
|
|
- * will be ignored it's guaranteed that the XMPP server
|
|
|
- * will be queried for the roster.
|
|
|
- */
|
|
|
- if (ignore_cache) {
|
|
|
- _converse.send_initial_presence = true;
|
|
|
- _converse.roster.fetchFromServer()
|
|
|
- .then(() => {
|
|
|
- _converse.emit('rosterContactsFetched');
|
|
|
- _converse.sendInitialPresence();
|
|
|
- }).catch((reason) => {
|
|
|
- _converse.log(reason, Strophe.LogLevel.ERROR);
|
|
|
- _converse.sendInitialPresence();
|
|
|
- });
|
|
|
- } else {
|
|
|
- _converse.rostergroups.fetchRosterGroups().then(() => {
|
|
|
- _converse.emit('rosterGroupsFetched');
|
|
|
- return _converse.roster.fetchRosterContacts();
|
|
|
- }).then(() => {
|
|
|
- _converse.emit('rosterContactsFetched');
|
|
|
- _converse.sendInitialPresence();
|
|
|
- }).catch((reason) => {
|
|
|
- _converse.log(reason, Strophe.LogLevel.ERROR);
|
|
|
- _converse.sendInitialPresence();
|
|
|
- });
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
this.unregisterPresenceHandler = function () {
|
|
|
if (!_.isUndefined(_converse.presence_ref)) {
|
|
@@ -832,16 +779,6 @@
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- this.registerPresenceHandler = function () {
|
|
|
- _converse.unregisterPresenceHandler();
|
|
|
- _converse.presence_ref = _converse.connection.addHandler(
|
|
|
- function (presence) {
|
|
|
- _converse.roster.presenceHandler(presence);
|
|
|
- return true;
|
|
|
- }, null, 'presence', null);
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
this.sendInitialPresence = function () {
|
|
|
if (_converse.send_initial_presence) {
|
|
|
_converse.xmppstatus.sendPresence();
|
|
@@ -849,24 +786,7 @@
|
|
|
};
|
|
|
|
|
|
this.onStatusInitialized = function (reconnecting) {
|
|
|
- /* Continue with session establishment (e.g. fetching chat boxes,
|
|
|
- * populating the roster etc.) necessary once the connection has
|
|
|
- * been established.
|
|
|
- */
|
|
|
- _converse.emit('statusInitialized');
|
|
|
- if (reconnecting) {
|
|
|
- // No need to recreate the roster, otherwise we lose our
|
|
|
- // cached data. However we still emit an event, to give
|
|
|
- // event handlers a chance to register views for the
|
|
|
- // roster and its groups, before we start populating.
|
|
|
- _converse.emit('rosterReadyAfterReconnection');
|
|
|
- } else {
|
|
|
- _converse.registerIntervalHandler();
|
|
|
- _converse.initRoster();
|
|
|
- }
|
|
|
- _converse.roster.onConnected();
|
|
|
- _converse.populateRoster(reconnecting);
|
|
|
- _converse.registerPresenceHandler();
|
|
|
+ _converse.emit('statusInitialized', reconnecting);
|
|
|
if (reconnecting) {
|
|
|
_converse.emit('reconnected');
|
|
|
} else {
|
|
@@ -894,627 +814,6 @@
|
|
|
_converse.initStatus(reconnecting)
|
|
|
};
|
|
|
|
|
|
- this.RosterContact = Backbone.Model.extend({
|
|
|
-
|
|
|
- defaults: {
|
|
|
- 'chat_state': undefined,
|
|
|
- 'chat_status': 'offline',
|
|
|
- 'image': _converse.DEFAULT_IMAGE,
|
|
|
- 'image_type': _converse.DEFAULT_IMAGE_TYPE,
|
|
|
- 'num_unread': 0,
|
|
|
- 'status': '',
|
|
|
- },
|
|
|
-
|
|
|
- initialize (attributes) {
|
|
|
- const { jid } = attributes,
|
|
|
- bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
|
|
|
- resource = Strophe.getResourceFromJid(jid);
|
|
|
-
|
|
|
- attributes.jid = bare_jid;
|
|
|
- this.set(_.assignIn({
|
|
|
- 'fullname': bare_jid,
|
|
|
- 'groups': [],
|
|
|
- 'id': bare_jid,
|
|
|
- 'jid': bare_jid,
|
|
|
- 'resources': {},
|
|
|
- 'user_id': Strophe.getNodeFromJid(jid)
|
|
|
- }, attributes));
|
|
|
-
|
|
|
- this.on('change:chat_status', function (item) {
|
|
|
- _converse.emit('contactStatusChanged', item.attributes);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- subscribe (message) {
|
|
|
- /* Send a presence subscription request to this roster contact
|
|
|
- *
|
|
|
- * Parameters:
|
|
|
- * (String) message - An optional message to explain the
|
|
|
- * reason for the subscription request.
|
|
|
- */
|
|
|
- const pres = $pres({to: this.get('jid'), type: "subscribe"});
|
|
|
- if (message && message !== "") {
|
|
|
- pres.c("status").t(message).up();
|
|
|
- }
|
|
|
- const nick = _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname');
|
|
|
- if (nick) {
|
|
|
- pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
|
|
|
- }
|
|
|
- _converse.connection.send(pres);
|
|
|
- this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- ackSubscribe () {
|
|
|
- /* Upon receiving the presence stanza of type "subscribed",
|
|
|
- * the user SHOULD acknowledge receipt of that subscription
|
|
|
- * state notification by sending a presence stanza of type
|
|
|
- * "subscribe" to the contact
|
|
|
- */
|
|
|
- _converse.connection.send($pres({
|
|
|
- 'type': 'subscribe',
|
|
|
- 'to': this.get('jid')
|
|
|
- }));
|
|
|
- },
|
|
|
-
|
|
|
- ackUnsubscribe () {
|
|
|
- /* Upon receiving the presence stanza of type "unsubscribed",
|
|
|
- * the user SHOULD acknowledge receipt of that subscription state
|
|
|
- * notification by sending a presence stanza of type "unsubscribe"
|
|
|
- * this step lets the user's server know that it MUST no longer
|
|
|
- * send notification of the subscription state change to the user.
|
|
|
- * Parameters:
|
|
|
- * (String) jid - The Jabber ID of the user who is unsubscribing
|
|
|
- */
|
|
|
- _converse.connection.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));
|
|
|
- this.removeFromRoster();
|
|
|
- this.destroy();
|
|
|
- },
|
|
|
-
|
|
|
- unauthorize (message) {
|
|
|
- /* Unauthorize this contact's presence subscription
|
|
|
- * Parameters:
|
|
|
- * (String) message - Optional message to send to the person being unauthorized
|
|
|
- */
|
|
|
- _converse.rejectPresenceSubscription(this.get('jid'), message);
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- authorize (message) {
|
|
|
- /* Authorize presence subscription
|
|
|
- * Parameters:
|
|
|
- * (String) message - Optional message to send to the person being authorized
|
|
|
- */
|
|
|
- const pres = $pres({'to': this.get('jid'), 'type': "subscribed"});
|
|
|
- if (message && message !== "") {
|
|
|
- pres.c("status").t(message);
|
|
|
- }
|
|
|
- _converse.connection.send(pres);
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- addResource (presence) {
|
|
|
- /* Adds a new resource and it's associated attributes as taken
|
|
|
- * from the passed in presence stanza.
|
|
|
- *
|
|
|
- * Also updates the contact's chat_status if the presence has
|
|
|
- * higher priority (and is newer).
|
|
|
- */
|
|
|
- const jid = presence.getAttribute('from'),
|
|
|
- chat_status = _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
|
|
|
- resource = Strophe.getResourceFromJid(jid),
|
|
|
- delay = presence.querySelector(
|
|
|
- `delay[xmlns="${Strophe.NS.DELAY}"]`
|
|
|
- ),
|
|
|
- timestamp = _.isNull(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format();
|
|
|
-
|
|
|
- let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0;
|
|
|
- priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10);
|
|
|
-
|
|
|
- const resources = _.isObject(this.get('resources')) ? this.get('resources') : {};
|
|
|
- resources[resource] = {
|
|
|
- 'name': resource,
|
|
|
- 'priority': priority,
|
|
|
- 'status': chat_status,
|
|
|
- 'timestamp': timestamp
|
|
|
- };
|
|
|
- const changed = {'resources': resources};
|
|
|
- const hpr = this.getHighestPriorityResource();
|
|
|
- if (priority == hpr.priority && timestamp == hpr.timestamp) {
|
|
|
- // Only set the chat status if this is the newest resource
|
|
|
- // with the highest priority
|
|
|
- changed.chat_status = chat_status;
|
|
|
- }
|
|
|
- this.save(changed);
|
|
|
- return resources;
|
|
|
- },
|
|
|
-
|
|
|
- removeResource (resource) {
|
|
|
- /* Remove the passed in resource from the contact's resources map.
|
|
|
- *
|
|
|
- * Also recomputes the chat_status given that there's one less
|
|
|
- * resource.
|
|
|
- */
|
|
|
- let resources = this.get('resources');
|
|
|
- if (!_.isObject(resources)) {
|
|
|
- resources = {};
|
|
|
- } else {
|
|
|
- delete resources[resource];
|
|
|
- }
|
|
|
- this.save({
|
|
|
- 'resources': resources,
|
|
|
- 'chat_status': _.propertyOf(
|
|
|
- this.getHighestPriorityResource())('status') || 'offline'
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- getHighestPriorityResource () {
|
|
|
- /* Return the resource with the highest priority.
|
|
|
- *
|
|
|
- * If multiple resources have the same priority, take the
|
|
|
- * newest one.
|
|
|
- */
|
|
|
- const resources = this.get('resources');
|
|
|
- if (_.isObject(resources) && _.size(resources)) {
|
|
|
- const val = _.flow(
|
|
|
- _.values,
|
|
|
- _.partial(_.sortBy, _, ['priority', 'timestamp']),
|
|
|
- _.reverse
|
|
|
- )(resources)[0];
|
|
|
- if (!_.isUndefined(val)) {
|
|
|
- return val;
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- removeFromRoster (callback, errback) {
|
|
|
- /* Instruct the XMPP server to remove this contact from our roster
|
|
|
- * Parameters:
|
|
|
- * (Function) callback
|
|
|
- */
|
|
|
- const iq = $iq({type: 'set'})
|
|
|
- .c('query', {xmlns: Strophe.NS.ROSTER})
|
|
|
- .c('item', {jid: this.get('jid'), subscription: "remove"});
|
|
|
- _converse.connection.sendIQ(iq, callback, errback);
|
|
|
- return this;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
- this.RosterContacts = Backbone.Collection.extend({
|
|
|
- model: _converse.RosterContact,
|
|
|
-
|
|
|
- comparator (contact1, contact2) {
|
|
|
- const status1 = contact1.get('chat_status') || 'offline';
|
|
|
- const status2 = contact2.get('chat_status') || 'offline';
|
|
|
- if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) {
|
|
|
- const name1 = (contact1.get('fullname') || contact1.get('jid')).toLowerCase();
|
|
|
- const name2 = (contact2.get('fullname') || contact2.get('jid')).toLowerCase();
|
|
|
- return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
|
|
|
- } else {
|
|
|
- return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- onConnected () {
|
|
|
- /* Called as soon as the connection has been established
|
|
|
- * (either after initial login, or after reconnection).
|
|
|
- *
|
|
|
- * Use the opportunity to register stanza handlers.
|
|
|
- */
|
|
|
- this.registerRosterHandler();
|
|
|
- this.registerRosterXHandler();
|
|
|
- },
|
|
|
-
|
|
|
- registerRosterHandler () {
|
|
|
- /* Register a handler for roster IQ "set" stanzas, which update
|
|
|
- * roster contacts.
|
|
|
- */
|
|
|
- _converse.connection.addHandler(
|
|
|
- _converse.roster.onRosterPush.bind(_converse.roster),
|
|
|
- Strophe.NS.ROSTER, 'iq', "set"
|
|
|
- );
|
|
|
- },
|
|
|
-
|
|
|
- registerRosterXHandler () {
|
|
|
- /* Register a handler for RosterX message stanzas, which are
|
|
|
- * used to suggest roster contacts to a user.
|
|
|
- */
|
|
|
- let t = 0;
|
|
|
- _converse.connection.addHandler(
|
|
|
- function (msg) {
|
|
|
- window.setTimeout(
|
|
|
- function () {
|
|
|
- _converse.connection.flush();
|
|
|
- _converse.roster.subscribeToSuggestedItems.bind(_converse.roster)(msg);
|
|
|
- }, t);
|
|
|
- t += msg.querySelectorAll('item').length*250;
|
|
|
- return true;
|
|
|
- },
|
|
|
- Strophe.NS.ROSTERX, 'message', null
|
|
|
- );
|
|
|
- },
|
|
|
-
|
|
|
- fetchRosterContacts () {
|
|
|
- /* Fetches the roster contacts, first by trying the
|
|
|
- * sessionStorage cache, and if that's empty, then by querying
|
|
|
- * the XMPP server.
|
|
|
- *
|
|
|
- * Returns a promise which resolves once the contacts have been
|
|
|
- * fetched.
|
|
|
- */
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- this.fetch({
|
|
|
- 'add': true,
|
|
|
- 'silent': true,
|
|
|
- success (collection) {
|
|
|
- if (collection.length === 0) {
|
|
|
- _converse.send_initial_presence = true;
|
|
|
- _converse.roster.fetchFromServer().then(resolve).catch(reject);
|
|
|
- } else {
|
|
|
- _converse.emit('cachedRoster', collection);
|
|
|
- resolve();
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- subscribeToSuggestedItems (msg) {
|
|
|
- _.each(msg.querySelectorAll('item'), function (item) {
|
|
|
- if (item.getAttribute('action') === 'add') {
|
|
|
- _converse.roster.addAndSubscribe(
|
|
|
- item.getAttribute('jid'),
|
|
|
- _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname')
|
|
|
- );
|
|
|
- }
|
|
|
- });
|
|
|
- return true;
|
|
|
- },
|
|
|
-
|
|
|
- isSelf (jid) {
|
|
|
- return u.isSameBareJID(jid, _converse.connection.jid);
|
|
|
- },
|
|
|
-
|
|
|
- addAndSubscribe (jid, name, groups, message, attributes) {
|
|
|
- /* Add a roster contact and then once we have confirmation from
|
|
|
- * the XMPP server we subscribe to that contact's presence updates.
|
|
|
- * Parameters:
|
|
|
- * (String) jid - The Jabber ID of the user being added and subscribed to.
|
|
|
- * (String) name - The name of that user
|
|
|
- * (Array of Strings) groups - Any roster groups the user might belong to
|
|
|
- * (String) message - An optional message to explain the
|
|
|
- * reason for the subscription request.
|
|
|
- * (Object) attributes - Any additional attributes to be stored on the user's model.
|
|
|
- */
|
|
|
- const handler = (contact) => {
|
|
|
- if (contact instanceof _converse.RosterContact) {
|
|
|
- contact.subscribe(message);
|
|
|
- }
|
|
|
- }
|
|
|
- this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
|
|
|
- },
|
|
|
-
|
|
|
- sendContactAddIQ (jid, name, groups, callback, errback) {
|
|
|
- /* Send an IQ stanza to the XMPP server to add a new roster contact.
|
|
|
- *
|
|
|
- * Parameters:
|
|
|
- * (String) jid - The Jabber ID of the user being added
|
|
|
- * (String) name - The name of that user
|
|
|
- * (Array of Strings) groups - Any roster groups the user might belong to
|
|
|
- * (Function) callback - A function to call once the IQ is returned
|
|
|
- * (Function) errback - A function to call if an error occured
|
|
|
- */
|
|
|
- name = _.isEmpty(name)? jid: name;
|
|
|
- const iq = $iq({type: 'set'})
|
|
|
- .c('query', {xmlns: Strophe.NS.ROSTER})
|
|
|
- .c('item', { jid, name });
|
|
|
- _.each(groups, function (group) { iq.c('group').t(group).up(); });
|
|
|
- _converse.connection.sendIQ(iq, callback, errback);
|
|
|
- },
|
|
|
-
|
|
|
- addContactToRoster (jid, name, groups, attributes) {
|
|
|
- /* Adds a RosterContact instance to _converse.roster and
|
|
|
- * registers the contact on the XMPP server.
|
|
|
- * Returns a promise which is resolved once the XMPP server has
|
|
|
- * responded.
|
|
|
- *
|
|
|
- * Parameters:
|
|
|
- * (String) jid - The Jabber ID of the user being added and subscribed to.
|
|
|
- * (String) name - The name of that user
|
|
|
- * (Array of Strings) groups - Any roster groups the user might belong to
|
|
|
- * (Object) attributes - Any additional attributes to be stored on the user's model.
|
|
|
- */
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- groups = groups || [];
|
|
|
- name = _.isEmpty(name)? jid: name;
|
|
|
- this.sendContactAddIQ(jid, name, groups,
|
|
|
- () => {
|
|
|
- const contact = this.create(_.assignIn({
|
|
|
- 'ask': undefined,
|
|
|
- 'fullname': name,
|
|
|
- groups,
|
|
|
- jid,
|
|
|
- 'requesting': false,
|
|
|
- 'subscription': 'none'
|
|
|
- }, attributes), {sort: false});
|
|
|
- resolve(contact);
|
|
|
- },
|
|
|
- function (err) {
|
|
|
- alert(__('Sorry, there was an error while trying to add %1$s as a contact.', name));
|
|
|
- _converse.log(err, Strophe.LogLevel.ERROR);
|
|
|
- resolve(err);
|
|
|
- }
|
|
|
- );
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- subscribeBack (bare_jid, presence) {
|
|
|
- const contact = this.get(bare_jid);
|
|
|
- if (contact instanceof _converse.RosterContact) {
|
|
|
- contact.authorize().subscribe();
|
|
|
- } else {
|
|
|
- // Can happen when a subscription is retried or roster was deleted
|
|
|
- const handler = (contact) => {
|
|
|
- if (contact instanceof _converse.RosterContact) {
|
|
|
- contact.authorize().subscribe();
|
|
|
- }
|
|
|
- }
|
|
|
- const nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null);
|
|
|
- this.addContactToRoster(bare_jid, nickname, [], {'subscription': 'from'}).then(handler, handler);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- getNumOnlineContacts () {
|
|
|
- let ignored = ['offline', 'unavailable'];
|
|
|
- if (_converse.show_only_online_users) {
|
|
|
- ignored = _.union(ignored, ['dnd', 'xa', 'away']);
|
|
|
- }
|
|
|
- return _.sum(this.models.filter((model) => !_.includes(ignored, model.get('chat_status'))));
|
|
|
- },
|
|
|
-
|
|
|
- onRosterPush (iq) {
|
|
|
- /* Handle roster updates from the XMPP server.
|
|
|
- * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
|
|
|
- *
|
|
|
- * Parameters:
|
|
|
- * (XMLElement) IQ - The IQ stanza received from the XMPP server.
|
|
|
- */
|
|
|
- const id = iq.getAttribute('id');
|
|
|
- const from = iq.getAttribute('from');
|
|
|
- if (from && from !== "" && Strophe.getBareJidFromJid(from) !== _converse.bare_jid) {
|
|
|
- // Receiving client MUST ignore stanza unless it has no from or from = user's bare JID.
|
|
|
- // XXX: Some naughty servers apparently send from a full
|
|
|
- // JID so we need to explicitly compare bare jids here.
|
|
|
- // https://github.com/jcbrand/converse.js/issues/493
|
|
|
- _converse.connection.send(
|
|
|
- $iq({type: 'error', id, from: _converse.connection.jid})
|
|
|
- .c('error', {'type': 'cancel'})
|
|
|
- .c('service-unavailable', {'xmlns': Strophe.NS.ROSTER })
|
|
|
- );
|
|
|
- return true;
|
|
|
- }
|
|
|
- _converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid}));
|
|
|
- const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
|
|
|
- _.each(items, this.updateContact.bind(this));
|
|
|
- _converse.emit('rosterPush', iq);
|
|
|
- return true;
|
|
|
- },
|
|
|
-
|
|
|
- fetchFromServer () {
|
|
|
- /* Fetch the roster from the XMPP server */
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const iq = $iq({
|
|
|
- 'type': 'get',
|
|
|
- 'id': _converse.connection.getUniqueId('roster')
|
|
|
- }).c('query', {xmlns: Strophe.NS.ROSTER});
|
|
|
-
|
|
|
- const callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
|
|
|
- const errback = function (iq) {
|
|
|
- const errmsg = "Error while trying to fetch roster from the server";
|
|
|
- _converse.log(errmsg, Strophe.LogLevel.ERROR);
|
|
|
- reject(new Error(errmsg));
|
|
|
- }
|
|
|
- return _converse.connection.sendIQ(iq, callback, errback);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- onReceivedFromServer (iq) {
|
|
|
- /* An IQ stanza containing the roster has been received from
|
|
|
- * the XMPP server.
|
|
|
- */
|
|
|
- const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
|
|
|
- _.each(items, this.updateContact.bind(this));
|
|
|
- _converse.emit('roster', iq);
|
|
|
- },
|
|
|
-
|
|
|
- updateContact (item) {
|
|
|
- /* Update or create RosterContact models based on items
|
|
|
- * received in the IQ from the server.
|
|
|
- */
|
|
|
- const jid = item.getAttribute('jid');
|
|
|
- if (this.isSelf(jid)) { return; }
|
|
|
-
|
|
|
- const contact = this.get(jid),
|
|
|
- subscription = item.getAttribute("subscription"),
|
|
|
- ask = item.getAttribute("ask"),
|
|
|
- groups = _.map(item.getElementsByTagName('group'), Strophe.getText);
|
|
|
-
|
|
|
- if (!contact) {
|
|
|
- if ((subscription === "none" && ask === null) || (subscription === "remove")) {
|
|
|
- return; // We're lazy when adding contacts.
|
|
|
- }
|
|
|
- this.create({
|
|
|
- 'ask': ask,
|
|
|
- 'fullname': item.getAttribute("name") || jid,
|
|
|
- 'groups': groups,
|
|
|
- 'jid': jid,
|
|
|
- 'subscription': subscription
|
|
|
- }, {sort: false});
|
|
|
- } else {
|
|
|
- if (subscription === "remove") {
|
|
|
- return contact.destroy();
|
|
|
- }
|
|
|
- // We only find out about requesting contacts via the
|
|
|
- // presence handler, so if we receive a contact
|
|
|
- // here, we know they aren't requesting anymore.
|
|
|
- // see docs/DEVELOPER.rst
|
|
|
- contact.save({
|
|
|
- 'subscription': subscription,
|
|
|
- 'ask': ask,
|
|
|
- 'requesting': null,
|
|
|
- 'groups': groups
|
|
|
- });
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- createRequestingContact (presence) {
|
|
|
- /* Creates a Requesting Contact.
|
|
|
- *
|
|
|
- * Note: this method gets completely overridden by converse-vcard.js
|
|
|
- */
|
|
|
- const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
|
|
|
- nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null);
|
|
|
- const user_data = {
|
|
|
- 'jid': bare_jid,
|
|
|
- 'subscription': 'none',
|
|
|
- 'ask': null,
|
|
|
- 'requesting': true,
|
|
|
- 'fullname': nickname
|
|
|
- };
|
|
|
- this.create(user_data);
|
|
|
- _converse.emit('contactRequest', user_data);
|
|
|
- },
|
|
|
-
|
|
|
- handleIncomingSubscription (presence) {
|
|
|
- const jid = presence.getAttribute('from'),
|
|
|
- bare_jid = Strophe.getBareJidFromJid(jid),
|
|
|
- contact = this.get(bare_jid);
|
|
|
-
|
|
|
- if (!_converse.allow_contact_requests) {
|
|
|
- _converse.rejectPresenceSubscription(
|
|
|
- jid,
|
|
|
- __("This client does not allow presence subscriptions")
|
|
|
- );
|
|
|
- }
|
|
|
- if (_converse.auto_subscribe) {
|
|
|
- if ((!contact) || (contact.get('subscription') !== 'to')) {
|
|
|
- this.subscribeBack(bare_jid, presence);
|
|
|
- } else {
|
|
|
- contact.authorize();
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (contact) {
|
|
|
- if (contact.get('subscription') !== 'none') {
|
|
|
- contact.authorize();
|
|
|
- } else if (contact.get('ask') === "subscribe") {
|
|
|
- contact.authorize();
|
|
|
- }
|
|
|
- } else {
|
|
|
- this.createRequestingContact(presence);
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- presenceHandler (presence) {
|
|
|
- const presence_type = presence.getAttribute('type');
|
|
|
- if (presence_type === 'error') { return true; }
|
|
|
-
|
|
|
- const jid = presence.getAttribute('from'),
|
|
|
- bare_jid = Strophe.getBareJidFromJid(jid),
|
|
|
- resource = Strophe.getResourceFromJid(jid),
|
|
|
- chat_status = _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
|
|
|
- status_message = _.propertyOf(presence.querySelector('status'))('textContent'),
|
|
|
- contact = this.get(bare_jid);
|
|
|
-
|
|
|
- if (this.isSelf(bare_jid)) {
|
|
|
- if ((_converse.connection.jid !== jid) &&
|
|
|
- (presence_type !== 'unavailable') &&
|
|
|
- (_converse.synchronize_availability === true ||
|
|
|
- _converse.synchronize_availability === resource)) {
|
|
|
- // Another resource has changed its status and
|
|
|
- // synchronize_availability option set to update,
|
|
|
- // we'll update ours as well.
|
|
|
- _converse.xmppstatus.save({'status': chat_status});
|
|
|
- if (status_message) {
|
|
|
- _converse.xmppstatus.save({'status_message': status_message});
|
|
|
- }
|
|
|
- }
|
|
|
- if (_converse.jid === jid && presence_type === 'unavailable') {
|
|
|
- // XXX: We've received an "unavailable" presence from our
|
|
|
- // own resource. Apparently this happens due to a
|
|
|
- // Prosody bug, whereby we send an IQ stanza to remove
|
|
|
- // a roster contact, and Prosody then sends
|
|
|
- // "unavailable" globally, instead of directed to the
|
|
|
- // particular user that's removed.
|
|
|
- //
|
|
|
- // Here is the bug report: https://prosody.im/issues/1121
|
|
|
- //
|
|
|
- // I'm not sure whether this might legitimately happen
|
|
|
- // in other cases.
|
|
|
- //
|
|
|
- // As a workaround for now we simply send our presence again,
|
|
|
- // otherwise we're treated as offline.
|
|
|
- _converse.xmppstatus.sendPresence();
|
|
|
- }
|
|
|
- return;
|
|
|
- } else if (sizzle(`query[xmlns="${Strophe.NS.MUC}"]`, presence).length) {
|
|
|
- return; // Ignore MUC
|
|
|
- }
|
|
|
- if (contact && (status_message !== contact.get('status'))) {
|
|
|
- contact.save({'status': status_message});
|
|
|
- }
|
|
|
- if (presence_type === 'subscribed' && contact) {
|
|
|
- contact.ackSubscribe();
|
|
|
- } else if (presence_type === 'unsubscribed' && contact) {
|
|
|
- contact.ackUnsubscribe();
|
|
|
- } else if (presence_type === 'unsubscribe') {
|
|
|
- return;
|
|
|
- } else if (presence_type === 'subscribe') {
|
|
|
- this.handleIncomingSubscription(presence);
|
|
|
- } else if (presence_type === 'unavailable' && contact) {
|
|
|
- contact.removeResource(resource);
|
|
|
- } else if (contact) {
|
|
|
- // presence_type is undefined
|
|
|
- contact.addResource(presence);
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
- this.RosterGroup = Backbone.Model.extend({
|
|
|
-
|
|
|
- initialize (attributes) {
|
|
|
- this.set(_.assignIn({
|
|
|
- description: __('Click to hide these contacts'),
|
|
|
- state: _converse.OPENED
|
|
|
- }, attributes));
|
|
|
- // Collection of contacts belonging to this group.
|
|
|
- this.contacts = new _converse.RosterContacts();
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
- this.RosterGroups = Backbone.Collection.extend({
|
|
|
- model: _converse.RosterGroup,
|
|
|
-
|
|
|
- fetchRosterGroups () {
|
|
|
- /* Fetches all the roster groups from sessionStorage.
|
|
|
- *
|
|
|
- * Returns a promise which resolves once the groups have been
|
|
|
- * returned.
|
|
|
- */
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- this.fetch({
|
|
|
- silent: true, // We need to first have all groups before
|
|
|
- // we can start positioning them, so we set
|
|
|
- // 'silent' to true.
|
|
|
- success: resolve
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
|
|
|
this.ConnectionFeedback = Backbone.Model.extend({
|
|
|
defaults: {
|
|
@@ -1816,9 +1115,6 @@
|
|
|
*/
|
|
|
_converse.emit('beforeTearDown');
|
|
|
_converse.unregisterPresenceHandler();
|
|
|
- if (_converse.roster) {
|
|
|
- _converse.roster.off().reset(); // Removes roster contacts
|
|
|
- }
|
|
|
if (!_.isUndefined(_converse.session)) {
|
|
|
_converse.session.destroy();
|
|
|
}
|
|
@@ -1981,25 +1277,6 @@
|
|
|
_.each(promises, addPromise);
|
|
|
}
|
|
|
},
|
|
|
- 'contacts': {
|
|
|
- 'get' (jids) {
|
|
|
- const _getter = function (jid) {
|
|
|
- return _converse.roster.get(Strophe.getBareJidFromJid(jid)) || null;
|
|
|
- };
|
|
|
- if (_.isUndefined(jids)) {
|
|
|
- jids = _converse.roster.pluck('jid');
|
|
|
- } else if (_.isString(jids)) {
|
|
|
- return _getter(jids);
|
|
|
- }
|
|
|
- return _.map(jids, _getter);
|
|
|
- },
|
|
|
- 'add' (jid, name) {
|
|
|
- if (!_.isString(jid) || !_.includes(jid, '@')) {
|
|
|
- throw new TypeError('contacts.add: invalid jid');
|
|
|
- }
|
|
|
- _converse.roster.addAndSubscribe(jid, _.isEmpty(name)? jid: name);
|
|
|
- }
|
|
|
- },
|
|
|
'tokens': {
|
|
|
'get' (id) {
|
|
|
if (!_converse.expose_rid_and_sid || _.isUndefined(_converse.connection)) {
|