finish implemenation of SessionContainer

This commit is contained in:
Bruno Windels 2020-04-20 19:48:21 +02:00
parent 87b23d062c
commit 164d9d594f
4 changed files with 56 additions and 23 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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<LoadStatus>
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;