Bläddra i källkod

Fixes #1038: Support setting node config manually

JC Brand 7 månader sedan
förälder
incheckning
c4f67f4b63
3 ändrade filer med 183 tillägg och 28 borttagningar
  1. 1 0
      CHANGES.md
  2. 29 27
      src/headless/plugins/pubsub/api.js
  3. 153 1
      src/headless/plugins/pubsub/tests/config.js

+ 1 - 0
CHANGES.md

@@ -5,6 +5,7 @@
 ### Github Issues
 ### Github Issues
 - #122: Set horizontal layout direction based on the language
 - #122: Set horizontal layout direction based on the language
 - #698: Add support for MUC private messages
 - #698: Add support for MUC private messages
+- #1038: Support setting node config manually
 - #1057: Removed the `mobile` view mode. Instead of setting `view_mode` to `mobile`, set it to `fullscreen`.
 - #1057: Removed the `mobile` view mode. Instead of setting `view_mode` to `mobile`, set it to `fullscreen`.
 - #1174: Show MUC avatars in the rooms list
 - #1174: Show MUC avatars in the rooms list
 - #1195: Add actions to quote and copy messages
 - #1195: Add actions to quote and copy messages

+ 29 - 27
src/headless/plugins/pubsub/api.js

@@ -9,7 +9,7 @@ import log from '../../log.js';
 import { parseErrorStanza } from '../../shared/parsers.js';
 import { parseErrorStanza } from '../../shared/parsers.js';
 import { parseStanzaForPubSubConfig } from './parsers.js';
 import { parseStanzaForPubSubConfig } from './parsers.js';
 
 
-const { Strophe, stx, u } = converse.env;
+const { Strophe, stx } = converse.env;
 
 
 export default {
 export default {
     /**
     /**
@@ -49,10 +49,7 @@ export default {
                 try {
                 try {
                     response = await api.sendIQ(stanza);
                     response = await api.sendIQ(stanza);
                 } catch (error) {
                 } catch (error) {
-                    if (u.isErrorStanza(error)) {
-                        throw parseErrorStanza(error);
-                    }
-                    throw error;
+                    throw await parseErrorStanza(error);
                 }
                 }
                 return parseStanzaForPubSubConfig(response);
                 return parseStanzaForPubSubConfig(response);
             },
             },
@@ -72,7 +69,7 @@ export default {
                 const entity_jid = jid || bare_jid;
                 const entity_jid = jid || bare_jid;
                 const new_config = {
                 const new_config = {
                     ...(await api.pubsub.config.get(entity_jid, node)),
                     ...(await api.pubsub.config.get(entity_jid, node)),
-                    ...config
+                    ...config,
                 };
                 };
 
 
                 const stanza = stx`
                 const stanza = stx`
@@ -95,10 +92,7 @@ export default {
                 try {
                 try {
                     await api.sendIQ(stanza);
                     await api.sendIQ(stanza);
                 } catch (error) {
                 } catch (error) {
-                    if (u.isErrorStanza(error)) {
-                        throw parseErrorStanza(error);
-                    }
-                    throw error;
+                    throw await parseErrorStanza(error);
                 }
                 }
                 return new_config;
                 return new_config;
             },
             },
@@ -119,6 +113,9 @@ export default {
          * @returns {Promise<void|Element>}
          * @returns {Promise<void|Element>}
          */
          */
         async publish(jid, node, item, options, strict_options = true) {
         async publish(jid, node, item, options, strict_options = true) {
+            if (!node) throw new Error('api.pubsub.config.publish: node value required');
+            if (!item) throw new Error('api.pubsub.config.publish: item value required');
+
             const bare_jid = _converse.session.get('bare_jid');
             const bare_jid = _converse.session.get('bare_jid');
             const entity_jid = jid || bare_jid;
             const entity_jid = jid || bare_jid;
 
 
@@ -167,30 +164,35 @@ export default {
                         Strophe.getDomainFromJid(entity_jid)
                         Strophe.getDomainFromJid(entity_jid)
                     )));
                     )));
 
 
-            if (!supports_publish_options) {
-                if (strict_options) {
-                    log.warn(`api.pubsub.publish: #publish-options not supported, refusing to publish item.`);
-                    log.warn(stanza);
-                    return;
-                } else {
-                    log.warn(`api.pubsub.publish: #publish-options not supported, publishing anyway.`);
-                }
+            if (!supports_publish_options && strict_options) {
+                log.warn(`api.pubsub.publish: #publish-options not supported, refusing to publish item.`);
+                log.warn(stanza);
+                return;
             }
             }
 
 
             try {
             try {
                 await api.sendIQ(stanza);
                 await api.sendIQ(stanza);
             } catch (iq) {
             } catch (iq) {
+                const e = await parseErrorStanza(iq);
                 if (
                 if (
-                    iq instanceof Element &&
-                    !strict_options &&
-                    iq.querySelector(`precondition-not-met[xmlns="${Strophe.NS.PUBSUB_ERROR}"]`)
+                    e.name === 'conflict' &&
+                    /** @type {import('shared/errors').StanzaError} */(e).extra[Strophe.NS.PUBSUB_ERROR] === 'precondition-not-met'
                 ) {
                 ) {
-                    // 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);
+                    // Manually configure the node if we can't set it via publish-options
+                    await api.pubsub.config.set(entity_jid, node, options);
+                    try {
+                        await api.sendIQ(stanza);
+                    } catch (e) {
+                        log.error(e);
+                        if (!strict_options) {
+                            // 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: #publish-options precondition-not-met, publishing anyway.`);
+                            await api.sendIQ(el);
+                        }
+                    }
                 } else {
                 } else {
                     throw iq;
                     throw iq;
                 }
                 }

+ 153 - 1
src/headless/plugins/pubsub/tests/config.js

@@ -192,7 +192,7 @@ describe('The pubsub API', function () {
                 let first_error_thrown = false;
                 let first_error_thrown = false;
                 promise
                 promise
                     .catch((e) => {
                     .catch((e) => {
-                        expect(e instanceof errors.NotImplementedError).toBe(true);
+                        expect(e instanceof errors.FeatureNotImplementedError).toBe(true);
                         first_error_thrown = true;
                         first_error_thrown = true;
                     })
                     })
                     .finally(() => {
                     .finally(() => {
@@ -467,4 +467,156 @@ describe('The pubsub API', function () {
             })
             })
         );
         );
     });
     });
