UNPKG

converse.js

Version:
314 lines (255 loc) 15.1 kB
/*global mock, converse */ const { stx } = converse.env; const $iq = converse.env.$iq; const $msg = converse.env.$msg; const Strophe = converse.env.Strophe; const sizzle = converse.env.sizzle; const u = converse.env.utils; describe("XEP-0198 Stream Management", function () { beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza })); it("gets enabled with an <enable> stanza and resumed with a <resume> stanza", mock.initConverse( ['chatBoxesInitialized'], { auto_login: false, enable_smacks: true, show_controlbox_by_default: true, smacks_max_unacked_stanzas: 2, blacklisted_plugins: ['converse-blocklist'] }, async function (_converse) { await _converse.api.user.login('romeo@montague.lit/orchard', 'secret'); const sent_stanzas = _converse.api.connection.get().sent_stanzas; let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable'), 1000).pop()); expect(_converse.session.get('smacks_enabled')).toBe(false); expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>'); let result = stx`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); expect(_converse.session.get('smacks_enabled')).toBe(true); await mock.waitUntilDiscoConfirmed( _converse, "montague.lit", [], [Strophe.NS.CARBONS] ); let IQ_stanzas = _converse.api.connection.get().IQ_stanzas; await u.waitUntil(() => IQ_stanzas.length === 5); const disco_iq = IQ_stanzas[0]; expect(disco_iq).toEqualStanza(stx` <iq from="romeo@montague.lit/orchard" id="${disco_iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client"> <query xmlns="http://jabber.org/protocol/disco#info"/></iq>`); expect(IQ_stanzas[1]).toEqualStanza(stx` <iq id="${IQ_stanzas[1].getAttribute('id')}" type="get" xmlns="jabber:client"> <query xmlns="jabber:iq:roster"/></iq>`); await mock.waitForRoster(_converse, 'current', 1); expect(IQ_stanzas[2]).toEqualStanza(stx` <iq from="romeo@montague.lit" id="${IQ_stanzas[2].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client"> <pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`); expect(IQ_stanzas[3]).toEqualStanza(stx` <iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[3].getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client"> <query xmlns="http://jabber.org/protocol/disco#info"/></iq>`); expect(IQ_stanzas[4]).toEqualStanza(stx` <iq from="romeo@montague.lit/orchard" id="${IQ_stanzas[4].getAttribute('id')}" type="set" xmlns="jabber:client"> <enable xmlns="urn:xmpp:carbons:2"/></iq>`); await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'presence')).length); expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(3); expect(_converse.session.get('unacked_stanzas').length).toBe(6); // test handling of acks let ack = stx`<a xmlns="urn:xmpp:sm:3" h="2"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(ack)); expect(_converse.session.get('unacked_stanzas').length).toBe(4); // test handling of ack requests let r = stx`<r xmlns="urn:xmpp:sm:3"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(r)); // "h" is 3 because we received two IQ responses, for disco and the roster ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop()); expect(Strophe.serialize(ack)).toBe('<a h="2" xmlns="urn:xmpp:sm:3"/>'); const disco_result = stx` <iq xmlns="jabber:client" type="result" from="montague.lit" to="romeo@montague.lit/orchard" id="${disco_iq.getAttribute('id')}"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="server" type="im"/> <feature var="http://jabber.org/protocol/disco#info"/> <feature var="http://jabber.org/protocol/disco#items"/> </query> </iq>`; _converse.api.connection.get()._dataRecv(mock.createRequest(disco_result)); ack = stx`<a xmlns="urn:xmpp:sm:3" h="2"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(ack)); expect(_converse.session.get('unacked_stanzas').length).toBe(4); expect(_converse.session.get('unacked_stanzas')[0]).toBe(Strophe.serialize(IQ_stanzas[2])); expect(_converse.session.get('unacked_stanzas')[1]).toBe(Strophe.serialize(IQ_stanzas[3])); expect(_converse.session.get('unacked_stanzas')[2]).toBe(Strophe.serialize(IQ_stanzas[4])); expect(_converse.session.get('unacked_stanzas')[3]).toBe( `<presence xmlns="jabber:client"><priority>0</priority><x xmlns="vcard-temp:x:update"/>`+ `<c hash="sha-1" node="https://conversejs.org" ver="qgxN8hmrdSa2/4/7PUoM9bPFN2s=" xmlns="http://jabber.org/protocol/caps"/>`+ `</presence>`); r = stx`<r xmlns="urn:xmpp:sm:3"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(r)); ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '3')).pop()); expect(Strophe.serialize(ack)).toBe('<a h="3" xmlns="urn:xmpp:sm:3"/>'); await _converse.api.waitUntil('rosterInitialized'); // test session resumption _converse.api.connection.get().IQ_stanzas = []; IQ_stanzas = _converse.api.connection.get().IQ_stanzas; await _converse.api.connection.reconnect(); stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop(), 1000); expect(Strophe.serialize(stanza)).toEqual('<resume h="3" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>'); result = stx`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); // Another <enable> stanza doesn't get sent out expect(sent_stanzas.filter(s => (s.tagName === 'enable')).length).toBe(1); expect(_converse.session.get('smacks_enabled')).toBe(true); await new Promise(resolve => _converse.api.listen.once('reconnected', resolve)); await u.waitUntil(() => IQ_stanzas.length === 3); // Test that unacked stanzas get resent out let iq = IQ_stanzas.pop(); expect(iq).toEqualStanza(stx` <iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" type="set" xmlns="jabber:client"> <enable xmlns="urn:xmpp:carbons:2"/> </iq>`); iq = IQ_stanzas.pop(); expect(iq).toEqualStanza(stx` <iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client"> <query xmlns="http://jabber.org/protocol/disco#info"/> </iq>`); iq = IQ_stanzas.pop(); expect(iq).toEqualStanza(stx` <iq from="romeo@montague.lit" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client"> <pubsub xmlns="http://jabber.org/protocol/pubsub"> <items node="eu.siacs.conversations.axolotl.devicelist"/> </pubsub> </iq>`); expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0); })); it("might not resume and the session will then be reset", mock.initConverse( ['chatBoxesInitialized'], { 'auto_login': false, 'enable_smacks': true, 'show_controlbox_by_default': true, 'smacks_max_unacked_stanzas': 2 }, async function (_converse) { await _converse.api.user.login('romeo@montague.lit/orchard', 'secret'); const sent_stanzas = _converse.api.connection.get().sent_stanzas; let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop()); expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>'); let result = stx`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); await mock.waitForRoster(_converse, 'current', 1); // test session resumption await _converse.api.connection.reconnect(); stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop()); expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>'); result = stx` <failed xmlns="urn:xmpp:sm:3" h="another-sequence-number"> <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> </failed>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); // Session data gets reset expect(_converse.session.get('smacks_enabled')).toBe(false); expect(_converse.session.get('num_stanzas_handled')).toBe(0); expect(_converse.session.get('num_stanzas_handled_by_server')).toBe(0); expect(_converse.session.get('num_stanzas_since_last_ack')).toBe(0); expect(_converse.session.get('unacked_stanzas').length).toBe(0); expect(_converse.session.get('roster_cached')).toBeFalsy(); await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).length === 2); stanza = sent_stanzas.filter(s => (s.tagName === 'enable')).pop(); expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>'); result = stx`<enabled xmlns="urn:xmpp:sm:3" id="another-long-sm-id" resume="true"/>`; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); expect(_converse.session.get('smacks_enabled')).toBe(true); // Check that the roster gets fetched await mock.waitForRoster(_converse, 'current', 1); await new Promise(resolve => _converse.api.listen.once('reconnected', resolve)); })); it("can cause MUC messages to be received before chatboxes are initialized", mock.initConverse( ['chatBoxesInitialized'], { 'auto_login': false, 'blacklisted_plugins': 'converse-mam', 'enable_smacks': true, 'muc_fetch_members': false, 'show_controlbox_by_default': true, 'smacks_max_unacked_stanzas': 2 }, async function (_converse) { const { api } = _converse; const key = "converse-test-session/converse.session-romeo@montague.lit-converse.session-romeo@montague.lit"; sessionStorage.setItem( key, JSON.stringify({ "id": "converse.session-romeo@montague.lit", "jid": "romeo@montague.lit/converse.js-100020907", "bare_jid": "romeo@montague.lit", "resource": "converse.js-100020907", "domain": "montague.lit", "active": false, "smacks_enabled": true, "num_stanzas_handled": 580, "num_stanzas_handled_by_server": 525, "num_stanzas_since_last_ack": 0, "unacked_stanzas": [], "smacks_stream_id": "some-long-sm-id", "push_enabled": ["romeo@montague.lit"], "roster_cached": true }) ); const muc_jid = 'lounge@montague.lit'; const chatkey = `converse.chatboxes-romeo@montague.lit-${muc_jid}`; sessionStorage.setItem('converse.chatboxes-romeo@montague.lit', JSON.stringify([chatkey])); sessionStorage.setItem(chatkey, JSON.stringify({ hidden: false, message_type: "groupchat", name: "lounge", num_unread: 0, type: "chatroom", jid: muc_jid, id: muc_jid, box_id: "box-YXJnQGNvbmZlcmVuY2UuY2hhdC5leGFtcGxlLm9yZw==", nick: "romeo" }) ); const proto = Object.getPrototypeOf(api.connection.get()) const _changeConnectStatus = proto._changeConnectStatus; let count = 0; spyOn(proto, '_changeConnectStatus').and.callFake((status) => { if (status === Strophe.Status.CONNECTED && count === 0) { // Don't trigger CONNECTED count++; return; } _changeConnectStatus.call(api.connection.get(), status); }); await api.user.login('romeo@montague.lit', 'secret'); const sent_stanzas = api.connection.get().sent_stanzas; const stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop()); expect(Strophe.serialize(stanza)).toEqual('<resume h="580" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>'); const result = stx`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`; api.connection.get()._dataRecv(mock.createRequest(result)); expect(_converse.session.get('smacks_enabled')).toBe(true); const nick = 'romeo'; const func = _converse.chatboxes.onChatBoxesFetched; spyOn(_converse.chatboxes, 'onChatBoxesFetched').and.callFake(collection => { const muc = new _converse.ChatRoom({'jid': muc_jid, 'id': muc_jid, nick}, {'collection': _converse.chatboxes}); _converse.chatboxes.add(muc); func.call(_converse.chatboxes, collection); }); // A MUC message gets received const msg = $msg({ from: `${muc_jid}/juliet`, id: u.getUniqueId(), to: 'romeo@montague.lit', type: 'groupchat' }).c('body').t('First message').tree(); api.connection.get()._dataRecv(mock.createRequest(msg)); await api.waitUntil('chatBoxesFetched'); const muc = _converse.chatboxes.get(muc_jid); await mock.waitForMUCDiscoInfo(_converse, muc_jid); await mock.receiveOwnMUCPresence(_converse, muc_jid, nick); await u.waitUntil(() => (muc.session.get('connection_status') === converse.ROOMSTATUS.ENTERED)); await muc.messages.fetched; await u.waitUntil(() => muc.messages.length); expect(muc.messages.at(0).get('message')).toBe('First message') })); });