matrix-js-sdk
Version:
Matrix Client-Server SDK for Javascript
253 lines (208 loc) • 8.83 kB
JavaScript
;
var _getIterator2 = require("babel-runtime/core-js/get-iterator");
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _slicedToArray2 = require("babel-runtime/helpers/slicedToArray");
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _bluebird = require("bluebird");
var _bluebird2 = _interopRequireDefault(_bluebird);
var _memory = require("./memory");
var _utils = require("../utils");
var _utils2 = _interopRequireDefault(_utils);
var _indexeddbLocalBackend = require("./indexeddb-local-backend.js");
var _indexeddbLocalBackend2 = _interopRequireDefault(_indexeddbLocalBackend);
var _indexeddbRemoteBackend = require("./indexeddb-remote-backend.js");
var _indexeddbRemoteBackend2 = _interopRequireDefault(_indexeddbRemoteBackend);
var _user = require("../models/user");
var _user2 = _interopRequireDefault(_user);
var _event = require("../models/event");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* This is an internal module. See {@link IndexedDBStore} for the public class.
* @module store/indexeddb
*/
// If this value is too small we'll be writing very often which will cause
// noticable stop-the-world pauses. If this value is too big we'll be writing
// so infrequently that the /sync size gets bigger on reload. Writing more
// often does not affect the length of the pause since the entire /sync
// response is persisted each time.
var WRITE_DELAY_MS = 1000 * 60 * 5; // once every 5 minutes
/**
* Construct a new Indexed Database store, which extends MatrixInMemoryStore.
*
* This store functions like a MatrixInMemoryStore except it periodically persists
* the contents of the store to an IndexedDB backend.
*
* All data is still kept in-memory but can be loaded from disk by calling
* <code>startup()</code>. This can make startup times quicker as a complete
* sync from the server is not required. This does not reduce memory usage as all
* the data is eagerly fetched when <code>startup()</code> is called.
* <pre>
* let opts = { localStorage: window.localStorage };
* let store = new IndexedDBStore();
* await store.startup(); // load from indexed db
* let client = sdk.createClient({
* store: store,
* });
* client.startClient();
* client.on("sync", function(state, prevState, data) {
* if (state === "PREPARED") {
* console.log("Started up, now with go faster stripes!");
* }
* });
* </pre>
*
* @constructor
* @extends MatrixInMemoryStore
* @param {Object} opts Options object.
* @param {Object} opts.indexedDB The Indexed DB interface e.g.
* <code>window.indexedDB</code>
* @param {string=} opts.dbName Optional database name. The same name must be used
* to open the same database.
* @param {string=} opts.workerScript Optional URL to a script to invoke a web
* worker with to run IndexedDB queries on the web worker. The IndexedDbStoreWorker
* class is provided for this purpose and requires the application to provide a
* trivial wrapper script around it.
* @param {Object=} opts.workerApi The webWorker API object. If omitted, the global Worker
* object will be used if it exists.
* @prop {IndexedDBStoreBackend} backend The backend instance. Call through to
* this API if you need to perform specific indexeddb actions like deleting the
* database.
*/
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var IndexedDBStore = function IndexedDBStore(opts) {
_memory.MatrixInMemoryStore.call(this, opts);
if (!opts.indexedDB) {
throw new Error('Missing required option: indexedDB');
}
if (opts.workerScript) {
// try & find a webworker-compatible API
var workerApi = opts.workerApi;
if (!workerApi) {
// default to the global Worker object (which is where it in a browser)
workerApi = global.Worker;
}
this.backend = new _indexeddbRemoteBackend2.default(opts.workerScript, opts.dbName, workerApi);
} else {
this.backend = new _indexeddbLocalBackend2.default(opts.indexedDB, opts.dbName);
}
this.startedUp = false;
this._syncTs = 0;
// Records the last-modified-time of each user at the last point we saved
// the database, such that we can derive the set if users that have been
// modified since we last saved.
this._userModifiedMap = {
// user_id : timestamp
};
};
_utils2.default.inherits(IndexedDBStore, _memory.MatrixInMemoryStore);
/**
* @return {Promise} Resolved when loaded from indexed db.
*/
IndexedDBStore.prototype.startup = function () {
var _this = this;
if (this.startedUp) {
console.log("IndexedDBStore.startup: already started");
return _bluebird2.default.resolve();
}
console.log("IndexedDBStore.startup: connecting to backend");
return this.backend.connect().then(function () {
console.log("IndexedDBStore.startup: loading presence events");
return _this.backend.getUserPresenceEvents();
}).then(function (userPresenceEvents) {
console.log("IndexedDBStore.startup: processing presence events");
userPresenceEvents.forEach(function (_ref) {
var _ref2 = (0, _slicedToArray3.default)(_ref, 2),
userId = _ref2[0],
rawEvent = _ref2[1];
var u = new _user2.default(userId);
if (rawEvent) {
u.setPresenceEvent(new _event.MatrixEvent(rawEvent));
}
_this._userModifiedMap[u.userId] = u.getLastModifiedTime();
_this.storeUser(u);
});
});
};
/**
* @return {Promise} Resolves with a sync response to restore the
* client state to where it was at the last save, or null if there
* is no saved sync data.
*/
IndexedDBStore.prototype.getSavedSync = function () {
return this.backend.getSavedSync();
};
/**
* Delete all data from this store.
* @return {Promise} Resolves if the data was deleted from the database.
*/
IndexedDBStore.prototype.deleteAllData = function () {
_memory.MatrixInMemoryStore.prototype.deleteAllData.call(this);
return this.backend.clearDatabase().then(function () {
console.log("Deleted indexeddb data.");
}, function (err) {
console.error("Failed to delete indexeddb data: " + err);
throw err;
});
};
/**
* Possibly write data to the database.
* @return {Promise} Promise resolves after the write completes.
*/
IndexedDBStore.prototype.save = function () {
var now = Date.now();
if (now - this._syncTs > WRITE_DELAY_MS) {
return this._reallySave();
}
return _bluebird2.default.resolve();
};
IndexedDBStore.prototype._reallySave = function () {
this._syncTs = Date.now(); // set now to guard against multi-writes
// work out changed users (this doesn't handle deletions but you
// can't 'delete' users as they are just presence events).
var userTuples = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(this.getUsers()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var u = _step.value;
if (this._userModifiedMap[u.userId] === u.getLastModifiedTime()) continue;
if (!u.events.presence) continue;
userTuples.push([u.userId, u.events.presence.event]);
// note that we've saved this version of the user
this._userModifiedMap[u.userId] = u.getLastModifiedTime();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return this.backend.syncToDatabase(userTuples).catch(function (err) {
console.error("sync fail:", err);
});
};
IndexedDBStore.prototype.setSyncData = function (syncData) {
return this.backend.setSyncData(syncData);
};
module.exports.IndexedDBStore = IndexedDBStore;
//# sourceMappingURL=indexeddb.js.map