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 });