2020-04-19 19:02:10 +02:00
|
|
|
import HomeServerApi from "./net/HomeServerApi.js";
|
2020-04-09 23:19:49 +02:00
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
export const LoadStatus = createEnum(
|
|
|
|
"NotLoading",
|
|
|
|
"Login",
|
|
|
|
"LoginFailed",
|
2020-04-09 23:19:49 +02:00
|
|
|
"Loading",
|
|
|
|
"Migrating", //not used atm, but would fit here
|
2020-04-19 19:52:26 +02:00
|
|
|
"FirstSync",
|
2020-04-09 23:19:49 +02:00
|
|
|
"Error",
|
|
|
|
"Ready",
|
|
|
|
);
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
export const LoginFailure = createEnum(
|
|
|
|
"Network",
|
|
|
|
"Credentials",
|
|
|
|
"Unknown",
|
|
|
|
);
|
|
|
|
|
|
|
|
export class SessionContainer {
|
|
|
|
constructor({clock, random, onlineStatus, request, storageFactory, sessionsStore}) {
|
|
|
|
this._random = random;
|
|
|
|
this._clock = clock;
|
|
|
|
this._onlineStatus = onlineStatus;
|
|
|
|
this._request = request;
|
|
|
|
this._storageFactory = storageFactory;
|
|
|
|
this._sessionsStore = sessionsStore;
|
|
|
|
|
|
|
|
this._status = new ObservableValue(LoadStatus.NotLoading);
|
|
|
|
this._error = null;
|
|
|
|
this._loginFailure = null;
|
|
|
|
this._reconnector = null;
|
|
|
|
this._session = null;
|
|
|
|
this._sync = null;
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
_createNewSessionId() {
|
|
|
|
return (Math.floor(this._random() * Number.MAX_SAFE_INTEGER)).toString();
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
async startWithExistingSession(sessionId) {
|
|
|
|
if (this._status.get() !== LoadStatus.NotLoading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._status.set(LoadStatus.Loading);
|
|
|
|
try {
|
|
|
|
const sessionInfo = await this._sessionsStore.get(sessionId);
|
|
|
|
await this._loadSessionInfo(sessionInfo);
|
|
|
|
} catch (err) {
|
|
|
|
this._error = err;
|
|
|
|
this._status.set(LoadStatus.Error);
|
|
|
|
}
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
async startWithLogin(homeServer, username, password) {
|
|
|
|
if (this._status.get() !== LoadStatus.NotLoading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._status.set(LoadStatus.Login);
|
|
|
|
let sessionInfo;
|
|
|
|
try {
|
|
|
|
const hsApi = new HomeServerApi({homeServer, request: this._request});
|
|
|
|
const loginData = await hsApi.passwordLogin(username, password).response();
|
|
|
|
const sessionId = this._createNewSessionId();
|
|
|
|
sessionInfo = {
|
|
|
|
id: sessionId,
|
|
|
|
deviceId: loginData.device_id,
|
|
|
|
userId: loginData.user_id,
|
|
|
|
homeServer: homeServer,
|
|
|
|
accessToken: loginData.access_token,
|
|
|
|
lastUsed: this._clock.now()
|
|
|
|
};
|
|
|
|
await this._sessionsStore.add(sessionInfo);
|
|
|
|
} catch (err) {
|
|
|
|
this._error = err;
|
|
|
|
if (err instanceof HomeServerError) {
|
|
|
|
if (err.statusCode === 403) {
|
|
|
|
this._loginFailure = LoginFailure.Credentials;
|
|
|
|
} else {
|
|
|
|
this._loginFailure = LoginFailure.Unknown;
|
|
|
|
}
|
|
|
|
this._status.set(LoadStatus.LoginFailure);
|
2020-04-19 19:05:12 +02:00
|
|
|
} else if (err instanceof ConnectionError) {
|
2020-04-18 19:16:16 +02:00
|
|
|
this._loginFailure = LoginFailure.Network;
|
|
|
|
this._status.set(LoadStatus.LoginFailure);
|
|
|
|
} else {
|
|
|
|
this._status.set(LoadStatus.Error);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// loading the session can only lead to
|
|
|
|
// LoadStatus.Error in case of an error,
|
|
|
|
// so separate try/catch
|
|
|
|
try {
|
|
|
|
await this._loadSessionInfo(sessionInfo);
|
|
|
|
} catch (err) {
|
|
|
|
this._error = err;
|
|
|
|
this._status.set(LoadStatus.Error);
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
async _loadSessionInfo(sessionInfo) {
|
|
|
|
this._status.set(LoadStatus.Loading);
|
|
|
|
this._reconnector = new Reconnector({
|
|
|
|
onlineStatus: this._onlineStatus,
|
|
|
|
delay: new ExponentialRetryDelay(2000, this._clock.createTimeout),
|
|
|
|
createMeasure: this._clock.createMeasure
|
|
|
|
});
|
|
|
|
const hsApi = new HomeServerApi({
|
|
|
|
homeServer: sessionInfo.homeServer,
|
|
|
|
accessToken: sessionInfo.accessToken,
|
|
|
|
request: this._request,
|
|
|
|
reconnector: this._reconnector,
|
|
|
|
});
|
|
|
|
const storage = await this._storageFactory.create(sessionInfo.id);
|
|
|
|
// no need to pass access token to session
|
|
|
|
const filteredSessionInfo = {
|
|
|
|
deviceId: sessionInfo.deviceId,
|
|
|
|
userId: sessionInfo.userId,
|
|
|
|
homeServer: sessionInfo.homeServer,
|
|
|
|
};
|
|
|
|
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 => {
|
|
|
|
if (state === ConnectionStatus.Online) {
|
|
|
|
this._sync.start();
|
|
|
|
this._session.start(this._reconnector.lastVersionsResponse);
|
|
|
|
}
|
|
|
|
});
|
2020-04-19 19:02:10 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _waitForFirstSync() {
|
2020-04-18 19:16:16 +02:00
|
|
|
try {
|
2020-04-19 19:52:26 +02:00
|
|
|
this._sync.start();
|
|
|
|
this._status.set(LoadStatus.FirstSync);
|
2020-04-18 19:16:16 +02:00
|
|
|
} catch (err) {
|
2020-04-19 19:05:12 +02:00
|
|
|
// swallow ConnectionError here and continue,
|
2020-04-18 19:16:16 +02:00
|
|
|
// as the reconnector above will call
|
|
|
|
// sync.start again to retry in this case
|
2020-04-19 19:05:12 +02:00
|
|
|
if (!(err instanceof ConnectionError)) {
|
2020-04-18 19:16:16 +02:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// only transition into Ready once the first sync has succeeded
|
2020-04-19 19:02:10 +02:00
|
|
|
this._waitForFirstSyncHandle = this._sync.status.waitFor(s => s === SyncStatus.Syncing);
|
|
|
|
try {
|
|
|
|
await this._waitForFirstSyncHandle.promise;
|
|
|
|
} catch (err) {
|
|
|
|
// if dispose is called from stop, bail out
|
|
|
|
if (err instanceof AbortError) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
} finally {
|
|
|
|
this._waitForFirstSyncHandle = null;
|
|
|
|
}
|
2020-04-18 19:16:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get loadStatus() {
|
|
|
|
return this._status;
|
|
|
|
}
|
|
|
|
|
|
|
|
get loadError() {
|
|
|
|
return this._error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** only set at loadStatus InitialSync, CatchupSync or Ready */
|
2020-04-09 23:19:49 +02:00
|
|
|
get sync() {
|
|
|
|
return this._sync;
|
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
/** only set at loadStatus InitialSync, CatchupSync or Ready */
|
2020-04-09 23:19:49 +02:00
|
|
|
get session() {
|
|
|
|
return this._session;
|
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
stop() {
|
|
|
|
this._reconnectSubscription();
|
|
|
|
this._reconnectSubscription = null;
|
|
|
|
this._sync.stop();
|
|
|
|
this._session.stop();
|
2020-04-19 19:02:10 +02:00
|
|
|
if (this._waitForFirstSyncHandle) {
|
|
|
|
this._waitForFirstSyncHandle.dispose();
|
|
|
|
this._waitForFirstSyncHandle = null;
|
|
|
|
}
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
2020-04-18 19:16:16 +02:00
|
|
|
}
|
2020-04-09 23:19:49 +02:00
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
/*
|
|
|
|
function main() {
|
|
|
|
// these are only required for external classes,
|
|
|
|
// SessionFactory has it's defaults for internal classes
|
|
|
|
const sessionFactory = new SessionFactory({
|
|
|
|
Clock: DOMClock,
|
|
|
|
OnlineState: DOMOnlineState,
|
|
|
|
SessionsStore: 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
|
|
|
|
request,
|
|
|
|
});
|
2020-04-09 23:19:49 +02:00
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
// lets not do this in a first cut
|
|
|
|
// internally in the matrix lib
|
|
|
|
const room = new creator.ctor("Room", Room)({});
|
|
|
|
|
|
|
|
// or short
|
|
|
|
const sessionFactory = new SessionFactory(WebFactory);
|
|
|
|
// sessionFactory.sessionInfoStore
|
|
|
|
|
|
|
|
// registration
|
|
|
|
// const registration = sessionFactory.registerUser();
|
|
|
|
// registration.stage
|
|
|
|
|
|
|
|
|
|
|
|
const container = sessionFactory.startWithRegistration(registration);
|
|
|
|
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);
|
|
|
|
|
|
|
|
// loader isn't needed anymore from now on
|
|
|
|
const {session, sync, reconnector} = container;
|
|
|
|
container.stop();
|
2020-04-09 23:19:49 +02:00
|
|
|
}
|
2020-04-18 19:16:16 +02:00
|
|
|
*/
|