From 95d303a8ddc2da290c5b387241f3a5305642b93f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 10:18:59 +0100 Subject: [PATCH 01/13] Small tidy-up to sync.ts --- src/sync.ts | 87 ++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 4abd4fb5bb2..a5eb37d9f82 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -100,13 +100,11 @@ const MSC2716_ROOM_VERSIONS = [ function getFilterName(userId: string, suffix?: string): string { // scope this on the user ID because people may login on many accounts // and they all need to be stored! - return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : ""); + return `FILTER_SYNC_${userId}${suffix ? "_" + suffix : ""}`; } function debuglog(...params) { - if (!DEBUG) { - return; - } + if (!DEBUG) return; logger.log(...params); } @@ -286,7 +284,7 @@ export class SyncApi { * historical messages are shown when we paginate `/messages` again. * @param {Room} room The room where the marker event was sent * @param {MatrixEvent} markerEvent The new marker event - * @param {ISetStateOptions} setStateOptions When `timelineWasEmpty` is set + * @param {IMarkerFoundOptions} setStateOptions When `timelineWasEmpty` is set * as `true`, the given marker event will be ignored */ private onMarkerStateEvent( @@ -622,8 +620,12 @@ export class SyncApi { this.running = true; - if (global.window && global.window.addEventListener) { - global.window.addEventListener("online", this.onOnline, false); + global.window?.addEventListener?.("online", this.onOnline, false); + + if (client.isGuest()) { + // no push rules for guests, no access to POST filter for guests. + this.doSync({}); + return; } let savedSyncPromise = Promise.resolve(); @@ -715,14 +717,14 @@ export class SyncApi { const getFilter = async () => { debuglog("Getting filter..."); - let filter; + let filter: Filter; if (this.opts.filter) { filter = this.opts.filter; } else { filter = buildDefaultFilter(); } - let filterId; + let filterId: string; try { filterId = await client.getOrCreateFilter(getFilterName(client.credentials.userId), filter); } catch (err) { @@ -754,32 +756,27 @@ export class SyncApi { this.doSync({ filterId }); }; - if (client.isGuest()) { - // no push rules for guests, no access to POST filter for guests. - this.doSync({}); - } else { - // Pull the saved sync token out first, before the worker starts sending - // all the sync data which could take a while. This will let us send our - // first incremental sync request before we've processed our saved data. - debuglog("Getting saved sync token..."); - savedSyncPromise = client.store.getSavedSyncToken().then((tok) => { - debuglog("Got saved sync token"); - savedSyncToken = tok; - debuglog("Getting saved sync..."); - return client.store.getSavedSync(); - }).then((savedSync) => { - debuglog(`Got reply from saved sync, exists? ${!!savedSync}`); - if (savedSync) { - return this.syncFromCache(savedSync); - } - }).catch(err => { - logger.error("Getting saved sync failed", err); - }); - // Now start the first incremental sync request: this can also - // take a while so if we set it going now, we can wait for it - // to finish while we process our saved sync data. - getPushRules(); - } + // Pull the saved sync token out first, before the worker starts sending + // all the sync data which could take a while. This will let us send our + // first incremental sync request before we've processed our saved data. + debuglog("Getting saved sync token..."); + savedSyncPromise = client.store.getSavedSyncToken().then((tok) => { + debuglog("Got saved sync token"); + savedSyncToken = tok; + debuglog("Getting saved sync..."); + return client.store.getSavedSync(); + }).then((savedSync) => { + debuglog(`Got reply from saved sync, exists? ${!!savedSync}`); + if (savedSync) { + return this.syncFromCache(savedSync); + } + }).catch(err => { + logger.error("Getting saved sync failed", err); + }); + // Now start the first incremental sync request: this can also + // take a while so if we set it going now, we can wait for it + // to finish while we process our saved sync data. + getPushRules(); } /** @@ -791,9 +788,7 @@ export class SyncApi { // global.window AND global.window.removeEventListener. // Some platforms (e.g. React Native) register global.window, // but do not have global.window.removeEventListener. - if (global.window && global.window.removeEventListener) { - global.window.removeEventListener("online", this.onOnline, false); - } + global.window?.removeEventListener?.("online", this.onOnline, false); this.running = false; this.currentSyncRequest?.abort(); if (this.keepAliveTimer) { @@ -878,7 +873,7 @@ export class SyncApi { const syncToken = client.store.getSyncToken(); - let data; + let data: ISyncResponse; try { //debuglog('Starting sync since=' + syncToken); if (this.currentSyncRequest === null) { @@ -974,7 +969,7 @@ export class SyncApi { private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams { let pollTimeout = this.opts.pollTimeout; - if (this.getSyncState() !== 'SYNCING' || this.catchingUp) { + if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) { // unless we are happily syncing already, we want the server to return // as quickly as possible, even if there are no events queued. This // serves two purposes: @@ -1013,7 +1008,7 @@ export class SyncApi { qps._cacheBuster = Date.now(); } - if (this.getSyncState() == 'ERROR' || this.getSyncState() == 'RECONNECTING') { + if (this.getSyncState() == SyncState.Error || this.getSyncState() == SyncState.Reconnecting) { // we think the connection is dead. If it comes back up, we won't know // about it till /sync returns. If the timeout= is high, this could // be a long time. Set it to 0 when doing retries so we don't have to wait @@ -1133,7 +1128,7 @@ export class SyncApi { // - The isBrandNewRoom boilerplate is boilerplatey. // handle presence events (User objects) - if (data.presence && Array.isArray(data.presence.events)) { + if (Array.isArray(data.presence?.events)) { data.presence.events.map(client.getEventMapper()).forEach( function(presenceEvent) { let user = client.store.getUser(presenceEvent.getSender()); @@ -1149,7 +1144,7 @@ export class SyncApi { } // handle non-room account_data - if (data.account_data && Array.isArray(data.account_data.events)) { + if (Array.isArray(data.account_data?.events)) { const events = data.account_data.events.map(client.getEventMapper()); const prevEventsMap = events.reduce((m, c) => { m[c.getId()] = client.store.getAccountData(c.getType()); @@ -1290,8 +1285,7 @@ export class SyncApi { // bother setting it here. We trust our calculations better than the // server's for this case, and therefore will assume that our non-zero // count is accurate. - if (!encrypted - || (encrypted && room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0)) { + if (!encrypted || room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0) { room.setUnreadNotificationCount( NotificationCountType.Highlight, joinObj.unread_notifications.highlight_count, @@ -1304,8 +1298,7 @@ export class SyncApi { if (joinObj.isBrandNewRoom) { // set the back-pagination token. Do this *before* adding any // events so that clients can start back-paginating. - room.getLiveTimeline().setPaginationToken( - joinObj.timeline.prev_batch, EventTimeline.BACKWARDS); + room.getLiveTimeline().setPaginationToken(joinObj.timeline.prev_batch, EventTimeline.BACKWARDS); } else if (joinObj.timeline.limited) { let limited = true; From 5063c2527922cebc936edfc575d5ec4ef666ffc2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 12:13:40 +0100 Subject: [PATCH 02/13] Convert doSync into a while loop --- src/sync.ts | 154 +++++++++++++++++++++++++--------------------------- 1 file changed, 75 insertions(+), 79 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index a5eb37d9f82..761f032272e 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -859,103 +859,99 @@ export class SyncApi { * @param {boolean} syncOptions.hasSyncedBefore */ private async doSync(syncOptions: ISyncOptions): Promise { - const client = this.client; + while (this.running) { + const syncToken = this.client.store.getSyncToken(); - if (!this.running) { - debuglog("Sync no longer running: exiting."); - if (this.connectionReturnedDefer) { - this.connectionReturnedDefer.reject(); - this.connectionReturnedDefer = null; + let data: ISyncResponse; + try { + //debuglog('Starting sync since=' + syncToken); + if (this.currentSyncRequest === null) { + this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken); + } + data = await this.currentSyncRequest; + } catch (e) { + this.onSyncError(e, syncOptions); + break; + } finally { + this.currentSyncRequest = null; } - this.updateSyncState(SyncState.Stopped); - return; - } - - const syncToken = client.store.getSyncToken(); - let data: ISyncResponse; - try { - //debuglog('Starting sync since=' + syncToken); - if (this.currentSyncRequest === null) { - this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken); - } - data = await this.currentSyncRequest; - } catch (e) { - this.onSyncError(e, syncOptions); - return; - } finally { - this.currentSyncRequest = null; - } + //debuglog('Completed sync, next_batch=' + data.next_batch); - //debuglog('Completed sync, next_batch=' + data.next_batch); + // set the sync token NOW *before* processing the events. We do this so + // if something barfs on an event we can skip it rather than constantly + // polling with the same token. + this.client.store.setSyncToken(data.next_batch); - // set the sync token NOW *before* processing the events. We do this so - // if something barfs on an event we can skip it rather than constantly - // polling with the same token. - client.store.setSyncToken(data.next_batch); + // Reset after a successful sync + this.failedSyncCount = 0; - // Reset after a successful sync - this.failedSyncCount = 0; + await this.client.store.setSyncData(data); - await client.store.setSyncData(data); + const syncEventData = { + oldSyncToken: syncToken, + nextSyncToken: data.next_batch, + catchingUp: this.catchingUp, + }; - const syncEventData = { - oldSyncToken: syncToken, - nextSyncToken: data.next_batch, - catchingUp: this.catchingUp, - }; + if (this.opts.crypto) { + // tell the crypto module we're about to process a sync + // response + await this.opts.crypto.onSyncWillProcess(syncEventData); + } - if (this.opts.crypto) { - // tell the crypto module we're about to process a sync - // response - await this.opts.crypto.onSyncWillProcess(syncEventData); - } + try { + await this.processSyncResponse(syncEventData, data); + } catch (e) { + // log the exception with stack if we have it, else fall back + // to the plain description + logger.error("Caught /sync error", e); + + // Emit the exception for client handling + this.client.emit(ClientEvent.SyncUnexpectedError, e); + } - try { - await this.processSyncResponse(syncEventData, data); - } catch (e) { - // log the exception with stack if we have it, else fall back - // to the plain description - logger.error("Caught /sync error", e); + // update this as it may have changed + syncEventData.catchingUp = this.catchingUp; - // Emit the exception for client handling - this.client.emit(ClientEvent.SyncUnexpectedError, e); - } + // emit synced events + if (!syncOptions.hasSyncedBefore) { + this.updateSyncState(SyncState.Prepared, syncEventData); + syncOptions.hasSyncedBefore = true; + } - // update this as it may have changed - syncEventData.catchingUp = this.catchingUp; + // tell the crypto module to do its processing. It may block (to do a + // /keys/changes request). + if (this.opts.crypto) { + await this.opts.crypto.onSyncCompleted(syncEventData); + } - // emit synced events - if (!syncOptions.hasSyncedBefore) { - this.updateSyncState(SyncState.Prepared, syncEventData); - syncOptions.hasSyncedBefore = true; - } + // keep emitting SYNCING -> SYNCING for clients who want to do bulk updates + this.updateSyncState(SyncState.Syncing, syncEventData); + + if (this.client.store.wantsSave()) { + // We always save the device list (if it's dirty) before saving the sync data: + // this means we know the saved device list data is at least as fresh as the + // stored sync data which means we don't have to worry that we may have missed + // device changes. We can also skip the delay since we're not calling this very + // frequently (and we don't really want to delay the sync for it). + if (this.opts.crypto) { + await this.opts.crypto.saveDeviceList(0); + } - // tell the crypto module to do its processing. It may block (to do a - // /keys/changes request). - if (this.opts.crypto) { - await this.opts.crypto.onSyncCompleted(syncEventData); + // tell databases that everything is now in a consistent state and can be saved. + this.client.store.save(); + } } - // keep emitting SYNCING -> SYNCING for clients who want to do bulk updates - this.updateSyncState(SyncState.Syncing, syncEventData); - - if (client.store.wantsSave()) { - // We always save the device list (if it's dirty) before saving the sync data: - // this means we know the saved device list data is at least as fresh as the - // stored sync data which means we don't have to worry that we may have missed - // device changes. We can also skip the delay since we're not calling this very - // frequently (and we don't really want to delay the sync for it). - if (this.opts.crypto) { - await this.opts.crypto.saveDeviceList(0); + if (!this.running) { + debuglog("Sync no longer running: exiting."); + if (this.connectionReturnedDefer) { + this.connectionReturnedDefer.reject(); + this.connectionReturnedDefer = null; } - - // tell databases that everything is now in a consistent state and can be saved. - client.store.save(); + this.updateSyncState(SyncState.Stopped); } - - // Begin next sync - this.doSync(syncOptions); } private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IAbortablePromise { From 4b8c791870d0863ac49a388d91364eae164d4411 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 12:31:23 +0100 Subject: [PATCH 03/13] Apply `initialSyncLimit` only to initial syncs --- src/client.ts | 3 +- src/sync.ts | 291 ++++++++++++++++++++++++++------------------------ 2 files changed, 152 insertions(+), 142 deletions(-) diff --git a/src/client.ts b/src/client.ts index e1d805d9d88..28f2094f83c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -390,8 +390,7 @@ export interface IStartClientOpts { pollTimeout?: number; /** - * The filter to apply to /sync calls. This will override the opts.initialSyncLimit, which would - * normally result in a timeline limit filter. + * The filter to apply to /sync calls. */ filter?: Filter; diff --git a/src/sync.ts b/src/sync.ts index 761f032272e..645c983c6fd 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -437,7 +437,7 @@ export class SyncApi { // XXX: copypasted from /sync until we kill off this minging v1 API stuff) // handle presence events (User objects) - if (response.presence && Array.isArray(response.presence)) { + if (Array.isArray(response.presence)) { response.presence.map(client.getEventMapper()).forEach( function(presenceEvent) { let user = client.store.getUser(presenceEvent.getContent().user_id); @@ -612,160 +612,128 @@ export class SyncApi { return false; } - /** - * Main entry point - */ - public sync(): void { - const client = this.client; - - this.running = true; - - global.window?.addEventListener?.("online", this.onOnline, false); - - if (client.isGuest()) { - // no push rules for guests, no access to POST filter for guests. - this.doSync({}); - return; + private getPushRules = async () => { + try { + debuglog("Getting push rules..."); + const result = await this.client.getPushRules(); + debuglog("Got push rules"); + + this.client.pushRules = result; + } catch (err) { + logger.error("Getting push rules failed", err); + if (this.shouldAbortSync(err)) return; + // wait for saved sync to complete before doing anything else, + // otherwise the sync state will end up being incorrect + debuglog("Waiting for saved sync before retrying push rules..."); + await this.recoverFromSyncStartupError(this.savedSyncPromise, err); + return this.getPushRules(); // try again } + }; - let savedSyncPromise = Promise.resolve(); - let savedSyncToken = null; - - // We need to do one-off checks before we can begin the /sync loop. - // These are: - // 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. + private buildDefaultFilter = () => { + return new Filter(this.client.credentials.userId); + }; - const getPushRules = async () => { - try { - debuglog("Getting push rules..."); - const result = await client.getPushRules(); - debuglog("Got push rules"); - - client.pushRules = result; - } catch (err) { - logger.error("Getting push rules failed", err); - if (this.shouldAbortSync(err)) return; - // wait for saved sync to complete before doing anything else, - // otherwise the sync state will end up being incorrect - debuglog("Waiting for saved sync before retrying push rules..."); - await this.recoverFromSyncStartupError(savedSyncPromise, err); - getPushRules(); - return; + private checkLazyLoadStatus = async () => { + debuglog("Checking lazy load status..."); + if (this.opts.lazyLoadMembers && this.client.isGuest()) { + this.opts.lazyLoadMembers = false; + } + if (this.opts.lazyLoadMembers) { + debuglog("Checking server lazy load support..."); + const supported = await this.client.doesServerSupportLazyLoading(); + if (supported) { + debuglog("Enabling lazy load on sync filter..."); + if (!this.opts.filter) { + this.opts.filter = this.buildDefaultFilter(); + } + this.opts.filter.setLazyLoadMembers(true); + } else { + debuglog("LL: lazy loading requested but not supported " + + "by server, so disabling"); + this.opts.lazyLoadMembers = false; } - checkLazyLoadStatus(); // advance to the next stage - }; + } + // need to vape the store when enabling LL and wasn't enabled before + debuglog("Checking whether lazy loading has changed in store..."); + const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers); + if (shouldClear) { + this.storeIsInvalid = true; + const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; + const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers); + this.updateSyncState(SyncState.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. + logger.warn("InvalidStoreError: store is not usable: stopping sync."); + return; + } + if (this.opts.lazyLoadMembers) { + this.opts.crypto?.enableLazyLoading(); + } + try { + debuglog("Storing client options..."); + await this.client.storeClientOptions(); + debuglog("Stored client options"); + } catch (err) { + logger.error("Storing client options failed", err); + throw err; + } + }; - const buildDefaultFilter = () => { - const filter = new Filter(client.credentials.userId); - filter.setTimelineLimit(this.opts.initialSyncLimit); - return filter; - }; + private getFilter = async (): Promise<{ + filterId: string; + filter: Filter; + }> => { + debuglog("Getting filter..."); + let filter: Filter; + if (this.opts.filter) { + filter = this.opts.filter; + } else { + filter = this.buildDefaultFilter(); + } - const checkLazyLoadStatus = async () => { - debuglog("Checking lazy load status..."); - if (this.opts.lazyLoadMembers && client.isGuest()) { - this.opts.lazyLoadMembers = false; - } - if (this.opts.lazyLoadMembers) { - debuglog("Checking server lazy load support..."); - const supported = await client.doesServerSupportLazyLoading(); - if (supported) { - debuglog("Enabling lazy load on sync filter..."); - if (!this.opts.filter) { - this.opts.filter = buildDefaultFilter(); - } - this.opts.filter.setLazyLoadMembers(true); - } else { - debuglog("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 - debuglog("Checking whether lazy loading has changed in store..."); - const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers); - if (shouldClear) { - this.storeIsInvalid = true; - const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; - const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers); - this.updateSyncState(SyncState.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. - logger.warn("InvalidStoreError: store is not usable: stopping sync."); - return; - } - if (this.opts.lazyLoadMembers && this.opts.crypto) { - this.opts.crypto.enableLazyLoading(); - } - try { - debuglog("Storing client options..."); - await this.client.storeClientOptions(); - debuglog("Stored client options"); - } catch (err) { - logger.error("Storing client options failed", err); - throw err; - } + let filterId: string; + try { + filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter); + } catch (err) { + logger.error("Getting filter failed", err); + if (this.shouldAbortSync(err)) return; + // wait for saved sync to complete before doing anything else, + // otherwise the sync state will end up being incorrect + debuglog("Waiting for saved sync before retrying filter..."); + await this.recoverFromSyncStartupError(this.savedSyncPromise, err); + return this.getFilter(); // try again + } + return { filter, filterId }; + }; - getFilter(); // Now get the filter and start syncing - }; + private savedSyncPromise: Promise; - const getFilter = async () => { - debuglog("Getting filter..."); - let filter: Filter; - if (this.opts.filter) { - filter = this.opts.filter; - } else { - filter = buildDefaultFilter(); - } + /** + * Main entry point + */ + public async sync(): Promise { + this.running = true; - let filterId: string; - try { - filterId = await client.getOrCreateFilter(getFilterName(client.credentials.userId), filter); - } catch (err) { - logger.error("Getting filter failed", err); - if (this.shouldAbortSync(err)) return; - // wait for saved sync to complete before doing anything else, - // otherwise the sync state will end up being incorrect - debuglog("Waiting for saved sync before retrying filter..."); - await this.recoverFromSyncStartupError(savedSyncPromise, err); - getFilter(); - return; - } - // reset the notifications timeline to prepare it to paginate from - // the current point in time. - // The right solution would be to tie /sync pagination tokens into - // /notifications API somehow. - client.resetNotifTimelineSet(); - - if (this.currentSyncRequest === null) { - // Send this first sync request here so we can then wait for the saved - // sync data to finish processing before we process the results of this one. - debuglog("Sending first sync request..."); - this.currentSyncRequest = this.doSyncRequest({ filterId }, savedSyncToken); - } + global.window?.addEventListener?.("online", this.onOnline, false); - // Now wait for the saved sync to finish... - debuglog("Waiting for saved sync before starting sync processing..."); - await savedSyncPromise; - this.doSync({ filterId }); - }; + if (this.client.isGuest()) { + // no push rules for guests, no access to POST filter for guests. + return this.doSync({}); + } // Pull the saved sync token out first, before the worker starts sending // all the sync data which could take a while. This will let us send our // first incremental sync request before we've processed our saved data. debuglog("Getting saved sync token..."); - savedSyncPromise = client.store.getSavedSyncToken().then((tok) => { + const savedSyncTokenPromise = this.client.store.getSavedSyncToken().then(tok => { debuglog("Got saved sync token"); - savedSyncToken = tok; - debuglog("Getting saved sync..."); - return client.store.getSavedSync(); - }).then((savedSync) => { + return tok; + }); + + this.savedSyncPromise = this.client.store.getSavedSync().then((savedSync) => { debuglog(`Got reply from saved sync, exists? ${!!savedSync}`); if (savedSync) { return this.syncFromCache(savedSync); @@ -773,10 +741,53 @@ export class SyncApi { }).catch(err => { logger.error("Getting saved sync failed", err); }); + + // We need to do one-off checks before we can begin the /sync loop. + // These are: + // 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. + // Now start the first incremental sync request: this can also // take a while so if we set it going now, we can wait for it // to finish while we process our saved sync data. - getPushRules(); + await this.getPushRules(); + await this.checkLazyLoadStatus(); + const { filterId, filter } = await this.getFilter(); + + // reset the notifications timeline to prepare it to paginate from + // the current point in time. + // The right solution would be to tie /sync pagination tokens into + // /notifications API somehow. + this.client.resetNotifTimelineSet(); + + if (this.currentSyncRequest === null) { + let firstSyncFilter = filterId; + const savedSyncToken = await savedSyncTokenPromise; + + if (savedSyncToken) { + debuglog("Sending first sync request..."); + } else { + debuglog("Sending initial sync request..."); + const initialFilter = this.buildDefaultFilter(); + initialFilter.setDefinition(filter.getDefinition()); + initialFilter.setTimelineLimit(this.opts.initialSyncLimit); + // Use an inline filter, no point uploading it for a single usage + firstSyncFilter = JSON.stringify(initialFilter.getDefinition()); + } + + // Send this first sync request here so we can then wait for the saved + // sync data to finish processing before we process the results of this one. + this.currentSyncRequest = this.doSyncRequest({ filterId: firstSyncFilter }, savedSyncToken); + } + + // Now wait for the saved sync to finish... + debuglog("Waiting for saved sync before starting sync processing..."); + await this.savedSyncPromise; + // process the first sync request and continue syncing with the normal filterId + return this.doSync({ filterId }); } /** From 56e9f4cb5c38b3c32507b67a811294397e8bf7a2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 12:36:33 +0100 Subject: [PATCH 04/13] Convert matrix-client-syncing spec to TS --- ....spec.js => matrix-client-syncing.spec.ts} | 168 ++++++++++-------- spec/test-utils/test-utils.ts | 6 +- 2 files changed, 94 insertions(+), 80 deletions(-) rename spec/integ/{matrix-client-syncing.spec.js => matrix-client-syncing.spec.ts} (93%) diff --git a/spec/integ/matrix-client-syncing.spec.js b/spec/integ/matrix-client-syncing.spec.ts similarity index 93% rename from spec/integ/matrix-client-syncing.spec.js rename to spec/integ/matrix-client-syncing.spec.ts index 0c571707ad3..42b3e099947 100644 --- a/spec/integ/matrix-client-syncing.spec.js +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -14,13 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventTimeline, MatrixEvent, RoomEvent, RoomStateEvent, RoomMemberEvent } from "../../src"; -import { UNSTABLE_MSC2716_MARKER } from "../../src/@types/event"; +import { + EventTimeline, + MatrixEvent, + RoomEvent, + RoomStateEvent, + RoomMemberEvent, + UNSTABLE_MSC2716_MARKER, + MatrixClient, +} from "../../src"; import * as utils from "../test-utils/test-utils"; import { TestClient } from "../TestClient"; -describe("MatrixClient syncing", function() { - let client = null; +describe("MatrixClient syncing", () => { + let client: MatrixClient = null; let httpBackend = null; const selfUserId = "@alice:localhost"; const selfAccessToken = "aseukfgwef"; @@ -31,7 +38,7 @@ describe("MatrixClient syncing", function() { const roomOne = "!foo:localhost"; const roomTwo = "!bar:localhost"; - beforeEach(function() { + beforeEach(() => { const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken); httpBackend = testClient.httpBackend; client = testClient.client; @@ -40,39 +47,38 @@ describe("MatrixClient syncing", function() { httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); }); - afterEach(function() { + afterEach(() => { httpBackend.verifyNoOutstandingExpectation(); client.stopClient(); return httpBackend.stop(); }); - describe("startClient", function() { + describe("startClient", () => { const syncData = { next_batch: "batch_token", rooms: {}, presence: {}, }; - it("should /sync after /pushrules and /filter.", function(done) { + it("should /sync after /pushrules and /filter.", (done) => { httpBackend.when("GET", "/sync").respond(200, syncData); client.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend.flushAllExpected().then(() => { done(); }); }); - it("should pass the 'next_batch' token from /sync to the since= param " + - " of the next /sync", function(done) { + it("should pass the 'next_batch' token from /sync to the since= param of the next /sync", (done) => { httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").check(function(req) { + httpBackend.when("GET", "/sync").check((req) => { expect(req.queryParams.since).toEqual(syncData.next_batch); }).respond(200, syncData); client.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend.flushAllExpected().then(() => { done(); }); }); @@ -184,7 +190,7 @@ describe("MatrixClient syncing", function() { }); }); - describe("resolving invites to profile info", function() { + describe("resolving invites to profile info", () => { const syncData = { next_batch: "s_5_3", presence: { @@ -197,7 +203,7 @@ describe("MatrixClient syncing", function() { }, }; - beforeEach(function() { + beforeEach(() => { syncData.presence.events = []; syncData.rooms.join[roomOne] = { timeline: { @@ -226,7 +232,7 @@ describe("MatrixClient syncing", function() { }; }); - it("should resolve incoming invites from /sync", function() { + it("should resolve incoming invites from /sync", () => { syncData.rooms.join[roomOne].state.events.push( utils.mkMembership({ room: roomOne, mship: "invite", user: userC, @@ -248,19 +254,21 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Boss"); expect( - member.getAvatarUrl("home.server.url", null, null, null, false), + member.getAvatarUrl("home.server.url", null, null, null, false, false), ).toBeTruthy(); }); }); - it("should use cached values from m.presence wherever possible", function() { + it("should use cached values from m.presence wherever possible", () => { syncData.presence.events = [ utils.mkPresence({ - user: userC, presence: "online", name: "The Ghost", + user: userC, + presence: "online", + name: "The Ghost", }), ]; syncData.rooms.join[roomOne].state.events.push( @@ -278,16 +286,18 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Ghost"); }); }); - it("should result in events on the room member firing", function() { + it("should result in events on the room member firing", () => { syncData.presence.events = [ utils.mkPresence({ - user: userC, presence: "online", name: "The Ghost", + user: userC, + presence: "online", + name: "The Ghost", }), ]; syncData.rooms.join[roomOne].state.events.push( @@ -299,7 +309,7 @@ describe("MatrixClient syncing", function() { httpBackend.when("GET", "/sync").respond(200, syncData); let latestFiredName = null; - client.on(RoomMemberEvent.Name, function(event, m) { + client.on(RoomMemberEvent.Name, (event, m) => { if (m.userId === userC && m.roomId === roomOne) { latestFiredName = m.name; } @@ -312,12 +322,12 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { expect(latestFiredName).toEqual("The Ghost"); }); }); - it("should no-op if resolveInvitesToProfiles is not set", function() { + it("should no-op if resolveInvitesToProfiles is not set", () => { syncData.rooms.join[roomOne].state.events.push( utils.mkMembership({ room: roomOne, mship: "invite", user: userC, @@ -331,33 +341,34 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); expect(member.name).toEqual(userC); expect( - member.getAvatarUrl("home.server.url", null, null, null, false), + member.getAvatarUrl("home.server.url", null, null, null, false, false), ).toBe(null); }); }); }); - describe("users", function() { + describe("users", () => { const syncData = { next_batch: "nb", presence: { events: [ utils.mkPresence({ - user: userA, presence: "online", + user: userA, + presence: "online", }), utils.mkPresence({ - user: userB, presence: "unavailable", + user: userB, + presence: "unavailable", }), ], }, }; - it("should create users for presence events from /sync", - function() { + it("should create users for presence events from /sync", () => { httpBackend.when("GET", "/sync").respond(200, syncData); client.startClient(); @@ -365,14 +376,14 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { expect(client.getUser(userA).presence).toEqual("online"); expect(client.getUser(userB).presence).toEqual("unavailable"); }); }); }); - describe("room state", function() { + describe("room state", () => { const msgText = "some text here"; const otherDisplayName = "Bob Smith"; @@ -478,7 +489,7 @@ describe("MatrixClient syncing", function() { }, }; - it("should continually recalculate the right room name.", function() { + it("should continually recalculate the right room name.", () => { httpBackend.when("GET", "/sync").respond(200, syncData); httpBackend.when("GET", "/sync").respond(200, nextSyncData); @@ -487,7 +498,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomOne); // should have clobbered the name to the one from /events expect(room.name).toEqual( @@ -496,7 +507,7 @@ describe("MatrixClient syncing", function() { }); }); - it("should store the right events in the timeline.", function() { + it("should store the right events in the timeline.", () => { httpBackend.when("GET", "/sync").respond(200, syncData); httpBackend.when("GET", "/sync").respond(200, nextSyncData); @@ -505,7 +516,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomTwo); // should have added the message from /events expect(room.timeline.length).toEqual(2); @@ -513,7 +524,7 @@ describe("MatrixClient syncing", function() { }); }); - it("should set the right room name.", function() { + it("should set the right room name.", () => { httpBackend.when("GET", "/sync").respond(200, syncData); httpBackend.when("GET", "/sync").respond(200, nextSyncData); @@ -521,14 +532,14 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomTwo); // should use the display name of the other person. expect(room.name).toEqual(otherDisplayName); }); }); - it("should set the right user's typing flag.", function() { + it("should set the right user's typing flag.", () => { httpBackend.when("GET", "/sync").respond(200, syncData); httpBackend.when("GET", "/sync").respond(200, nextSyncData); @@ -537,7 +548,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomTwo); let member = room.getMember(otherUserId); expect(member).toBeTruthy(); @@ -552,7 +563,7 @@ describe("MatrixClient syncing", function() { // events that arrive in the incremental sync as if they preceeded the // timeline events, however this breaks peeking, so it's disabled // (see sync.js) - xit("should correctly interpret state in incremental sync.", function() { + xit("should correctly interpret state in incremental sync.", () => { httpBackend.when("GET", "/sync").respond(200, syncData); httpBackend.when("GET", "/sync").respond(200, nextSyncData); @@ -560,7 +571,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomOne); const stateAtStart = room.getLiveTimeline().getState( EventTimeline.BACKWARDS, @@ -576,11 +587,11 @@ describe("MatrixClient syncing", function() { }); }); - xit("should update power levels for users in a room", function() { + xit("should update power levels for users in a room", () => { }); - xit("should update the room topic", function() { + xit("should update the room topic", () => { }); @@ -829,7 +840,7 @@ describe("MatrixClient syncing", function() { const room = client.getRoom(roomOne); let emitCount = 0; - room.on(RoomEvent.HistoryImportedWithinTimeline, function(markerEvent, room) { + room.on(RoomEvent.HistoryImportedWithinTimeline, (markerEvent, room) => { expect(markerEvent.getId()).toEqual(markerEventId); expect(room.roomId).toEqual(roomOne); emitCount += 1; @@ -1028,7 +1039,7 @@ describe("MatrixClient syncing", function() { const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` + `${encodeURIComponent(eventsInRoom[0].event_id)}`; httpBackend.when("GET", contextUrl) - .respond(200, function() { + .respond(200, () => { return { start: "start_token", events_before: [EVENTS[1], EVENTS[0]], @@ -1056,8 +1067,8 @@ describe("MatrixClient syncing", function() { }); }); - describe("timeline", function() { - beforeEach(function() { + describe("timeline", () => { + beforeEach(() => { const syncData = { next_batch: "batch_token", rooms: { @@ -1084,7 +1095,7 @@ describe("MatrixClient syncing", function() { ]); }); - it("should set the back-pagination token on new rooms", function() { + it("should set the back-pagination token on new rooms", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1107,7 +1118,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomTwo); expect(room).toBeTruthy(); const tok = room.getLiveTimeline() @@ -1116,7 +1127,7 @@ describe("MatrixClient syncing", function() { }); }); - it("should set the back-pagination token on gappy syncs", function() { + it("should set the back-pagination token on gappy syncs", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1138,7 +1149,7 @@ describe("MatrixClient syncing", function() { let resetCallCount = 0; // the token should be set *before* timelineReset is emitted - client.on(RoomEvent.TimelineReset, function(room) { + client.on(RoomEvent.TimelineReset, (room) => { resetCallCount++; const tl = room.getLiveTimeline(); @@ -1150,7 +1161,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomOne); const tl = room.getLiveTimeline(); expect(tl.getEvents().length).toEqual(1); @@ -1159,7 +1170,7 @@ describe("MatrixClient syncing", function() { }); }); - describe("receipts", function() { + describe("receipts", () => { const syncData = { rooms: { join: { @@ -1202,13 +1213,13 @@ describe("MatrixClient syncing", function() { }, }; - beforeEach(function() { + beforeEach(() => { syncData.rooms.join[roomOne].ephemeral = { events: [], }; }); - it("should sync receipts from /sync.", function() { + it("should sync receipts from /sync.", () => { const ackEvent = syncData.rooms.join[roomOne].timeline.events[0]; const receipt = {}; receipt[ackEvent.event_id] = { @@ -1229,7 +1240,7 @@ describe("MatrixClient syncing", function() { return Promise.all([ httpBackend.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { const room = client.getRoom(roomOne); expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{ type: "m.read", @@ -1242,22 +1253,22 @@ describe("MatrixClient syncing", function() { }); }); - describe("of a room", function() { + describe("of a room", () => { xit("should sync when a join event (which changes state) for the user" + - " arrives down the event stream (e.g. join from another device)", function() { + " arrives down the event stream (e.g. join from another device)", () => { }); - xit("should sync when the user explicitly calls joinRoom", function() { + xit("should sync when the user explicitly calls joinRoom", () => { }); }); - describe("syncLeftRooms", function() { - beforeEach(function(done) { + describe("syncLeftRooms", () => { + beforeEach((done) => { client.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend.flushAllExpected().then(() => { // the /sync call from syncLeftRooms ends up in the request // queue behind the call from the running client; add a response // to flush the client's one out. @@ -1267,15 +1278,18 @@ describe("MatrixClient syncing", function() { }); }); - it("should create and use an appropriate filter", function() { - httpBackend.when("POST", "/filter").check(function(req) { + it("should create and use an appropriate filter", () => { + httpBackend.when("POST", "/filter").check((req) => { expect(req.data).toEqual({ - room: { timeline: { limit: 1 }, - include_leave: true } }); + room: { + timeline: { limit: 1 }, + include_leave: true, + }, + }); }).respond(200, { filter_id: "another_id" }); - const prom = new Promise((resolve) => { - httpBackend.when("GET", "/sync").check(function(req) { + const prom = new Promise((resolve) => { + httpBackend.when("GET", "/sync").check((req) => { expect(req.queryParams.filter).toEqual("another_id"); resolve(); }).respond(200, {}); @@ -1286,7 +1300,7 @@ describe("MatrixClient syncing", function() { // first flush the filter request; this will make syncLeftRooms // make its /sync call return Promise.all([ - httpBackend.flush("/filter").then(function() { + httpBackend.flush("/filter").then(() => { // flush the syncs return httpBackend.flushAllExpected(); }), @@ -1294,7 +1308,7 @@ describe("MatrixClient syncing", function() { ]); }); - it("should set the back-pagination token on left rooms", function() { + it("should set the back-pagination token on left rooms", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1320,7 +1334,7 @@ describe("MatrixClient syncing", function() { httpBackend.when("GET", "/sync").respond(200, syncData); return Promise.all([ - client.syncLeftRooms().then(function() { + client.syncLeftRooms().then(() => { const room = client.getRoom(roomTwo); const tok = room.getLiveTimeline().getPaginationToken( EventTimeline.BACKWARDS); @@ -1329,7 +1343,7 @@ describe("MatrixClient syncing", function() { }), // first flush the filter request; this will make syncLeftRooms make its /sync call - httpBackend.flush("/filter").then(function() { + httpBackend.flush("/filter").then(() => { return httpBackend.flushAllExpected(); }), ]); @@ -1342,7 +1356,7 @@ describe("MatrixClient syncing", function() { * @param {Number?} numSyncs number of syncs to wait for * @returns {Promise} promise which resolves after the sync events have happened */ - function awaitSyncEvent(numSyncs) { + function awaitSyncEvent(numSyncs?: number) { return utils.syncPromise(client, numSyncs); } }); diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index abb328e0c82..16d1cb5655b 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -147,9 +147,9 @@ export function mkEventCustom(base: T): T & GeneratedMetadata { interface IPresenceOpts { user?: string; sender?: string; - url: string; - name: string; - ago: number; + url?: string; + name?: string; + ago?: number; presence?: string; event?: boolean; } From 34e0e1dd4a686d038544103649df52e24ec14880 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 12:47:53 +0100 Subject: [PATCH 05/13] Add tests around initial sync filtering --- spec/integ/matrix-client-syncing.spec.ts | 35 ++++++++++++++++++++++++ src/sync.ts | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 42b3e099947..8d74b8e7484 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -190,6 +190,41 @@ describe("MatrixClient syncing", () => { }); }); + describe("initial sync", () => { + const syncData = { + next_batch: "batch_token", + rooms: {}, + presence: {}, + }; + + it("should only apply initialSyncLimit to the initial sync", () => { + // 1st request + httpBackend.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room.timeline.limit).toEqual(1); + }).respond(200, syncData); + // 2nd request + httpBackend.when("GET", "/sync").check((req) => { + expect(req.queryParams.filter).toEqual("a filter id"); + }).respond(200, syncData); + + client.startClient({ initialSyncLimit: 1 }); + + httpBackend.flushSync(); + return httpBackend.flushAllExpected(); + }); + + it("should not apply initialSyncLimit to a first sync if we have a stored token", () => { + httpBackend.when("GET", "/sync").check((req) => { + expect(req.queryParams.filter).toEqual("a filter id"); + }).respond(200, syncData); + + client.store.getSavedSyncToken = jest.fn().mockResolvedValue("this-is-a-token"); + client.startClient({ initialSyncLimit: 1 }); + + return httpBackend.flushAllExpected(); + }); + }); + describe("resolving invites to profile info", () => { const syncData = { next_batch: "s_5_3", diff --git a/src/sync.ts b/src/sync.ts index 645c983c6fd..24fbd2e0ddd 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -100,7 +100,7 @@ const MSC2716_ROOM_VERSIONS = [ function getFilterName(userId: string, suffix?: string): string { // scope this on the user ID because people may login on many accounts // and they all need to be stored! - return `FILTER_SYNC_${userId}${suffix ? "_" + suffix : ""}`; + return `FILTER_SYNC_${userId}` + suffix ? "_" + suffix : ""; } function debuglog(...params) { From edf04c07a63990de066d935f14a45feea83e5a53 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 13:15:23 +0100 Subject: [PATCH 06/13] Switch confusing filterId field for `filter` --- src/sync.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 24fbd2e0ddd..9e15033deac 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -109,7 +109,7 @@ function debuglog(...params) { } interface ISyncOptions { - filterId?: string; + filter?: string; hasSyncedBefore?: boolean; } @@ -780,14 +780,14 @@ export class SyncApi { // Send this first sync request here so we can then wait for the saved // sync data to finish processing before we process the results of this one. - this.currentSyncRequest = this.doSyncRequest({ filterId: firstSyncFilter }, savedSyncToken); + this.currentSyncRequest = this.doSyncRequest({ filter: firstSyncFilter }, savedSyncToken); } // Now wait for the saved sync to finish... debuglog("Waiting for saved sync before starting sync processing..."); await this.savedSyncPromise; // process the first sync request and continue syncing with the normal filterId - return this.doSync({ filterId }); + return this.doSync({ filter: filterId }); } /** @@ -992,13 +992,13 @@ export class SyncApi { pollTimeout = 0; } - let filterId = syncOptions.filterId; - if (this.client.isGuest() && !filterId) { - filterId = this.getGuestFilter(); + let filter = syncOptions.filter; + if (this.client.isGuest() && !filter) { + filter = this.getGuestFilter(); } const qps: ISyncParams = { - filter: filterId, + filter, timeout: pollTimeout, }; From a237d40e8864d1ef595104fd5b78e3f76363bc30 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Aug 2022 13:38:47 +0100 Subject: [PATCH 07/13] Tweak doSync error control flow --- src/sync.ts | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 9e15033deac..c936834d5d3 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -881,8 +881,9 @@ export class SyncApi { } data = await this.currentSyncRequest; } catch (e) { - this.onSyncError(e, syncOptions); - break; + const abort = await this.onSyncError(e); + if (abort) return; + continue; } finally { this.currentSyncRequest = null; } @@ -1026,7 +1027,7 @@ export class SyncApi { return qps; } - private onSyncError(err: MatrixError, syncOptions: ISyncOptions): void { + private async onSyncError(err: MatrixError): Promise { if (!this.running) { debuglog("Sync no longer running: exiting"); if (this.connectionReturnedDefer) { @@ -1034,19 +1035,25 @@ export class SyncApi { this.connectionReturnedDefer = null; } this.updateSyncState(SyncState.Stopped); - return; + return true; // abort } logger.error("/sync error %s", err); - logger.error(err); if (this.shouldAbortSync(err)) { - return; + return true; // abort } this.failedSyncCount++; logger.log('Number of consecutive failed sync requests:', this.failedSyncCount); + // Transition from RECONNECTING to ERROR after a given number of failed syncs + this.updateSyncState( + this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? + SyncState.Error : SyncState.Reconnecting, + { error: err }, + ); + debuglog("Starting keep-alive"); // Note that we do *not* mark the sync connection as // lost yet: we only do this if a keepalive poke @@ -1055,28 +1062,19 @@ export class SyncApi { // erroneous. We set the state to 'reconnecting' // instead, so that clients can observe this state // if they wish. - this.startKeepAlives().then((connDidFail) => { - // Only emit CATCHUP if we detected a connectivity error: if we didn't, - // it's quite likely the sync will fail again for the same reason and we - // want to stay in ERROR rather than keep flip-flopping between ERROR - // and CATCHUP. - if (connDidFail && this.getSyncState() === SyncState.Error) { - this.updateSyncState(SyncState.Catchup, { - oldSyncToken: null, - nextSyncToken: null, - catchingUp: true, - }); - } - this.doSync(syncOptions); - }); - - this.currentSyncRequest = null; - // Transition from RECONNECTING to ERROR after a given number of failed syncs - this.updateSyncState( - this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? - SyncState.Error : SyncState.Reconnecting, - { error: err }, - ); + const connDidFail = await this.startKeepAlives(); + // Only emit CATCHUP if we detected a connectivity error: if we didn't, + // it's quite likely the sync will fail again for the same reason and we + // want to stay in ERROR rather than keep flip-flopping between ERROR + // and CATCHUP. + if (connDidFail && this.getSyncState() === SyncState.Error) { + this.updateSyncState(SyncState.Catchup, { + oldSyncToken: null, + nextSyncToken: null, + catchingUp: true, + }); + } + return false; } /** From 715633e6348a00e08ca31de9725a5171af947c6e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 12 Aug 2022 11:07:58 +0100 Subject: [PATCH 08/13] Fix error control flow intricacies --- src/sync.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index c936834d5d3..a2401333776 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1047,13 +1047,6 @@ export class SyncApi { this.failedSyncCount++; logger.log('Number of consecutive failed sync requests:', this.failedSyncCount); - // Transition from RECONNECTING to ERROR after a given number of failed syncs - this.updateSyncState( - this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? - SyncState.Error : SyncState.Reconnecting, - { error: err }, - ); - debuglog("Starting keep-alive"); // Note that we do *not* mark the sync connection as // lost yet: we only do this if a keepalive poke @@ -1062,7 +1055,18 @@ export class SyncApi { // erroneous. We set the state to 'reconnecting' // instead, so that clients can observe this state // if they wish. - const connDidFail = await this.startKeepAlives(); + const keepAlivePromise = this.startKeepAlives(); + + this.currentSyncRequest = null; + // Transition from RECONNECTING to ERROR after a given number of failed syncs + this.updateSyncState( + this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? + SyncState.Error : SyncState.Reconnecting, + { error: err }, + ); + + const connDidFail = await keepAlivePromise; + // Only emit CATCHUP if we detected a connectivity error: if we didn't, // it's quite likely the sync will fail again for the same reason and we // want to stay in ERROR rather than keep flip-flopping between ERROR From 2f0cfa5e9db112cf122c3135e127cae4afa87af5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 12 Aug 2022 11:37:45 +0100 Subject: [PATCH 09/13] use includes --- src/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync.ts b/src/sync.ts index a2401333776..a245cce8f4e 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1016,7 +1016,7 @@ export class SyncApi { qps._cacheBuster = Date.now(); } - if (this.getSyncState() == SyncState.Error || this.getSyncState() == SyncState.Reconnecting) { + if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState())) { // we think the connection is dead. If it comes back up, we won't know // about it till /sync returns. If the timeout= is high, this could // be a long time. Set it to 0 when doing retries so we don't have to wait From 86e281001424f13b39d7e0837339fb958220fb3b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 12 Aug 2022 12:00:33 +0100 Subject: [PATCH 10/13] Add tests --- spec/integ/matrix-client-syncing.spec.ts | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 8d74b8e7484..bef015c37a1 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -188,6 +188,34 @@ describe("MatrixClient syncing", () => { expect(fires).toBe(3); }); + + it("should honour lazyLoadMembers if user is not a guest", () => { + client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + + httpBackend.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room.state.lazy_load_members).toBeTruthy(); + }).respond(200, syncData); + + client.setGuest(false); + client.startClient({ lazyLoadMembers: true }); + + return httpBackend.flushAllExpected(); + }); + + it("should not honour lazyLoadMembers if user is a guest", () => { + httpBackend.expectedRequests = []; + httpBackend.when("GET", "/versions").respond(200, {}); + client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + + httpBackend.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room?.state?.lazy_load_members).toBeFalsy(); + }).respond(200, syncData); + + client.setGuest(true); + client.startClient({ lazyLoadMembers: true }); + + return httpBackend.flushAllExpected(); + }); }); describe("initial sync", () => { From c8187042faf3b5c83d8ca35d9fb3e9e33a1e4920 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 23 Aug 2022 14:45:27 +0100 Subject: [PATCH 11/13] Fix some strict mode errors --- spec/integ/matrix-client-syncing.spec.ts | 203 ++++++++++++----------- 1 file changed, 103 insertions(+), 100 deletions(-) diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index bef015c37a1..c4e6bf10c19 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { Optional } from "matrix-events-sdk/lib/types"; +import HttpBackend from "matrix-mock-request"; + import { EventTimeline, MatrixEvent, @@ -27,8 +30,8 @@ import * as utils from "../test-utils/test-utils"; import { TestClient } from "../TestClient"; describe("MatrixClient syncing", () => { - let client: MatrixClient = null; - let httpBackend = null; + let client: Optional = null; + let httpBackend: Optional = null; const selfUserId = "@alice:localhost"; const selfAccessToken = "aseukfgwef"; const otherUserId = "@bob:localhost"; @@ -42,15 +45,15 @@ describe("MatrixClient syncing", () => { const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken); httpBackend = testClient.httpBackend; client = testClient.client; - httpBackend.when("GET", "/versions").respond(200, {}); - httpBackend.when("GET", "/pushrules").respond(200, {}); - httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + httpBackend!.when("GET", "/versions").respond(200, {}); + httpBackend!.when("GET", "/pushrules").respond(200, {}); + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); }); afterEach(() => { - httpBackend.verifyNoOutstandingExpectation(); + httpBackend!.verifyNoOutstandingExpectation(); client.stopClient(); - return httpBackend.stop(); + return httpBackend!.stop(); }); describe("startClient", () => { @@ -61,24 +64,24 @@ describe("MatrixClient syncing", () => { }; it("should /sync after /pushrules and /filter.", (done) => { - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); - httpBackend.flushAllExpected().then(() => { + httpBackend!.flushAllExpected().then(() => { done(); }); }); it("should pass the 'next_batch' token from /sync to the since= param of the next /sync", (done) => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.since).toEqual(syncData.next_batch); }).respond(200, syncData); client.startClient(); - httpBackend.flushAllExpected().then(() => { + httpBackend!.flushAllExpected().then(() => { done(); }); }); @@ -102,14 +105,14 @@ describe("MatrixClient syncing", () => { }, }, }; - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: inviteSyncRoomSection, }); // Second sync: a leave (reject of some kind) - httpBackend.when("POST", "/leave").respond(200, {}); - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("POST", "/leave").respond(200, {}); + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: { leave: { @@ -149,7 +152,7 @@ describe("MatrixClient syncing", () => { }); // Third sync: another invite - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: inviteSyncRoomSection, }); @@ -184,7 +187,7 @@ describe("MatrixClient syncing", () => { // noinspection ES6MissingAwait client.startClient(); - await httpBackend.flushAllExpected(); + await httpBackend!.flushAllExpected(); expect(fires).toBe(3); }); @@ -192,29 +195,29 @@ describe("MatrixClient syncing", () => { it("should honour lazyLoadMembers if user is not a guest", () => { client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(JSON.parse(req.queryParams.filter).room.state.lazy_load_members).toBeTruthy(); }).respond(200, syncData); client.setGuest(false); client.startClient({ lazyLoadMembers: true }); - return httpBackend.flushAllExpected(); + return httpBackend!.flushAllExpected(); }); it("should not honour lazyLoadMembers if user is a guest", () => { - httpBackend.expectedRequests = []; - httpBackend.when("GET", "/versions").respond(200, {}); + httpBackend!.expectedRequests = []; + httpBackend!.when("GET", "/versions").respond(200, {}); client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(JSON.parse(req.queryParams.filter).room?.state?.lazy_load_members).toBeFalsy(); }).respond(200, syncData); client.setGuest(true); client.startClient({ lazyLoadMembers: true }); - return httpBackend.flushAllExpected(); + return httpBackend!.flushAllExpected(); }); }); @@ -227,29 +230,29 @@ describe("MatrixClient syncing", () => { it("should only apply initialSyncLimit to the initial sync", () => { // 1st request - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(JSON.parse(req.queryParams.filter).room.timeline.limit).toEqual(1); }).respond(200, syncData); // 2nd request - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.filter).toEqual("a filter id"); }).respond(200, syncData); client.startClient({ initialSyncLimit: 1 }); - httpBackend.flushSync(); - return httpBackend.flushAllExpected(); + httpBackend!.flushSync(undefined); + return httpBackend!.flushAllExpected(); }); it("should not apply initialSyncLimit to a first sync if we have a stored token", () => { - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.filter).toEqual("a filter id"); }).respond(200, syncData); client.store.getSavedSyncToken = jest.fn().mockResolvedValue("this-is-a-token"); client.startClient({ initialSyncLimit: 1 }); - return httpBackend.flushAllExpected(); + return httpBackend!.flushAllExpected(); }); }); @@ -302,8 +305,8 @@ describe("MatrixClient syncing", () => { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/profile/" + encodeURIComponent(userC)).respond( + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/profile/" + encodeURIComponent(userC)).respond( 200, { avatar_url: "mxc://flibble/wibble", displayname: "The Boss", @@ -315,7 +318,7 @@ describe("MatrixClient syncing", () => { }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); @@ -340,14 +343,14 @@ describe("MatrixClient syncing", () => { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient({ resolveInvitesToProfiles: true, }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); @@ -369,7 +372,7 @@ describe("MatrixClient syncing", () => { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); let latestFiredName = null; client.on(RoomMemberEvent.Name, (event, m) => { @@ -383,7 +386,7 @@ describe("MatrixClient syncing", () => { }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { expect(latestFiredName).toEqual("The Ghost"); @@ -397,12 +400,12 @@ describe("MatrixClient syncing", () => { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const member = client.getRoom(roomOne).getMember(userC); @@ -432,12 +435,12 @@ describe("MatrixClient syncing", () => { }; it("should create users for presence events from /sync", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { expect(client.getUser(userA).presence).toEqual("online"); @@ -553,13 +556,13 @@ describe("MatrixClient syncing", () => { }; it("should continually recalculate the right room name.", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { const room = client.getRoom(roomOne); @@ -571,13 +574,13 @@ describe("MatrixClient syncing", () => { }); it("should store the right events in the timeline.", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { const room = client.getRoom(roomTwo); @@ -588,12 +591,12 @@ describe("MatrixClient syncing", () => { }); it("should set the right room name.", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { const room = client.getRoom(roomTwo); @@ -603,13 +606,13 @@ describe("MatrixClient syncing", () => { }); it("should set the right user's typing flag.", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { const room = client.getRoom(roomTwo); @@ -627,12 +630,12 @@ describe("MatrixClient syncing", () => { // timeline events, however this breaks peeking, so it's disabled // (see sync.js) xit("should correctly interpret state in incremental sync.", () => { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { const room = client.getRoom(roomOne); @@ -724,12 +727,12 @@ describe("MatrixClient syncing", () => { expect(markerEvent.sender).toBeDefined(); expect(markerEvent.sender).not.toEqual(roomCreateEvent.sender); - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); @@ -795,11 +798,11 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -825,11 +828,11 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -858,11 +861,11 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -892,10 +895,10 @@ describe("MatrixClient syncing", () => { const markerEventId = nextSyncData.rooms.join[roomOne].timeline.events[0].event_id; // Only do the first sync - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -910,9 +913,9 @@ describe("MatrixClient syncing", () => { }); // Now do a subsequent sync with the marker event - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -947,12 +950,12 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); @@ -1003,10 +1006,10 @@ describe("MatrixClient syncing", () => { it("should be able to listen to state events even after " + "the timeline is reset during `limited` sync response", async () => { // Create a room from the sync - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -1043,10 +1046,10 @@ describe("MatrixClient syncing", () => { prev_batch: "newerTok", }, }; - httpBackend.when("GET", "/sync").respond(200, limitedSyncData); + httpBackend!.when("GET", "/sync").respond(200, limitedSyncData); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -1071,16 +1074,16 @@ describe("MatrixClient syncing", () => { { timelineSupport: true }, ); httpBackend = testClientWithTimelineSupport.httpBackend; - httpBackend.when("GET", "/versions").respond(200, {}); - httpBackend.when("GET", "/pushrules").respond(200, {}); - httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + httpBackend!.when("GET", "/versions").respond(200, {}); + httpBackend!.when("GET", "/pushrules").respond(200, {}); + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); client = testClientWithTimelineSupport.client; // Create a room from the sync - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -1101,7 +1104,7 @@ describe("MatrixClient syncing", () => { const eventsInRoom = syncData.rooms.join[roomOne].timeline.events; const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` + `${encodeURIComponent(eventsInRoom[0].event_id)}`; - httpBackend.when("GET", contextUrl) + httpBackend!.when("GET", contextUrl) .respond(200, () => { return { start: "start_token", @@ -1119,7 +1122,7 @@ describe("MatrixClient syncing", () => { // reference to change await Promise.all([ room.refreshLiveTimeline(), - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), ]); // Cause `RoomStateEvent.Update` to be fired @@ -1149,11 +1152,11 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); }); @@ -1176,10 +1179,10 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const room = client.getRoom(roomTwo); @@ -1208,7 +1211,7 @@ describe("MatrixClient syncing", () => { prev_batch: "newerTok", }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); let resetCallCount = 0; // the token should be set *before* timelineReset is emitted @@ -1222,7 +1225,7 @@ describe("MatrixClient syncing", () => { }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const room = client.getRoom(roomOne); @@ -1296,12 +1299,12 @@ describe("MatrixClient syncing", () => { room_id: roomOne, type: "m.receipt", }]; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); client.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { const room = client.getRoom(roomOne); @@ -1331,18 +1334,18 @@ describe("MatrixClient syncing", () => { beforeEach((done) => { client.startClient(); - httpBackend.flushAllExpected().then(() => { + httpBackend!.flushAllExpected().then(() => { // the /sync call from syncLeftRooms ends up in the request // queue behind the call from the running client; add a response // to flush the client's one out. - httpBackend.when("GET", "/sync").respond(200, {}); + httpBackend!.when("GET", "/sync").respond(200, {}); done(); }); }); it("should create and use an appropriate filter", () => { - httpBackend.when("POST", "/filter").check((req) => { + httpBackend!.when("POST", "/filter").check((req) => { expect(req.data).toEqual({ room: { timeline: { limit: 1 }, @@ -1352,7 +1355,7 @@ describe("MatrixClient syncing", () => { }).respond(200, { filter_id: "another_id" }); const prom = new Promise((resolve) => { - httpBackend.when("GET", "/sync").check((req) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.filter).toEqual("another_id"); resolve(); }).respond(200, {}); @@ -1363,9 +1366,9 @@ describe("MatrixClient syncing", () => { // first flush the filter request; this will make syncLeftRooms // make its /sync call return Promise.all([ - httpBackend.flush("/filter").then(() => { + httpBackend!.flush("/filter").then(() => { // flush the syncs - return httpBackend.flushAllExpected(); + return httpBackend!.flushAllExpected(); }), prom, ]); @@ -1390,11 +1393,11 @@ describe("MatrixClient syncing", () => { }, }; - httpBackend.when("POST", "/filter").respond(200, { + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "another_id", }); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); return Promise.all([ client.syncLeftRooms().then(() => { @@ -1406,8 +1409,8 @@ describe("MatrixClient syncing", () => { }), // first flush the filter request; this will make syncLeftRooms make its /sync call - httpBackend.flush("/filter").then(() => { - return httpBackend.flushAllExpected(); + httpBackend!.flush("/filter").then(() => { + return httpBackend!.flushAllExpected(); }), ]); }); From 6c6372fef2f3f8a6547decd0a8578e7860411adc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 23 Aug 2022 14:56:15 +0100 Subject: [PATCH 12/13] Fix more strict mode errors --- spec/integ/matrix-client-syncing.spec.ts | 132 +++++++++++------------ 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index c4e6bf10c19..9f3fb988703 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -52,7 +52,7 @@ describe("MatrixClient syncing", () => { afterEach(() => { httpBackend!.verifyNoOutstandingExpectation(); - client.stopClient(); + client!.stopClient(); return httpBackend!.stop(); }); @@ -66,7 +66,7 @@ describe("MatrixClient syncing", () => { it("should /sync after /pushrules and /filter.", (done) => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); httpBackend!.flushAllExpected().then(() => { done(); @@ -79,7 +79,7 @@ describe("MatrixClient syncing", () => { expect(req.queryParams.since).toEqual(syncData.next_batch); }).respond(200, syncData); - client.startClient(); + client!.startClient(); httpBackend!.flushAllExpected().then(() => { done(); @@ -159,21 +159,21 @@ describe("MatrixClient syncing", () => { // First fire: an initial invite let fires = 0; - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { // Room, string, string + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { // Room, string, string fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("invite"); expect(oldMembership).toBeFalsy(); // Second fire: a leave - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("leave"); expect(oldMembership).toBe("invite"); // Third/final fire: a second invite - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("invite"); @@ -182,25 +182,25 @@ describe("MatrixClient syncing", () => { }); // For maximum safety, "leave" the room after we register the handler - client.leave(roomId); + client!.leave(roomId); }); // noinspection ES6MissingAwait - client.startClient(); + client!.startClient(); await httpBackend!.flushAllExpected(); expect(fires).toBe(3); }); it("should honour lazyLoadMembers if user is not a guest", () => { - client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + client!.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); httpBackend!.when("GET", "/sync").check((req) => { expect(JSON.parse(req.queryParams.filter).room.state.lazy_load_members).toBeTruthy(); }).respond(200, syncData); - client.setGuest(false); - client.startClient({ lazyLoadMembers: true }); + client!.setGuest(false); + client!.startClient({ lazyLoadMembers: true }); return httpBackend!.flushAllExpected(); }); @@ -208,14 +208,14 @@ describe("MatrixClient syncing", () => { it("should not honour lazyLoadMembers if user is a guest", () => { httpBackend!.expectedRequests = []; httpBackend!.when("GET", "/versions").respond(200, {}); - client.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + client!.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); httpBackend!.when("GET", "/sync").check((req) => { expect(JSON.parse(req.queryParams.filter).room?.state?.lazy_load_members).toBeFalsy(); }).respond(200, syncData); - client.setGuest(true); - client.startClient({ lazyLoadMembers: true }); + client!.setGuest(true); + client!.startClient({ lazyLoadMembers: true }); return httpBackend!.flushAllExpected(); }); @@ -238,7 +238,7 @@ describe("MatrixClient syncing", () => { expect(req.queryParams.filter).toEqual("a filter id"); }).respond(200, syncData); - client.startClient({ initialSyncLimit: 1 }); + client!.startClient({ initialSyncLimit: 1 }); httpBackend!.flushSync(undefined); return httpBackend!.flushAllExpected(); @@ -249,8 +249,8 @@ describe("MatrixClient syncing", () => { expect(req.queryParams.filter).toEqual("a filter id"); }).respond(200, syncData); - client.store.getSavedSyncToken = jest.fn().mockResolvedValue("this-is-a-token"); - client.startClient({ initialSyncLimit: 1 }); + client!.store.getSavedSyncToken = jest.fn().mockResolvedValue("this-is-a-token"); + client!.startClient({ initialSyncLimit: 1 }); return httpBackend!.flushAllExpected(); }); @@ -313,7 +313,7 @@ describe("MatrixClient syncing", () => { }, ); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); @@ -321,7 +321,7 @@ describe("MatrixClient syncing", () => { httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const member = client.getRoom(roomOne).getMember(userC); + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Boss"); expect( member.getAvatarUrl("home.server.url", null, null, null, false, false), @@ -345,7 +345,7 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); @@ -353,7 +353,7 @@ describe("MatrixClient syncing", () => { httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const member = client.getRoom(roomOne).getMember(userC); + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Ghost"); }); }); @@ -375,13 +375,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); let latestFiredName = null; - client.on(RoomMemberEvent.Name, (event, m) => { + client!.on(RoomMemberEvent.Name, (event, m) => { if (m.userId === userC && m.roomId === roomOne) { latestFiredName = m.name; } }); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); @@ -402,13 +402,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const member = client.getRoom(roomOne).getMember(userC); + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual(userC); expect( member.getAvatarUrl("home.server.url", null, null, null, false, false), @@ -437,14 +437,14 @@ describe("MatrixClient syncing", () => { it("should create users for presence events from /sync", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - expect(client.getUser(userA).presence).toEqual("online"); - expect(client.getUser(userB).presence).toEqual("unavailable"); + expect(client!.getUser(userA).presence).toEqual("online"); + expect(client!.getUser(userB).presence).toEqual("unavailable"); }); }); }); @@ -559,13 +559,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); // should have clobbered the name to the one from /events expect(room.name).toEqual( nextSyncData.rooms.join[roomOne].state.events[0].content.name, @@ -577,13 +577,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { - const room = client.getRoom(roomTwo); + const room = client!.getRoom(roomTwo); // should have added the message from /events expect(room.timeline.length).toEqual(2); expect(room.timeline[1].getContent().body).toEqual(msgText); @@ -594,12 +594,12 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { - const room = client.getRoom(roomTwo); + const room = client!.getRoom(roomTwo); // should use the display name of the other person. expect(room.name).toEqual(otherDisplayName); }); @@ -609,13 +609,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { - const room = client.getRoom(roomTwo); + const room = client!.getRoom(roomTwo); let member = room.getMember(otherUserId); expect(member).toBeTruthy(); expect(member.typing).toEqual(true); @@ -633,12 +633,12 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]).then(() => { - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); const stateAtStart = room.getLiveTimeline().getState( EventTimeline.BACKWARDS, ); @@ -730,13 +730,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -800,13 +800,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -830,13 +830,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -863,13 +863,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -896,14 +896,14 @@ describe("MatrixClient syncing", () => { // Only do the first sync httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); let emitCount = 0; room.on(RoomEvent.HistoryImportedWithinTimeline, (markerEvent, room) => { @@ -953,13 +953,13 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(true); }); }); @@ -1007,18 +1007,18 @@ describe("MatrixClient syncing", () => { "the timeline is reset during `limited` sync response", async () => { // Create a room from the sync httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room).toBeTruthy(); let stateEventEmitCount = 0; - client.on(RoomStateEvent.Update, () => { + client!.on(RoomStateEvent.Update, () => { stateEventEmitCount += 1; }); @@ -1081,18 +1081,18 @@ describe("MatrixClient syncing", () => { // Create a room from the sync httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room).toBeTruthy(); let stateEventEmitCount = 0; - client.on(RoomStateEvent.Update, () => { + client!.on(RoomStateEvent.Update, () => { stateEventEmitCount += 1; }); @@ -1154,7 +1154,7 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), @@ -1185,7 +1185,7 @@ describe("MatrixClient syncing", () => { httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const room = client.getRoom(roomTwo); + const room = client!.getRoom(roomTwo); expect(room).toBeTruthy(); const tok = room.getLiveTimeline() .getPaginationToken(EventTimeline.BACKWARDS); @@ -1215,7 +1215,7 @@ describe("MatrixClient syncing", () => { let resetCallCount = 0; // the token should be set *before* timelineReset is emitted - client.on(RoomEvent.TimelineReset, (room) => { + client!.on(RoomEvent.TimelineReset, (room) => { resetCallCount++; const tl = room.getLiveTimeline(); @@ -1228,7 +1228,7 @@ describe("MatrixClient syncing", () => { httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); const tl = room.getLiveTimeline(); expect(tl.getEvents().length).toEqual(1); expect(resetCallCount).toEqual(1); @@ -1301,13 +1301,13 @@ describe("MatrixClient syncing", () => { }]; httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ httpBackend!.flushAllExpected(), awaitSyncEvent(), ]).then(() => { - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{ type: "m.read", userId: userC, @@ -1332,7 +1332,7 @@ describe("MatrixClient syncing", () => { describe("syncLeftRooms", () => { beforeEach((done) => { - client.startClient(); + client!.startClient(); httpBackend!.flushAllExpected().then(() => { // the /sync call from syncLeftRooms ends up in the request @@ -1361,7 +1361,7 @@ describe("MatrixClient syncing", () => { }).respond(200, {}); }); - client.syncLeftRooms(); + client!.syncLeftRooms(); // first flush the filter request; this will make syncLeftRooms // make its /sync call @@ -1400,8 +1400,8 @@ describe("MatrixClient syncing", () => { httpBackend!.when("GET", "/sync").respond(200, syncData); return Promise.all([ - client.syncLeftRooms().then(() => { - const room = client.getRoom(roomTwo); + client!.syncLeftRooms().then(() => { + const room = client!.getRoom(roomTwo); const tok = room.getLiveTimeline().getPaginationToken( EventTimeline.BACKWARDS); From 209c6a4cb59b13db719316be03a46cf65a6285da Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 23 Aug 2022 15:07:46 +0100 Subject: [PATCH 13/13] Fix some strict mode errors --- src/sync.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 7bb9bc87ac4..430e0260229 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -23,6 +23,8 @@ limitations under the License. * for HTTP and WS at some point. */ +import { Optional } from "matrix-events-sdk"; + import { User, UserEvent } from "./models/user"; import { NotificationCountType, Room, RoomEvent } from "./models/room"; import * as utils from "./utils"; @@ -159,14 +161,14 @@ type WrappedRoom = T & { * updating presence. */ export class SyncApi { - private _peekRoom: Room = null; - private currentSyncRequest: IAbortablePromise = null; - private syncState: SyncState = null; - private syncStateData: ISyncStateData = null; // additional data (eg. error object for failed sync) + private _peekRoom: Optional = null; + private currentSyncRequest: Optional> = null; + private syncState: Optional = null; + private syncStateData: Optional = null; // additional data (eg. error object for failed sync) private catchingUp = false; private running = false; - private keepAliveTimer: ReturnType = null; - private connectionReturnedDefer: IDeferred = null; + private keepAliveTimer: Optional> = null; + private connectionReturnedDefer: Optional> = null; private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response private failedSyncCount = 0; // Number of consecutive failed /sync requests private storeIsInvalid = false; // flag set if the store needs to be cleared before we can start @@ -611,8 +613,8 @@ export class SyncApi { }; private getFilter = async (): Promise<{ - filterId: string; - filter: Filter; + filterId?: string; + filter?: Filter; }> => { debuglog("Getting filter..."); let filter: Filter; @@ -627,7 +629,7 @@ export class SyncApi { filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter); } catch (err) { logger.error("Getting filter failed", err); - if (this.shouldAbortSync(err)) return; + if (this.shouldAbortSync(err)) return {}; // wait for saved sync to complete before doing anything else, // otherwise the sync state will end up being incorrect debuglog("Waiting for saved sync before retrying filter..."); @@ -684,6 +686,7 @@ export class SyncApi { await this.getPushRules(); await this.checkLazyLoadStatus(); const { filterId, filter } = await this.getFilter(); + if (!filter) return; // bail, getFilter failed // reset the notifications timeline to prepare it to paginate from // the current point in time. @@ -762,8 +765,7 @@ export class SyncApi { this.client.store.setSyncToken(nextSyncToken); // No previous sync, set old token to null - const syncEventData = { - oldSyncToken: null, + const syncEventData: ISyncStateData = { nextSyncToken, catchingUp: false, fromCache: true, @@ -1001,8 +1003,6 @@ export class SyncApi { // and CATCHUP. if (connDidFail && this.getSyncState() === SyncState.Error) { this.updateSyncState(SyncState.Catchup, { - oldSyncToken: null, - nextSyncToken: null, catchingUp: true, }); }