Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/style_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Software Style Guide
====================

Most of the style guide recommendations here come from Douglas Crockford's book
Many of the style guide recommendations here come from Douglas Crockford's book
`JavaScript, the good parts <http://shop.oreilly.com/product/9780596517748.do>`_

Tabs or spaces?
Expand Down
2 changes: 1 addition & 1 deletion src/headless/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import './plugins/bosh/index.js'; // XEP-0206 BOSH
import './plugins/caps/index.js'; // XEP-0115 Entity Capabilities
export { ChatBox, Message, Messages } from './plugins/chat/index.js'; // RFC-6121 Instant messaging
import './plugins/chatboxes/index.js';
import './plugins/disco/index.js'; // XEP-0030 Service discovery
export { DiscoEntity, DiscoEntities } from './plugins/disco/index.js'; // XEP-0030 Service discovery
import './plugins/adhoc/index.js'; // XEP-0050 Ad Hoc Commands
import './plugins/headlines/index.js'; // Support for headline messages
export { Device, Devices, DeviceList, DeviceLists } from './plugins/omemo/index.js'; // Support for headline messages
Expand Down
2 changes: 1 addition & 1 deletion src/headless/plugins/bookmarks/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class Bookmarks extends Collection {
async setBookmarksFromStanza(stanza) {
const bookmarks = await parseStanzaForBookmarks(stanza);
bookmarks.forEach(
/** @param {import('./types.js').BookmarkAttrs} attrs */
/** @param {import('./types.ts').BookmarkAttrs} attrs */
(attrs) => {
const bookmark = this.get(attrs.jid);
bookmark ? bookmark.save(attrs) : this.create(attrs);
Expand Down
2 changes: 2 additions & 0 deletions src/headless/plugins/disco/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {

const { Strophe } = converse.env;

export { DiscoEntity, DiscoEntities };

/**
* @typedef {Object} DiscoState
* @property {Array} _identities
Expand Down
119 changes: 116 additions & 3 deletions src/headless/plugins/pubsub/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import converse from '../../shared/api/public.js';
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import log from "@converse/log";
import log from '@converse/log';
import { parseErrorStanza } from '../../shared/parsers.js';
import { parseStanzaForPubSubConfig } from './parsers.js';

Expand Down Expand Up @@ -81,7 +81,7 @@ export default {
<configure node="${node}">
<x xmlns="${Strophe.NS.XFORM}" type="submit">
<field var="FORM_TYPE" type="hidden">
<value>${Strophe.NS.PUBSUB}#nodeconfig</value>
<value>${Strophe.NS.PUBSUB}#node_config</value>
</field>
${Object.entries(new_config).map(([k, v]) => stx`<field var="pubsub#${k}"><value>${v}</value></field>`)}
</x>
Expand Down Expand Up @@ -176,7 +176,8 @@ export default {
const e = await parseErrorStanza(iq);
if (
e.name === 'conflict' &&
/** @type {import('shared/errors').StanzaError} */(e).extra[Strophe.NS.PUBSUB_ERROR] === 'precondition-not-met'
/** @type {import('shared/errors').StanzaError} */ (e).extra[Strophe.NS.PUBSUB_ERROR] ===
'precondition-not-met'
) {
// Manually configure the node if we can't set it via publish-options
await api.pubsub.config.set(entity_jid, node, options);
Expand All @@ -198,5 +199,117 @@ export default {
}
}
},

/**
* Creates a PubSub node at a given service
* @param {string} jid - The PubSub service JID
* @param {string} node - The node to create
* @param {PubSubConfigOptions} [config] The configuration options
* @returns {Promise<void>}
*/
async create(jid, node, config) {
const own_jid = _converse.state.session.get('jid');
const iq = stx`
<iq xmlns="jabber:client"
type="set"
from="${own_jid}"
to="${jid}">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<create node="${node}"/>
${
config
? stx`
<configure>
<x xmlns="${Strophe.NS.XFORM}" type="submit">
<field var="FORM_TYPE" type="hidden">
<value>${Strophe.NS.PUBSUB}#node_config</value>
</field>
${Object.entries(config).map(([k, v]) => stx`<field var="pubsub#${k}"><value>${v}</value></field>`)}
</x>
</configure>`
: ''
}
</pubsub>
</iq>`;
return await api.sendIQ(iq);
},

/**
* Subscribes the local user to a PubSub node.
*
* @method _converse.api.pubsub.subscribe
* @param {string} jid - PubSub service JID.
* @param {string} node - The node to subscribe to
* @returns {Promise<void>}
*/
async subscribe(jid, node) {
const service = jid || (await api.disco.entities.find('http://jabber.org/protocol/pubsub'));
const own_jid = _converse.session.get('jid');
const iq = stx`
<iq type="set" from="${own_jid}" to="${service}" xmlns="jabber:client">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<subscribe node="${node}" jid="${own_jid}"/>
</pubsub>
</iq>`;
return await api.sendIQ(iq);
},

/**
* Unsubscribes the local user from a PubSub node.
* @method _converse.api.pubsub.unsubscribe
* @param {string} jid - The PubSub service JID
* @param {string} node - The node to unsubscribe from
* @returns {Promise<void>}
*/
async unsubscribe(jid, node) {
const own_jid = _converse.session.get('jid');
const iq = stx`
<iq type="set" from="${own_jid}" to="${jid}" xmlns="jabber:client">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<unsubscribe node="${node}" jid="${own_jid}"/>
</pubsub>
</iq>`;
await api.sendIQ(iq);
},

/**
* Retrieves the subscriptions for the local user.
* @method _converse.api.pubsub.subscriptions
* @param {string} [jid] - The PubSub service JID.
* @param {string} [node] - The node to retrieve subscriptions from.
* @returns {Promise<import('./types').PubSubSubscription[]>}
*/
async subscriptions(jid, node) {
const service = jid || (await api.disco.entities.find(Strophe.NS.PUBSUB));
const own_jid = _converse.session.get('bare_jid');
const iq = stx`
<iq xmlns="jabber:client"
type="get"
from="${own_jid}"
to="${service}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<subscriptions${node ? ` node="${node}"` : ''}/>
</pubsub>
</iq>`;

let response;
try {
response = await api.sendIQ(iq);
} catch (e) {
log.warn(e);
return [];
}

const subs_el = response.querySelector('pubsub subscriptions');
if (!subs_el) return [];

const subs = Array.from(subs_el.querySelectorAll('subscription')).map((el) => ({
node: el.getAttribute('node'),
jid: el.getAttribute('jid'),
subscription: el.getAttribute('subscription'),
subid: el.hasAttribute('subid') ? el.getAttribute('subid') : undefined,
}));
return subs;
},
},
};
17 changes: 17 additions & 0 deletions src/headless/plugins/pubsub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
import _converse from '../../shared/_converse.js';
import converse from '../../shared/api/public.js';
import pubsub_api from './api.js';
import { default as PubSubNode } from './node.js';
import { default as PubSubNodes } from './nodes.js';
import '../disco/index.js';


const { Strophe, sizzle } = converse.env;

Strophe.addNamespace('PUBSUB_ERROR', Strophe.NS.PUBSUB + '#errors');
Expand All @@ -18,6 +21,18 @@ converse.plugins.add('converse-pubsub', {
initialize() {
const { api } = _converse;
Object.assign(_converse.api, pubsub_api);
Object.assign(_converse.exports, { PubSubNodes });

api.listen.on('connected', () => {
const pubsub_nodes = new _converse.exports.PubSubNodes();
Object.assign(_converse.state, { pubsub_nodes });
/**
* @event _converse#pubSubNodesInitialized
* @example _converse.api.listen.on('pubSubInitialized', () => { ... });
* @example _converse.api.waitUntil('pubSubInitialized').then(() => { ... });
*/
api.trigger('pubSubNodesInitialized');
});

api.listen.on(
'parseErrorStanza',
Expand All @@ -38,3 +53,5 @@ converse.plugins.add('converse-pubsub', {
);
},
});

export { PubSubNode, PubSubNodes };
169 changes: 169 additions & 0 deletions src/headless/plugins/pubsub/tests/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/* global converse */
import mock from '../../../tests/mock.js';

const { stx, Strophe, u } = converse.env;

describe('pubsub subscribe/unsubscribe API', function () {
beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));

it(
'sends correct IQ for subscribe',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
const { api, state } = _converse;
const own_jid = state.session.get('jid');
const sent = api.connection.get().sent_stanzas;
const service = 'pubsub.example.org';
const node = 'testnode';
const subscribePromise = api.pubsub.subscribe(service, node);

const stanza = sent.filter((iq) => iq.querySelector('pubsub subscribe')).pop();
expect(stanza).toEqualStanza(stx`
<iq type="set"
from="${own_jid}"
to="${service}"
xmlns="jabber:client"
id="${stanza.getAttribute('id')}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<subscribe node="${node}" jid="${own_jid}"/>
</pubsub>
</iq>`);

_converse.api.connection.get()._dataRecv(
mock.createRequest(stx`
<iq type="result"
xmlns="jabber:client"
from="${service}"
to="${own_jid}"
id="${stanza.getAttribute('id')}"/>
`)
);
await subscribePromise;
})
);

it(
'sends correct IQ for unsubscribe',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
const { api, state } = _converse;
const own_jid = state.session.get('jid');
const sent = api.connection.get().sent_stanzas;
const service = 'pubsub.example.org';
const node = 'testnode';
const unsubscribePromise = api.pubsub.unsubscribe(service, node);
const stanza = sent.filter((iq) => iq.querySelector('pubsub unsubscribe')).pop();
_converse.api.connection.get()._dataRecv(
mock.createRequest(stx`
<iq type="result"
xmlns="jabber:client"
from="${service}"
to="${own_jid}"
id="${stanza.getAttribute('id')}"/>
`)
);
await unsubscribePromise;
expect(stanza).toEqualStanza(stx`
<iq type="set"
from="${own_jid}"
to="${service}"
xmlns="jabber:client"
id="${stanza.getAttribute('id')}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<unsubscribe node="${node}" jid="${own_jid}"/>
</pubsub>
</iq>`);
})
);

