mam.js 62 KB

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