Converse converse.js

Source: headless/plugins/roster/contact.js

import '@converse/headless/plugins/status/api.js';
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from "@converse/headless/core";
import { getOpenPromise } from '@converse/openpromise';
import { rejectPresenceSubscription } from './utils.js';

const { Strophe, $iq, $pres } = converse.env;

/**
 * @class
 * @namespace RosterContact
 */
const RosterContact = Model.extend({
    idAttribute: 'jid',

    defaults: {
        'chat_state': undefined,
        'groups': [],
        'image': _converse.DEFAULT_IMAGE,
        'image_type': _converse.DEFAULT_IMAGE_TYPE,
        'num_unread': 0,
        'status': undefined,
    },

    async initialize (attributes) {
        this.initialized = getOpenPromise();
        this.setPresence();
        const { jid } = attributes;
        this.set({
            ...attributes,
            ...{
                'jid': Strophe.getBareJidFromJid(jid).toLowerCase(),
                'user_id': Strophe.getNodeFromJid(jid)
            }
        });
        /**
         * When a contact's presence status has changed.
         * The presence status is either `online`, `offline`, `dnd`, `away` or `xa`.
         * @event _converse#contactPresenceChanged
         * @type { _converse.RosterContact }
         * @example _converse.api.listen.on('contactPresenceChanged', contact => { ... });
         */
        this.listenTo(this.presence, 'change:show', () => api.trigger('contactPresenceChanged', this));
        this.listenTo(this.presence, 'change:show', () => this.trigger('presenceChanged'));
        /**
         * Synchronous event which provides a hook for further initializing a RosterContact
         * @event _converse#rosterContactInitialized
         * @param { _converse.RosterContact } contact
         */
        await api.trigger('rosterContactInitialized', this, {'Synchronous': true});
        this.initialized.resolve();
    },

    setPresence () {
        const jid = this.get('jid');
        this.presence = _converse.presences.findWhere(jid) || _converse.presences.create({ jid });
    },

    openChat () {
        const attrs = this.attributes;
        api.chats.open(attrs.jid, attrs, true);
    },

    /**
     * Return a string of tab-separated values that are to be used when
     * matching against filter text.
     *
     * The goal is to be able to filter against the VCard fullname,
     * roster nickname and JID.
     * @returns { String } Lower-cased, tab-separated values
     */
    getFilterCriteria () {
        const nick = this.get('nickname');
        const jid = this.get('jid');
        let criteria = this.getDisplayName();
        criteria = !criteria.includes(jid) ? criteria.concat(`   ${jid}`) : criteria;
        criteria = !criteria.includes(nick) ? criteria.concat(`   ${nick}`) : criteria;
        return criteria.toLowerCase();
    },

    getDisplayName () {
        // Gets overridden in converse-vcard where the fullname is may be returned
        if (this.get('nickname')) {
            return this.get('nickname');
        } else {
            return this.get('jid');
        }
    },

    getFullname () {
        // Gets overridden in converse-vcard where the fullname may be returned
        return this.get('jid');
    },

    /**
     * Send a presence subscription request to this roster contact
     * @method _converse.RosterContacts#subscribe
     * @param { String } message - An optional message to explain the
     *      reason for the subscription request.
     */
    subscribe (message) {
        api.user.presence.send('subscribe', this.get('jid'), message);
        this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
        return this;
    },

    /**
     * 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
     * @private
     * @method _converse.RosterContacts#ackSubscribe
     */
    ackSubscribe () {
        api.send($pres({
            'type': 'subscribe',
            'to': this.get('jid')
        }));
    },

    /**
     * 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.
     * @private
     * @method _converse.RosterContacts#ackUnsubscribe
     */
    ackUnsubscribe () {
        api.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));
        this.removeFromRoster();
        this.destroy();
    },

    /**
     * Unauthorize this contact's presence subscription
     * @private
     * @method _converse.RosterContacts#unauthorize
     * @param { String } message - Optional message to send to the person being unauthorized
     */
    unauthorize (message) {
        rejectPresenceSubscription(this.get('jid'), message);
        return this;
    },

    /**
     * Authorize presence subscription
     * @private
     * @method _converse.RosterContacts#authorize
     * @param { String } message - Optional message to send to the person being authorized
     */
    authorize (message) {
        const pres = $pres({'to': this.get('jid'), 'type': "subscribed"});
        if (message && message !== "") {
            pres.c("status").t(message);
        }
        api.send(pres);
        return this;
    },

    /**
     * Instruct the XMPP server to remove this contact from our roster
     * @private
     * @method _converse.RosterContacts#
     * @returns { Promise }
     */
    removeFromRoster () {
        const iq = $iq({type: 'set'})
            .c('query', {xmlns: Strophe.NS.ROSTER})
            .c('item', {jid: this.get('jid'), subscription: "remove"});
        return api.sendIQ(iq);
    }
});

export default RosterContact;