Browse Source

Batched writes to IndexedDB

- Update to the latest @converse/skeletor (which uses mergebounce to batch writes)
- Flush storage before logging out
- Flush when reloading the tab
- Create initStorage method
JC Brand 4 years ago
parent
commit
57ccf4c20e

+ 53 - 54
package-lock.json

@@ -2836,13 +2836,6 @@
 						"lodash": "^4.17.11"
 					}
 				},
-				"strophe.js": {
-					"version": "github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f",
-					"from": "strophe.js@github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f",
-					"requires": {
-						"abab": "^2.0.3"
-					}
-				},
 				"twemoji": {
 					"version": "12.1.5",
 					"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.5.tgz",
@@ -2866,12 +2859,19 @@
 				}
 			}
 		},
+		"@converse/openpromise": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmjs.org/@converse/openpromise/-/openpromise-0.0.1.tgz",
+			"integrity": "sha512-oA1TKrm6H838isYZJxMWXpXyOUezkD49eMJ6bkI+FfL2MsVuOV3ZbhBV+c07mLSknKXO7pUbWTVa5f7bXJXYjQ=="
+		},
 		"@converse/skeletor": {
-			"version": "github:conversejs/skeletor#482acb03ff4ec92a5cabccf023294912a574132c",
-			"from": "github:conversejs/skeletor#482acb03ff4ec92a5cabccf023294912a574132c",
+			"version": "0.0.3",
+			"resolved": "https://registry.npmjs.org/@converse/skeletor/-/skeletor-0.0.3.tgz",
+			"integrity": "sha512-U/xzBxfTPeBJphu42/4ttGQoawfAIP9b1TdXtb/1Q0tYI04JUJICWdHA5Oen4lljcHbiAJmMYB8w5o3Rcn+hBw==",
 			"requires": {
 				"lit-html": "^1.2.1",
-				"lodash-es": "^4.17.15"
+				"lodash-es": "^4.17.15",
+				"mergebounce": "0.0.2"
 			}
 		},
 		"@discoveryjs/json-ext": {
@@ -4488,9 +4488,9 @@
 			}
 		},
 		"@octokit/openapi-types": {
-			"version": "6.1.0",
-			"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.1.0.tgz",
-			"integrity": "sha512-Z9fDZVbGj4dFLErEoXUSuZhk3wJ8KVGnbrUwoPijsQ9EyNwOeQ+U2jSqaHUz8WtgIWf0aeO59oJyhMpWCKaabg==",
+			"version": "6.2.0",
+			"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.2.0.tgz",
+			"integrity": "sha512-V2vFYuawjpP5KUb8CPYsq20bXT4qnE8sH1QKpYqUlcNOntBiRr/VzGVvY0s+YXGgrVbFUVO4EI0VnHYSVBWfBg==",
 			"dev": true
 		},
 		"@octokit/plugin-enterprise-rest": {
@@ -4632,18 +4632,18 @@
 			}
 		},
 		"@octokit/types": {
-			"version": "6.13.1",
-			"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.1.tgz",
-			"integrity": "sha512-UF/PL0y4SKGx/p1azFf7e6j9lB78tVwAFvnHtslzOJ6VipshYks74qm9jjTEDlCyaTmbhbk2h3Run5l0CtCF6A==",
+			"version": "6.14.0",
+			"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.14.0.tgz",
+			"integrity": "sha512-43qHvDsPsKgNt4W4al3dyU6s2XZ7ZMsiiIw8rQcM9CyEo7g9W8/6m1W4xHuRqmEjTfG1U4qsE/E4Jftw1/Ak1g==",
 			"dev": true,
 			"requires": {
-				"@octokit/openapi-types": "^6.0.0"
+				"@octokit/openapi-types": "^6.2.0"
 			}
 		},
 		"@sinonjs/commons": {
-			"version": "1.7.2",
-			"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz",
-			"integrity": "sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==",
+			"version": "1.8.2",
+			"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz",
+			"integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==",
 			"dev": true,
 			"requires": {
 				"type-detect": "4.0.8"
@@ -4658,20 +4658,10 @@
 				"@sinonjs/commons": "^1.7.0"
 			}
 		},
