mam.js 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "test-utils"], factory);
  3. } (this, function (jasmine, mock, test_utils) {
  4. "use strict";
  5. const _ = converse.env._;
  6. const Backbone = converse.env.Backbone;
  7. const Strophe = converse.env.Strophe;
  8. const $iq = converse.env.$iq;
  9. const $msg = converse.env.$msg;
  10. const dayjs = converse.env.dayjs;
  11. const u = converse.env.utils;
  12. const sizzle = converse.env.sizzle;
  13. // See: https://xmpp.org/rfcs/rfc3921.html
  14. // Implements the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
  15. describe("Message Archive Management", function () {
  16. describe("The XEP-0313 Archive", function () {
  17. it("is queried when the user enters a new MUC",
  18. mock.initConverse(
  19. null, ['discoInitialized'], {'archived_messages_page_size': 2},
  20. async function (done, _converse) {
  21. spyOn(_converse.ChatBox.prototype, 'fetchArchivedMessages').and.callThrough();
  22. const sent_IQs = _converse.connection.IQ_stanzas;
  23. const muc_jid = 'orchard@chat.shakespeare.lit';
  24. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  25. let view = _converse.chatboxviews.get(muc_jid);
  26. let iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  27. expect(Strophe.serialize(iq_get)).toBe(
  28. `<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
  29. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">`+
  30. `<x type="submit" xmlns="jabber:x:data">`+
  31. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  32. `</x>`+
  33. `<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><before></before></set>`+
  34. `</query>`+
  35. `</iq>`);
  36. let first_msg_id = _converse.connection.getUniqueId();
  37. let last_msg_id = _converse.connection.getUniqueId();
  38. let message = u.toStanza(
  39. `<message xmlns="jabber:client"
  40. to="romeo@montague.lit/orchard"
  41. from="${muc_jid}">
  42. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
  43. <forwarded xmlns="urn:xmpp:forward:0">
  44. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:15:23Z"/>
  45. <message from="${muc_jid}/some1" type="groupchat">
  46. <body>2nd Message</body>
  47. </message>
  48. </forwarded>
  49. </result>
  50. </message>`);
  51. _converse.connection._dataRecv(test_utils.createRequest(message));
  52. message = u.toStanza(
  53. `<message xmlns="jabber:client"
  54. to="romeo@montague.lit/orchard"
  55. from="${muc_jid}">
  56. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
  57. <forwarded xmlns="urn:xmpp:forward:0">
  58. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:16:23Z"/>
  59. <message from="${muc_jid}/some1" type="groupchat">
  60. <body>3rd Message</body>
  61. </message>
  62. </forwarded>
  63. </result>
  64. </message>`);
  65. _converse.connection._dataRecv(test_utils.createRequest(message));
  66. // Clear so that we don't match the older query
  67. while (sent_IQs.length) { sent_IQs.pop(); }
  68. // XXX: Even though the count is 3, when fetching messages for
  69. // the first time, we don't paginate, so that message
  70. // is not fetched. The user needs to manually load older
  71. // messages for it to be fetched.
  72. // TODO: we need to add a clickable link to load older messages
  73. let result = u.toStanza(
  74. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  75. <fin xmlns='urn:xmpp:mam:2'>
  76. <set xmlns='http://jabber.org/protocol/rsm'>
  77. <first index='0'>${first_msg_id}</first>
  78. <last>${last_msg_id}</last>
  79. <count>3</count>
  80. </set>
  81. </fin>
  82. </iq>`);
  83. _converse.connection._dataRecv(test_utils.createRequest(result));
  84. await u.waitUntil(() => view.model.messages.length === 2);
  85. view.close();
  86. // Clear so that we don't match the older query
  87. while (sent_IQs.length) { sent_IQs.pop(); }
  88. await u.waitUntil(() => _converse.chatboxes.length === 1);
  89. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  90. view = _converse.chatboxviews.get(muc_jid);
  91. await u.waitUntil(() => view.model.messages.length);
  92. iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  93. expect(Strophe.serialize(iq_get)).toBe(
  94. `<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
  95. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">`+
  96. `<x type="submit" xmlns="jabber:x:data">`+
  97. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  98. `</x>`+
  99. `<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><after>${message.querySelector('result').getAttribute('id')}</after></set>`+
  100. `</query>`+
  101. `</iq>`);
  102. first_msg_id = _converse.connection.getUniqueId();
  103. last_msg_id = _converse.connection.getUniqueId();
  104. message = u.toStanza(
  105. `<message xmlns="jabber:client"
  106. to="romeo@montague.lit/orchard"
  107. from="${muc_jid}">
  108. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
  109. <forwarded xmlns="urn:xmpp:forward:0">
  110. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  111. <message from="${muc_jid}/some1" type="groupchat">
  112. <body>4th Message</body>
  113. </message>
  114. </forwarded>
  115. </result>
  116. </message>`);
  117. _converse.connection._dataRecv(test_utils.createRequest(message));
  118. message = u.toStanza(
  119. `<message xmlns="jabber:client"
  120. to="romeo@montague.lit/orchard"
  121. from="${muc_jid}">
  122. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
  123. <forwarded xmlns="urn:xmpp:forward:0">
  124. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:18:23Z"/>
  125. <message from="${muc_jid}/some1" type="groupchat">
  126. <body>5th Message</body>
  127. </message>
  128. </forwarded>
  129. </result>
  130. </message>`);
  131. _converse.connection._dataRecv(test_utils.createRequest(message));
  132. // Clear so that we don't match the older query
  133. while (sent_IQs.length) { sent_IQs.pop(); }
  134. result = u.toStanza(
  135. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  136. <fin xmlns='urn:xmpp:mam:2'>
  137. <set xmlns='http://jabber.org/protocol/rsm'>
  138. <first index='0'>${first_msg_id}</first>
  139. <last>${last_msg_id}</last>
  140. <count>5</count>
  141. </set>
  142. </fin>
  143. </iq>`);
  144. _converse.connection._dataRecv(test_utils.createRequest(result));
  145. await u.waitUntil(() => view.model.messages.length === 4);
  146. iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  147. expect(Strophe.serialize(iq_get)).toBe(
  148. `<iq id="${iq_get.getAttribute('id')}" to="orchard@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
  149. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="urn:xmpp:mam:2">`+
  150. `<x type="submit" xmlns="jabber:x:data">`+
  151. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  152. `</x>`+
  153. `<set xmlns="http://jabber.org/protocol/rsm">`+
  154. `<max>2</max><after>${last_msg_id}</after>`+
  155. `</set>`+
  156. `</query>`+
  157. `</iq>`);
  158. const msg_id = _converse.connection.getUniqueId();
  159. message = u.toStanza(
  160. `<message xmlns="jabber:client"
  161. to="romeo@montague.lit/orchard"
  162. from="${muc_jid}">
  163. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${msg_id}">
  164. <forwarded xmlns="urn:xmpp:forward:0">
  165. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:19:23Z"/>
  166. <message from="${muc_jid}/some1" type="groupchat">
  167. <body>6th Message</body>
  168. </message>
  169. </forwarded>
  170. </result>
  171. </message>`);
  172. _converse.connection._dataRecv(test_utils.createRequest(message));
  173. result = u.toStanza(
  174. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  175. <fin xmlns="urn:xmpp:mam:2" complete="true">
  176. <set xmlns="http://jabber.org/protocol/rsm">
  177. <first index="0">${msg_id}</first>
  178. <last>${msg_id}</last>
  179. <count>6</count>
  180. </set>
  181. </fin>
  182. </iq>`);
  183. _converse.connection._dataRecv(test_utils.createRequest(result));
  184. await u.waitUntil(() => view.model.messages.length === 5);
  185. expect(view.model.fetchArchivedMessages.calls.count()).toBe(3);
  186. const msg_els = view.content.querySelectorAll('.chat-msg__text');
  187. expect(Array.from(msg_els).map(e => e.textContent).join(' ')).toBe("2nd Message 3rd Message 4th Message 5th Message 6th Message");
  188. done();
  189. }));
  190. });
  191. describe("An archived message", function () {
  192. describe("when recieved", function () {
  193. it("is discarded if it doesn't come from the right sender",
  194. mock.initConverse(
  195. null, ['discoInitialized'], {},
  196. async function (done, _converse) {
  197. await test_utils.waitForRoster(_converse, 'current', 1);
  198. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  199. await test_utils.openChatBoxFor(_converse, contact_jid);
  200. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  201. const sent_IQs = _converse.connection.IQ_stanzas;
  202. const stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  203. const queryid = stanza.querySelector('query').getAttribute('queryid');
  204. let msg = $msg({'id': _converse.connection.getUniqueId(), 'from': 'impersonator@capulet.lit', 'to': _converse.bare_jid})
  205. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id': _converse.connection.getUniqueId()})
  206. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  207. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  208. .c('message', {
  209. 'xmlns':'jabber:client',
  210. 'to': _converse.bare_jid,
  211. 'id': _converse.connection.getUniqueId(),
  212. 'from': contact_jid,
  213. 'type':'chat'
  214. }).c('body').t("Meet me at the dance");
  215. spyOn(_converse, 'log');
  216. _converse.connection._dataRecv(test_utils.createRequest(msg));
  217. expect(_converse.log).toHaveBeenCalledWith(`Ignoring alleged MAM message from ${msg.nodeTree.getAttribute('from')}`, Strophe.LogLevel.WARN);
  218. msg = $msg({'id': _converse.connection.getUniqueId(), 'to': _converse.bare_jid})
  219. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id': _converse.connection.getUniqueId()})
  220. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  221. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  222. .c('message', {
  223. 'xmlns':'jabber:client',
  224. 'to': _converse.bare_jid,
  225. 'id': _converse.connection.getUniqueId(),
  226. 'from': contact_jid,
  227. 'type':'chat'
  228. }).c('body').t("Thrice the brinded cat hath mew'd.");
  229. _converse.connection._dataRecv(test_utils.createRequest(msg));
  230. const iq_result = $iq({'type': 'result', 'id': stanza.getAttribute('id')})
  231. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  232. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  233. .c('first', {'index': '0'}).t('23452-4534-1').up()
  234. .c('last').t('09af3-cc343-b409f').up()
  235. .c('count').t('16');
  236. _converse.connection._dataRecv(test_utils.createRequest(iq_result));
  237. const view = _converse.chatboxviews.get(contact_jid);
  238. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  239. expect(view.model.messages.length).toBe(1);
  240. expect(view.model.messages.at(0).get('message')).toBe("Thrice the brinded cat hath mew'd.");
  241. done();
  242. }));
  243. it("updates the is_archived value of an already cached version",
  244. mock.initConverse(
  245. null, ['discoInitialized'], {},
  246. async function (done, _converse) {
  247. await test_utils.openAndEnterChatRoom(_converse, 'trek-radio@conference.lightwitch.org', 'romeo');
  248. const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
  249. let stanza = u.toStanza(
  250. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" type="groupchat" from="trek-radio@conference.lightwitch.org/some1">
  251. <body>Hello</body>
  252. <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
  253. </message>`);
  254. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  255. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  256. expect(view.model.messages.length).toBe(1);
  257. expect(view.model.messages.at(0).get('is_archived')).toBe(false);
  258. expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
  259. stanza = u.toStanza(
  260. `<message xmlns="jabber:client"
  261. to="romeo@montague.lit/orchard"
  262. from="trek-radio@conference.lightwitch.org">
  263. <result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
  264. <forwarded xmlns="urn:xmpp:forward:0">
  265. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  266. <message from="trek-radio@conference.lightwitch.org/some1" type="groupchat">
  267. <body>Hello</body>
  268. </message>
  269. </forwarded>
  270. </result>
  271. </message>`);
  272. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  273. spyOn(view.model, 'updateMessage').and.callThrough();
  274. view.model.onMessage(stanza);
  275. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  276. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  277. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  278. expect(result instanceof _converse.Message).toBe(true);
  279. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  280. await u.waitUntil(() => view.model.updateMessage.calls.count());
  281. expect(view.model.messages.length).toBe(1);
  282. expect(view.model.messages.at(0).get('is_archived')).toBe(true);
  283. expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
  284. done();
  285. }));
  286. it("isn't shown as duplicate by comparing its stanza id or archive id",
  287. mock.initConverse(
  288. null, ['discoInitialized'], {},
  289. async function (done, _converse) {
  290. await test_utils.openAndEnterChatRoom(_converse, 'trek-radio@conference.lightwitch.org', 'jcbrand');
  291. const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
  292. let stanza = u.toStanza(
  293. `<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452" type="groupchat" from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)">
  294. <body>negan</body>
  295. <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
  296. </message>`);
  297. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  298. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  299. // Not sure whether such a race-condition might pose a problem
  300. // in "real-world" situations.
  301. stanza = u.toStanza(
  302. `<message xmlns="jabber:client"
  303. to="jcbrand@lightwitch.org/converse.js-73057452"
  304. from="trek-radio@conference.lightwitch.org">
  305. <result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
  306. <forwarded xmlns="urn:xmpp:forward:0">
  307. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  308. <message from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)" type="groupchat">
  309. <body>negan</body>
  310. </message>
  311. </forwarded>
  312. </result>
  313. </message>`);
  314. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  315. view.model.onMessage(stanza);
  316. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  317. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  318. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  319. expect(result instanceof _converse.Message).toBe(true);
  320. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  321. done();
  322. }));
  323. it("isn't shown as duplicate by comparing only the archive id",
  324. mock.initConverse(
  325. null, ['discoInitialized'], {},
  326. async function (done, _converse) {
  327. await test_utils.openAndEnterChatRoom(_converse, 'discuss@conference.conversejs.org', 'romeo');
  328. const view = _converse.chatboxviews.get('discuss@conference.conversejs.org');
  329. let stanza = u.toStanza(
  330. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="discuss@conference.conversejs.org">
  331. <result xmlns="urn:xmpp:mam:2" queryid="06fea9ca-97c9-48c4-8583-009ff54ea2e8" id="7a9fde91-4387-4bf8-b5d3-978dab8f6bf3">
  332. <forwarded xmlns="urn:xmpp:forward:0">
  333. <delay xmlns="urn:xmpp:delay" stamp="2018-12-05T04:53:12Z"/>
  334. <message xmlns="jabber:client" to="discuss@conference.conversejs.org" type="groupchat" xml:lang="en" from="discuss@conference.conversejs.org/prezel">
  335. <body>looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published</body>
  336. <x xmlns="http://jabber.org/protocol/muc#user">
  337. <item affiliation="none" jid="prezel@blubber.im" role="participant"/>
  338. </x>
  339. </message>
  340. </forwarded>
  341. </result>
  342. </message>`);
  343. view.model.onMessage(stanza);
  344. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  345. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  346. stanza = u.toStanza(
  347. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="discuss@conference.conversejs.org">
  348. <result xmlns="urn:xmpp:mam:2" queryid="06fea9ca-97c9-48c4-8583-009ff54ea2e8" id="7a9fde91-4387-4bf8-b5d3-978dab8f6bf3">
  349. <forwarded xmlns="urn:xmpp:forward:0">
  350. <delay xmlns="urn:xmpp:delay" stamp="2018-12-05T04:53:12Z"/>
  351. <message xmlns="jabber:client" to="discuss@conference.conversejs.org" type="groupchat" xml:lang="en" from="discuss@conference.conversejs.org/prezel">
  352. <body>looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published</body>
  353. <x xmlns="http://jabber.org/protocol/muc#user">
  354. <item affiliation="none" jid="prezel@blubber.im" role="participant"/>
  355. </x>
  356. </message>
  357. </forwarded>
  358. </result>
  359. </message>`);
  360. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  361. view.model.onMessage(stanza);
  362. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  363. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  364. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  365. expect(result instanceof _converse.Message).toBe(true);
  366. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  367. done();
  368. }))
  369. });
  370. });
  371. describe("The archive.query API", function () {
  372. it("can be used to query for all archived messages",
  373. mock.initConverse(
  374. null, ['discoInitialized'], {},
  375. async function (done, _converse) {
  376. const sendIQ = _converse.connection.sendIQ;
  377. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  378. let sent_stanza, IQ_id;
  379. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  380. sent_stanza = iq;
  381. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  382. });
  383. _converse.api.archive.query();
  384. await u.waitUntil(() => sent_stanza);
  385. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  386. expect(sent_stanza.toString()).toBe(
  387. `<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
  388. done();
  389. }));
  390. it("can be used to query for all messages to/from a particular JID",
  391. mock.initConverse(
  392. null, [], {},
  393. async function (done, _converse) {
  394. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  395. let sent_stanza, IQ_id;
  396. const sendIQ = _converse.connection.sendIQ;
  397. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  398. sent_stanza = iq;
  399. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  400. });
  401. _converse.api.archive.query({'with':'juliet@capulet.lit'});
  402. await u.waitUntil(() => sent_stanza);
  403. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  404. expect(sent_stanza.toString()).toBe(
  405. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  406. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  407. `<x type="submit" xmlns="jabber:x:data">`+
  408. `<field type="hidden" var="FORM_TYPE">`+
  409. `<value>urn:xmpp:mam:2</value>`+
  410. `</field>`+
  411. `<field var="with">`+
  412. `<value>juliet@capulet.lit</value>`+
  413. `</field>`+
  414. `</x>`+
  415. `</query>`+
  416. `</iq>`);
  417. done();
  418. }));
  419. it("can be used to query for archived messages from a chat room",
  420. mock.initConverse(
  421. null, [], {},
  422. async function (done, _converse) {
  423. const room_jid = 'coven@chat.shakespeare.lit';
  424. _converse.api.archive.query({'with': room_jid, 'groupchat': true});
  425. await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
  426. const sent_stanzas = _converse.connection.sent_stanzas;
  427. const stanza = await u.waitUntil(
  428. () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
  429. const queryid = stanza.querySelector('query').getAttribute('queryid');
  430. expect(Strophe.serialize(stanza)).toBe(
  431. `<iq id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
  432. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  433. `<x type="submit" xmlns="jabber:x:data">`+
  434. `<field type="hidden" var="FORM_TYPE">`+
  435. `<value>urn:xmpp:mam:2</value>`+
  436. `</field>`+
  437. `</x>`+
  438. `</query>`+
  439. `</iq>`);
  440. done();
  441. }));
  442. it("checks whether returned MAM messages from a MUC room are from the right JID",
  443. mock.initConverse(
  444. null, [], {},
  445. async function (done, _converse) {
  446. const room_jid = 'coven@chat.shakespeare.lit';
  447. const promise = _converse.api.archive.query({'with': room_jid, 'groupchat': true, 'max':'10'});
  448. await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
  449. const sent_stanzas = _converse.connection.sent_stanzas;
  450. const sent_stanza = await u.waitUntil(
  451. () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
  452. const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
  453. /* <message id='iasd207' from='coven@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda'>
  454. * <result xmlns='urn:xmpp:mam:2' queryid='g27' id='34482-21985-73620'>
  455. * <forwarded xmlns='urn:xmpp:forward:0'>
  456. * <delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37Z'/>
  457. * <message xmlns="jabber:client"
  458. * from='coven@chat.shakespeare.lit/firstwitch'
  459. * id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2'
  460. * type='groupchat'>
  461. * <body>Thrice the brinded cat hath mew'd.</body>
  462. * <x xmlns='http://jabber.org/protocol/muc#user'>
  463. * <item affiliation='none'
  464. * jid='witch1@shakespeare.lit'
  465. * role='participant' />
  466. * </x>
  467. * </message>
  468. * </forwarded>
  469. * </result>
  470. * </message>
  471. */
  472. const msg1 = $msg({'id':'iasd207', 'from': 'other@chat.shakespear.lit', 'to': 'romeo@montague.lit'})
  473. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'34482-21985-73620'})
  474. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  475. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  476. .c('message', {
  477. 'xmlns':'jabber:client',
  478. 'to':'romeo@montague.lit',
  479. 'id':'162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2',
  480. 'from':'coven@chat.shakespeare.lit/firstwitch',
  481. 'type':'groupchat' })
  482. .c('body').t("Thrice the brinded cat hath mew'd.");
  483. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  484. /* Send an <iq> stanza to indicate the end of the result set.
  485. *
  486. * <iq type='result' id='juliet1'>
  487. * <fin xmlns='urn:xmpp:mam:2'>
  488. * <set xmlns='http://jabber.org/protocol/rsm'>
  489. * <first index='0'>28482-98726-73623</first>
  490. * <last>09af3-cc343-b409f</last>
  491. * <count>20</count>
  492. * </set>
  493. * </iq>
  494. */
  495. const stanza = $iq({'type': 'result', 'id': sent_stanza.getAttribute('id')})
  496. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  497. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  498. .c('first', {'index': '0'}).t('23452-4534-1').up()
  499. .c('last').t('09af3-cc343-b409f').up()
  500. .c('count').t('16');
  501. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  502. const result = await promise;
  503. expect(result.messages.length).toBe(0);
  504. done();
  505. }));
  506. it("can be used to query for all messages in a certain timespan",
  507. mock.initConverse(
  508. null, [], {},
  509. async function (done, _converse) {
  510. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  511. let sent_stanza, IQ_id;
  512. const sendIQ = _converse.connection.sendIQ;
  513. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  514. sent_stanza = iq;
  515. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  516. });
  517. const start = '2010-06-07T00:00:00Z';
  518. const end = '2010-07-07T13:23:54Z';
  519. _converse.api.archive.query({
  520. 'start': start,
  521. 'end': end
  522. });
  523. await u.waitUntil(() => sent_stanza);
  524. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  525. expect(sent_stanza.toString()).toBe(
  526. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  527. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  528. `<x type="submit" xmlns="jabber:x:data">`+
  529. `<field type="hidden" var="FORM_TYPE">`+
  530. `<value>urn:xmpp:mam:2</value>`+
  531. `</field>`+
  532. `<field var="start">`+
  533. `<value>${dayjs(start).toISOString()}</value>`+
  534. `</field>`+
  535. `<field var="end">`+
  536. `<value>${dayjs(end).toISOString()}</value>`+
  537. `</field>`+
  538. `</x>`+
  539. `</query>`+
  540. `</iq>`
  541. );
  542. done();
  543. }));
  544. it("throws a TypeError if an invalid date is provided",
  545. mock.initConverse(
  546. null, [], {},
  547. async function (done, _converse) {
  548. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  549. try {
  550. await _converse.api.archive.query({'start': 'not a real date'});
  551. } catch (e) {
  552. expect(() => {throw e}).toThrow(new TypeError('archive.query: invalid date provided for: start'));
  553. }
  554. done();
  555. }));
  556. it("can be used to query for all messages after a certain time",
  557. mock.initConverse(
  558. null, [], {},
  559. async function (done, _converse) {
  560. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  561. let sent_stanza, IQ_id;
  562. const sendIQ = _converse.connection.sendIQ;
  563. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  564. sent_stanza = iq;
  565. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  566. });
  567. if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
  568. _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
  569. }
  570. const start = '2010-06-07T00:00:00Z';
  571. _converse.api.archive.query({'start': start});
  572. await u.waitUntil(() => sent_stanza);
  573. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  574. expect(sent_stanza.toString()).toBe(
  575. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  576. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  577. `<x type="submit" xmlns="jabber:x:data">`+
  578. `<field type="hidden" var="FORM_TYPE">`+
  579. `<value>urn:xmpp:mam:2</value>`+
  580. `</field>`+
  581. `<field var="start">`+
  582. `<value>${dayjs(start).toISOString()}</value>`+
  583. `</field>`+
  584. `</x>`+
  585. `</query>`+
  586. `</iq>`
  587. );
  588. done();
  589. }));
  590. it("can be used to query for a limited set of results",
  591. mock.initConverse(
  592. null, [], {},
  593. async function (done, _converse) {
  594. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  595. let sent_stanza, IQ_id;
  596. const sendIQ = _converse.connection.sendIQ;
  597. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  598. sent_stanza = iq;
  599. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  600. });
  601. const start = '2010-06-07T00:00:00Z';
  602. _converse.api.archive.query({'start': start, 'max':10});
  603. await u.waitUntil(() => sent_stanza);
  604. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  605. expect(sent_stanza.toString()).toBe(
  606. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  607. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  608. `<x type="submit" xmlns="jabber:x:data">`+
  609. `<field type="hidden" var="FORM_TYPE">`+
  610. `<value>urn:xmpp:mam:2</value>`+
  611. `</field>`+
  612. `<field var="start">`+
  613. `<value>${dayjs(start).toISOString()}</value>`+
  614. `</field>`+
  615. `</x>`+
  616. `<set xmlns="http://jabber.org/protocol/rsm">`+
  617. `<max>10</max>`+
  618. `</set>`+
  619. `</query>`+
  620. `</iq>`
  621. );
  622. done();
  623. }));
  624. it("can be used to page through results",
  625. mock.initConverse(
  626. null, [], {},
  627. async function (done, _converse) {
  628. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  629. let sent_stanza, IQ_id;
  630. const sendIQ = _converse.connection.sendIQ;
  631. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  632. sent_stanza = iq;
  633. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  634. });
  635. const start = '2010-06-07T00:00:00Z';
  636. _converse.api.archive.query({
  637. 'start': start,
  638. 'after': '09af3-cc343-b409f',
  639. 'max':10
  640. });
  641. await u.waitUntil(() => sent_stanza);
  642. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  643. expect(sent_stanza.toString()).toBe(
  644. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  645. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  646. `<x type="submit" xmlns="jabber:x:data">`+
  647. `<field type="hidden" var="FORM_TYPE">`+
  648. `<value>urn:xmpp:mam:2</value>`+
  649. `</field>`+
  650. `<field var="start">`+
  651. `<value>${dayjs(start).toISOString()}</value>`+
  652. `</field>`+
  653. `</x>`+
  654. `<set xmlns="http://jabber.org/protocol/rsm">`+
  655. `<max>10</max>`+
  656. `<after>09af3-cc343-b409f</after>`+
  657. `</set>`+
  658. `</query>`+
  659. `</iq>`);
  660. done();
  661. }));
  662. it("accepts \"before\" with an empty string as value to reverse the order",
  663. mock.initConverse(
  664. null, [], {},
  665. async function (done, _converse) {
  666. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  667. let sent_stanza, IQ_id;
  668. const sendIQ = _converse.connection.sendIQ;
  669. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  670. sent_stanza = iq;
  671. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  672. });
  673. _converse.api.archive.query({'before': '', 'max':10});
  674. await u.waitUntil(() => sent_stanza);
  675. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  676. expect(sent_stanza.toString()).toBe(
  677. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  678. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  679. `<x type="submit" xmlns="jabber:x:data">`+
  680. `<field type="hidden" var="FORM_TYPE">`+
  681. `<value>urn:xmpp:mam:2</value>`+
  682. `</field>`+
  683. `</x>`+
  684. `<set xmlns="http://jabber.org/protocol/rsm">`+
  685. `<max>10</max>`+
  686. `<before></before>`+
  687. `</set>`+
  688. `</query>`+
  689. `</iq>`);
  690. done();
  691. }));
  692. it("accepts a _converse.RSM object for the query options",
  693. mock.initConverse(
  694. null, [], {},
  695. async function (done, _converse) {
  696. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  697. let sent_stanza, IQ_id;
  698. const sendIQ = _converse.connection.sendIQ;
  699. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  700. sent_stanza = iq;
  701. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  702. });
  703. // Normally the user wouldn't manually make a _converse.RSM object
  704. // and pass it in. However, in the callback method an RSM object is
  705. // returned which can be reused for easy paging. This test is
  706. // more for that usecase.
  707. const rsm = new _converse.RSM({'max': '10'});
  708. rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
  709. rsm.start = '2010-06-07T00:00:00Z';
  710. _converse.api.archive.query(rsm);
  711. await u.waitUntil(() => sent_stanza);
  712. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  713. expect(sent_stanza.toString()).toBe(
  714. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  715. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  716. `<x type="submit" xmlns="jabber:x:data">`+
  717. `<field type="hidden" var="FORM_TYPE">`+
  718. `<value>urn:xmpp:mam:2</value>`+
  719. `</field>`+
  720. `<field var="with">`+
  721. `<value>romeo@montague.lit</value>`+
  722. `</field>`+
  723. `<field var="start">`+
  724. `<value>${dayjs(rsm.start).toISOString()}</value>`+
  725. `</field>`+
  726. `</x>`+
  727. `<set xmlns="http://jabber.org/protocol/rsm">`+
  728. `<max>10</max>`+
  729. `</set>`+
  730. `</query>`+
  731. `</iq>`);
  732. done();
  733. }));
  734. it("returns an object which includes the messages and a _converse.RSM object",
  735. mock.initConverse(
  736. null, [], {},
  737. async function (done, _converse) {
  738. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  739. let sent_stanza, IQ_id;
  740. const sendIQ = _converse.connection.sendIQ;
  741. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  742. sent_stanza = iq;
  743. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  744. });
  745. const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
  746. await u.waitUntil(() => sent_stanza);
  747. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  748. /* <message id='aeb213' to='juliet@capulet.lit/chamber'>
  749. * <result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
  750. * <forwarded xmlns='urn:xmpp:forward:0'>
  751. * <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
  752. * <message xmlns='jabber:client'
  753. * to='juliet@capulet.lit/balcony'
  754. * from='romeo@montague.lit/orchard'
  755. * type='chat'>
  756. * <body>Call me but love, and I'll be new baptized; Henceforth I never will be Romeo.</body>
  757. * </message>
  758. * </forwarded>
  759. * </result>
  760. * </message>
  761. */
  762. const msg1 = $msg({'id':'aeb212', 'to':'juliet@capulet.lit/chamber'})
  763. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73623'})
  764. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  765. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  766. .c('message', {
  767. 'xmlns':'jabber:client',
  768. 'to':'juliet@capulet.lit/balcony',
  769. 'from':'romeo@montague.lit/orchard',
  770. 'type':'chat' })
  771. .c('body').t("Call me but love, and I'll be new baptized;");
  772. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  773. const msg2 = $msg({'id':'aeb213', 'to':'juliet@capulet.lit/chamber'})
  774. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73624'})
  775. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  776. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  777. .c('message', {
  778. 'xmlns':'jabber:client',
  779. 'to':'juliet@capulet.lit/balcony',
  780. 'from':'romeo@montague.lit/orchard',
  781. 'type':'chat' })
  782. .c('body').t("Henceforth I never will be Romeo.");
  783. _converse.connection._dataRecv(test_utils.createRequest(msg2));
  784. /* Send an <iq> stanza to indicate the end of the result set.
  785. *
  786. * <iq type='result' id='juliet1'>
  787. * <fin xmlns='urn:xmpp:mam:2'>
  788. * <set xmlns='http://jabber.org/protocol/rsm'>
  789. * <first index='0'>28482-98726-73623</first>
  790. * <last>09af3-cc343-b409f</last>
  791. * <count>20</count>
  792. * </set>
  793. * </iq>
  794. */
  795. const stanza = $iq({'type': 'result', 'id': IQ_id})
  796. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  797. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  798. .c('first', {'index': '0'}).t('23452-4534-1').up()
  799. .c('last').t('09af3-cc343-b409f').up()
  800. .c('count').t('16');
  801. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  802. const result = await promise;
  803. expect(result.messages.length).toBe(2);
  804. expect(result.messages[0].outerHTML).toBe(msg1.nodeTree.outerHTML);
  805. expect(result.messages[1].outerHTML).toBe(msg2.nodeTree.outerHTML);
  806. expect(result.rsm['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
  807. expect(result.rsm.max).toBe('10');
  808. expect(result.rsm.count).toBe('16');
  809. expect(result.rsm.first).toBe('23452-4534-1');
  810. expect(result.rsm.last).toBe('09af3-cc343-b409f');
  811. done()
  812. }));
  813. });
  814. describe("The default preference", function () {
  815. it("is set once server support for MAM has been confirmed",
  816. mock.initConverse(
  817. null, [], {},
  818. async function (done, _converse) {
  819. const entity = await _converse.api.disco.entities.get(_converse.domain);
  820. let sent_stanza, IQ_id;
  821. const sendIQ = _converse.connection.sendIQ;
  822. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  823. sent_stanza = iq;
  824. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  825. });
  826. spyOn(_converse, 'onMAMPreferences').and.callThrough();
  827. _converse.message_archiving = 'never';
  828. const feature = new Backbone.Model({
  829. 'var': Strophe.NS.MAM
  830. });
  831. spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
  832. entity.onFeatureAdded(feature);
  833. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  834. expect(sent_stanza.toLocaleString()).toBe(
  835. `<iq id="${IQ_id}" type="get" xmlns="jabber:client">`+
  836. `<prefs xmlns="urn:xmpp:mam:2"/>`+
  837. `</iq>`);
  838. /* Example 20. Server responds with current preferences
  839. *
  840. * <iq type='result' id='juliet2'>
  841. * <prefs xmlns='urn:xmpp:mam:0' default='roster'>
  842. * <always/>
  843. * <never/>
  844. * </prefs>
  845. * </iq>
  846. */
  847. let stanza = $iq({'type': 'result', 'id': IQ_id})
  848. .c('prefs', {'xmlns': Strophe.NS.MAM, 'default':'roster'})
  849. .c('always').c('jid').t('romeo@montague.lit').up().up()
  850. .c('never').c('jid').t('montague@montague.lit');
  851. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  852. await u.waitUntil(() => _converse.onMAMPreferences.calls.count());
  853. expect(_converse.onMAMPreferences).toHaveBeenCalled();
  854. expect(sent_stanza.toString()).toBe(
  855. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  856. `<prefs default="never" xmlns="urn:xmpp:mam:2">`+
  857. `<always><jid>romeo@montague.lit</jid></always>`+
  858. `<never><jid>montague@montague.lit</jid></never>`+
  859. `</prefs>`+
  860. `</iq>`
  861. );
  862. expect(feature.get('preference')).toBe(undefined);
  863. /* <iq type='result' id='juliet3'>
  864. * <prefs xmlns='urn:xmpp:mam:0' default='always'>
  865. * <always>
  866. * <jid>romeo@montague.lit</jid>
  867. * </always>
  868. * <never>
  869. * <jid>montague@montague.lit</jid>
  870. * </never>
  871. * </prefs>
  872. * </iq>
  873. */
  874. stanza = $iq({'type': 'result', 'id': IQ_id})
  875. .c('prefs', {'xmlns': Strophe.NS.MAM, 'default':'always'})
  876. .c('always').up()
  877. .c('never');
  878. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  879. await u.waitUntil(() => feature.save.calls.count());
  880. expect(feature.save).toHaveBeenCalled();
  881. expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
  882. done();
  883. }));
  884. });
  885. });
  886. describe("Chatboxes", function () {
  887. describe("A Chatbox", function () {
  888. it("will fetch archived messages once it's opened",
  889. mock.initConverse(
  890. null, ['discoInitialized'], {},
  891. async function (done, _converse) {
  892. await test_utils.waitForRoster(_converse, 'current', 1);
  893. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  894. await test_utils.openChatBoxFor(_converse, contact_jid);
  895. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  896. let sent_stanza, IQ_id;
  897. const sendIQ = _converse.connection.sendIQ;
  898. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  899. sent_stanza = iq;
  900. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  901. });
  902. await u.waitUntil(() => sent_stanza);
  903. const stanza_el = sent_stanza.root().nodeTree;
  904. const queryid = stanza_el.querySelector('query').getAttribute('queryid');
  905. expect(sent_stanza.toString()).toBe(
  906. `<iq id="${stanza_el.getAttribute('id')}" type="set" xmlns="jabber:client">`+
  907. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  908. `<x type="submit" xmlns="jabber:x:data">`+
  909. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  910. `<field var="with"><value>mercutio@montague.lit</value></field>`+
  911. `</x>`+
  912. `<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
  913. `</query>`+
  914. `</iq>`
  915. );
  916. const msg1 = $msg({'id':'aeb212', 'to': contact_jid})
  917. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73623'})
  918. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  919. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  920. .c('message', {
  921. 'xmlns':'jabber:client',
  922. 'to': contact_jid,
  923. 'from': _converse.bare_jid,
  924. 'type':'chat' })
  925. .c('body').t("Call me but love, and I'll be new baptized;");
  926. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  927. const msg2 = $msg({'id':'aeb213', 'to': contact_jid})
  928. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73624'})
  929. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  930. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  931. .c('message', {
  932. 'xmlns':'jabber:client',
  933. 'to': contact_jid,
  934. 'from': _converse.bare_jid,
  935. 'type':'chat' })
  936. .c('body').t("Henceforth I never will be Romeo.");
  937. _converse.connection._dataRecv(test_utils.createRequest(msg2));
  938. const stanza = $iq({'type': 'result', 'id': IQ_id})
  939. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  940. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  941. .c('first', {'index': '0'}).t('23452-4534-1').up()
  942. .c('last').t('09af3-cc343-b409f').up()
  943. .c('count').t('16');
  944. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  945. done();
  946. }));
  947. it("will show an error message if the MAM query times out",
  948. mock.initConverse(
  949. null, ['discoInitialized'], {},
  950. async function (done, _converse) {
  951. const sendIQ = _converse.connection.sendIQ;
  952. let timeout_happened = false;
  953. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  954. sendIQ.bind(this)(iq, callback, errback);
  955. if (!timeout_happened) {
  956. if (typeof(iq.tree) === "function") {
  957. iq = iq.tree();
  958. }
  959. if (sizzle('query[xmlns="urn:xmpp:mam:2"]', iq).length) {
  960. // We emulate a timeout event
  961. callback(null);
  962. timeout_happened = true;
  963. }
  964. }
  965. });
  966. await test_utils.waitForRoster(_converse, 'current', 1);
  967. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  968. await test_utils.openChatBoxFor(_converse, contact_jid);
  969. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  970. const IQ_stanzas = _converse.connection.IQ_stanzas;
  971. let sent_stanza = await u.waitUntil(() => IQ_stanzas.filter(iq => sizzle('query[xmlns="urn:xmpp:mam:2"]', iq).length).pop());
  972. let queryid = sent_stanza.querySelector('query').getAttribute('queryid');
  973. expect(Strophe.serialize(sent_stanza)).toBe(
  974. `<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
  975. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  976. `<x type="submit" xmlns="jabber:x:data">`+
  977. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  978. `<field var="with"><value>mercutio@montague.lit</value></field>`+
  979. `</x>`+
  980. `<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
  981. `</query>`+
  982. `</iq>`);
  983. const view = _converse.chatboxviews.get(contact_jid);
  984. expect(view.model.messages.length).toBe(1);
  985. expect(view.model.messages.at(0).get('ephemeral')).toBe(false);
  986. expect(view.model.messages.at(0).get('type')).toBe('error');
  987. expect(view.model.messages.at(0).get('message')).toBe('Timeout while trying to fetch archived messages.');
  988. let err_message = view.el.querySelector('.message.chat-error');
  989. err_message.querySelector('.retry').click();
  990. expect(err_message.querySelector('.spinner')).not.toBe(null);
  991. while (_converse.connection.IQ_stanzas.length) {
  992. _converse.connection.IQ_stanzas.pop();
  993. }
  994. sent_stanza = await u.waitUntil(() => IQ_stanzas.filter(iq => sizzle('query[xmlns="urn:xmpp:mam:2"]', iq).length).pop());
  995. queryid = sent_stanza.querySelector('query').getAttribute('queryid');
  996. expect(Strophe.serialize(sent_stanza)).toBe(
  997. `<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
  998. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  999. `<x type="submit" xmlns="jabber:x:data">`+
  1000. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  1001. `<field var="with"><value>mercutio@montague.lit</value></field>`+
  1002. `</x>`+
  1003. `<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
  1004. `</query>`+
  1005. `</iq>`);
  1006. const msg1 = $msg({'id':'aeb212', 'to': contact_jid})
  1007. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid': queryid, 'id':'28482-98726-73623'})
  1008. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  1009. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  1010. .c('message', {
  1011. 'xmlns':'jabber:client',
  1012. 'to': contact_jid,
  1013. 'from': _converse.bare_jid,
  1014. 'type':'chat' })
  1015. .c('body').t("Call me but love, and I'll be new baptized;");
  1016. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  1017. const msg2 = $msg({'id':'aeb213', 'to': contact_jid})
  1018. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid': queryid, 'id':'28482-98726-73624'})
  1019. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  1020. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:18:25Z'}).up()
  1021. .c('message', {
  1022. 'xmlns':'jabber:client',
  1023. 'to': contact_jid,
  1024. 'from': _converse.bare_jid,
  1025. 'type':'chat' })
  1026. .c('body').t("Henceforth I never will be Romeo.");
  1027. _converse.connection._dataRecv(test_utils.createRequest(msg2));
  1028. const stanza = $iq({'type': 'result', 'id': sent_stanza.getAttribute('id')})
  1029. .c('fin', {'xmlns': 'urn:xmpp:mam:2', 'complete': true})
  1030. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  1031. .c('first', {'index': '0'}).t('28482-98726-73623').up()
  1032. .c('last').t('28482-98726-73624').up()
  1033. .c('count').t('2');
  1034. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1035. await u.waitUntil(() => view.model.messages.length === 2, 500);
  1036. err_message = view.el.querySelector('.message.chat-error');
  1037. expect(err_message).toBe(null);
  1038. done();
  1039. }));
  1040. });
  1041. });
  1042. }));