_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**: - **Breaking changes**:
- Rename `muc_disable_moderator_commands` to [muc_disable_slash_commands](https://conversejs.org/docs/html/configuration.html#muc-disable-slash-commands). - 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 ### 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(); this.addSpinner();
let result;
_converse.api.archive.query(Object.assign({ try {
'groupchat': is_groupchat, result = await _converse.api.archive.query(Object.assign({
'before': '', 'groupchat': is_groupchat,
// Page backwards from the most recent message 'before': '',
'max': _converse.archived_messages_page_size, // Page backwards from the most recent message
'with': this.model.get('jid') 'max': _converse.archived_messages_page_size,
}, options), messages => { 'with': this.model.get('jid')
// Success }, options));
this.clearSpinner(); } catch (e) {
_.each(messages, message_handler);
}, e => {
// Error
this.clearSpinner();
_converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR); _converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR);
_converse.log(e, Strophe.LogLevel.ERROR); _converse.log(e, Strophe.LogLevel.ERROR);
}); } finally {
this.clearSpinner();
}
if (result.messages) {
result.messages.forEach(message_handler);
}
}, },
onScroll(ev) { 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 RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management
const MAM_ATTRIBUTES = ['with', 'start', 'end']; 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', { _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam', {
dependencies: ['converse-muc'], dependencies: ['converse-muc'],
overrides: { overrides: {
@ -66613,15 +66499,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* * `before` * * `before`
* * `index` * * `index`
* * `count` * * `count`
* @param {Function} callback A function to call whenever * @throws {Error} An error is thrown if the XMPP server responds with an error.
* we receive query-relevant stanza. * @returns {Promise<Object>} A promise which resolves to an object which
* When the callback is called, a Strophe.RSM object is * will have keys `messages` and `rsm` which contains a Strophe.RSM object
* returned on which "next" or "previous" can be called * on which "next" or "previous" can be called before passing it in again
* before passing it in again to this method, to * to this method, to get the next or previous page in the result set.
* 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.
* *
* @example * @example
* // Requesting all archived messages * // 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. * // 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. * // 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( * let result;
* (messages) => { * try {
* // Do something with the messages, like showing them in your webpage. * result = await _converse.api.archive.query();
* }, * } catch (e) {
* (iq) => { * // The query was not successful, perhaps inform the user?
* // The query was not successful, perhaps inform the user? * // The IQ stanza returned by the XMPP server is passed in, so that you
* // The IQ stanza returned by the XMPP server is passed in, so that you * // may inspect it and determine what the problem was.
* // 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
* // 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()
* }
* });
* *
* @example * @example
* // Requesting all archived messages for a particular contact or room * // 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. * // room under the `with` key.
* *
* // For a particular user * // 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 * // 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 * @example
* // Requesting all archived messages before or after a certain date * // 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', * 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z' * '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 * @example
* // Limiting the amount of messages returned * // 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. * // By default, the messages are returned from oldest to newest.
* *
* // Return maximum 10 archived messages * // 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 * @example
* // Paging forwards through a set of archived messages * // 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. * // 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 * // 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 * // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // Management*) object which contains the query parameters you passed in, as well * // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`. * // as two utility methods `next`, and `previous`.
* // * //
* // When you call one of these utility methods on the returned RSM object, and then * // 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 * // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results. * // to limit your results.
* *
* const callback = function (messages, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* // You can now use the returned "rsm" object, to fetch the next batch of messages: * } catch (e) {
* _converse.api.archive.query(rsm.next(10), callback, errback)) * // 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 * @example
* // Paging backwards through a set of archived messages * // 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 * // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`. * // message, pass in the `before` parameter with an empty string value `''`.
* *
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'before': '', 'max':5});
* // You can now use the returned "rsm" object, to fetch the previous batch of messages: * } catch (e) {
* rsm.previous(5); // Call previous method, to update the object's parameters, * // The query was not successful
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* } * }
* // 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()) { if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session'); 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", it("can be used to query for all archived messages",
mock.initConverse( mock.initConverse(
null, ['discoInitialized'], {}, null, ['discoInitialized'], {},
function (done, _converse) { async function (done, _converse) {
let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; 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) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); 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(); _converse.api.archive.query();
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`); `<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
@ -185,10 +184,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@ -196,6 +192,7 @@
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
_converse.api.archive.query({'with':'juliet@capulet.lit'}); _converse.api.archive.query({'with':'juliet@capulet.lit'});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -218,22 +215,16 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'nicky', [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
const callback = jasmine.createSpy('callback'); _converse.api.archive.query({'with': 'coven@chat.shakespeare.lit', 'groupchat': true});
await test_utils.waitUntil(() => sent_stanza);
_converse.api.archive.query({'with': 'coven@chat.shakespeare.lit', 'groupchat': true}, callback);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+ `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
@ -252,19 +243,15 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'nicky', [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
const callback = jasmine.createSpy('callback'); const promise = _converse.api.archive.query({'with': 'coven@chat.shakespear.lit', 'groupchat': true, 'max':'10'});
await test_utils.waitUntil(() => sent_stanza);
_converse.api.archive.query({'with': 'coven@chat.shakespear.lit', 'groupchat': true, 'max':'10'}, callback);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
/* <message id='iasd207' from='coven@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda'> /* <message id='iasd207' from='coven@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda'>
@ -318,10 +305,8 @@
.c('count').t('16'); .c('count').t('16');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => callback.calls.count()); const result = await promise;
expect(callback).toHaveBeenCalled(); expect(result.messages.length).toBe(0);
const args = callback.calls.argsFor(0);
expect(args[0].length).toBe(0);
done(); done();
})); }));
@ -330,23 +315,20 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); 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 start = '2010-06-07T00:00:00Z';
const end = '2010-07-07T13:23:54Z'; const end = '2010-07-07T13:23:54Z';
_converse.api.archive.query({ _converse.api.archive.query({
'start': start, 'start': start,
'end': end 'end': end
}); });
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -373,13 +355,12 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) { try {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM}); 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(); done();
})); }));
@ -388,10 +369,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@ -403,6 +381,7 @@
} }
const start = '2010-06-07T00:00:00Z'; const start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start}); _converse.api.archive.query({'start': start});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -426,10 +405,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@ -438,6 +414,7 @@
}); });
const start = '2010-06-07T00:00:00Z'; const start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start, 'max':10}); _converse.api.archive.query({'start': start, 'max':10});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -464,10 +441,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@ -480,6 +454,7 @@
'after': '09af3-cc343-b409f', 'after': '09af3-cc343-b409f',
'max':10 'max':10
}); });
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -506,10 +481,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@ -517,6 +489,7 @@
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
_converse.api.archive.query({'before': '', 'max':10}); _converse.api.archive.query({'before': '', 'max':10});
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -540,10 +513,7 @@
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { 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['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
rsm.start = '2010-06-07T00:00:00Z'; rsm.start = '2010-06-07T00:00:00Z';
_converse.api.archive.query(rsm); _converse.api.archive.query(rsm);
await test_utils.waitUntil(() => sent_stanza);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
@ -582,24 +552,20 @@
done(); 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( mock.initConverse(
null, [], {}, null, [], {},
async function (done, _converse) { async function (done, _converse) {
const entity = await _converse.api.disco.entities.get(_converse.domain); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
if (!entity.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
}
let sent_stanza, IQ_id; let sent_stanza, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
const callback = jasmine.createSpy('callback'); const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
await test_utils.waitUntil(() => sent_stanza);
_converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'}, callback);
const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid'); const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
/* <message id='aeb213' to='juliet@capulet.lit/chamber'> /* <message id='aeb213' to='juliet@capulet.lit/chamber'>
@ -659,17 +625,15 @@
.c('count').t('16'); .c('count').t('16');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => callback.calls.count()); const result = await promise;
expect(callback).toHaveBeenCalled(); expect(result.messages.length).toBe(2);
const args = callback.calls.argsFor(0); expect(result.messages[0].outerHTML).toBe(msg1.nodeTree.outerHTML);
expect(args[0].length).toBe(2); expect(result.messages[1].outerHTML).toBe(msg2.nodeTree.outerHTML);
expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML); expect(result.rsm['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML); expect(result.rsm.max).toBe('10');
expect(args[1]['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation expect(result.rsm.count).toBe('16');
expect(args[1].max).toBe('10'); expect(result.rsm.first).toBe('23452-4534-1');
expect(args[1].count).toBe('16'); expect(result.rsm.last).toBe('09af3-cc343-b409f');
expect(args[1].first).toBe('23452-4534-1');
expect(args[1].last).toBe('09af3-cc343-b409f');
done() done()
})); }));
}); });

View File

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

View File

@ -21,88 +21,6 @@ const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'cou
const MAM_ATTRIBUTES = ['with', 'start', 'end']; 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', { converse.plugins.add('converse-mam', {
dependencies: ['converse-muc'], dependencies: ['converse-muc'],
@ -260,15 +178,11 @@ converse.plugins.add('converse-mam', {
* * `before` * * `before`
* * `index` * * `index`
* * `count` * * `count`
* @param {Function} callback A function to call whenever * @throws {Error} An error is thrown if the XMPP server responds with an error.
* we receive query-relevant stanza. * @returns {Promise<Object>} A promise which resolves to an object which
* When the callback is called, a Strophe.RSM object is * will have keys `messages` and `rsm` which contains a Strophe.RSM object
* returned on which "next" or "previous" can be called * on which "next" or "previous" can be called before passing it in again
* before passing it in again to this method, to * to this method, to get the next or previous page in the result set.
* 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.
* *
* @example * @example
* // Requesting all archived messages * // 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. * // 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. * // 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( * let result;
* (messages) => { * try {
* // Do something with the messages, like showing them in your webpage. * result = await _converse.api.archive.query();
* }, * } catch (e) {
* (iq) => { * // The query was not successful, perhaps inform the user?
* // The query was not successful, perhaps inform the user? * // The IQ stanza returned by the XMPP server is passed in, so that you
* // The IQ stanza returned by the XMPP server is passed in, so that you * // may inspect it and determine what the problem was.
* // 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
* // 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()
* }
* });
* *
* @example * @example
* // Requesting all archived messages for a particular contact or room * // Requesting all archived messages for a particular contact or room
@ -321,10 +211,20 @@ converse.plugins.add('converse-mam', {
* // room under the `with` key. * // room under the `with` key.
* *
* // For a particular user * // 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 * // 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 * @example
* // Requesting all archived messages before or after a certain date * // 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', * 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z' * '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 * @example
* // Limiting the amount of messages returned * // 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. * // By default, the messages are returned from oldest to newest.
* *
* // Return maximum 10 archived messages * // 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 * @example
* // Paging forwards through a set of archived messages * // 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. * // 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 * // 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 * // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // Management*) object which contains the query parameters you passed in, as well * // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`. * // as two utility methods `next`, and `previous`.
* // * //
* // When you call one of these utility methods on the returned RSM object, and then * // 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 * // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results. * // to limit your results.
* *
* const callback = function (messages, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* // You can now use the returned "rsm" object, to fetch the next batch of messages: * } catch (e) {
* _converse.api.archive.query(rsm.next(10), callback, errback)) * // 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 * @example
* // Paging backwards through a set of archived messages * // 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 * // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`. * // message, pass in the `before` parameter with an empty string value `''`.
* *
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'before': '', 'max':5});
* // You can now use the returned "rsm" object, to fetch the previous batch of messages: * } catch (e) {
* rsm.previous(5); // Call previous method, to update the object's parameters, * // The query was not successful
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* } * }
* // 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()) { if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session'); 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 RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management
const MAM_ATTRIBUTES = ['with', 'start', 'end']; 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', { _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam', {
dependencies: ['converse-muc'], dependencies: ['converse-muc'],
overrides: { overrides: {
@ -44861,15 +44746,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
* * `before` * * `before`
* * `index` * * `index`
* * `count` * * `count`
* @param {Function} callback A function to call whenever * @throws {Error} An error is thrown if the XMPP server responds with an error.
* we receive query-relevant stanza. * @returns {Promise<Object>} A promise which resolves to an object which
* When the callback is called, a Strophe.RSM object is * will have keys `messages` and `rsm` which contains a Strophe.RSM object
* returned on which "next" or "previous" can be called * on which "next" or "previous" can be called before passing it in again
* before passing it in again to this method, to * to this method, to get the next or previous page in the result set.
* 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.
* *
* @example * @example
* // Requesting all archived messages * // 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. * // 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. * // 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( * let result;
* (messages) => { * try {
* // Do something with the messages, like showing them in your webpage. * result = await _converse.api.archive.query();
* }, * } catch (e) {
* (iq) => { * // The query was not successful, perhaps inform the user?
* // The query was not successful, perhaps inform the user? * // The IQ stanza returned by the XMPP server is passed in, so that you
* // The IQ stanza returned by the XMPP server is passed in, so that you * // may inspect it and determine what the problem was.
* // 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
* // 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()
* }
* });
* *
* @example * @example
* // Requesting all archived messages for a particular contact or room * // 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. * // room under the `with` key.
* *
* // For a particular user * // 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 * // 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 * @example
* // Requesting all archived messages before or after a certain date * // 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', * 'start': '2010-06-07T00:00:00Z',
* 'end': '2010-07-07T13:23:54Z' * '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 * @example
* // Limiting the amount of messages returned * // 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. * // By default, the messages are returned from oldest to newest.
* *
* // Return maximum 10 archived messages * // 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 * @example
* // Paging forwards through a set of archived messages * // 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. * // 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 * // 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 * // with the returned archived messages, but also a special Strophe.RSM (*Result Set Management*)
* // Management*) object which contains the query parameters you passed in, as well * // object which contains the query parameters you passed in, as well
* // as two utility methods `next`, and `previous`. * // as two utility methods `next`, and `previous`.
* // * //
* // When you call one of these utility methods on the returned RSM object, and then * // 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 * // archived messages. Please note, when calling these methods, pass in an integer
* // to limit your results. * // to limit your results.
* *
* const callback = function (messages, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'with': 'john@doe.net', 'max':10});
* // You can now use the returned "rsm" object, to fetch the next batch of messages: * } catch (e) {
* _converse.api.archive.query(rsm.next(10), callback, errback)) * // 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 * @example
* // Paging backwards through a set of archived messages * // 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 * // `before` parameter. If you simply want to page backwards from the most recent
* // message, pass in the `before` parameter with an empty string value `''`. * // message, pass in the `before` parameter with an empty string value `''`.
* *
* _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) { * let result;
* // Do something with the messages, like showing them in your webpage. * try {
* // ... * result = await _converse.api.archive.query({'before': '', 'max':5});
* // You can now use the returned "rsm" object, to fetch the previous batch of messages: * } catch (e) {
* rsm.previous(5); // Call previous method, to update the object's parameters, * // The query was not successful
* // passing in a limit value of 5.
* // Now we query again, to get the previous batch.
* _converse.api.archive.query(rsm, callback, errback);
* } * }
* // 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()) { if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session'); 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
};
} }
} }
}); });