it(
'sends correct IQ for create',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
const { api, state } = _converse;
const own_jid = state.session.get('jid');
const sent = api.connection.get().sent_stanzas;
const service = 'pubsub.example.org';
const node = 'newnode';
const config = { access_model: 'open', max_items: '10' };
const createPromise = api.pubsub.create(service, node, config);
const stanza = await u.waitUntil(() => sent.filter((iq) => iq.querySelector('pubsub create')).pop());
expect(stanza).toEqualStanza(stx`
<iq type="set"
from="${own_jid}"
to="${service}"
xmlns="jabber:client"
id="${stanza.getAttribute('id')}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<create node="${node}"/>
<configure>
<x xmlns="${Strophe.NS.XFORM}" type="submit">
<field var="FORM_TYPE" type="hidden">
<value>${Strophe.NS.PUBSUB}#nodeconfig</value>
</field>
<field var="pubsub#access_model"><value>${config.access_model}</value></field>
<field var="pubsub#max_items"><value>${config.max_items}</value></field>
</x>
</configure>
</pubsub>
</iq>`);

_converse.api.connection.get()._dataRecv(
mock.createRequest(stx`
<iq type="result"
xmlns="jabber:client"
from="${service}"
to="${_converse.bare_jid}"
id="${stanza.getAttribute('id')}"/>
`)
);
await createPromise;
})
);

