Converse converse.js

Source: headless/plugins/roster/presence.js

import isNaN from "lodash-es/isNaN";
import { Collection } from "@converse/skeletor/src/collection";
import { Model } from '@converse/skeletor/src/model.js';
import { converse } from "@converse/headless/core";
import { initStorage } from '@converse/headless/utils/storage.js';

const { Strophe, dayjs, sizzle } = converse.env;

export const Resource = Model.extend({'idAttribute': 'name'});
export const Resources = Collection.extend({'model': Resource});


export const Presence = Model.extend({
    idAttribute: 'jid',

    defaults: {
        'show': 'offline'
    },

    initialize () {
        this.resources = new Resources();
        const id = `converse.identities-${this.get('jid')}`;
        initStorage(this.resources, id, 'session');

        this.listenTo(this.resources, 'update', this.onResourcesChanged);
        this.listenTo(this.resources, 'change', this.onResourcesChanged);
    },

    onResourcesChanged () {
        const hpr = this.getHighestPriorityResource();
        const show = hpr?.attributes?.show || 'offline';
        if (this.get('show') !== show) {
            this.save({ show });
        }
    },

    /**
     * Return the resource with the highest priority.
     * If multiple resources have the same priority, take the latest one.
     * @private
     */
    getHighestPriorityResource () {
        return this.resources.sortBy(r => `${r.get('priority')}-${r.get('timestamp')}`).reverse()[0];
    },

    /**
     * Adds a new resource and it's associated attributes as taken
     * from the passed in presence stanza.
     * Also updates the presence if the resource has higher priority (and is newer).
     * @private
     * @param { Element } presence: The presence stanza
     */
    addResource (presence) {
        const jid = presence.getAttribute('from');
        const name = Strophe.getResourceFromJid(jid);
        const delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop();
        const priority = presence.querySelector('priority')?.textContent;
        const resource = this.resources.get(name);
        const settings = {
            name,
            'priority': isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
            'show': presence.querySelector('show')?.textContent ?? 'online',
            'timestamp': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : (new Date()).toISOString()
        };
        if (resource) {
            resource.save(settings);
        } else {
            this.resources.create(settings);
        }
    },

    /**
     * Remove the passed in resource from the resources map.
     * Also redetermines the presence given that there's one less
     * resource.
     * @private
     * @param { string } name: The resource name
     */
    removeResource (name) {
        const resource = this.resources.get(name);
        resource?.destroy();
    }
});


export const Presences = Collection.extend({'model': Presence });