mam.js 59 KB

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