diff --git a/src/client.js b/src/client.js index 577cf5ba8..d5ca91059 100644 --- a/src/client.js +++ b/src/client.js @@ -44,22 +44,11 @@ const ContentHelpers = require("./content-helpers"); import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; -import {InvalidStoreError} from './errors'; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. Promise.config({warnings: false}); -const LAZY_LOADING_MESSAGES_FILTER = { - lazy_load_members: true, -}; - -const LAZY_LOADING_SYNC_FILTER = { - room: { - state: LAZY_LOADING_MESSAGES_FILTER, - }, -}; - const SCROLLBACK_DELAY_MS = 3000; let CRYPTO_ENABLED = false; @@ -2071,7 +2060,7 @@ MatrixClient.prototype.getEventTimeline = function(timelineSet, eventId) { let params = undefined; if (this._clientOpts.lazyLoadMembers) { - params = {filter: JSON.stringify(LAZY_LOADING_MESSAGES_FILTER)}; + params = {filter: JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER)}; } // TODO: we should implement a backoff (as per scrollback()) to deal more @@ -2152,7 +2141,7 @@ function(roomId, fromToken, limit, dir, timelineFilter = undefined) { if (this._clientOpts.lazyLoadMembers) { // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER, // so the timelineFilter doesn't get written into it below - filter = Object.assign({}, LAZY_LOADING_MESSAGES_FILTER); + filter = Object.assign({}, Filter.LAZY_LOADING_MESSAGES_FILTER); } if (timelineFilter) { // XXX: it's horrific that /messages' filter parameter doesn't match @@ -3109,29 +3098,6 @@ MatrixClient.prototype.startClient = async function(opts) { // shallow-copy the opts dict before modifying and storing it opts = Object.assign({}, opts); - if (opts.lazyLoadMembers && this.isGuest()) { - opts.lazyLoadMembers = false; - } - if (opts.lazyLoadMembers) { - const supported = await this.doesServerSupportLazyLoading(); - if (supported) { - opts.filter = await this.createFilter(LAZY_LOADING_SYNC_FILTER); - } else { - console.log("LL: lazy loading requested but not supported " + - "by server, so disabling"); - opts.lazyLoadMembers = false; - } - } - // need to vape the store when enabling LL and wasn't enabled before - const shouldClear = await this._wasLazyLoadingToggled(opts.lazyLoadMembers); - if (shouldClear) { - const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; - throw new InvalidStoreError(reason, !!opts.lazyLoadMembers); - } - if (opts.lazyLoadMembers && this._crypto) { - this._crypto.enableLazyLoading(); - } - opts.crypto = this._crypto; opts.canResetEntireTimeline = (roomId) => { if (!this._canResetTimelineCallback) { @@ -3140,40 +3106,19 @@ MatrixClient.prototype.startClient = async function(opts) { return this._canResetTimelineCallback(roomId); }; this._clientOpts = opts; - await this._storeClientOptions(this._clientOpts); this._syncApi = new SyncApi(this, opts); this._syncApi.sync(); }; -/** - * Is the lazy loading option different than in previous session? - * @param {bool} lazyLoadMembers current options for lazy loading - * @return {bool} whether or not the option has changed compared to the previous session */ -MatrixClient.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) { - lazyLoadMembers = !!lazyLoadMembers; - // assume it was turned off before - // if we don't know any better - let lazyLoadMembersBefore = false; - const isStoreNewlyCreated = await this.store.isNewlyCreated(); - if (!isStoreNewlyCreated) { - const prevClientOptions = await this.store.getClientOptions(); - if (prevClientOptions) { - lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers; - } - return lazyLoadMembersBefore !== lazyLoadMembers; - } - return false; -}; - /** * store client options with boolean/string/numeric values * to know in the next session what flags the sync data was * created with (e.g. lazy loading) * @param {object} opts the complete set of client options * @return {Promise} for store operation */ -MatrixClient.prototype._storeClientOptions = function(opts) { +MatrixClient.prototype._storeClientOptions = function() { const primTypes = ["boolean", "string", "number"]; - const serializableOpts = Object.entries(opts) + const serializableOpts = Object.entries(this._clientOpts) .filter(([key, value]) => { return primTypes.includes(typeof value); }) diff --git a/src/errors.js b/src/errors.js index 04e14f2c8..337b058db 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,7 +2,7 @@ // because of http://babeljs.io/docs/usage/caveats/#classes function InvalidStoreError(reason, value) { const message = `Store is invalid because ${reason}, ` + - `please delete all data and retry`; + `please stopthe client, delete all data and start the client again`; const instance = Reflect.construct(Error, [message]); Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this)); instance.reason = reason; diff --git a/src/filter.js b/src/filter.js index e0f03daa7..a63fcee13 100644 --- a/src/filter.js +++ b/src/filter.js @@ -51,6 +51,17 @@ function Filter(userId, filterId) { this.definition = {}; } +Filter.LAZY_LOADING_MESSAGES_FILTER = { + lazy_load_members: true, +}; + +Filter.LAZY_LOADING_SYNC_FILTER = { + room: { + state: Filter.LAZY_LOADING_MESSAGES_FILTER, + }, +}; + + /** * Get the ID of this filter on your homeserver (if known) * @return {?Number} The filter ID diff --git a/src/sync.js b/src/sync.js index 39e82abe4..634d07e1e 100644 --- a/src/sync.js +++ b/src/sync.js @@ -33,6 +33,8 @@ const utils = require("./utils"); const Filter = require("./filter"); const EventTimeline = require("./models/event-timeline"); +import {InvalidStoreError} from './errors'; + const DEBUG = true; // /sync requests allow you to set a timeout= but the request may continue @@ -100,6 +102,7 @@ function SyncApi(client, opts) { this._connectionReturnedDefer = null; this._notifEvents = []; // accumulator of sync events in the current sync response this._failedSyncCount = 0; // Number of consecutive failed /sync requests + this._storeIsInvalid = false; // flag set if the store needs to be cleared before we can start if (client.getNotifTimelineSet()) { client.reEmitter.reEmit(client.getNotifTimelineSet(), @@ -422,6 +425,28 @@ SyncApi.prototype.recoverFromSyncStartupError = async function(savedSyncPromise, await keepaliveProm; }; +/** + * Is the lazy loading option different than in previous session? + * @param {bool} lazyLoadMembers current options for lazy loading + * @return {bool} whether or not the option has changed compared to the previous session */ +SyncApi.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) { + lazyLoadMembers = !!lazyLoadMembers; + // assume it was turned off before + // if we don't know any better + let lazyLoadMembersBefore = false; + const isStoreNewlyCreated = await this.client.store.isNewlyCreated(); + console.log("store newly created? "+isStoreNewlyCreated); + if (!isStoreNewlyCreated) { + const prevClientOptions = await this.client.store.getClientOptions(); + if (prevClientOptions) { + lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers; + } + console.log("prev ll: "+lazyLoadMembersBefore); + return lazyLoadMembersBefore !== lazyLoadMembers; + } + return false; +}; + /** * Main entry point */ @@ -444,6 +469,8 @@ SyncApi.prototype.sync = function() { // 1) We need to get push rules so we can check if events should bing as we get // them from /sync. // 2) We need to get/create a filter which we can use for /sync. + // 3) We need to check the lazy loading option matches what was used in the + // stored sync. If it doesn't, we can't use the stored sync. async function getPushRules() { try { @@ -458,9 +485,49 @@ SyncApi.prototype.sync = function() { getPushRules(); return; } - getFilter(); // Now get the filter and start syncing + checkLazyLoadStatus(); // advance to the next stage } + const checkLazyLoadStatus = async () => { + if (this.opts.lazyLoadMembers && client.isGuest()) { + this.opts.lazyLoadMembers = false; + } + if (this.opts.lazyLoadMembers) { + const supported = await client.doesServerSupportLazyLoading(); + console.log("server supports ll? "+supported); + if (supported) { + this.opts.filter = await client.createFilter( + Filter.LAZY_LOADING_SYNC_FILTER, + ); + } else { + console.log("LL: lazy loading requested but not supported " + + "by server, so disabling"); + this.opts.lazyLoadMembers = false; + } + } + // need to vape the store when enabling LL and wasn't enabled before + const shouldClear = await this._wasLazyLoadingToggled(this.opts.lazyLoadMembers); + console.log("was toggled? "+shouldClear); + if (shouldClear) { + this._storeIsInvalid = true; + const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; + const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers); + this._updateSyncState("ERROR", { error }); + // bail out of the sync loop now: the app needs to respond to this error. + // we leave the state as 'ERROR' which isn't great since this normally means + // we're retrying. The client must be stopped before clearing the stores anyway + // so the app should stop the client, clear the store and start it again. + console.warn("InvalidStoreError: store is not usable: stopping sync."); + return; + } + if (this.opts.lazyLoadMembers && this._crypto) { + this.opts.crypto.enableLazyLoading(); + } + await this.client._storeClientOptions(); + + getFilter(); // Now get the filter and start syncing + }; + async function getFilter() { let filter; if (self.opts.filter) { @@ -588,7 +655,12 @@ SyncApi.prototype._syncFromCache = async function(savedSync) { console.error("Error processing cached sync", e.stack || e); } - this._updateSyncState("PREPARED", syncEventData); + // Don't emit a prepared if we've bailed because the store is invalid: + // in this case the client will not be usable until stopped & restarted + // so this would be useless and misleading. + if (!this._storeIsInvalid) { + this._updateSyncState("PREPARED", syncEventData); + } }; /**