`_converse.api.archive.query` now returns a Promise

instead of accepting a callback functions.
This commit is contained in:
JC Brand 2019-05-03 19:47:49 +02:00
parent 8bb852b139
commit 15b2273631
6 changed files with 615 additions and 630 deletions

View File

@ -17,6 +17,7 @@
- **Breaking changes**:
- Rename `muc_disable_moderator_commands` to [muc_disable_slash_commands](https://conversejs.org/docs/html/configuration.html#muc-disable-slash-commands).
- `_converse.api.archive.query` now returns a Promise instead of accepting a callback functions.
### API changes

402
dist/converse.js vendored
View File

@ -52121,26 +52121,27 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
}
this.addSpinner();
let result;
_converse.api.archive.query(Object.assign({
'groupchat': is_groupchat,
'before': '',
// Page backwards from the most recent message
'max': _converse.archived_messages_page_size,
'with': this.model.get('jid')
}, options), messages => {
// Success
this.clearSpinner();
_.each(messages, message_handler);
}, e => {
// Error
this.clearSpinner();
try {
result = await _converse.api.archive.query(Object.assign({
'groupchat': is_groupchat,
'before': '',
// Page backwards from the most recent message
'max': _converse.archived_messages_page_size,
'with': this.model.get('jid')
}, options));
} catch (e) {
_converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR);
_converse.log(e, Strophe.LogLevel.ERROR);
});
} finally {
this.clearSpinner();
}
if (result.messages) {
result.messages.forEach(message_handler);
}
},
onScroll(ev) {
@ -66323,121 +66324,6 @@ const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils;
const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management
const MAM_ATTRIBUTES = ['with', 'start', 'end'];
function queryForArchivedMessages(_converse, options, callback, errback) {
/* Internal function, called by the "archive.query" API method.
*/
let date;
if (_.isFunction(options)) {
callback = options;
errback = callback;
options = null;
}
const queryid = _converse.connection.getUniqueId();
const attrs = {
'type': 'set'
};
if (options && options.groupchat) {
if (!options['with']) {
// eslint-disable-line dot-notation
throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const stanza = $iq(attrs).c('query', {
'xmlns': Strophe.NS.MAM,
'queryid': queryid
});
if (options) {
stanza.c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) {
// eslint-disable-line dot-notation
stanza.c('field', {
'var': 'with'
}).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation
}
_.each(['start', 'end'], function (t) {
if (options[t]) {
date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {
'var': t
}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler(message => {
if (options.groupchat && message.getAttribute('from') !== options['with']) {
// eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
_converse.api.sendIQ(stanza, _converse.message_archiving_timeout).then(iq => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(callback)) {
const set = iq.querySelector('set');
let rsm;
if (!_.isUndefined(set)) {
rsm = new Strophe.RSM({
xml: set
});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
callback(messages, rsm);
}
}).catch(e => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(errback)) {
errback.apply(this, arguments);
}
return;
});
}
_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam', {
dependencies: ['converse-muc'],
overrides: {
@ -66613,15 +66499,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* * `before`
* * `index`
* * `count`
* @param {Function} callback A function to call whenever
* we receive query-relevant stanza.
* When the callback is called, a Strophe.RSM object is
* returned on which "next" or "previous" can be called
* before passing it in again to this method, to
* get the next or previous page in the result set.
* @param {Function} errback A function to call when an
* error stanza is received, for example when it
* doesn't support message archiving.
* @throws {Error} An error is thrown if the XMPP server responds with an error.
* @returns {Promise<Object>} A promise which resolves to an object which
* will have keys `messages` and `rsm` which contains a Strophe.RSM object
* on which "next" or "previous" can be called before passing it in again
* to this method, to get the next or previous page in the result set.
*
* @example
* // Requesting all archived messages
@ -66629,41 +66511,17 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* //
* // The simplest query that can be made is to simply not pass in any parameters.
* // Such a query will return all archived messages for the current user.
* //
* // Generally, you'll however always want to pass in a callback method, to receive
* // the returned messages.
*
* this._converse.api.archive.query(
* (messages) => {
* // Do something with the messages, like showing them in your webpage.
* },
* (iq) => {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* )
* @example
* // Waiting until server support has been determined
* // ================================================
* //
* // The query method will only work if Converse has been able to determine that
* // the server supports MAM queries, otherwise the following error will be raised:
* //
* // "This server does not support XEP-0313, Message Archive Management"
* //
* // The very first time Converse loads in a browser tab, if you call the query
* // API too quickly, the above error might appear because service discovery has not
* // yet been completed.
* //
* // To work solve this problem, you can first listen for the `serviceDiscovered` event,
* // through which you can be informed once support for MAM has been determined.
*
* _converse.api.listen.on('serviceDiscovered', function (feature) {
* if (feature.get('var') === converse.env.Strophe.NS.MAM) {
* _converse.api.archive.query()
* }
* });
* let result;
* try {
* result = await _converse.api.archive.query();
* } catch (e) {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* @example
* // Requesting all archived messages for a particular contact or room
@ -66674,10 +66532,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // room under the `with` key.
*
* // For a particular user
* this._converse.api.archive.query({'with': 'john@doe.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net'});
* } catch (e) {
* // The query was not successful
* }
*
* // For a particular room
* this._converse.api.archive.query({'with': 'discuss@conference.doglovers.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'discuss@conference.doglovers.net'});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Requesting all archived messages before or after a certain date
@ -66692,7 +66560,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z'
* };
* this._converse.api.archive.query(options, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query(options);
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Limiting the amount of messages returned
@ -66702,7 +66575,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // By default, the messages are returned from oldest to newest.
*
* // Return maximum 10 archived messages
* this._converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Paging forwards through a set of archived messages
@ -66712,8 +66590,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // repeatedly make a further query to fetch the next batch of messages.
* //
* // To simplify this usecase for you, the callback method receives not only an array
* // with the returned archived messages, but also a special RSM (*Result Set
* // Management*) object which contains the query parameters you passed in, as well
* // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`.
* //
* // When you call one of these utility methods on the returned RSM object, and then
@ -66721,14 +66599,24 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results.
*
* const callback = function (messages, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the next batch of messages:
* _converse.api.archive.query(rsm.next(10), callback, errback))
*
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* while (result.rsm) {
* try {
* result = await _converse.api.archive.query(rsm.next(10));
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
* }
* _converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
*
* @example
* // Paging backwards through a set of archived messages
@ -66739,22 +66627,142 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`.
*
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the previous batch of messages:
* rsm.previous(5); // Call previous method, to update the object's parameters,
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'before': '', 'max':5});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* // Now we query again, to get the previous batch.
* try {
* result = await _converse.api.archive.query(rsm.previous(5););
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
*/
'query': function query(options, callback, errback) {
'query': async function query(options) {
if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session');
}
return queryForArchivedMessages(_converse, options, callback, errback);
const attrs = {
'type': 'set'
};
if (options && options.groupchat) {
if (!options['with']) {
// eslint-disable-line dot-notation
throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const jid = attrs.to || _converse.bare_jid;
const supported = await _converse.api.disco.supports(Strophe.NS.MAM, jid);
if (!supported.length) {
_converse.log(`Did not fetch MAM archive for ${jid} because it doesn't support ${Strophe.NS.MAM}`);
return {
'messages': []
};
}
const queryid = _converse.connection.getUniqueId();
const stanza = $iq(attrs).c('query', {
'xmlns': Strophe.NS.MAM,
'queryid': queryid
});
if (options) {
stanza.c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) {
// eslint-disable-line dot-notation
stanza.c('field', {
'var': 'with'
}).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation
}
['start', 'end'].forEach(t => {
if (options[t]) {
const date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {
'var': t
}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler(message => {
if (options.groupchat && message.getAttribute('from') !== options['with']) {
// eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
let iq;
try {
iq = await _converse.api.sendIQ(stanza, _converse.message_archiving_timeout);
} catch (e) {
_converse.connection.deleteHandler(message_handler);
throw e;
}
_converse.connection.deleteHandler(message_handler);
const set = iq.querySelector('set');
let rsm;
if (!_.isNull(set)) {
rsm = new Strophe.RSM({
'xml': set
});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
return {
messages,
rsm
};
}
}
});

View File

@ -162,18 +162,17 @@
it("can be used to query for all archived messages",
mock.initConverse(
null, ['discoInitialized'], {},
function (done, _converse) {
async function (done, _converse) {
let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ;
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
let sent_stanza, IQ_id;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
_converse.api.archive.query();
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
@ -185,10 +184,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -196,6 +192,7 @@
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
_converse.api.archive.query({'with':'juliet@capulet.lit'});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -218,22 +215,16 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'nicky', [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);
});
const callback = jasmine.createSpy('callback');
_converse.api.archive.query({'with': 'coven@chat.shakespeare.lit', 'groupchat': true}, callback);
_converse.api.archive.query({'with': 'coven@chat.shakespeare.lit', 'groupchat': true});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
@ -252,19 +243,15 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'nicky', [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);
});
const callback = jasmine.createSpy('callback');
_converse.api.archive.query({'with': 'coven@chat.shakespear.lit', 'groupchat': true, 'max':'10'}, callback);
const promise = _converse.api.archive.query({'with': 'coven@chat.shakespear.lit', 'groupchat': true, 'max':'10'});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
/* <message id='iasd207' from='coven@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda'>
@ -318,10 +305,8 @@
.c('count').t('16');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => callback.calls.count());
expect(callback).toHaveBeenCalled();
const args = callback.calls.argsFor(0);
expect(args[0].length).toBe(0);
const result = await promise;
expect(result.messages.length).toBe(0);
done();
}));
@ -330,23 +315,20 @@
null, [], {},
async function (done, _converse) {
await test_utils.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);
});
const entities = await _converse.api.disco.entities.get();
if (!entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
const start = '2010-06-07T00:00:00Z';
const end = '2010-07-07T13:23:54Z';
_converse.api.archive.query({
'start': start,
'end': end
});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -373,13 +355,12 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
try {
await _converse.api.archive.query({'start': 'not a real date'});
} catch (e) {
expect(() => {throw e}).toThrow(new TypeError('archive.query: invalid date provided for: start'));
}
expect(_.partial(_converse.api.archive.query, {'start': 'not a real date'})).toThrow(
new TypeError('archive.query: invalid date provided for: start')
);
done();
}));
@ -388,10 +369,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -403,6 +381,7 @@
}
const start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -426,10 +405,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -438,6 +414,7 @@
});
const start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start, 'max':10});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -464,10 +441,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -480,6 +454,7 @@
'after': '09af3-cc343-b409f',
'max':10
});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -506,10 +481,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -517,6 +489,7 @@
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
_converse.api.archive.query({'before': '', 'max':10});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -540,10 +513,7 @@
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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) {
@ -558,7 +528,7 @@
rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
rsm.start = '2010-06-07T00:00:00Z';
_converse.api.archive.query(rsm);
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -582,24 +552,20 @@
done();
}));
it("accepts a callback function, which it passes the messages and a Strophe.RSM object",
it("returns an object which includes the messages and a Strophe.RSM object",
mock.initConverse(
null, [], {},
async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
await test_utils.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);
});
const callback = jasmine.createSpy('callback');
_converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'}, callback);
const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
/* <message id='aeb213' to='juliet@capulet.lit/chamber'>
@ -659,17 +625,15 @@
.c('count').t('16');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => callback.calls.count());
expect(callback).toHaveBeenCalled();
const args = callback.calls.argsFor(0);
expect(args[0].length).toBe(2);
expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML);
expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML);
expect(args[1]['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
expect(args[1].max).toBe('10');
expect(args[1].count).toBe('16');
expect(args[1].first).toBe('23452-4534-1');
expect(args[1].last).toBe('09af3-cc343-b409f');
const result = await promise;
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');
done()
}));
});

View File

@ -82,10 +82,10 @@ converse.plugins.add('converse-mam-views', {
async fetchArchivedMessages (options) {
const { _converse } = this.__super__;
if (this.disable_mam) { return; }
if (this.disable_mam) {
return;
}
const is_groupchat = this.model.get('type') === CHATROOMS_TYPE;
let mam_jid, message_handler;
if (is_groupchat) {
mam_jid = this.model.get('jid');
@ -94,32 +94,31 @@ converse.plugins.add('converse-mam-views', {
mam_jid = _converse.bare_jid;
message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes)
}
const supported = await _converse.api.disco.supports(Strophe.NS.MAM, mam_jid);
if (!supported.length) {
return;
}
this.addSpinner();
_converse.api.archive.query(
Object.assign({
'groupchat': is_groupchat,
'before': '', // Page backwards from the most recent message
'max': _converse.archived_messages_page_size,
'with': this.model.get('jid'),
}, options),
messages => { // Success
this.clearSpinner();
_.each(messages, message_handler);
},
e => { // Error
this.clearSpinner();
_converse.log(
"Error or timeout while trying to fetch "+
"archived messages", Strophe.LogLevel.ERROR);
_converse.log(e, Strophe.LogLevel.ERROR);
}
);
let result;
try {
result = await _converse.api.archive.query(
Object.assign({
'groupchat': is_groupchat,
'before': '', // Page backwards from the most recent message
'max': _converse.archived_messages_page_size,
'with': this.model.get('jid'),
}, options));
} catch (e) {
_converse.log(
"Error or timeout while trying to fetch "+
"archived messages", Strophe.LogLevel.ERROR);
_converse.log(e, Strophe.LogLevel.ERROR);
} finally {
this.clearSpinner();
}
if (result.messages) {
result.messages.forEach(message_handler);
}
},
onScroll (ev) {

View File

@ -21,88 +21,6 @@ const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'cou
const MAM_ATTRIBUTES = ['with', 'start', 'end'];
function queryForArchivedMessages (_converse, options, callback, errback) {
/* Internal function, called by the "archive.query" API method.
*/
let date;
if (_.isFunction(options)) {
callback = options;
errback = callback;
options = null;
}
const queryid = _converse.connection.getUniqueId();
const attrs = {'type':'set'};
if (options && options.groupchat) {
if (!options['with']) { // eslint-disable-line dot-notation
throw new Error(
'You need to specify a "with" value containing '+
'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid});
if (options) {
stanza.c('x', {'xmlns':Strophe.NS.XFORM, 'type': 'submit'})
.c('field', {'var':'FORM_TYPE', 'type': 'hidden'})
.c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) { // eslint-disable-line dot-notation
stanza.c('field', {'var':'with'}).c('value')
.t(options['with']).up().up(); // eslint-disable-line dot-notation
}
_.each(['start', 'end'], function (t) {
if (options[t]) {
date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {'var':t}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler((message) => {
if (options.groupchat && message.getAttribute('from') !== options['with']) { // eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
_converse.api.sendIQ(stanza, _converse.message_archiving_timeout)
.then(iq => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(callback)) {
const set = iq.querySelector('set');
let rsm;
if (!_.isUndefined(set)) {
rsm = new Strophe.RSM({xml: set});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
callback(messages, rsm);
}
}).catch(e => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(errback)) {
errback.apply(this, arguments);
}
return;
});
}
converse.plugins.add('converse-mam', {
dependencies: ['converse-muc'],
@ -260,15 +178,11 @@ converse.plugins.add('converse-mam', {
* * `before`
* * `index`
* * `count`
* @param {Function} callback A function to call whenever
* we receive query-relevant stanza.
* When the callback is called, a Strophe.RSM object is
* returned on which "next" or "previous" can be called
* before passing it in again to this method, to
* get the next or previous page in the result set.
* @param {Function} errback A function to call when an
* error stanza is received, for example when it
* doesn't support message archiving.
* @throws {Error} An error is thrown if the XMPP server responds with an error.
* @returns {Promise<Object>} A promise which resolves to an object which
* will have keys `messages` and `rsm` which contains a Strophe.RSM object
* on which "next" or "previous" can be called before passing it in again
* to this method, to get the next or previous page in the result set.
*
* @example
* // Requesting all archived messages
@ -276,41 +190,17 @@ converse.plugins.add('converse-mam', {
* //
* // The simplest query that can be made is to simply not pass in any parameters.
* // Such a query will return all archived messages for the current user.
* //
* // Generally, you'll however always want to pass in a callback method, to receive
* // the returned messages.
*
* this._converse.api.archive.query(
* (messages) => {
* // Do something with the messages, like showing them in your webpage.
* },
* (iq) => {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* )
* @example
* // Waiting until server support has been determined
* // ================================================
* //
* // The query method will only work if Converse has been able to determine that
* // the server supports MAM queries, otherwise the following error will be raised:
* //
* // "This server does not support XEP-0313, Message Archive Management"
* //
* // The very first time Converse loads in a browser tab, if you call the query
* // API too quickly, the above error might appear because service discovery has not
* // yet been completed.
* //
* // To work solve this problem, you can first listen for the `serviceDiscovered` event,
* // through which you can be informed once support for MAM has been determined.
*
* _converse.api.listen.on('serviceDiscovered', function (feature) {
* if (feature.get('var') === converse.env.Strophe.NS.MAM) {
* _converse.api.archive.query()
* }
* });
* let result;
* try {
* result = await _converse.api.archive.query();
* } catch (e) {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* @example
* // Requesting all archived messages for a particular contact or room
@ -321,10 +211,20 @@ converse.plugins.add('converse-mam', {
* // room under the `with` key.
*
* // For a particular user
* this._converse.api.archive.query({'with': 'john@doe.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net'});
* } catch (e) {
* // The query was not successful
* }
*
* // For a particular room
* this._converse.api.archive.query({'with': 'discuss@conference.doglovers.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'discuss@conference.doglovers.net'});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Requesting all archived messages before or after a certain date
@ -339,7 +239,12 @@ converse.plugins.add('converse-mam', {
* 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z'
* };
* this._converse.api.archive.query(options, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query(options);
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Limiting the amount of messages returned
@ -349,7 +254,12 @@ converse.plugins.add('converse-mam', {
* // By default, the messages are returned from oldest to newest.
*
* // Return maximum 10 archived messages
* this._converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Paging forwards through a set of archived messages
@ -359,8 +269,8 @@ converse.plugins.add('converse-mam', {
* // repeatedly make a further query to fetch the next batch of messages.
* //
* // To simplify this usecase for you, the callback method receives not only an array
* // with the returned archived messages, but also a special RSM (*Result Set
* // Management*) object which contains the query parameters you passed in, as well
* // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`.
* //
* // When you call one of these utility methods on the returned RSM object, and then
@ -368,14 +278,24 @@ converse.plugins.add('converse-mam', {
* // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results.
*
* const callback = function (messages, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the next batch of messages:
* _converse.api.archive.query(rsm.next(10), callback, errback))
*
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* while (result.rsm) {
* try {
* result = await _converse.api.archive.query(rsm.next(10));
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
* }
* _converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
*
* @example
* // Paging backwards through a set of archived messages
@ -386,21 +306,107 @@ converse.plugins.add('converse-mam', {
* // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`.
*
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the previous batch of messages:
* rsm.previous(5); // Call previous method, to update the object's parameters,
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'before': '', 'max':5});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* // Now we query again, to get the previous batch.
* try {
* result = await _converse.api.archive.query(rsm.previous(5););
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
*/
'query': function (options, callback, errback) {
'query': async function (options) {
if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session');
}
return queryForArchivedMessages(_converse, options, callback, errback);
const attrs = {'type':'set'};
if (options && options.groupchat) {
if (!options['with']) { // eslint-disable-line dot-notation
throw new Error(
'You need to specify a "with" value containing '+
'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const jid = attrs.to || _converse.bare_jid;
const supported = await _converse.api.disco.supports(Strophe.NS.MAM, jid);
if (!supported.length) {
_converse.log(`Did not fetch MAM archive for ${jid} because it doesn't support ${Strophe.NS.MAM}`);
return {
'messages': []
};
}
const queryid = _converse.connection.getUniqueId();
const stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid});
if (options) {
stanza.c('x', {'xmlns':Strophe.NS.XFORM, 'type': 'submit'})
.c('field', {'var':'FORM_TYPE', 'type': 'hidden'})
.c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) { // eslint-disable-line dot-notation
stanza.c('field', {'var':'with'}).c('value')
.t(options['with']).up().up(); // eslint-disable-line dot-notation
}
['start', 'end'].forEach(t => {
if (options[t]) {
const date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {'var':t}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler(message => {
if (options.groupchat && message.getAttribute('from') !== options['with']) { // eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
let iq;
try {
iq = await _converse.api.sendIQ(stanza, _converse.message_archiving_timeout)
} catch (e) {
_converse.connection.deleteHandler(message_handler);
throw(e);
}
_converse.connection.deleteHandler(message_handler);
const set = iq.querySelector('set');
let rsm;
if (!_.isNull(set)) {
rsm = new Strophe.RSM({'xml': set});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
return {
messages,
rsm
}
}
}
});

View File

@ -44571,121 +44571,6 @@ const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils;
const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management
const MAM_ATTRIBUTES = ['with', 'start', 'end'];
function queryForArchivedMessages(_converse, options, callback, errback) {
/* Internal function, called by the "archive.query" API method.
*/
let date;
if (_.isFunction(options)) {
callback = options;
errback = callback;
options = null;
}
const queryid = _converse.connection.getUniqueId();
const attrs = {
'type': 'set'
};
if (options && options.groupchat) {
if (!options['with']) {
// eslint-disable-line dot-notation
throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const stanza = $iq(attrs).c('query', {
'xmlns': Strophe.NS.MAM,
'queryid': queryid
});
if (options) {
stanza.c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) {
// eslint-disable-line dot-notation
stanza.c('field', {
'var': 'with'
}).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation
}
_.each(['start', 'end'], function (t) {
if (options[t]) {
date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {
'var': t
}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler(message => {
if (options.groupchat && message.getAttribute('from') !== options['with']) {
// eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
_converse.api.sendIQ(stanza, _converse.message_archiving_timeout).then(iq => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(callback)) {
const set = iq.querySelector('set');
let rsm;
if (!_.isUndefined(set)) {
rsm = new Strophe.RSM({
xml: set
});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
callback(messages, rsm);
}
}).catch(e => {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(errback)) {
errback.apply(this, arguments);
}
return;
});
}
_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam', {
dependencies: ['converse-muc'],
overrides: {
@ -44861,15 +44746,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* * `before`
* * `index`
* * `count`
* @param {Function} callback A function to call whenever
* we receive query-relevant stanza.
* When the callback is called, a Strophe.RSM object is
* returned on which "next" or "previous" can be called
* before passing it in again to this method, to
* get the next or previous page in the result set.
* @param {Function} errback A function to call when an
* error stanza is received, for example when it
* doesn't support message archiving.
* @throws {Error} An error is thrown if the XMPP server responds with an error.
* @returns {Promise<Object>} A promise which resolves to an object which
* will have keys `messages` and `rsm` which contains a Strophe.RSM object
* on which "next" or "previous" can be called before passing it in again
* to this method, to get the next or previous page in the result set.
*
* @example
* // Requesting all archived messages
@ -44877,41 +44758,17 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* //
* // The simplest query that can be made is to simply not pass in any parameters.
* // Such a query will return all archived messages for the current user.
* //
* // Generally, you'll however always want to pass in a callback method, to receive
* // the returned messages.
*
* this._converse.api.archive.query(
* (messages) => {
* // Do something with the messages, like showing them in your webpage.
* },
* (iq) => {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* )
* @example
* // Waiting until server support has been determined
* // ================================================
* //
* // The query method will only work if Converse has been able to determine that
* // the server supports MAM queries, otherwise the following error will be raised:
* //
* // "This server does not support XEP-0313, Message Archive Management"
* //
* // The very first time Converse loads in a browser tab, if you call the query
* // API too quickly, the above error might appear because service discovery has not
* // yet been completed.
* //
* // To work solve this problem, you can first listen for the `serviceDiscovered` event,
* // through which you can be informed once support for MAM has been determined.
*
* _converse.api.listen.on('serviceDiscovered', function (feature) {
* if (feature.get('var') === converse.env.Strophe.NS.MAM) {
* _converse.api.archive.query()
* }
* });
* let result;
* try {
* result = await _converse.api.archive.query();
* } catch (e) {
* // The query was not successful, perhaps inform the user?
* // The IQ stanza returned by the XMPP server is passed in, so that you
* // may inspect it and determine what the problem was.
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* @example
* // Requesting all archived messages for a particular contact or room
@ -44922,10 +44779,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // room under the `with` key.
*
* // For a particular user
* this._converse.api.archive.query({'with': 'john@doe.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net'});
* } catch (e) {
* // The query was not successful
* }
*
* // For a particular room
* this._converse.api.archive.query({'with': 'discuss@conference.doglovers.net'}, callback, errback);)
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'discuss@conference.doglovers.net'});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Requesting all archived messages before or after a certain date
@ -44940,7 +44807,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z'
* };
* this._converse.api.archive.query(options, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query(options);
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Limiting the amount of messages returned
@ -44950,7 +44822,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // By default, the messages are returned from oldest to newest.
*
* // Return maximum 10 archived messages
* this._converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
*
* @example
* // Paging forwards through a set of archived messages
@ -44960,8 +44837,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // repeatedly make a further query to fetch the next batch of messages.
* //
* // To simplify this usecase for you, the callback method receives not only an array
* // with the returned archived messages, but also a special RSM (*Result Set
* // Management*) object which contains the query parameters you passed in, as well
* // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`.
* //
* // When you call one of these utility methods on the returned RSM object, and then
@ -44969,14 +44846,24 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results.
*
* const callback = function (messages, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the next batch of messages:
* _converse.api.archive.query(rsm.next(10), callback, errback))
*
* let result;
* try {
* result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* while (result.rsm) {
* try {
* result = await _converse.api.archive.query(rsm.next(10));
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
* }
* _converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback);
*
* @example
* // Paging backwards through a set of archived messages
@ -44987,22 +44874,142 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`.
*
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) {
* // Do something with the messages, like showing them in your webpage.
* // ...
* // You can now use the returned "rsm" object, to fetch the previous batch of messages:
* rsm.previous(5); // Call previous method, to update the object's parameters,
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* let result;
* try {
* result = await _converse.api.archive.query({'before': '', 'max':5});
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
* // Now we query again, to get the previous batch.
* try {
* result = await _converse.api.archive.query(rsm.previous(5););
* } catch (e) {
* // The query was not successful
* }
* // Do something with the messages, like showing them in your webpage.
* result.messages.forEach(m => this.showMessage(m));
*
*/
'query': function query(options, callback, errback) {
'query': async function query(options) {
if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session');
}
return queryForArchivedMessages(_converse, options, callback, errback);
const attrs = {
'type': 'set'
};
if (options && options.groupchat) {
if (!options['with']) {
// eslint-disable-line dot-notation
throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.');
}
attrs.to = options['with']; // eslint-disable-line dot-notation
}
const jid = attrs.to || _converse.bare_jid;
const supported = await _converse.api.disco.supports(Strophe.NS.MAM, jid);
if (!supported.length) {
_converse.log(`Did not fetch MAM archive for ${jid} because it doesn't support ${Strophe.NS.MAM}`);
return {
'messages': []
};
}
const queryid = _converse.connection.getUniqueId();
const stanza = $iq(attrs).c('query', {
'xmlns': Strophe.NS.MAM,
'queryid': queryid
});
if (options) {
stanza.c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) {
// eslint-disable-line dot-notation
stanza.c('field', {
'var': 'with'
}).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation
}
['start', 'end'].forEach(t => {
if (options[t]) {
const date = moment(options[t]);
if (date.isValid()) {
stanza.c('field', {
'var': t
}).c('value').t(date.format()).up().up();
} else {
throw new TypeError(`archive.query: invalid date provided for: ${t}`);
}
}
});
stanza.up();
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, Object.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
const messages = [];
const message_handler = _converse.connection.addHandler(message => {
if (options.groupchat && message.getAttribute('from') !== options['with']) {
// eslint-disable-line dot-notation
return true;
}
const result = message.querySelector('result');
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
return true;
}, Strophe.NS.MAM);
let iq;
try {
iq = await _converse.api.sendIQ(stanza, _converse.message_archiving_timeout);
} catch (e) {
_converse.connection.deleteHandler(message_handler);
throw e;
}
_converse.connection.deleteHandler(message_handler);
const set = iq.querySelector('set');
let rsm;
if (!_.isNull(set)) {
rsm = new Strophe.RSM({
'xml': set
});
Object.assign(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
return {
messages,
rsm
};
}
}
});