mam.js 70 KB

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