smacks.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*global mock, converse */
  2. const $iq = converse.env.$iq;
  3. const $msg = converse.env.$msg;
  4. const Strophe = converse.env.Strophe;
  5. const sizzle = converse.env.sizzle;
  6. const u = converse.env.utils;
  7. describe("XEP-0198 Stream Management", function () {
  8. it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
  9. mock.initConverse(
  10. ['chatBoxesInitialized'],
  11. { 'auto_login': false,
  12. 'enable_smacks': true,
  13. 'show_controlbox_by_default': true,
  14. 'smacks_max_unacked_stanzas': 2
  15. },
  16. async function (done, _converse) {
  17. const view = _converse.chatboxviews.get('controlbox');
  18. spyOn(view, 'renderControlBoxPane').and.callThrough();
  19. await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
  20. const sent_stanzas = _converse.connection.sent_stanzas;
  21. let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable'), 1000).pop());
  22. expect(_converse.session.get('smacks_enabled')).toBe(false);
  23. expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
  24. let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
  25. _converse.connection._dataRecv(mock.createRequest(result));
  26. expect(_converse.session.get('smacks_enabled')).toBe(true);
  27. await u.waitUntil(() => view.renderControlBoxPane.calls?.count());
  28. let IQ_stanzas = _converse.connection.IQ_stanzas;
  29. await u.waitUntil(() => IQ_stanzas.length === 4);
  30. let iq = IQ_stanzas[IQ_stanzas.length-1];
  31. expect(Strophe.serialize(iq)).toBe(
  32. `<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
  33. await mock.waitForRoster(_converse, 'current', 1);
  34. IQ_stanzas.pop();
  35. const expected_IQs = disco_iq => ([
  36. `<iq from="romeo@montague.lit" id="${disco_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
  37. `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`,
  38. `<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
  39. `<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`,
  40. `<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
  41. `<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`]);
  42. const disco_iq = IQ_stanzas.pop();
  43. expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
  44. iq = IQ_stanzas.pop();
  45. expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
  46. iq = IQ_stanzas.pop();
  47. expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
  48. expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2);
  49. expect(_converse.session.get('unacked_stanzas').length).toBe(5);
  50. // test handling of acks
  51. let ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="2"/>`);
  52. _converse.connection._dataRecv(mock.createRequest(ack));
  53. expect(_converse.session.get('unacked_stanzas').length).toBe(3);
  54. // test handling of ack requests
  55. let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
  56. _converse.connection._dataRecv(mock.createRequest(r));
  57. ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
  58. expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
  59. const disco_result = $iq({
  60. 'type': 'result',
  61. 'from': 'montague.lit',
  62. 'to': 'romeo@montague.lit/orchard',
  63. 'id': disco_iq.getAttribute('id'),
  64. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
  65. .c('identity', {
  66. 'category': 'server',
  67. 'type': 'im'
  68. }).up()
  69. .c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up()
  70. .c('feature', {'var': 'http://jabber.org/protocol/disco#items'});
  71. _converse.connection._dataRecv(mock.createRequest(disco_result));
  72. ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="3"/>`);
  73. _converse.connection._dataRecv(mock.createRequest(ack));
  74. expect(_converse.session.get('unacked_stanzas').length).toBe(2);
  75. r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
  76. _converse.connection._dataRecv(mock.createRequest(r));
  77. ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
  78. expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
  79. await _converse.api.waitUntil('rosterInitialized');
  80. // test session resumption
  81. _converse.connection.IQ_stanzas = [];
  82. IQ_stanzas = _converse.connection.IQ_stanzas;
  83. await _converse.api.connection.reconnect();
  84. stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop(), 1000);
  85. expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
  86. result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
  87. _converse.connection._dataRecv(mock.createRequest(result));
  88. // Another <enable> stanza doesn't get sent out
  89. expect(sent_stanzas.filter(s => (s.tagName === 'enable')).length).toBe(1);
  90. expect(_converse.session.get('smacks_enabled')).toBe(true);
  91. await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
  92. await u.waitUntil(() => IQ_stanzas.length === 1);
  93. // Test that unacked stanzas get resent out
  94. iq = IQ_stanzas.pop();
  95. expect(Strophe.serialize(iq)).toBe(`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
  96. expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
  97. done();
  98. }));
  99. it("might not resume and the session will then be reset",
  100. mock.initConverse(
  101. ['chatBoxesInitialized'],
  102. { 'auto_login': false,
  103. 'enable_smacks': true,
  104. 'show_controlbox_by_default': true,
  105. 'smacks_max_unacked_stanzas': 2
  106. },
  107. async function (done, _converse) {
  108. await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
  109. const sent_stanzas = _converse.connection.sent_stanzas;
  110. let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
  111. expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
  112. let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
  113. _converse.connection._dataRecv(mock.createRequest(result));
  114. await mock.waitForRoster(_converse, 'current', 1);
  115. // test session resumption
  116. await _converse.api.connection.reconnect();
  117. stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
  118. expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
  119. result = u.toStanza(
  120. `<failed xmlns="urn:xmpp:sm:3" h="another-sequence-number">`+
  121. `<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>`+
  122. `</failed>`);
  123. _converse.connection._dataRecv(mock.createRequest(result));
  124. // Session data gets reset
  125. expect(_converse.session.get('smacks_enabled')).toBe(false);
  126. expect(_converse.session.get('num_stanzas_handled')).toBe(0);
  127. expect(_converse.session.get('num_stanzas_handled_by_server')).toBe(0);
  128. expect(_converse.session.get('num_stanzas_since_last_ack')).toBe(0);
  129. expect(_converse.session.get('unacked_stanzas').length).toBe(0);
  130. expect(_converse.session.get('roster_cached')).toBeFalsy();
  131. await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).length === 2);
  132. stanza = sent_stanzas.filter(s => (s.tagName === 'enable')).pop();
  133. expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
  134. result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="another-long-sm-id" resume="true"/>`);
  135. _converse.connection._dataRecv(mock.createRequest(result));
  136. expect(_converse.session.get('smacks_enabled')).toBe(true);
  137. // Check that the roster gets fetched
  138. await mock.waitForRoster(_converse, 'current', 1);
  139. await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
  140. done();
  141. }));
  142. it("can cause MUC messages to be received before chatboxes are initialized",
  143. mock.initConverse(
  144. ['chatBoxesInitialized'],
  145. { 'auto_login': false,
  146. 'blacklisted_plugins': 'converse-mam',
  147. 'enable_smacks': true,
  148. 'muc_fetch_members': false,
  149. 'show_controlbox_by_default': true,
  150. 'smacks_max_unacked_stanzas': 2
  151. },
  152. async function (done, _converse) {
  153. const key = "converse-test-session/converse.session-romeo@montague.lit-converse.session-romeo@montague.lit";
  154. sessionStorage.setItem(
  155. key,
  156. JSON.stringify({
  157. "id": "converse.session-romeo@montague.lit",
  158. "jid": "romeo@montague.lit/converse.js-100020907",
  159. "bare_jid": "romeo@montague.lit",
  160. "resource": "converse.js-100020907",
  161. "domain": "montague.lit",
  162. "active": false,
  163. "smacks_enabled": true,
  164. "num_stanzas_handled": 580,
  165. "num_stanzas_handled_by_server": 525,
  166. "num_stanzas_since_last_ack": 0,
  167. "unacked_stanzas": [],
  168. "smacks_stream_id": "some-long-sm-id",
  169. "push_enabled": ["romeo@montague.lit"],
  170. "carbons_enabled": true,
  171. "roster_cached": true
  172. })
  173. );
  174. const muc_jid = 'lounge@montague.lit';
  175. const chatkey = `converse.chatboxes-romeo@montague.lit-${muc_jid}`;
  176. sessionStorage.setItem('converse.chatboxes-romeo@montague.lit', JSON.stringify([chatkey]));
  177. sessionStorage.setItem(chatkey,
  178. JSON.stringify({
  179. hidden: false,
  180. message_type: "groupchat",
  181. name: "lounge",
  182. num_unread: 0,
  183. type: "chatroom",
  184. jid: muc_jid,
  185. id: muc_jid,
  186. box_id: "box-YXJnQGNvbmZlcmVuY2UuY2hhdC5leGFtcGxlLm9yZw==",
  187. nick: "romeo"
  188. })
  189. );
  190. _converse.no_connection_on_bind = true; // XXX Don't trigger CONNECTED in tests/mock.js
  191. await _converse.api.user.login('romeo@montague.lit', 'secret');
  192. delete _converse.no_connection_on_bind;
  193. const sent_stanzas = _converse.connection.sent_stanzas;
  194. const stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
  195. expect(Strophe.serialize(stanza)).toEqual('<resume h="580" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
  196. const result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
  197. _converse.connection._dataRecv(mock.createRequest(result));
  198. expect(_converse.session.get('smacks_enabled')).toBe(true);
  199. const nick = 'romeo';
  200. const func = _converse.chatboxes.onChatBoxesFetched;
  201. spyOn(_converse.chatboxes, 'onChatBoxesFetched').and.callFake(collection => {
  202. const muc = new _converse.ChatRoom({'jid': muc_jid, 'id': muc_jid, nick}, {'collection': _converse.chatboxes});
  203. _converse.chatboxes.add(muc);
  204. func.call(_converse.chatboxes, collection);
  205. });
  206. // A MUC message gets received
  207. const msg = $msg({
  208. from: `${muc_jid}/juliet`,
  209. id: u.getUniqueId(),
  210. to: 'romeo@montague.lit',
  211. type: 'groupchat'
  212. }).c('body').t('First message').tree();
  213. _converse.connection._dataRecv(mock.createRequest(msg));
  214. await _converse.api.waitUntil('chatBoxesFetched');
  215. const muc = _converse.chatboxes.get(muc_jid);
  216. await u.waitUntil(() => muc.message_queue.length === 1);
  217. const view = _converse.chatboxviews.get(muc_jid);
  218. await mock.getRoomFeatures(_converse, muc_jid);
  219. await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
  220. await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
  221. await view.model.messages.fetched;
  222. await u.waitUntil(() => muc.messages.length);
  223. expect(muc.messages.at(0).get('message')).toBe('First message')
  224. done();
  225. }));
  226. });