瀏覽代碼

Fixes #1561 Don't clear localStorage and sessionStorage

JC Brand 6 年之前
父節點
當前提交
cd392bb197

+ 12 - 12
spec/chatbox.js

@@ -263,7 +263,7 @@
                 const view = await test_utils.openChatBoxFor(_converse, contact_jid);
                 const el = sizzle('a.open-chat:contains("'+view.model.getDisplayName()+'")', _converse.rosterview.el).pop();
                 const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 el.click();
                 await u.waitUntil(() => _converse.api.trigger.calls.count(), 500);
                 expect(_converse.chatboxes.length).toEqual(2);
@@ -280,7 +280,7 @@
                 await test_utils.waitForRoster(_converse, 'current');
                 test_utils.openControlBox();
 
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 test_utils.openControlBox();
 
                 test_utils.openChatBoxes(_converse, 6);
@@ -323,7 +323,7 @@
 
                 spyOn(chatview, 'close').and.callThrough();
                 spyOn(controlview, 'close').and.callThrough();
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
 
                 // We need to rebind all events otherwise our spy won't be called
                 controlview.delegateEvents();
@@ -356,7 +356,7 @@
                 const trimmed_chatboxes = _converse.minimized_chats;
                 const chatview = _converse.chatboxviews.get(contact_jid);
                 spyOn(chatview, 'minimize').and.callThrough();
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 // We need to rebind all events otherwise our spy won't be called
                 chatview.delegateEvents();
                 chatview.el.querySelector('.toggle-chatbox-button').click();
@@ -390,7 +390,7 @@
                 await test_utils.waitForRoster(_converse, 'current');
                 test_utils.openControlBox();
                 await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 _converse.chatboxes.browserStorage._clear();
 
                 test_utils.closeControlBox();
@@ -538,7 +538,7 @@
 
                     let toolbar, call_button;
                     const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
-                    spyOn(_converse.api, "trigger");
+                    spyOn(_converse.api, "trigger").and.callThrough();
                     // First check that the button doesn't show if it's not enabled
                     // via "visible_toolbar_buttons"
                     _converse.visible_toolbar_buttons.call = false;
@@ -603,7 +603,7 @@
                     await test_utils.waitForRoster(_converse, 'current');
                     test_utils.openControlBox();
 