+
+    describe('publishing to a node', function () {
+        it(
+            "will try to manually configure the node if publish-options aren't supported",
+            mock.initConverse([], {}, async function (_converse) {
+                await mock.waitForRoster(_converse, 'current', 0);
+
+                const pubsub_jid = 'pubsub.shakespeare.lit';
+
+                const { api } = _converse;
+                const sent_stanzas = api.connection.get().sent_stanzas;
+                const own_jid = _converse.session.get('jid');
+
+                const node = 'princely_musings';
+                const promise = api.pubsub.publish(pubsub_jid, node, stx`<item></item>`, { access_model: 'whitelist' });
+
+                await mock.waitUntilDiscoConfirmed(
+                    _converse,
+                    pubsub_jid,
+                    [{ 'category': 'pubsub', 'type': 'pep' }],
+                    ['http://jabber.org/protocol/pubsub#publish-options']
+                );
+
+                let sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas.filter((iq) => iq.querySelector('pubsub publish')).pop()
+                );
+                expect(sent_stanza).toEqualStanza(stx`
+                    <iq type="set"
+                            from="${_converse.bare_jid}"
+                            to="${pubsub_jid}"
+                            xmlns="jabber:client"
+                            id="${sent_stanza.getAttribute('id')}">
+                        <pubsub xmlns="http://jabber.org/protocol/pubsub">
+                            <publish node="princely_musings"><item/></publish>
+                            <publish-options>
+                                <x xmlns="jabber:x:data" type="submit">
+                                    <field var="FORM_TYPE" type="hidden">
+                                        <value>http://jabber.org/protocol/pubsub#publish-options</value>
+                                    </field>
+                                    <field var="pubsub#access_model"><value>whitelist</value></field>
+                                </x>
+                            </publish-options>
+                        </pubsub>
+                    </iq>`);
+
+                let response = stx`<iq type='error'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}">
+                    <error type='modify'>
+                        <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+                        <precondition-not-met xmlns='http://jabber.org/protocol/pubsub#errors'/>
+                    </error>
+                </iq>`;
+                _converse.api.connection.get()._dataRecv(mock.createRequest(response));
+
+                sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas.filter((iq) => iq.querySelector('pubsub configure')).pop()
+                );
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}">
+                    <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+                        <configure node='${node}'>
+                        <x xmlns='jabber:x:data' type='form'>
+                            <field var='pubsub#access_model' type='list-single' label='Specify the subscriber model'>
+                                <option><value>authorize</value></option>
+                                <option><value>open</value></option>
+                                <option><value>presence</value></option>
+                                <option><value>roster</value></option>
+                                <option><value>whitelist</value></option>
+                                <value>open</value>
+                            </field>
+                        </x>
+                        </configure>
+                    </pubsub>
+                </iq>`)
+                );
+
+                sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas
+                        .filter((iq) => iq.getAttribute('type') === 'set' && iq.querySelector('pubsub configure'))
+                        .pop()
+                );
+
+                expect(sent_stanza).toEqualStanza(stx`<iq xmlns="jabber:client"
+                    from="${_converse.bare_jid}"
+                    to="${pubsub_jid}"
+                    type="set"
+                    id="${sent_stanza.getAttribute('id')}">
+                        <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+                            <configure node="princely_musings">
+                            <x xmlns="jabber:x:data" type="submit">
+                                <field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#nodeconfig</value></field>
+                                <field var="access_model"><value>whitelist</value></field>
+                            </x>
+                            </configure>
+                        </pubsub>
+                    </iq>`);
+
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}"></iq>`)
+                );
+
+                // Clear old stanzas
+                while (sent_stanzas.length) sent_stanzas.pop();
+
+                sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas.filter((iq) => iq.querySelector('pubsub publish')).pop()
+                );
+                expect(sent_stanza).toEqualStanza(stx`
+                    <iq type="set"
+                            from="${_converse.bare_jid}"
+                            to="${pubsub_jid}"
+                            xmlns="jabber:client"
+                            id="${sent_stanza.getAttribute('id')}">
+                        <pubsub xmlns="http://jabber.org/protocol/pubsub">
+                            <publish node="princely_musings"><item/></publish>
+                            <publish-options>
+                                <x xmlns="jabber:x:data" type="submit">
+                                    <field var="FORM_TYPE" type="hidden">
+                                        <value>http://jabber.org/protocol/pubsub#publish-options</value>
+                                    </field>
+                                    <field var="pubsub#access_model"><value>whitelist</value></field>
+                                </x>
+                            </publish-options>
+                        </pubsub>
+                    </iq>`);
+
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}"></iq>`)
+                );
+
+                await promise;
+            })
+        );
+    });
 });
 });