Commit b5783c06 authored by JC Brand's avatar JC Brand

Refactor `converse-api.query` and the RSM class

- The `converse.api.query` method now no longer accepts an RSM instance.
- The RSM class now separates `query` parameters from `result` attributes
- Improve JSDoc docs and remove need to make `converse-rsm` a plugin
- Add typedefs for the options expected by RSM and `api.archive.query`
parent e7a3bb87
......@@ -33,6 +33,7 @@ Soon we'll deprecate the latter, so prepare now.
- #2201: added html to converse.env
- #2213: added CustomElement to converse.env
- #2220: fix rendering of emojis in case `use_system_emojis == false` (again).
- The `api.archive.query` method no longer accepts an RSM instance as argument.
- The plugin `converse-uniview` has been removed and its functionality merged into `converse-chatboxviews`
- Removed the mockups from the project. Recommended to use tests instead.
- The API method `api.settings.update` has been deprecated in favor of `api.settings.extend`.
......
......@@ -32,7 +32,7 @@ describe("Message Archive Management", function () {
`<x type="submit" xmlns="jabber:x:data">`+
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><before></before></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>2</max></set>`+
`</query>`+
`</iq>`);
......@@ -105,7 +105,7 @@ describe("Message Archive Management", function () {
`<x type="submit" xmlns="jabber:x:data">`+
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><after>${message.querySelector('result').getAttribute('id')}</after></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><after>${message.querySelector('result').getAttribute('id')}</after><max>2</max></set>`+
`</query>`+
`</iq>`);
......@@ -165,7 +165,8 @@ describe("Message Archive Management", function () {
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm">`+
`<max>2</max><after>${last_msg_id}</after>`+
`<after>${last_msg_id}</after>`+
`<max>2</max>`+
`</set>`+
`</query>`+
`</iq>`);
......@@ -695,8 +696,8 @@ describe("Message Archive Management", function () {
`</field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm">`+
`<max>10</max>`+
`<after>09af3-cc343-b409f</after>`+
`<max>10</max>`+
`</set>`+
`</query>`+
`</iq>`);
......@@ -725,49 +726,7 @@ describe("Message Archive Management", function () {
`</field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm">`+
`<max>10</max>`+
`<before></before>`+
`</set>`+
`</query>`+
`</iq>`);
done();
}));
it("accepts a _converse.RSM object for the query options",
mock.initConverse([], {}, async function (done, _converse) {
await mock.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
// Normally the user wouldn't manually make a _converse.RSM object
// and pass it in. However, in the callback method an RSM object is
// returned which can be reused for easy paging. This test is
// more for that usecase.
const rsm = new _converse.RSM({'max': '10'});
rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
rsm.start = '2010-06-07T00:00:00Z';
_converse.api.archive.query(rsm);
await u.waitUntil(() => sent_stanza);
const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
`<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
`<x type="submit" xmlns="jabber:x:data">`+
`<field type="hidden" var="FORM_TYPE">`+
`<value>urn:xmpp:mam:2</value>`+
`</field>`+
`<field var="with">`+
`<value>romeo@montague.lit</value>`+
`</field>`+
`<field var="start">`+
`<value>${dayjs(rsm.start).toISOString()}</value>`+
`</field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm">`+
`<max>10</max>`+
`</set>`+
`</query>`+
......@@ -850,11 +809,10 @@ describe("Message Archive Management", function () {
expect(result.messages.length).toBe(2);
expect(result.messages[0].outerHTML).toBe(msg1.nodeTree.outerHTML);
expect(result.messages[1].outerHTML).toBe(msg2.nodeTree.outerHTML);
expect(result.rsm['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
expect(result.rsm.max).toBe('10');
expect(result.rsm.count).toBe('16');
expect(result.rsm.first).toBe('23452-4534-1');
expect(result.rsm.last).toBe('09af3-cc343-b409f');
expect(result.rsm.query.max).toBe('10');
expect(result.rsm.result.count).toBe(16);
expect(result.rsm.result.first).toBe('23452-4534-1');
expect(result.rsm.result.last).toBe('09af3-cc343-b409f');
done()
}));
});
......@@ -962,7 +920,7 @@ describe("Chatboxes", function () {
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`<field var="with"><value>mercutio@montague.lit</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>`+
`</query>`+
`</iq>`
);
......@@ -1033,7 +991,7 @@ describe("Chatboxes", function () {
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`<field var="with"><value>mercutio@montague.lit</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>`+
`</query>`+
`</iq>`);
......@@ -1058,7 +1016,7 @@ describe("Chatboxes", function () {
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`<field var="with"><value>mercutio@montague.lit</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>`+
`</query>`+
`</iq>`);
......
......@@ -458,7 +458,7 @@ describe("Groupchats", function () {
`<x type="submit" xmlns="jabber:x:data">`+
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
`</x>`+
`<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
`<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>`+
`</query>`+
`</iq>`);
......
This diff is collapsed.
......@@ -6,64 +6,102 @@
* Some code taken from the Strophe RSM plugin, licensed under the MIT License
* Copyright 2006-2017 Strophe (https://github.com/strophe/strophejs)
*/
import { converse } from "./converse-core";
import { _converse, converse } from "./converse-core";
import { pick } from 'lodash-es'
const { Strophe, $build } = converse.env;
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
converse.plugins.add('converse-rsm', {
initialize () {
const { _converse } = this;
const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count'];
_converse.RSM_ATTRIBUTES = RSM_ATTRIBUTES;
/**
* @typedef { Object } RSMQueryParameters
* [XEP-0059 RSM](https://xmpp.org/extensions/xep-0059.html) Attributes that can be used to filter query results
* @property { String } [after] - The XEP-0359 stanza ID of a message after which messages should be returned. Implies forward paging.
* @property { String } [before] - The XEP-0359 stanza ID of a message before which messages should be returned. Implies backward paging.
* @property { Integer } [index=0] - The index of the results page to return.
* @property { Integer } [max] - The maximum number of items to return.
*/
const RSM_QUERY_PARAMETERS = ['after', 'before', 'index', 'max'];
class RSM {
constructor (options) {
if (typeof options.xml != 'undefined') {
this.fromXMLElement(options.xml);
} else {
for (let ii = 0; ii < RSM_ATTRIBUTES.length; ii++) {
const attrib = RSM_ATTRIBUTES[ii];
this[attrib] = options[attrib];
}
}
}
const toNumber = v => Number(v);
const toString = v => v.toString();
toXML () {
let xml = $build('set', {xmlns: Strophe.NS.RSM});
for (let ii = 0; ii < RSM_ATTRIBUTES.length; ii++) {
const attrib = RSM_ATTRIBUTES[ii];
if (typeof this[attrib] != 'undefined') {
xml = xml.c(attrib).t(this[attrib].toString()).up();
}
}
return xml.tree();
}
export const RSM_TYPES = {
'after': toString,
'before': toString,
'count': toNumber,
'first': toString,
'index': toNumber,
'last': toString,
'max': toNumber
};
next (max, before) {
return new RSM({max: max, after: this.last, before});
}
const isUndefined = (x) => typeof x === 'undefined';
previous (max, after) {
return new RSM({max: max, before: this.first, after});
}
fromXMLElement (xmlElement) {
for (var ii = 0; ii < RSM_ATTRIBUTES.length; ii++) {
const attrib = RSM_ATTRIBUTES[ii];
const elem = xmlElement.getElementsByTagName(attrib)[0];
if (typeof elem != 'undefined' && elem !== null) {
this[attrib] = Strophe.getText(elem);
if (attrib == 'first') {
this.index = elem.getAttribute('index');
}
}
// This array contains both query attributes and response attributes
export const RSM_ATTRIBUTES = Object.keys(RSM_TYPES);
/**
* Instances of this class are used to page through query results according to XEP-0059 Result Set Management
* @class RSM
*/
export class RSM {
static getQueryParameters (options={}) {
return pick(options, RSM_QUERY_PARAMETERS);
}
static parseXMLResult (set) {
const result = {};
for (var i = 0; i < RSM_ATTRIBUTES.length; i++) {
const attr = RSM_ATTRIBUTES[i];
const elem = set.getElementsByTagName(attr)[0];
if (!isUndefined(elem) && elem !== null) {
result[attr] = RSM_TYPES[attr](Strophe.getText(elem));
if (attr == 'first') {
result.index = RSM_TYPES['index'](elem.getAttribute('index'));
}
}
}
_converse.RSM = RSM;
return result;
}
/**
* Create a new RSM instance
* @param { Object } options - Configuration options
* @constructor
*/
constructor (options={}) {
this.query = RSM.getQueryParameters(options);
this.result = options.xml ? RSM.parseXMLResult(options.xml) : {};
}
/**
* Returns a `<set>` XML element that confirms to XEP-0059 Result Set Management.
* The element is constructed based on the { @link module:converse-rsm~RSMQueryParameters }
* that are set on this RSM instance.
* @returns { XMLElement }
*/
toXML () {
const xml = $build('set', {xmlns: Strophe.NS.RSM});
const reducer = (xml, a) => !isUndefined(this.query[a]) ? xml.c(a).t((this.query[a] || '').toString()).up() : xml;
return RSM_QUERY_PARAMETERS.reduce(reducer, xml).tree();
}
next (max, before) {
const options = Object.assign({}, this.query, { after: this.result.last, before, max });
return new RSM(options);
}
});
previous (max, after) {
const options = Object.assign({}, this.query, { after, before: this.result.first, max });
return new RSM(options);
}
}
_converse.RSM_ATTRIBUTES = RSM_ATTRIBUTES;
_converse.RSM = RSM;
......@@ -16,7 +16,6 @@ import "./converse-muc"; // XEP-0045 Multi-user chat
import "./converse-ping"; // XEP-0199 XMPP Ping
import "./converse-pubsub"; // XEP-0060 Pubsub
import "./converse-roster"; // RFC-6121 Contacts Roster
import "./converse-rsm"; // XEP-0059 Result Set management
import "./converse-smacks"; // XEP-0198 Stream Management
import "./converse-status"; // XEP-0199 XMPP Ping
import "./converse-vcard"; // XEP-0054 VCard-temp
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment