2
0

mam.js 66 KB

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