From 90074f9c7e486094025e9f6cb37165db909f3bd3 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 19 Dec 2024 08:23:37 +0200 Subject: [PATCH] Refactor the pubsub plugin - Turn it into a folder - Use the stx tagged template literal --- src/headless/index.js | 3 +- src/headless/plugins/bookmarks/collection.js | 4 +- src/headless/plugins/pubsub.js | 93 -------------------- src/headless/plugins/pubsub/api.js | 86 ++++++++++++++++++ src/headless/plugins/pubsub/index.js | 21 +++++ src/headless/plugins/pubsub/types.ts | 42 +++++++++ src/headless/types/plugins/pubsub/api.d.ts | 21 +++++ src/headless/types/plugins/pubsub/index.d.ts | 2 + src/headless/types/plugins/pubsub/types.d.ts | 24 +++++ 9 files changed, 199 insertions(+), 97 deletions(-) delete mode 100644 src/headless/plugins/pubsub.js create mode 100644 src/headless/plugins/pubsub/api.js create mode 100644 src/headless/plugins/pubsub/index.js create mode 100644 src/headless/plugins/pubsub/types.ts create mode 100644 src/headless/types/plugins/pubsub/api.d.ts create mode 100644 src/headless/types/plugins/pubsub/index.d.ts create mode 100644 src/headless/types/plugins/pubsub/types.d.ts diff --git a/src/headless/index.js b/src/headless/index.js index 62e4b5e092..79fa78c1b2 100644 --- a/src/headless/index.js +++ b/src/headless/index.js @@ -32,9 +32,8 @@ export { MAMPlaceholderMessage } from './plugins/mam/index.js'; // XEP-0045 Multi-user chat export { MUCMessage, MUCMessages, MUC, MUCOccupant, MUCOccupants } from './plugins/muc/index.js'; - import './plugins/ping/index.js'; // XEP-0199 XMPP Ping -import './plugins/pubsub.js'; // XEP-0060 Pubsub +import './plugins/pubsub/index.js'; // XEP-0060 Pubsub // RFC-6121 Contacts Roster export { RosterContact, RosterContacts, RosterFilter, Presence, Presences } from './plugins/roster/index.js'; diff --git a/src/headless/plugins/bookmarks/collection.js b/src/headless/plugins/bookmarks/collection.js index 2175fc453d..1a1574ffe1 100644 --- a/src/headless/plugins/bookmarks/collection.js +++ b/src/headless/plugins/bookmarks/collection.js @@ -1,9 +1,9 @@ /** * @typedef {import('../muc/muc.js').default} MUC */ +import { Stanza } from 'strophe.js'; import { Collection } from '@converse/skeletor'; import { getOpenPromise } from '@converse/openpromise'; -import '../../plugins/muc/index.js'; import Bookmark from './model.js'; import _converse from '../../shared/_converse.js'; import api from '../../shared/api/index.js'; @@ -11,7 +11,7 @@ import converse from '../../shared/api/public.js'; import log from '../../log.js'; import { initStorage } from '../../utils/storage.js'; import { parseStanzaForBookmarks } from './parsers.js'; -import { Stanza } from 'strophe.js'; +import '../../plugins/muc/index.js'; const { Strophe, sizzle, stx } = converse.env; diff --git a/src/headless/plugins/pubsub.js b/src/headless/plugins/pubsub.js deleted file mode 100644 index 02b0c5db0a..0000000000 --- a/src/headless/plugins/pubsub.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @module converse-pubsub - * @copyright The Converse.js contributors - * @license Mozilla Public License (MPLv2) - * @typedef {import('strophe.js').Builder} Strophe.Builder - */ -import "./disco/index.js"; -import _converse from '../shared/_converse.js'; -import api from '../shared/api/index.js'; -import converse from '../shared/api/public.js'; -import log from "../log.js"; - -const { Strophe, $iq } = converse.env; - -Strophe.addNamespace('PUBSUB_ERROR', Strophe.NS.PUBSUB+"#errors"); - - -converse.plugins.add('converse-pubsub', { - - dependencies: ["converse-disco"], - - initialize () { - - /************************ BEGIN API ************************/ - // We extend the default converse.js API to add methods specific to MUC groupchats. - Object.assign(_converse.api, { - /** - * The "pubsub" namespace groups methods relevant to PubSub - * - * @namespace _converse.api.pubsub - * @memberOf _converse.api - */ - pubsub: { - /** - * Publshes an item to a PubSub node - * - * @method _converse.api.pubsub.publish - * @param {string} jid The JID of the pubsub service where the node resides. - * @param {string} node The node being published to - * @param {Strophe.Builder} item The Strophe.Builder representation of the XML element being published - * @param {object} options An object representing the publisher options - * (see https://xmpp.org/extensions/xep-0060.html#publisher-publish-options) - * @param {boolean} strict_options Indicates whether the publisher - * options are a strict requirement or not. If they're NOT - * strict, then Converse will publish to the node even if - * the publish options precondition cannot be met. - */ - async publish (jid, node, item, options, strict_options=true) { - const bare_jid = _converse.session.get('bare_jid'); - const stanza = $iq({ - from: bare_jid, - type: 'set', - to: jid - }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) - .c('publish', {'node': node}) - .cnode(item.tree()).up().up(); - - if (options) { - jid = jid || bare_jid; - if (await api.disco.supports(Strophe.NS.PUBSUB + '#publish-options', jid)) { - stanza.c('publish-options') - .c('x', {'xmlns': Strophe.NS.XFORM, 'type': 'submit'}) - .c('field', {'var': 'FORM_TYPE', 'type': 'hidden'}) - .c('value').t(`${Strophe.NS.PUBSUB}#publish-options`).up().up() - - Object.keys(options).forEach(k => stanza.c('field', {'var': k}).c('value').t(options[k]).up().up()); - } else { - log.warn(`_converse.api.publish: ${jid} does not support #publish-options, `+ - `so we didn't set them even though they were provided.`) - } - } - try { - await api.sendIQ(stanza); - } catch (iq) { - if (iq instanceof Element && - strict_options && - iq.querySelector(`precondition-not-met[xmlns="${Strophe.NS.PUBSUB_ERROR}"]`)) { - - // The publish-options precondition couldn't be - // met. We re-publish but without publish-options. - const el = stanza.tree(); - el.querySelector('publish-options').outerHTML = ''; - log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`); - await api.sendIQ(el); - } else { - throw iq; - } - } - } - } - }); - } -}); diff --git a/src/headless/plugins/pubsub/api.js b/src/headless/plugins/pubsub/api.js new file mode 100644 index 0000000000..64a83ed8e4 --- /dev/null +++ b/src/headless/plugins/pubsub/api.js @@ -0,0 +1,86 @@ +/** + * @copyright The Converse.js contributors + * @license Mozilla Public License (MPLv2) + */ +import converse from '../../shared/api/public.js'; +import _converse from '../../shared/_converse.js'; +import api from '../../shared/api/index.js'; +import log from '../../log.js'; + +const { Strophe, stx } = converse.env; + +export default { + /** + * @typedef {import('strophe.js').Builder} Builder + * @typedef {import('strophe.js').Stanza} Stanza + * + * The "pubsub" namespace groups methods relevant to PubSub + * @namespace _converse.api.pubsub + * @memberOf _converse.api + */ + pubsub: { + /** + * Publshes an item to a PubSub node + * + * @method _converse.api.pubsub.publish + * @param {string} jid The JID of the pubsub service where the node resides. + * @param {string} node The node being published to + * @param {Builder|Stanza} item The Strophe.Builder representation of the XML element being published + * @param {import('./types').PubSubConfigOptions} options The publisher options + * (see https://xmpp.org/extensions/xep-0060.html#publisher-publish-options) + * @param {boolean} strict_options Indicates whether the publisher + * options are a strict requirement or not. If they're NOT + * strict, then Converse will publish to the node even if + * the publish options precondition cannot be met. + */ + async publish(jid, node, item, options, strict_options = true) { + const bare_jid = _converse.session.get('bare_jid') + const entity_jid = jid || bare_jid; + const supports_publish_options = await api.disco.supports( + Strophe.NS.PUBSUB + '#publish-options', + entity_jid + ); + if (!supports_publish_options) { + log.warn( + `api.pubsub.publish: ${entity_jid} does not support #publish-options, ` + + `so we didn't set them even though they were provided.` + ); + } + + const stanza = stx` + + ${item} + ${ + options && supports_publish_options + ? stx` + + + ${Strophe.NS.PUBSUB}#publish-options + + ${Object.entries(options).map((k, v) => stx`${v}`)} + ` + : '' + } + `; + + try { + await api.sendIQ(stanza); + } catch (iq) { + if ( + iq instanceof Element && + !strict_options && + iq.querySelector(`precondition-not-met[xmlns="${Strophe.NS.PUBSUB_ERROR}"]`) + ) { + // The publish-options precondition couldn't be + // met. We re-publish but without publish-options. + const el = stanza.tree(); + el.querySelector('publish-options').outerHTML = ''; + log.warn(`api.pubsub.publish: Republishing without publish options. ${el.outerHTML}`); + await api.sendIQ(el); + } else { + throw iq; + } + } + }, + }, +}; diff --git a/src/headless/plugins/pubsub/index.js b/src/headless/plugins/pubsub/index.js new file mode 100644 index 0000000000..d86a8e2a44 --- /dev/null +++ b/src/headless/plugins/pubsub/index.js @@ -0,0 +1,21 @@ +/** + * @module converse-pubsub + * @copyright The Converse.js contributors + * @license Mozilla Public License (MPLv2) + */ +import _converse from '../../shared/_converse.js'; +import converse from '../../shared/api/public.js'; +import pubsub_api from './api.js'; +import '../disco/index.js'; + +const { Strophe } = converse.env; + +Strophe.addNamespace('PUBSUB_ERROR', Strophe.NS.PUBSUB + '#errors'); + +converse.plugins.add('converse-pubsub', { + dependencies: ['converse-disco'], + + initialize() { + Object.assign(_converse.api, pubsub_api); + }, +}); diff --git a/src/headless/plugins/pubsub/types.ts b/src/headless/plugins/pubsub/types.ts new file mode 100644 index 0000000000..3cf477e0f2 --- /dev/null +++ b/src/headless/plugins/pubsub/types.ts @@ -0,0 +1,42 @@ +export type PubSubConfigOptions = { + 'pubsub#access_model': 'authorize' | 'open' | 'presence' | 'roster' | 'whitelist'; + // Payload XSLT + 'pubsub#dataform_xslt'?: string; + 'pubsub#deliver_notifications': boolean; + // Whether to deliver payloads with event notifications + 'pubsub#deliver_payloads': boolean; + // Time after which to automatically purge items. `max` for no specific limit other than a server imposed maximum. + 'pubsub#item_expire': string; + // Max # of items to persist. `max` for no specific limit other than a server imposed maximum. + 'pubsub#max_items': string; + // Max Payload size in bytes + 'pubsub#max_payload_size': string; + 'pubsub#notification_type': 'normal' | 'headline'; + // Notify subscribers when the node configuration changes + 'pubsub#notify_config': boolean; + // Notify subscribers when the node is deleted + 'pubsub#notify_delete': boolean; + // Notify subscribers when items are removed from the node + 'pubsub#notify_retract': boolean; + // + 'pubsub#title': string; + // Specify the semantic type of payload data to be provided at this node. + 'pubsub#type': string; +}; diff --git a/src/headless/types/plugins/pubsub/api.d.ts b/src/headless/types/plugins/pubsub/api.d.ts new file mode 100644 index 0000000000..ad71db6796 --- /dev/null +++ b/src/headless/types/plugins/pubsub/api.d.ts @@ -0,0 +1,21 @@ +declare namespace _default { + namespace pubsub { + /** + * Publshes an item to a PubSub node + * + * @method _converse.api.pubsub.publish + * @param {string} jid The JID of the pubsub service where the node resides. + * @param {string} node The node being published to + * @param {Builder|Stanza} item The Strophe.Builder representation of the XML element being published + * @param {import('./types').PubSubConfigOptions} options The publisher options + * (see https://xmpp.org/extensions/xep-0060.html#publisher-publish-options) + * @param {boolean} strict_options Indicates whether the publisher + * options are a strict requirement or not. If they're NOT + * strict, then Converse will publish to the node even if + * the publish options precondition cannot be met. + */ + function publish(jid: string, node: string, item: import("strophe.js").Builder | import("strophe.js").Stanza, options: import("./types").PubSubConfigOptions, strict_options?: boolean): Promise; + } +} +export default _default; +//# sourceMappingURL=api.d.ts.map \ No newline at end of file diff --git a/src/headless/types/plugins/pubsub/index.d.ts b/src/headless/types/plugins/pubsub/index.d.ts new file mode 100644 index 0000000000..e26a57a8ca --- /dev/null +++ b/src/headless/types/plugins/pubsub/index.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/src/headless/types/plugins/pubsub/types.d.ts b/src/headless/types/plugins/pubsub/types.d.ts new file mode 100644 index 0000000000..432010e924 --- /dev/null +++ b/src/headless/types/plugins/pubsub/types.d.ts @@ -0,0 +1,24 @@ +export type PubSubConfigOptions = { + 'pubsub#access_model': 'authorize' | 'open' | 'presence' | 'roster' | 'whitelist'; + 'pubsub#dataform_xslt'?: string; + 'pubsub#deliver_notifications': boolean; + 'pubsub#deliver_payloads': boolean; + 'pubsub#item_expire': string; + 'pubsub#max_items': string; + 'pubsub#max_payload_size': string; + 'pubsub#notification_type': 'normal' | 'headline'; + 'pubsub#notify_config': boolean; + 'pubsub#notify_delete': boolean; + 'pubsub#notify_retract': boolean; + 'pubsub#notify_sub': boolean; + 'pubsub#persist_items': boolean; + 'pubsub#presence_based_delivery': boolean; + 'pubsub#publish_model': 'publishers' | 'subscribers' | 'open'; + 'pubsub#purge_offline': boolean; + 'pubsub#roster_groups_allowed': string[]; + 'pubsub#send_last_published_item': 'never' | 'on_sub' | 'on_sub_and_presence'; + 'pubsub#subscribe': boolean; + 'pubsub#title': string; + 'pubsub#type': string; +}; +//# sourceMappingURL=types.d.ts.map \ No newline at end of file