-		"@sinonjs/formatio": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz",
-			"integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==",
-			"dev": true,
-			"requires": {
-				"@sinonjs/commons": "^1",
-				"@sinonjs/samsam": "^5.0.2"
-			}
-		},
 		"@sinonjs/samsam": {
-			"version": "5.0.3",
-			"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz",
-			"integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==",
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz",
+			"integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==",
 			"dev": true,
 			"requires": {
 				"@sinonjs/commons": "^1.6.0",
@@ -12328,9 +12318,9 @@
 			}
 		},
 		"is-core-module": {
-			"version": "2.2.0",
-			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
-			"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz",
+			"integrity": "sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==",
 			"dev": true,
 			"requires": {
 				"has": "^1.0.3"
@@ -12821,9 +12811,9 @@
 			}
 		},
 		"just-extend": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz",
-			"integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==",
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz",
+			"integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==",
 			"dev": true
 		},
 		"karma": {
@@ -13935,6 +13925,15 @@
 			"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
 			"dev": true
 		},
+		"mergebounce": {
+			"version": "0.0.2",
+			"resolved": "https://registry.npmjs.org/mergebounce/-/mergebounce-0.0.2.tgz",
+			"integrity": "sha512-1nxx6ljFJkx26WlwQLzbaBQc6lDg7mqdHPhIDixpOW+7Idx6DdPBrUZCwinihWbw33B1/YhZbdLU7dAf1vyC6w==",
+			"requires": {
+				"@converse/openpromise": "0.0.1",
+				"lodash-es": "^4.17.21"
+			}
+		},
 		"methods": {
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -14369,9 +14368,9 @@
 			"dev": true
 		},
 		"nise": {
-			"version": "4.0.3",
-			"resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz",
-			"integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==",
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz",
+			"integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==",
 			"dev": true,
 			"requires": {
 				"@sinonjs/commons": "^1.7.0",
@@ -21244,17 +21243,16 @@
 			"dev": true
 		},
 		"sinon": {
-			"version": "9.0.2",
-			"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz",
-			"integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==",
+			"version": "9.2.4",
+			"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz",
+			"integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==",
 			"dev": true,
 			"requires": {
-				"@sinonjs/commons": "^1.7.2",
+				"@sinonjs/commons": "^1.8.1",
 				"@sinonjs/fake-timers": "^6.0.1",
-				"@sinonjs/formatio": "^5.0.1",
-				"@sinonjs/samsam": "^5.0.3",
+				"@sinonjs/samsam": "^5.3.1",
 				"diff": "^4.0.2",
-				"nise": "^4.0.1",
+				"nise": "^4.0.4",
 				"supports-color": "^7.1.0"
 			},
 			"dependencies": {
@@ -21265,9 +21263,9 @@
 					"dev": true
 				},
 				"supports-color": {
-					"version": "7.1.0",
-					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
-					"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+					"version": "7.2.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+					"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
 					"dev": true,
 					"requires": {
 						"has-flag": "^4.0.0"
@@ -22167,8 +22165,9 @@
 			}
 		},
 		"strophe.js": {
-			"version": "github:strophe/strophejs#ae0131a48a2080c51a8f6f312ecc46361e96b098",
-			"from": "github:strophe/strophejs#ae0131a48a2080c51a8f6f312ecc46361e96b098",
+			"version": "1.4.2",
+			"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.4.2.tgz",
+			"integrity": "sha512-jkyZQCZLm7Zgmra0zJKxpHPNIUncYj/e/eYfgxFoc5gwrWeHWigNBs0q7wtqhCiqG6Qxcf22PUpcyBq8cK+9ew==",
 			"requires": {
 				"abab": "^2.0.3",
 				"ws": "^7.0.0",

+ 1 - 1
package.json

@@ -117,7 +117,7 @@
     "prettierx": "^0.12.1",
     "run-headless-chromium": "^0.1.1",
     "sass-loader": "^8.0.2",
-    "sinon": "^9.0.2",
+    "sinon": "^9.2.4",
     "style-loader": "^0.23.1",
     "urijs": "^1.19.6",
     "webpack": "^4.43.0",

+ 10 - 16
src/headless/core.js

@@ -20,6 +20,7 @@ import syncDriver from 'localforage-webextensionstorage-driver/sync';
 import u from '@converse/headless/utils/core';
 import { Collection } from "@converse/skeletor/src/collection";
 import { Connection, MockConnection } from '@converse/headless/shared/connection.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import {
     clearUserSettings,
     extendAppSettings,
@@ -347,7 +348,13 @@ export const api = _converse.api = {
          * @method _converse.api.user.logout
          * @example _converse.api.user.logout();
          */
-        logout () {
+        async logout () {
+            /**
+             * Triggered before the user is logged out
+             * @event _converse#beforeLogout
+             */
+            await api.trigger('beforeLogout', {'synchronous': true});
+
             const promise = u.getResolveablePromise();
             const complete = () => {
                 // Recreate all the promises
@@ -773,19 +780,6 @@ function initPersistentStorage () {
     _converse.storage['persistent'] = Storage.localForage.createInstance(config);
 }
 
-
-_converse.getDefaultStore = function () {
-    if (_converse.config.get('trusted')) {
-        const is_non_persistent = api.settings.get('persistent_store') === 'sessionStorage';
-        return is_non_persistent ? 'session': 'persistent';
-    } else {
-        return 'session';
-    }
-}
-
-_converse.createStore = createStore;
-
-
 function initPlugins () {
     // If initialize gets called a second time (e.g. during tests), then we
     // need to re-apply all plugins (for a new converse instance), and we
@@ -993,8 +987,8 @@ async function initSession (jid) {
     const bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase();
     const id = `converse.session-${bare_jid}`;
     if (_converse.session?.get('id') !== id) {
-        _converse.session = new Model({id});
-        _converse.session.browserStorage = createStore(id, is_shared_session ? "persistent" : "session");
+        _converse.session = new Model({ id });
+        initStorage(_converse.session, id, is_shared_session ? "persistent" : "session");
         await new Promise(r => _converse.session.fetch({'success': r, 'error': r}));
 
         if (!is_shared_session && _converse.session.get('active')) {

+ 0 - 18
src/headless/package-lock.json

@@ -73,30 +73,12 @@
 			"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
 			"dev": true
 		},
-		"strophe.js": {
-			"version": "1.4.0",
-			"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.4.0.tgz",
-			"integrity": "sha512-Y+Smiwb2Q+bMqd0b7k5Qp46y3MvyxOTB+qANcL5W+UPlTWMlJZkdR7KkPFeqSMn7UTNnZa1yhkdWYqCu2SITPQ==",
-			"dev": true,
-			"requires": {
-				"abab": "^2.0.3",
-				"ws": "^7.0.0",
-				"xmldom": "^0.1.27"
-			}
-		},
 		"ws": {
 			"version": "7.3.1",
 			"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
 			"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
 			"dev": true,
 			"optional": true
-		},
-		"xmldom": {
-			"version": "0.1.31",
-			"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
-			"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==",
-			"dev": true,
-			"optional": true
 		}
 	}
 }

+ 3 - 3
src/headless/package.json

@@ -36,16 +36,16 @@
   },
   "gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2",
   "devDependencies": {
-    "@converse/skeletor": "conversejs/skeletor#482acb03ff4ec92a5cabccf023294912a574132c",
+    "@converse/skeletor": "0.0.3",
     "filesize": "^6.1.0",
     "localforage": "^1.9.0",
+    "localforage-driver-memory": "^1.0.5",
     "localforage-setitems": "^1.4.0",
     "localforage-webextensionstorage-driver": "^2.0.0",
-    "localforage-driver-memory": "^1.0.5",
     "lodash-es": "^4.17.15",
     "pluggable.js": "2.0.1",
     "sizzle": "^2.3.5",
     "sprintf-js": "^1.1.2",
-    "strophe.js": "strophe/strophejs#ae0131a48a2080c51a8f6f312ecc46361e96b098"
+    "strophe.js": "1.4.2"
   }
 }

+ 2 - 1
src/headless/plugins/bookmarks/collection.js

@@ -3,6 +3,7 @@ import Bookmark from './model.js';
 import log from "@converse/headless/log.js";
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { Strophe, $iq, sizzle } = converse.env;
 const u = converse.env.utils;
@@ -24,7 +25,7 @@ const Bookmarks = {
 
         const cache_key = `converse.room-bookmarks${_converse.bare_jid}`;
         this.fetched_flag = cache_key+'fetched';
-        this.browserStorage = _converse.createStore(cache_key);
+        initStorage(this, cache_key);
     },
 
     async openBookmarkedRoom (bookmark) {

+ 4 - 4
src/headless/plugins/chat/model.js

@@ -6,6 +6,7 @@ import log from '@converse/headless/log';
 import pick from "lodash/pick";
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api, converse } from "../../core.js";
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { parseMessage } from './parsers.js';
 import { sendMarker } from '@converse/headless/shared/actions';
 
@@ -62,7 +63,6 @@ const ChatBox = ModelWithContact.extend({
         }
         this.on('change:chat_state', this.sendChatState, this);
 
-
         await this.fetchMessages();
         /**
          * Triggered once a {@link _converse.ChatBox} has been created and initialized.
@@ -96,7 +96,8 @@ const ChatBox = ModelWithContact.extend({
             api.trigger('afterMessagesFetched', this);
         });
         this.messages.chatbox = this;
-        this.messages.browserStorage = _converse.createStore(this.getMessagesCacheKey());
+        initStorage(this.messages, this.getMessagesCacheKey());
+
         this.listenTo(this.messages, 'change:upload', message => {
             if (message.get('upload') === _converse.SUCCESS) {
                 api.send(this.createMessageStanza(message));
@@ -852,8 +853,7 @@ const ChatBox = ModelWithContact.extend({
     async createMessage (attrs, options) {
         attrs.time = attrs.time || (new Date()).toISOString();
         await this.messages.fetched;
-        const p = this.messages.create(attrs, Object.assign({'wait': true, 'promise':true}, options));
-        return p;
+        return this.messages.create(attrs, options);
     },
 
     /**

+ 2 - 1
src/headless/plugins/chatboxes/chatboxes.js

@@ -1,5 +1,6 @@
 import { Collection } from "@converse/skeletor/src/collection";
 import { _converse, api } from "../../core.js";
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const ChatBoxes = Collection.extend({
     comparator: 'time_opened',
@@ -24,7 +25,7 @@ const ChatBoxes = Collection.extend({
 
     onConnected (reconnecting) {
         if (reconnecting) { return; }
-        this.browserStorage = _converse.createStore(`converse.chatboxes-${_converse.bare_jid}`);
+        initStorage(this, `converse.chatboxes-${_converse.bare_jid}`);
         this.fetch({
             'add': true,
             'success': c => this.onChatBoxesFetched(c)

+ 5 - 1
src/headless/plugins/muc/muc.js

@@ -11,6 +11,7 @@ import { Model } from '@converse/skeletor/src/model.js';
 import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
 import { _converse, api, converse } from '../../core.js';
 import { computeAffiliationsDelta, setAffiliations, getAffiliationList }  from './affiliations/utils.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { isArchived } from '@converse/headless/shared/parsers';
 import { parseMUCMessage, parseMUCPresence } from './parsers.js';
 import { sendMarker } from '@converse/headless/shared/actions';
@@ -346,7 +347,7 @@ const ChatRoomMixin = {
     restoreSession () {
         const id = `muc.session-${_converse.bare_jid}-${this.get('jid')}`;
         this.session = new MUCSession({ id });
-        this.session.browserStorage = _converse.createStore(id, 'session');
+        initStorage(this.session, id, 'session');
         return new Promise(r => this.session.fetch({ 'success': r, 'error': r }));
     },
 
@@ -362,10 +363,12 @@ const ChatRoomMixin = {
             )
         );
         this.features.browserStorage = _converse.createStore(id, 'session');
+        this.features.listenTo(_converse, 'beforeLogout', () => this.features.browserStorage.flush());
 
         id = `converse.muc-config-{_converse.bare_jid}-${this.get('jid')}`;
         this.config = new Model();
         this.config.browserStorage = _converse.createStore(id, 'session');
+        this.config.listenTo(_converse, 'beforeLogout', () => this.config.browserStorage.flush());
     },
 
     initOccupants () {
@@ -373,6 +376,7 @@ const ChatRoomMixin = {
         const id = `converse.occupants-${_converse.bare_jid}${this.get('jid')}`;
         this.occupants.browserStorage = _converse.createStore(id, 'session');
         this.occupants.chatroom = this;
+        this.occupants.listenTo(_converse, 'beforeLogout', () => this.occupants.browserStorage.flush());
     },
 
     fetchOccupants () {

+ 2 - 1
src/headless/plugins/roster/contacts.js

@@ -5,6 +5,7 @@ import { Collection } from "@converse/skeletor/src/collection";
 import { Model } from "@converse/skeletor/src/model";
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { Strophe, $iq, sizzle } = converse.env;
 const u = converse.env.utils;
@@ -16,7 +17,7 @@ const RosterContacts = Collection.extend({
     initialize () {
         const id = `roster.state-${_converse.bare_jid}-${this.get('jid')}`;
         this.state = new Model({ id, 'collapsed_groups': [] });
-        this.state.browserStorage = _converse.createStore(id);
+        initStorage(this.state, id);
         this.state.fetch();
     },
 

+ 2 - 2
src/headless/plugins/roster/index.js

@@ -1,5 +1,4 @@
 /**
- * @module converse-roster
  * @copyright The Converse.js contributors
  * @license Mozilla Public License (MPLv2)
  */
@@ -13,6 +12,7 @@ import { Presence, Presences } from './presence.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { clearPresences, initRoster, updateUnreadCounter } from './utils.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { $pres } = converse.env;
 
@@ -168,7 +168,7 @@ converse.plugins.add('converse-roster', {
             } else {
                 _converse.presences = new _converse.Presences();
                 const id = `converse.presences-${_converse.bare_jid}`;
-                _converse.presences.browserStorage = _converse.createStore(id, "session");
+                initStorage(_converse.presences, id, 'session');
                 // We might be continuing an existing session, so we fetch
                 // cached presence data.
                 _converse.presences.fetch();

+ 4 - 2
src/headless/plugins/roster/presence.js

@@ -1,7 +1,8 @@
 import isNaN from "lodash/isNaN";
 import { Collection } from "@converse/skeletor/src/collection";
 import { Model } from '@converse/skeletor/src/model.js';
-import { _converse, converse } from "@converse/headless/core";
+import { converse } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { Strophe, dayjs, sizzle } = converse.env;
 
@@ -17,7 +18,8 @@ export const Presence = Model.extend({
     initialize () {
         this.resources = new Resources();
         const id = `converse.identities-${this.get('jid')}`;
-        this.resources.browserStorage = _converse.createStore(id, "session");
+        initStorage(this.resources, id, 'session');
+
         this.listenTo(this.resources, 'update', this.onResourcesChanged);
         this.listenTo(this.resources, 'change', this.onResourcesChanged);
     },

+ 3 - 2
src/headless/plugins/roster/utils.js

@@ -1,5 +1,6 @@
 import { _converse, api } from "@converse/headless/core";
 import { Model } from '@converse/skeletor/src/model.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 
 export async function initRoster () {
@@ -8,12 +9,12 @@ export async function initRoster () {
     await api.waitUntil('VCardsInitialized');
     _converse.roster = new _converse.RosterContacts();
     let id = `converse.contacts-${_converse.bare_jid}`;
-    _converse.roster.browserStorage = _converse.createStore(id);
+    initStorage(_converse.roster, id);
 
     _converse.roster.data = new Model();
     id = `converse-roster-model-${_converse.bare_jid}`;
     _converse.roster.data.id = id;
-    _converse.roster.data.browserStorage = _converse.createStore(id);
+    initStorage(_converse.roster.data, id);
     _converse.roster.data.fetch();
     /**
      * Triggered once the `_converse.RosterContacts`

+ 3 - 2
src/headless/plugins/status.js

@@ -6,6 +6,7 @@
 import isNaN from "lodash-es/isNaN";
 import isObject from "lodash-es/isObject";
 import { Model } from '@converse/skeletor/src/model.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { _converse, api, converse } from "@converse/headless/core";
 
 const { Strophe, $build, $pres } = converse.env;
@@ -214,8 +215,8 @@ converse.plugins.add('converse-status', {
                 onStatusInitialized(reconnecting);
             } else {
                 const id = `converse.xmppstatus-${_converse.bare_jid}`;
-                _converse.xmppstatus = new _converse.XMPPStatus({'id': id});
-                _converse.xmppstatus.browserStorage = _converse.createStore(id, "session");
+                _converse.xmppstatus = new _converse.XMPPStatus({ id });
+                initStorage(_converse.xmppstatus, id, 'session');
                 _converse.xmppstatus.fetch({
                     'success': () => onStatusInitialized(reconnecting),
                     'error': () => onStatusInitialized(reconnecting),

+ 3 - 1
src/headless/plugins/vcard.js

@@ -8,6 +8,7 @@ import log from "@converse/headless/log";
 import { Collection } from "@converse/skeletor/src/collection";
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api, converse } from "../core.js";
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { Strophe, $iq, dayjs } = converse.env;
 const u = converse.env.utils;
@@ -213,7 +214,8 @@ converse.plugins.add('converse-vcard', {
 
         _converse.initVCardCollection = async function () {
             _converse.vcards = new _converse.VCards();
-            _converse.vcards.browserStorage = _converse.createStore(`${_converse.bare_jid}-converse.vcards`);
+            const id = `${_converse.bare_jid}-converse.vcards`;
+            initStorage(_converse.vcards, id);
             await new Promise(resolve => {
                 _converse.vcards.fetch({
                     'success': resolve,

+ 2 - 2
src/headless/shared/settings.js

@@ -5,7 +5,7 @@ import log from '@converse/headless/log';
 import pick from 'lodash/pick';
 import u from '@converse/headless/utils/core';
 import { Model } from '@converse/skeletor/src/model.js';
-import { createStore } from '@converse/headless/shared/utils.js';
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 let init_settings = {}; // Container for settings passed in via converse.initialize
 let app_settings = {};
@@ -108,7 +108,7 @@ function initUserSettings () {
     if (!user_settings?.fetched) {
         const id = `converse.user-settings.${_converse.bare_jid}`;
         user_settings = new Model({id});
-        user_settings.browserStorage = createStore(id);
+        initStorage(user_settings, id);
         user_settings.fetched = user_settings.fetch({'promise': true});
     }
     return user_settings.fetched;

+ 16 - 4
src/headless/shared/utils.js

@@ -1,7 +1,7 @@
 import Storage from '@converse/skeletor/src/storage.js';
-import { _converse, api } from '@converse/headless/core';
 import log from '@converse/headless/log';
 import u from '@converse/headless/utils/core';
+import { _converse, api } from '@converse/headless/core';
 
 export function getDefaultStore () {
     if (_converse.config.get('trusted')) {
@@ -12,12 +12,24 @@ export function getDefaultStore () {
     }
 }
 
-export function createStore (id, storage) {
-    const s = _converse.storage[storage || getDefaultStore()];
+export function createStore (id, store) {
+    const name = store || getDefaultStore();
+    const s = _converse.storage[name];
     if (typeof s === 'undefined') {
         throw new TypeError(`createStore: Could not find store for %{id}`);
     }
-    return new Storage(id, s);
+    return new Storage(id, s, api.settings.get('persistent_store') === 'IndexedDB');
+}
+
+export function initStorage (model, id, type) {
+    const store = type || getDefaultStore();
+    model.browserStorage = _converse.createStore(id, store);
+    if (store === 'persistent' && api.settings.get('persistent_store') === 'IndexedDB') {
+        const flush = () => model.browserStorage.flush();
+        window.addEventListener(_converse.unloadevent, flush);
+        model.on('destroy', () => window.removeEventListener(_converse.unloadevent, flush));
+        model.listenTo(_converse, 'beforeLogout', flush);
+    }
 }
 
 export function replacePromise (name) {

+ 2 - 1
src/plugins/bookmark-views/bookmarks-list.js

@@ -1,6 +1,7 @@
 import tpl_bookmarks_list from './templates/list.js';
 import { ElementView } from '@converse/skeletor/src/element.js';
 import { _converse, api, converse } from '@converse/headless/core';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { render } from 'lit-html';
 
 const { Strophe } = converse.env;
@@ -19,7 +20,7 @@ export default class BookmarksView extends ElementView {
 
         const id = `converse.room-bookmarks${_converse.bare_jid}-list-model`;
         this.model = new _converse.BookmarksList({ id });
-        this.model.browserStorage = _converse.createStore(id);
+        initStorage(this.model, id);
         this.model.fetch({ 'success': () => this.render(), 'error': () => this.render() });
     }
 

+ 2 - 1
src/plugins/minimize/view.js

@@ -2,6 +2,7 @@ import MinimizedChatsToggle from './toggle.js';
 import tpl_chats_panel from './templates/chats-panel.js';
 import { ElementView } from '@converse/skeletor/src/element.js';
 import { _converse, api } from '@converse/headless/core';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { render } from 'lit-html';
 
 
@@ -34,7 +35,7 @@ class MinimizedChats extends ElementView {
     async initToggle () {
         const id = `converse.minchatstoggle-${_converse.bare_jid}`;
         this.minchats = new MinimizedChatsToggle({id});
-        this.minchats.browserStorage = _converse.createStore(id, 'session');
+        initStorage(this.minchats, id, 'session');
         await new Promise(resolve => this.minchats.fetch({'success': resolve, 'error': resolve}));
     }
 

+ 6 - 1
src/plugins/omemo/devicelist.js

@@ -1,6 +1,7 @@
 import log from '@converse/headless/log';
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api, converse } from '@converse/headless/core';
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { restoreOMEMOSession } from './utils.js';
 
 const { Strophe, $build, $iq, sizzle } = converse.env;
@@ -14,9 +15,13 @@ const DeviceList = Model.extend({
     idAttribute: 'jid',
 
     initialize () {
+        this.initDevices();
+    },
+
+    initDevices () {
         this.devices = new _converse.Devices();
         const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`;
-        this.devices.browserStorage = _converse.createStore(id);
+        initStorage(this.devices, id);
         this.fetchDevices();
     },
 

+ 4 - 4
src/plugins/omemo/utils.js

@@ -4,6 +4,7 @@ import log from '@converse/headless/log';
 import { __ } from 'i18n';
 import { _converse, converse, api } from '@converse/headless/core';
 import { html } from 'lit-html';
+import { initStorage } from '@converse/headless/shared/utils.js';
 
 const { Strophe, sizzle, u } = converse.env;
 
@@ -405,8 +406,8 @@ export function registerPEPPushHandler () {
 export function restoreOMEMOSession () {
     if (_converse.omemo_store === undefined) {
         const id = `converse.omemosession-${_converse.bare_jid}`;
-        _converse.omemo_store = new _converse.OMEMOStore({ 'id': id });
-        _converse.omemo_store.browserStorage = _converse.createStore(id);
+        _converse.omemo_store = new _converse.OMEMOStore({ id });
+        initStorage(_converse.omemo_store, id);
     }
     return _converse.omemo_store.fetchSession();
 }
@@ -433,8 +434,7 @@ export async function initOMEMO () {
     }
     _converse.devicelists = new _converse.DeviceLists();
     const id = `converse.devicelists-${_converse.bare_jid}`;
-    _converse.devicelists.browserStorage = _converse.createStore(id);
-
+    initStorage(_converse.devicelists, id);
     try {
         await fetchOwnDevices();
         await restoreOMEMOSession();

+ 1 - 2
src/plugins/profile/statusview.js

@@ -28,9 +28,8 @@ class ProfileView extends ElementViewWithAvatar {
     async initialize () {
         this.model = _converse.xmppstatus;
         this.listenTo(this.model, "change", this.render);
-        this.listenTo(this.model.vcard, "change", this.render);
-
         await api.waitUntil('VCardsInitialized');
+        this.listenTo(this.model.vcard, "change", this.render);
         this.render();
     }
 

+ 3 - 4
src/plugins/roomslist/view.js

@@ -4,6 +4,7 @@ import { ElementView } from '@converse/skeletor/src/element.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { render } from 'lit-html';
 
 const { Strophe } = converse.env;
@@ -31,10 +32,8 @@ export class RoomsList extends ElementView {
 
     initialize () {
         const id = `converse.roomspanel${_converse.bare_jid}`;
-        this.model = new (RoomsListModel.extend({
-            id,
-            'browserStorage': _converse.createStore(id)
-        }))();
+        this.model = new RoomsListModel({ id });
+        initStorage(this.model, id);
         this.model.fetch();
 
         this.listenTo(_converse.chatboxes, 'add', this.renderIfChatRoom)

+ 2 - 1
src/plugins/rosterview/filterview.js

@@ -3,6 +3,7 @@ import tpl_roster_filter from "./templates/roster_filter.js";
 import { ElementView } from '@converse/skeletor/src/element.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api } from "@converse/headless/core";
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { render } from 'lit-html';
 
 export const RosterFilter = Model.extend({
@@ -21,7 +22,7 @@ export class RosterFilterView extends ElementView {
     initialize () {
         const model = new _converse.RosterFilter();
         model.id = `_converse.rosterfilter-${_converse.bare_jid}`;
-        model.browserStorage = _converse.createStore(model.id);
+        initStorage(model, model.id);
         this.model = model;
         _converse.roster_filter = model;
 

+ 2 - 1
src/shared/chat/emoji-picker.js

@@ -6,6 +6,7 @@ import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { html } from "lit-element";
+import { initStorage } from '@converse/headless/shared/utils.js';
 import { tpl_emoji_picker } from "./templates/emoji-picker.js";
 import { until } from 'lit-html/directives/until.js';
 
@@ -296,7 +297,7 @@ export class EmojiDropdown extends BaseDropdown {
                 await api.emojis.initialize()
                 const id = `converse.emoji-${_converse.bare_jid}-${this.chatview.model.get('jid')}`;
                 this.model = new _converse.EmojiPicker({'id': id});
-                this.model.browserStorage = _converse.createStore(id);
+                initStorage(this.model, id);
                 await new Promise(resolve => this.model.fetch({'success': resolve, 'error': resolve}));
                 // We never want still be in the autocompleting state upon page load
                 this.model.set({'autocompleting': null, 'ac_position': null});

+ 1 - 0
webpack.html

@@ -31,6 +31,7 @@
         modtools_disable_query: ['moderator', 'participant', 'visitor'],
         enable_smacks: true,
         // connection_options: { 'worker': '/dist/shared-connection-worker.js' },
+        persistent_store: 'IndexedDB',
         message_archiving: 'always',
         muc_domain: 'conference.chat.example.org',
         muc_respect_autojoin: true,