2
0

mam.js 61 KB

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