-                    spyOn(_converse.api, "trigger");
+                    spyOn(_converse.api, "trigger").and.callThrough();
                     const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     // <composing> state
                     const msg = $msg({
@@ -690,7 +690,7 @@
                         var view = _converse.chatboxviews.get(contact_jid);
                         expect(view.model.get('chat_state')).toBe('active');
                         spyOn(_converse.connection, 'send');
-                        spyOn(_converse.api, "trigger");
+                        spyOn(_converse.api, "trigger").and.callThrough();
                         view.onKeyDown({
                             target: view.el.querySelector('textarea.chat-textarea'),
                             keyCode: 1
@@ -724,7 +724,7 @@
                         test_utils.openControlBox();
 
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
-                        spyOn(_converse.api, "trigger");
+                        spyOn(_converse.api, "trigger").and.callThrough();
                         const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
                         await test_utils.openChatBoxFor(_converse, sender_jid);
@@ -1052,7 +1052,7 @@
                         test_utils.openControlBox();
                         const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
-                        spyOn(_converse.api, "trigger");
+                        spyOn(_converse.api, "trigger").and.callThrough();
                         await test_utils.openChatBoxFor(_converse, sender_jid);
                         const view = _converse.chatboxviews.get(sender_jid);
                         expect(view.el.querySelectorAll('.chat-event').length).toBe(0);
@@ -1093,7 +1093,7 @@
                         await test_utils.waitForRoster(_converse, 'current', 3);
                         test_utils.openControlBox();
 
-                        spyOn(_converse.api, "trigger");
+                        spyOn(_converse.api, "trigger").and.callThrough();
                         const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                         // <paused> state
                         const msg = $msg({
@@ -1125,7 +1125,7 @@
                 test_utils.openControlBox();
                 const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 await test_utils.openChatBoxFor(_converse, contact_jid);
                 const view = _converse.chatboxviews.get(contact_jid);
                 let message = 'This message is another sent from this chatbox';

+ 3 - 3
spec/controlbox.js

@@ -24,7 +24,7 @@
             expect(u.isVisible(el)).toBe(false);
             spyOn(_converse.controlboxtoggle, 'onClick').and.callThrough();
             spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
-            spyOn(_converse.api, "trigger");
+            spyOn(_converse.api, "trigger").and.callThrough();
             // Redelegate so that the spies are now registered as the event handlers (specifically for 'onClick')
             _converse.controlboxtoggle.delegateEvents();
             document.querySelector('.toggle-controlbox').click();
@@ -38,12 +38,12 @@
 
         describe("The \"Contacts\" section", function () {
 
-            it("can be used to add contact and it checks for case-sensivity", 
+            it("can be used to add contact and it checks for case-sensivity",
                 mock.initConverse(
                     null, ['rosterGroupsFetched'], {},
                     async function (done, _converse) {
 
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 spyOn(_converse.rosterview, 'update').and.callThrough();
                 test_utils.openControlBox();
                 // Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)

+ 3 - 3
spec/messages.js

@@ -737,7 +737,7 @@
             const include_nick = false;
             await test_utils.waitForRoster(_converse, 'current', 2, include_nick);
             test_utils.openControlBox();
-            spyOn(_converse.api, "trigger");
+            spyOn(_converse.api, "trigger").and.callThrough();
             const contact_name = mock.cur_names[1];
             const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
 
@@ -1382,7 +1382,7 @@
 
                 await test_utils.waitForRoster(_converse, 'current');
                 test_utils.openControlBox();
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                 await test_utils.openChatBoxFor(_converse, contact_jid)
                 expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
@@ -1626,7 +1626,7 @@
                     _converse.allow_non_roster_messaging = false;
                     _converse.api.trigger('rosterContactsFetched');
 
-                    spyOn(_converse.api, "trigger");
+                    spyOn(_converse.api, "trigger").and.callThrough();
                     const message = 'This is a received message from someone not on the roster';
                     const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                     const msg = $msg({

+ 5 - 5
spec/muc.js

@@ -1969,7 +1969,7 @@
 
                 const text = 'This is a received message';
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 if (!view.el.querySelectorAll('.chat-area').length) {
                     view.renderChatArea();
@@ -2001,7 +2001,7 @@
                     async function (done, _converse) {
 
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 if (!view.el.querySelectorAll('.chat-area').length) {
                     view.renderChatArea();
@@ -2742,7 +2742,7 @@
 
                 spyOn(view, 'onMinimized').and.callThrough();
                 spyOn(view, 'onMaximized').and.callThrough();
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 view.el.querySelector('.toggle-chatbox-button').click();
 
@@ -2770,7 +2770,7 @@
                 await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 spyOn(view, 'close').and.callThrough();
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 spyOn(view.model, 'leave');
                 view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 view.el.querySelector('.close-chatbox-button').click();
@@ -3787,7 +3787,7 @@
                     'from': view.model.get('jid'),
                     'to': _converse.connection.jid
                 });
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 expect(_converse.chatboxes.length).toBe(2);
                 _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
                 await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED));

+ 1 - 1
spec/protocol.js

@@ -516,7 +516,7 @@
                 null, ['rosterGroupsFetched'], {},
                 async function (done, _converse) {
 
-                spyOn(_converse.api, "trigger");
+                spyOn(_converse.api, "trigger").and.callThrough();
                 test_utils.openControlBox(_converse);
                 // Create some contacts so that we can test positioning
                 test_utils.createContacts(_converse, 'current');

+ 9 - 19
src/converse-controlbox.js

@@ -586,19 +586,18 @@ converse.plugins.add('converse-controlbox', {
         });
 
         _converse.api.listen.on('clearSession', () => {
-            if (_converse.config.get('trusted')) {
-                const chatboxes = _.get(_converse, 'chatboxes', null);
-                if (!_.isNil(chatboxes)) {
-                    const controlbox = chatboxes.get('controlbox');
-                    if (controlbox &&
-                            controlbox.collection &&
-                            controlbox.collection.browserStorage) {
-                        controlbox.save({'connected': false});
-                    }
-                }
+            const chatboxviews = _.get(_converse, 'chatboxviews', null);
+            const view = chatboxviews && chatboxviews.get('controlbox');
+            if (view) {
+               u.safeSave(view.model, {'connected': false});
+               if (_.get(view, 'controlbox_pane')) {
+                  view.controlbox_pane.remove();
+                  delete view.controlbox_pane;
+               }
             }
         });
 
+
         Promise.all([
             _converse.api.waitUntil('connectionInitialized'),
             _converse.api.waitUntil('chatBoxViewsInitialized')
@@ -621,15 +620,6 @@ converse.plugins.add('converse-controlbox', {
         _converse.api.listen.on('disconnected', () => disconnect().renderLoginPanel());
         _converse.api.listen.on('will-reconnect', disconnect);
 
-        _converse.api.listen.on('clearSession', () => {
-            const view = _converse.chatboxviews.get('controlbox');
-            if (view && view.controlbox_pane) {
-                view.controlbox_pane.remove();
-                delete view.controlbox_pane;
-            }
-        });
-
-
         /************************ API ************************/
 
         Object.assign(_converse.api, {

+ 2 - 0
src/converse-muc-views.js

@@ -1930,6 +1930,8 @@ converse.plugins.add('converse-muc-views', {
         _converse.api.listen.on('clearSession', () => {
             const view = _converse.chatboxviews.get('controlbox');
             if (view && view.roomspanel) {
+                view.roomspanel.model.destroy();
+                view.roomspanel.model.browserStorage._clear();
                 view.roomspanel.remove();
                 delete view.roomspanel;
             }

+ 1 - 1
src/converse-oauth.js

@@ -77,7 +77,7 @@ converse.plugins.add("converse-oauth", {
             'oauth_providers': {},
         });
 
-        _converse.OAuthProviders = Backbone.Collection.extend({
+        _converse.OAuthProviders = _converse.Collection.extend({
             'sync': __.noop,
 
             initialize () {

+ 16 - 9
src/converse-omemo.js

@@ -977,7 +977,7 @@ converse.plugins.add('converse-omemo', {
             }
         });
 
-        _converse.Devices = Backbone.Collection.extend({
+        _converse.Devices = _converse.Collection.extend({
             model: _converse.Device,
         });
 
@@ -1082,7 +1082,7 @@ converse.plugins.add('converse-omemo', {
          * @namespace _converse.DeviceLists
          * @memberOf _converse
          */
-        _converse.DeviceLists = Backbone.Collection.extend({
+        _converse.DeviceLists = _converse.Collection.extend({
             model: _converse.DeviceList,
             /**
              * Returns the {@link _converse.DeviceList} for a particular JID.
@@ -1240,6 +1240,8 @@ converse.plugins.add('converse-omemo', {
             }
         }
 
+        /******************** Event Handlers ********************/
+
         _converse.api.waitUntil('chatBoxesInitialized').then(() =>
             _converse.chatboxes.on('add', chatbox => {
                 checkOMEMOSupported(chatbox);
@@ -1250,12 +1252,6 @@ converse.plugins.add('converse-omemo', {
             })
         );
 
-        _converse.api.listen.on('afterTearDown', () => {
-            if (_converse.devicelists) {
-                _converse.devicelists.reset();
-            }
-            delete _converse.omemo_store;
-        });
         _converse.api.listen.on('connected', registerPEPPushHandler);
         _converse.api.listen.on('renderToolbar', view => view.renderOMEMOToolbarButton());
         _converse.api.listen.on('statusInitialized', initOMEMO);
@@ -1271,7 +1267,18 @@ converse.plugins.add('converse-omemo', {
             _converse.generateFingerprints(_converse.bare_jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
         });
 
-        /************************ BEGIN API ************************/
+        _converse.api.listen.on('afterTearDown', () => (delete _converse.omemo_store));
+
+        _converse.api.listen.on('clearSession', () => {
+            if (_converse.shouldClearCache() && _converse.devicelists) {
+                _converse.devicelists.clearSession();
+                delete _converse.devicelists;
+            }
+        });
+
+
+        /************************ API ************************/
+
         Object.assign(_converse.api, {
             /**
              * The "omemo" namespace groups methods relevant to OMEMO

+ 1 - 1
src/converse-roomslist.js

@@ -47,7 +47,7 @@ converse.plugins.add('converse-roomslist', {
         _converse.api.promises.add('roomsListInitialized');
 
 
-        _converse.OpenRooms = Backbone.Collection.extend({
+        _converse.OpenRooms = _converse.Collection.extend({
 
             comparator (room) {
                 if (_converse.bookmarks && room.get('bookmarked')) {

+ 2 - 3
src/converse-rosterview.js

@@ -290,7 +290,7 @@ converse.plugins.add('converse-rosterview', {
             },
 
             shouldBeVisible () {
-                return _converse.roster.length >= 5 || this.isActive();
+                return _converse.roster && _converse.roster.length >= 5 || this.isActive();
             },
 
             showOrHide () {
@@ -859,7 +859,6 @@ converse.plugins.add('converse-rosterview', {
             },
 
             reset () {
-                _converse.roster.reset();
                 this.removeAll();
                 this.render().update();
                 return this;
@@ -943,7 +942,7 @@ converse.plugins.add('converse-rosterview', {
         /* -------- Event Handlers ----------- */
         _converse.api.listen.on('chatBoxesInitialized', () => {
             function highlightRosterItem (chatbox) {
-                const contact = _converse.roster.findWhere({'jid': chatbox.get('jid')});
+                const contact = _converse.roster && _converse.roster.findWhere({'jid': chatbox.get('jid')});
                 if (contact !== undefined) {
                     contact.trigger('highlight');
                 }

+ 1 - 1
src/headless/converse-bookmarks.js

@@ -93,7 +93,7 @@ converse.plugins.add('converse-bookmarks', {
 
         _converse.Bookmark = Backbone.Model;
 
-        _converse.Bookmarks = Backbone.Collection.extend({
+        _converse.Bookmarks = _converse.Collection.extend({
             model: _converse.Bookmark,
             comparator: (item) => item.get('name').toLowerCase(),
 

+ 1 - 0
src/headless/converse-bosh.js

@@ -107,6 +107,7 @@ converse.plugins.add('converse-bosh', {
                 sessionStorage.removeItem(`${id}-${id}`);
             } else {
                 _converse.bosh_session.destroy();
+                _converse.bosh_session.browserStorage._clear();
                 delete _converse.bosh_session;
             }
         });

+ 2 - 2
src/headless/converse-chatboxes.js

@@ -238,7 +238,7 @@ converse.plugins.add('converse-chatboxes', {
         });
 
 
-        _converse.Messages = Backbone.Collection.extend({
+        _converse.Messages = _converse.Collection.extend({
             model: _converse.Message,
             comparator: 'time'
         });
@@ -977,7 +977,7 @@ converse.plugins.add('converse-chatboxes', {
         });
 
 
-        _converse.ChatBoxes = Backbone.Collection.extend({
+        _converse.ChatBoxes = _converse.Collection.extend({
             comparator: 'time_opened',
 
             model (attrs, options) {

+ 44 - 35
src/headless/converse-core.js

@@ -108,6 +108,15 @@ _converse.VERSION_NAME = "v5.0.0dev";
 
 Object.assign(_converse, Backbone.Events);
 
+_converse.Collection = Backbone.Collection.extend({
+    clearSession () {
+        Array.from(this.models).forEach(m => m.destroy());
+        this.browserStorage._clear();
+        this.reset();
+    }
+});
+
+
 // Make converse pluggable
 pluggable.enable(_converse, '_converse', 'pluggable');
 
@@ -510,18 +519,23 @@ function reconnect () {
 const debouncedReconnect = _.debounce(reconnect, 2000);
 
 
+_converse.shouldClearCache = function () {
+    return !_converse.config.get('trusted') || _converse.isTestEnv();
+}
+
 function clearSession  () {
     if (_converse.session !== undefined) {
         _converse.session.destroy();
+        _converse.session.browserStorage._clear();
         delete _converse.session;
     }
-    // TODO: Refactor so that we don't clear
-    if (!_converse.config.get('trusted') || _converse.isTestEnv()) {
-        window.localStorage.clear();
-        window.sessionStorage.clear();
+    if (_converse.shouldClearCache()) {
+        _converse.xmppstatus.destroy();
+        _converse.xmppstatus.browserStorage._clear();
+        delete _converse.xmppstatus;
     }
     /**
-     * Triggered once the session information has been cleared,
+     * Triggered once the user session has been cleared,
      * for example when the user has logged out or when Converse has
      * disconnected for some other reason.
      * @event _converse#clearSession
@@ -675,6 +689,29 @@ async function finishInitialization () {
     }
 }
 
+
+/**
+ * Properly tear down the session so that it's possible to manually connect again.
+ * @method finishDisconnection
+ * @emits _converse#disconnected
+ * @private
+ */
+function finishDisconnection () {
+    _converse.log('DISCONNECTED');
+    delete _converse.connection.reconnecting;
+    _converse.connection.reset();
+    tearDown();
+    clearSession();
+    /**
+     * Triggered after converse.js has disconnected from the XMPP server.
+     * @event _converse#disconnected
+     * @memberOf _converse
+     * @example _converse.api.listen.on('disconnected', () => { ... });
+     */
+    _converse.api.trigger('disconnected');
+}
+
+
 function fetchLoginCredentials (wait=0) {
     return new Promise(
         _.debounce((resolve, reject) => {
@@ -937,28 +974,6 @@ _converse.initialize = async function (settings, callback) {
     };
 
 
-    /**
-     * Properly tear down the session so that it's possible to manually connect again.
-     * @method finishDisconnection
-     * @private
-     * @memberOf _converse
-     */
-    this.finishDisconnection = function () {
-        _converse.log('DISCONNECTED');
-        delete _converse.connection.reconnecting;
-        _converse.connection.reset();
-        tearDown();
-        clearSession();
-        /**
-         * Triggered after converse.js has disconnected from the XMPP server.
-         * @event _converse#disconnected
-         * @memberOf _converse
-         * @example _converse.api.listen.on('disconnected', () => { ... });
-         */
-        _converse.api.trigger('disconnected');
-    };
-
-
     /**
      * Gets called once strophe's status reaches Strophe.Status.DISCONNECTED.
      * Will either start a teardown process for converse.js or attempt
@@ -981,14 +996,14 @@ _converse.initialize = async function (settings, callback) {
                  */
                 return _converse.api.connection.reconnect();
             } else {
-                return _converse.finishDisconnection();
+                return finishDisconnection();
             }
         } else if (_converse.disconnection_cause === _converse.LOGOUT ||
                 (reason !== undefined && reason === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) ||
                 reason === "host-unknown" ||
                 reason === "remote-connection-failed" ||
                 !_converse.auto_reconnect) {
-            return _converse.finishDisconnection();
+            return finishDisconnection();
         }
         _converse.api.connection.reconnect();
     };
@@ -1364,9 +1379,6 @@ _converse.api = {
         disconnect () {
             if (_converse.connection) {
                 _converse.connection.disconnect();
-            } else {
-                tearDown();
-                clearSession();
             }
         },
 
@@ -1527,12 +1539,9 @@ _converse.api = {
          * @example _converse.api.user.logout();
          */
         logout () {
-            clearSession();
             _converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
             if (_converse.connection !== undefined) {
                 _converse.connection.disconnect();
-            } else {
-                tearDown();
             }
             // Recreate all the promises
             Object.keys(_converse.promises).forEach(addPromise);

+ 19 - 16
src/headless/converse-disco.js

@@ -43,24 +43,24 @@ converse.plugins.add('converse-disco', {
             initialize (attrs, options) {
                 this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
 
-                this.dataforms = new Backbone.Collection();
+                this.dataforms = new _converse.Collection();
                 this.dataforms.browserStorage = new BrowserStorage.session(
                     `converse.dataforms-${this.get('jid')}`
                 );
 
-                this.features = new Backbone.Collection();
+                this.features = new _converse.Collection();
                 this.features.browserStorage = new BrowserStorage.session(
                     `converse.features-${this.get('jid')}`
                 );
                 this.features.on('add', this.onFeatureAdded, this);
 
-                this.fields = new Backbone.Collection();
+                this.fields = new _converse.Collection();
                 this.fields.browserStorage = new BrowserStorage.session(
                     `converse.fields-${this.get('jid')}`
                 );
                 this.fields.on('add', this.onFieldAdded, this);
 
-                this.identities = new Backbone.Collection();
+                this.identities = new _converse.Collection();
                 this.identities.browserStorage = new BrowserStorage.session(
                     `converse.identities-${this.get('jid')}`
                 );
@@ -226,7 +226,7 @@ converse.plugins.add('converse-disco', {
             }
         });
 
-        _converse.DiscoEntities = Backbone.Collection.extend({
+        _converse.DiscoEntities = _converse.Collection.extend({
             model: _converse.DiscoEntity,
 
             fetchEntities () {
@@ -267,7 +267,7 @@ converse.plugins.add('converse-disco', {
             const bare_jid = Strophe.getBareJidFromJid(_converse.jid);
             const id = `converse.stream-features-${bare_jid}`;
             if (!_converse.stream_features || _converse.stream_features.browserStorage.id !== id) {
-                _converse.stream_features = new Backbone.Collection();
+                _converse.stream_features = new _converse.Collection();
                 _converse.stream_features.browserStorage = new BrowserStorage.session(id);
                 _converse.stream_features.fetch({
                     success (collection) {
@@ -363,17 +363,20 @@ converse.plugins.add('converse-disco', {
         _converse.api.listen.on('connected', initializeDisco);
 
         _converse.api.listen.on('beforeTearDown', () => {
-            if (_converse.disco_entities) {
-                _converse.disco_entities.each((entity) => {
-                    entity.features.reset();
-                    entity.features.browserStorage._clear();
-                });
-                _converse.disco_entities.reset();
-                _converse.disco_entities.browserStorage._clear();
-            }
             if (_converse.stream_features) {
-                Array.from(_converse.stream_features.models).forEach(f => f.destroy());
-                _converse.stream_features.browserStorage._clear();
+                _converse.stream_features.clearSession();
+                delete _converse.stream_features;
+            }
+        });
+
+        _converse.api.listen.on('clearSession', () => {
+            if (_converse.shouldClearCache() && _converse.disco_entities) {
+                Array.from(_converse.disco_entities.models).forEach(e => e.features.clearSession());
+                Array.from(_converse.disco_entities.models).forEach(e => e.identities.clearSession());
+                Array.from(_converse.disco_entities.models).forEach(e => e.dataforms.clearSession());
+                Array.from(_converse.disco_entities.models).forEach(e => e.fields.clearSession());
+                _converse.disco_entities.clearSession();
+                delete _converse.disco_entities;
             }
         });
 

+ 12 - 2
src/headless/converse-muc.js

@@ -331,7 +331,7 @@ converse.plugins.add('converse-muc', {
          * @namespace _converse.ChatRoomMessages
          * @memberOf _converse
          */
-        _converse.ChatRoomMessages = Backbone.Collection.extend({
+        _converse.ChatRoomMessages = _converse.Collection.extend({
             model: _converse.ChatRoomMessage,
             comparator: 'time'
         });
@@ -609,6 +609,16 @@ converse.plugins.add('converse-muc', {
                 this.removeHandlers();
             },
 
+            close () {
+                try {
+                    this.features.destroy();
+                    this.features.browserStorage._clear();
+                } catch (e) {
+                    _converse.log(e, Strophe.LogLevel.ERROR);
+                }
+                return _converse.ChatBox.prototype.close.call(this);
+            },
+
             sendUnavailablePresence (exit_msg) {
                 const presence = $pres({
                     type: "unavailable",
@@ -1908,7 +1918,7 @@ converse.plugins.add('converse-muc', {
         });
 
 
-        _converse.ChatRoomOccupants = Backbone.Collection.extend({
+        _converse.ChatRoomOccupants = _converse.Collection.extend({
             model: _converse.ChatRoomOccupant,
 
             comparator (occupant1, occupant2) {

+ 22 - 6
src/headless/converse-roster.js

@@ -141,7 +141,7 @@ converse.plugins.add('converse-roster', {
         };
 
         const Resource = Backbone.Model.extend({'idAttribute': 'name'});
-        const Resources = Backbone.Collection.extend({'model': Resource});
+        const Resources = _converse.Collection.extend({'model': Resource});
 
 
         _converse.Presence = Backbone.Model.extend({
@@ -216,7 +216,7 @@ converse.plugins.add('converse-roster', {
         });
 
 
-        _converse.Presences = Backbone.Collection.extend({
+        _converse.Presences = _converse.Collection.extend({
             model: _converse.Presence,
         });
 
@@ -393,7 +393,7 @@ converse.plugins.add('converse-roster', {
          * @namespace _converse.RosterContacts
          * @memberOf _converse
          */
-        _converse.RosterContacts = Backbone.Collection.extend({
+        _converse.RosterContacts = _converse.Collection.extend({
             model: _converse.RosterContact,
 
             comparator (contact1, contact2) {
@@ -861,7 +861,7 @@ converse.plugins.add('converse-roster', {
         });
 
 
-        _converse.RosterGroups = Backbone.Collection.extend({
+        _converse.RosterGroups = _converse.Collection.extend({
             model: _converse.RosterGroup,
 
             comparator (a, b) {
@@ -907,9 +907,10 @@ converse.plugins.add('converse-roster', {
         };
 
 
-        /********** Event Handlers *************/
+        /******************** Event Handlers ********************/
+
         function updateUnreadCounter (chatbox) {
-            const contact = _converse.roster.findWhere({'jid': chatbox.get('jid')});
+            const contact = _converse.roster && _converse.roster.findWhere({'jid': chatbox.get('jid')});
             if (contact !== undefined) {
                 contact.save({'num_unread': chatbox.get('num_unread')});
             }
@@ -946,12 +947,27 @@ converse.plugins.add('converse-roster', {
                     p.save({'show': 'offline'}, {'silent': true})
                 });
             }
+            if (_converse.roster) {
+                _converse.roster.reset();
+            }
         });
 
         _converse.api.listen.on('clearSession', () => {
             if (_converse.presences) {
                 _converse.presences.browserStorage._clear();
             }
+            if (_converse.shouldClearCache()) {
+                if (_converse.roster) {
+                    _.invoke(_converse, 'roster.data.destroy');
+                    _.invoke(_converse, 'roster.data.browserStorage._clear');
+                    _converse.roster.clearSession();
+                    delete _converse.roster;
+                }
+                if (_converse.rostergroups) {
+                    _converse.rostergroups.clearSession();
+                    delete _converse.rostergroups;
+                }
+            }
         });
 
         _converse.api.listen.on('statusInitialized', (reconnecting) => {

+ 8 - 1
src/headless/converse-vcard.js

@@ -53,7 +53,7 @@ converse.plugins.add('converse-vcard', {
         });
 
 
-        _converse.VCards = Backbone.Collection.extend({
+        _converse.VCards = _converse.Collection.extend({
             model: _converse.VCard,
 
             initialize () {
@@ -138,6 +138,13 @@ converse.plugins.add('converse-vcard', {
             }
         });
 
+        _converse.api.listen.on('clearSession', () => {
+            if (_converse.shouldClearCache() && _converse.vcards) {
+                _converse.vcards.clearSession();
+                delete _converse.vcards;
+            }
+        });
+
 
         _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.VCARD));