it(
'retrieves correct IQ for retrieve subscriptions',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
const { api, state } = _converse;
const service = 'pubsub.example.org';
const bare_jid = state.session.get('jid');
const subscriptionsPromise = api.pubsub.subscriptions(service);
const stanza = api.connection
.get()
.sent_stanzas.filter((iq) => iq.querySelector('pubsub subscriptions'))
.pop();
expect(stanza).toEqualStanza(stx`
<iq type="get"
from="${bare_jid}"
to="${service}"
xmlns="jabber:client"
id="${stanza.getAttribute('id')}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<subscriptions/>
</pubsub>
</iq>`);
_converse.api.connection.get()._dataRecv(
mock.createRequest(stx`
<iq type="result"
xmlns="jabber:client"
from="${service}"
to="${bare_jid}"
id="${stanza.getAttribute('id')}">
<pubsub xmlns="${Strophe.NS.PUBSUB}">
<subscriptions>
<subscription node="node1" jid="${bare_jid}" subscription="subscribed"/>
<subscription node="node2" jid="${bare_jid}" subscription="unconfigured" subid="sid1"/>
</subscriptions>
</pubsub>
</iq>
`)
);
const subs = await subscriptionsPromise;
expect(subs).toEqual([
{ node: 'node1', jid: bare_jid, subscription: 'subscribed', subid: undefined },
{ node: 'node2', jid: bare_jid, subscription: 'unconfigured', subid: 'sid1' },
]);
})
);
});
7 changes: 7 additions & 0 deletions src/headless/plugins/pubsub/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ export type PubSubConfigOptions = {
// Specify the semantic type of payload data to be provided at this node.
type?: string;
};

export type PubSubSubscription = {
node: string;
jid: string;
subscription: 'subscribed'|'unconfigured';
subid?: string;
};
Loading
Loading