mam.js 61 KB

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