Skip to content

Commit 63553c4

Browse files
committed
feat(pubsub): add api.pubsub.retract to delete node items
Implements XEP-0060 § 7.2 item retraction. Sends a `<retract>` for the given item id. Let the bookmarks plugin use the new retract api method.
1 parent 5cdba1d commit 63553c4

5 files changed

Lines changed: 130 additions & 29 deletions

File tree

src/headless/plugins/bookmarks/collection.js

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ class Bookmarks extends Collection {
3535
this.on('add', (bm) =>
3636
this.openBookmarkedRoom(bm)
3737
.then((bm) => this.markRoomAsBookmarked(bm))
38-
.catch((e) => log.fatal(e))
38+
.catch((e) => log.fatal(e)),
3939
);
4040
this.on('change:autojoin', this.onAutoJoinChanged, this);
4141
this.on(
4242
'remove',
4343
/** @param { Bookmark } bookmark }*/ (bookmark) => {
4444
this.sendRemoveBookmarkStanza(bookmark);
4545
this.leaveRoom(bookmark);
46-
}
46+
},
4747
);
4848

4949
const { storage_key, fetched_flag_key } = getStorageKeys();
@@ -142,21 +142,10 @@ class Bookmarks extends Collection {
142142
: Strophe.NS.BOOKMARKS;
143143

144144
if (node === Strophe.NS.BOOKMARKS2) {
145-
const stanza = stx`
146-
<iq from="${bare_jid}"
147-
to="${bare_jid}"
148-
type="set"
149-
xmlns="jabber:client">
150-
<pubsub xmlns="http://jabber.org/protocol/pubsub">
151-
<retract node="${node}" notify="true">
152-
<item id="${bookmark.get('jid')}"/>
153-
</retract>
154-
</pubsub>
155-
</iq>`;
156-
return api.sendIQ(stanza);
145+
return api.pubsub.retract(bare_jid, node, bookmark.get('jid'), { notify: true });
146+
} else {
147+
return this.sendBookmarkStanza().catch((iq) => this.onBookmarkError(iq));
157148
}
158-
159-
return this.sendBookmarkStanza().catch((iq) => this.onBookmarkError(iq));
160149
}
161150

162151
/**
@@ -191,7 +180,7 @@ class Bookmarks extends Collection {
191180
jid="${model.get('jid')}">
192181
${model.get('nick') ? stx`<nick>${model.get('nick')}</nick>` : ''}
193182
${model.get('password') ? stx`<password>${model.get('password')}</password>` : ''}
194-
</conference>`
183+
</conference>`,
195184
)}
196185
</storage>
197186
</item>`;
@@ -282,7 +271,7 @@ class Bookmarks extends Collection {
282271
(attrs) => {
283272
const bookmark = this.get(attrs.jid);
284273
bookmark ? bookmark.save(attrs) : this.create(attrs);
285-
}
274+
},
286275
);
287276
}
288277

@@ -309,7 +298,7 @@ class Bookmarks extends Collection {
309298
api.alert('error', __('Timeout Error'), [
310299
__(
311300
'The server did not return your bookmarks within the allowed time. ' +
312-
'You can reload the page to request them again.'
301+
'You can reload the page to request them again.',
313302
),
314303
]);
315304
deferred?.reject(new Error('Could not fetch bookmarks'));

src/headless/plugins/bookmarks/plugin.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Strophe.addNamespace('BOOKMARKS', 'storage:bookmarks');
1818
Strophe.addNamespace('BOOKMARKS2', 'urn:xmpp:bookmarks:1');
1919

2020
converse.plugins.add('converse-bookmarks', {
21-
dependencies: ['converse-chatboxes', 'converse-muc'],
21+
dependencies: ['converse-chatboxes', 'converse-muc', 'converse-pubsub'],
2222

2323
initialize() {
2424
// Configuration values for this plugin
@@ -48,7 +48,7 @@ converse.plugins.add('converse-bookmarks', {
4848
*/
4949
(muc, nick) => {
5050
return nick || getNicknameFromBookmark(muc.get('jid'));
51-
}
51+
},
5252
);
5353

