From 164d9d594f844726bb5dabbf507380fb0198677f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 20 Apr 2020 19:48:21 +0200 Subject: [PATCH] finish implemenation of SessionContainer --- doc/impl-thoughts/RECONNECTING.md | 3 ++- src/matrix/SendScheduler.js | 14 +++++++--- src/matrix/Session.js | 19 +++++++++++++- src/matrix/SessionContainer.js | 43 ++++++++++++++++++------------- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/doc/impl-thoughts/RECONNECTING.md b/doc/impl-thoughts/RECONNECTING.md index 78293fba..5e507834 100644 --- a/doc/impl-thoughts/RECONNECTING.md +++ b/doc/impl-thoughts/RECONNECTING.md @@ -42,7 +42,8 @@ rooms should report how many messages they have queued up, and each time they se - decide whether we want to inherit (no?) - DONE: cleanup Reconnector with recent changes, move generic code, make imports work - DONE: add SyncStatus as ObservableValue of enum in Sync - - cleanup SessionContainer + - DONE: cleanup SessionContainer + - move all imports to non-default - change main.js to pass in a creation function of a SessionContainer instead of everything it is replacing - show load progress in LoginView/SessionPickView and do away with loading screen - adjust BrawlViewModel, SessionPickViewModel and LoginViewModel to use a SessionContainer diff --git a/src/matrix/SendScheduler.js b/src/matrix/SendScheduler.js index ac6e557f..1280ca6a 100644 --- a/src/matrix/SendScheduler.js +++ b/src/matrix/SendScheduler.js @@ -48,7 +48,7 @@ export class SendScheduler { this._hsApi = hsApi; this._sendRequests = []; this._sendScheduled = false; - this._offline = false; + this._stopped = false; this._waitTime = 0; this._backoff = backoff; /* @@ -66,6 +66,14 @@ export class SendScheduler { // TODO: abort current requests and set offline } + start() { + this._stopped = false; + } + + get isStarted() { + return !this._stopped; + } + // this should really be per roomId to avoid head-of-line blocking // // takes a callback instead of returning a promise with the slot @@ -74,7 +82,7 @@ export class SendScheduler { let request; const promise = new Promise((resolve, reject) => request = {resolve, reject, sendCallback}); this._sendRequests.push(request); - if (!this._sendScheduled && !this._offline) { + if (!this._sendScheduled && !this._stopped) { this._sendLoop(); } return promise; @@ -91,7 +99,7 @@ export class SendScheduler { if (err instanceof ConnectionError) { // we're offline, everybody will have // to re-request slots when we come back online - this._offline = true; + this._stopped = true; for (const r of this._sendRequests) { r.reject(err); } diff --git a/src/matrix/Session.js b/src/matrix/Session.js index d513d0b2..7c4771e4 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -40,11 +40,28 @@ export default class Session { })); } + get isStarted() { + return this._sendScheduler.isStarted; + } + stop() { this._sendScheduler.stop(); } - start(lastVersionResponse) { + async start(lastVersionResponse) { + if (lastVersionResponse) { + // store /versions response + const txn = await this._storage.readWriteTxn([ + this._storage.storeNames.session + ]); + const newSessionData = Object.assign({}, this._session, {serverVersions: lastVersionResponse}); + txn.session.set(newSessionData); + // TODO: what can we do if this throws? + await txn.complete(); + this._session = newSessionData; + } + + this._sendScheduler.start(); for (const [, room] of this._rooms) { room.resumeSending(); } diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 777ddf9f..aad58959 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -1,4 +1,11 @@ +import createEnum from "../utils/enum.js"; +import ObservableValue from "../observable/ObservableValue.js"; import HomeServerApi from "./net/HomeServerApi.js"; +import {Reconnector, ConnectionStatus} from "./net/Reconnector.js"; +import ExponentialRetryDelay from "./net/ExponentialRetryDelay.js"; +import {HomeServerError, ConnectionError, AbortError} from "./error.js"; +import {Sync, SyncStatus} from "./Sync.js"; +import Session from "./Session.js"; export const LoadStatus = createEnum( "NotLoading", @@ -12,19 +19,19 @@ export const LoadStatus = createEnum( ); export const LoginFailure = createEnum( - "Network", + "Connection", "Credentials", "Unknown", ); export class SessionContainer { - constructor({clock, random, onlineStatus, request, storageFactory, sessionsStore}) { + constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage}) { this._random = random; this._clock = clock; this._onlineStatus = onlineStatus; this._request = request; this._storageFactory = storageFactory; - this._sessionsStore = sessionsStore; + this._sessionInfoStorage = sessionInfoStorage; this._status = new ObservableValue(LoadStatus.NotLoading); this._error = null; @@ -44,7 +51,7 @@ export class SessionContainer { } this._status.set(LoadStatus.Loading); try { - const sessionInfo = await this._sessionsStore.get(sessionId); + const sessionInfo = await this._sessionInfoStorage.get(sessionId); await this._loadSessionInfo(sessionInfo); } catch (err) { this._error = err; @@ -70,7 +77,7 @@ export class SessionContainer { accessToken: loginData.access_token, lastUsed: this._clock.now() }; - await this._sessionsStore.add(sessionInfo); + await this._sessionInfoStorage.add(sessionInfo); } catch (err) { this._error = err; if (err instanceof HomeServerError) { @@ -81,7 +88,7 @@ export class SessionContainer { } this._status.set(LoadStatus.LoginFailure); } else if (err instanceof ConnectionError) { - this._loginFailure = LoginFailure.Network; + this._loginFailure = LoginFailure.Connection; this._status.set(LoadStatus.LoginFailure); } else { this._status.set(LoadStatus.Error); @@ -122,12 +129,6 @@ export class SessionContainer { this._session = new Session({storage, sessionInfo: filteredSessionInfo, hsApi}); await this._session.load(); - const needsInitialSync = !this._session.syncToken; - if (!needsInitialSync) { - this._status.set(LoadStatus.CatchupSync); - } else { - } - this._sync = new Sync({hsApi, storage, session: this._session}); // notify sync and session when back online this._reconnectSubscription = this._reconnector.connectionStatus.subscribe(state => { @@ -137,17 +138,23 @@ export class SessionContainer { } }); await this._waitForFirstSync(); + this._status.set(LoadStatus.Ready); - // if this fails, the reconnector will start polling versions to reconnect - const lastVersionsResponse = await hsApi.versions({timeout: 10000}).response(); - this._session.start(lastVersionsResponse); + // if the sync failed, and then the reconnector + // restored the connection, it would have already + // started to session, so check first + // to prevent an extra /versions request + if (!this._session.isStarted) { + const lastVersionsResponse = await hsApi.versions({timeout: 10000}).response(); + this._session.start(lastVersionsResponse); + } } async _waitForFirstSync() { try { - this._sync.start(); this._status.set(LoadStatus.FirstSync); + this._sync.start(); } catch (err) { // swallow ConnectionError here and continue, // as the reconnector above will call @@ -209,7 +216,7 @@ function main() { const sessionFactory = new SessionFactory({ Clock: DOMClock, OnlineState: DOMOnlineState, - SessionsStore: LocalStorageSessionStore, // should be called SessionInfoStore? + SessionInfoStorage: LocalStorageSessionStore, // should be called SessionInfoStore? StorageFactory: window.indexedDB ? IDBStorageFactory : MemoryStorageFactory, // should be called StorageManager? // should be moved to StorageFactory as `KeyBounds`?: minStorageKey, middleStorageKey, maxStorageKey // would need to pass it into EventKey though @@ -233,7 +240,7 @@ function main() { const container = sessionFactory.startWithLogin(server, username, password); const container = sessionFactory.startWithExistingSession(sessionId); // container.loadStatus is an ObservableValue - await container.loadStatus.waitFor(s => s === LoadStatus.Loaded || s === LoadStatus.CatchupSync); + await container.loadStatus.waitFor(s => s === LoadStatus.FirstSync && container.sync.status === SyncStatus.CatchupSync || s === LoadStatus.Ready); // loader isn't needed anymore from now on const {session, sync, reconnector} = container;