5454
api.listen.on(
@@ -72,11 +72,11 @@ converse.plugins.add('converse-bookmarks', {
7272
name: bookmark?.get('name') ?? '',
7373
extensions: bookmark?.get('extensions') ?? [],
7474
});
75-
}
75+
},
7676
);
7777
}
7878
return attrs;
79-
}
79+
},
8080
);
8181

8282
api.listen.on(
@@ -91,7 +91,7 @@ converse.plugins.add('converse-bookmarks', {
9191
...(password ? { password } : {}),
9292
...(name ? { name } : {}),
9393
});
94-
}
94+
},
9595
);
9696

9797
api.listen.on(
@@ -112,9 +112,9 @@ converse.plugins.add('converse-bookmarks', {
112112
// on the MUC.
113113
{
114114
silent: true,
115-
}
115+
},
116116
);
117-
}
117+
},
118118
);
119119

120120
api.listen.on('addClientFeatures', () => {

src/headless/plugins/pubsub/api.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,42 @@ export default {
205205
}
206206
},
207207

208+
/**
209+
* Retracts (deletes) an item from a PubSub node (XEP-0060 § 7.2).
210+
* @method _converse.api.pubsub.retract
211+
* @param {string} jid - The JID of the pubsub service where the node
212+
* resides. Pass a falsy value to retract from your own PEP service.
213+
* @param {string} node - The node to retract the item from
214+
* @param {string} id - The id of the item to retract
215+
* @param {object} [options]
216+
* @param {boolean} [options.notify=true] - Whether to ask the server to
217+
* notify subscribers of the retraction.
218+
* @returns {Promise<void>}
219+
*/
220+
async retract(jid, node, id, options = {}) {
221+
if (!node) throw new Error('api.pubsub.retract: node value required');
222+
if (!id) throw new Error('api.pubsub.retract: id value required');
223+
224+
const { notify = true } = options;
225+
const bare_jid = _converse.session.get('bare_jid');
226+
const entity_jid = jid || bare_jid;
227+
228+
const stanza = stx`
229+
<iq xmlns="jabber:client" from="${bare_jid}" type="set" to="${entity_jid}">
230+
<pubsub xmlns="${Strophe.NS.PUBSUB}">
231+
<retract node="${node}" notify="${notify ? 'true' : 'false'}">
232+
<item id="${id}"/>
233+
</retract>
234+
</pubsub>
235+
</iq>`;
236+
237+
try {
238+
await api.sendIQ(stanza);
239+
} catch (error) {
240+
throw await parseErrorStanza(error);
241+
}
242+
},
243+
208244
/**
209245
* Creates a PubSub node at a given service
210246
* @param {string} jid - The PubSub service JID
@@ -213,6 +249,8 @@ export default {
213249
* @returns {Promise<void>}
214250
*/
215251
async create(jid, node, config) {
252+
if (!node) throw new Error('api.pubsub.create: node value required');
253+
216254
const own_jid = _converse.state.session.get('jid');
217255
const iq = stx`
218256
<iq xmlns="jabber:client"
@@ -233,7 +271,12 @@ export default {
233271
</configure>
234272
</pubsub>
235273
</iq>`;
236-
return await api.sendIQ(iq);
274+
275+
try {
276+
await api.sendIQ(iq);
277+
} catch (error) {
278+
throw await parseErrorStanza(error);
279+
}
237280
},
238281

239282
/**
@@ -244,6 +287,8 @@ export default {
244287
* @returns {Promise<void>}
245288
*/
246289
async subscribe(jid, node) {
290+
if (!node) throw new Error('api.pubsub.subscribe: node value required');
291+
247292
const service = jid || (await api.disco.entities.find('http://jabber.org/protocol/pubsub'));
248293
const own_jid = _converse.session.get('jid');
249294
const iq = stx`
@@ -252,7 +297,12 @@ export default {
252297
<subscribe node="${node}" jid="${own_jid}"/>
253298
</pubsub>
254299
</iq>`;
255-
return await api.sendIQ(iq);
300+
301+
try {
302+
await api.sendIQ(iq);
303+
} catch (error) {
304+
throw await parseErrorStanza(error);
305+
}
256306
},
257307

258308
/**
@@ -263,14 +313,21 @@ export default {
263313
* @returns {Promise<void>}
264314
*/
265315
async unsubscribe(jid, node) {
316+
if (!node) throw new Error('api.pubsub.unsubscribe: node value required');
317+
266318
const own_jid = _converse.session.get('jid');
267319
const iq = stx`
268320
<iq type="set" from="${own_jid}" to="${jid}" xmlns="jabber:client">
269321
<pubsub xmlns="${Strophe.NS.PUBSUB}">
270322
<unsubscribe node="${node}" jid="${own_jid}"/>
271323
</pubsub>
272324
</iq>`;
273-
await api.sendIQ(iq);
325+
326+
try {
327+
await api.sendIQ(iq);
328+
} catch (error) {
329+
throw await parseErrorStanza(error);
330+
}
274331
},
275332

276333
/**

src/headless/plugins/pubsub/tests/api.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,46 @@ describe('pubsub subscribe/unsubscribe API', function () {
176176
]);
177177
})
178178
);
179+
180+
it(
181+
'sends correct IQ for retract',
182+
mock.initConverse(converse, [], {}, async function (_converse) {
183+
await mock.waitForRoster(_converse, 'current', 0);
184+
const { api, state } = _converse;
185+
const bare_jid = state.session.get('bare_jid');
186+
const sent = api.connection.get().sent_stanzas;
187+
const service = 'pubsub.example.org';
188+
const node = 'urn:xmpp:microblog:0';
189+
const retractPromise = api.pubsub.retract(service, node, 'item-1');
190+
191+
const stanza = sent.filter((iq) => iq.querySelector('pubsub retract')).pop();
192+
expect(stanza).toEqualStanza(stx`
193+
<iq xmlns="jabber:client"
194+
from="${bare_jid}"
195+
type="set"
196+
to="${service}"
197+
id="${stanza.getAttribute('id')}">
198+
<pubsub xmlns="${Strophe.NS.PUBSUB}">
199+
<retract node="${node}" notify="true">
200+
<item id="item-1"/>
201+
</retract>
202+
</pubsub>
203+
</iq>`);
204+
205+
_converse.api.connection.get()._dataRecv(
206+
mock.createRequest(
207+
_converse,
208+
stx`
209+
<iq type="result"
210+
xmlns="jabber:client"
211+
from="${service}"
212+
to="${bare_jid}"
213+
id="${stanza.getAttribute('id')}"/>`,
214+
),
215+
);
216+
await retractPromise;
217+
})
218+
);
179219
});
180220

181221
describe('pubsub items API', function () {

src/headless/types/plugins/pubsub/api.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ declare namespace _default {
3434
* @returns {Promise<void|Element>}
3535
*/
3636
function publish(jid: string, node: string, item: import("strophe.js").Builder | import("strophe.js").Stanza | (import("strophe.js").Builder | import("strophe.js").Stanza)[], options: import("./types").PubSubConfigOptions, strict_options?: boolean): Promise<void | Element>;
37+
/**
38+
* Retracts (deletes) an item from a PubSub node (XEP-0060 § 7.2).
39+
* @method _converse.api.pubsub.retract
40+
* @param {string} jid - The JID of the pubsub service where the node
41+
* resides. Pass a falsy value to retract from your own PEP service.
42+
* @param {string} node - The node to retract the item from
43+
* @param {string} id - The id of the item to retract
44+
* @param {object} [options]
45+
* @param {boolean} [options.notify=true] - Whether to ask the server to
46+
* notify subscribers of the retraction.
47+
* @returns {Promise<void>}
48+
*/
49+
function retract(jid: string, node: string, id: string, options?: {
50+
notify?: boolean;
51+
}): Promise<void>;
3752
/**
3853
* Creates a PubSub node at a given service
3954
* @param {string} jid - The PubSub service JID

0 commit comments

Comments
 (0)