hydrogen-web/src/matrix/SessionContainer.js

227 lines
7.6 KiB
JavaScript
Raw Normal View History

2020-04-18 22:46:16 +05:30
import HomeServerApi from "./hs-api.js";
2020-04-10 02:49:49 +05:30
2020-04-18 22:46:16 +05:30
export const LoadStatus = createEnum(
"NotLoading",
"Login",
"LoginFailed",
2020-04-10 02:49:49 +05:30
"Loading",
"Migrating", //not used atm, but would fit here
2020-04-18 22:46:16 +05:30
"InitialSync",
"CatchupSync",
2020-04-10 02:49:49 +05:30
"Error",
"Ready",
);
2020-04-18 22:46:16 +05:30
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-10 02:49:49 +05:30
}
2020-04-18 22:46:16 +05:30
_createNewSessionId() {
return (Math.floor(this._random() * Number.MAX_SAFE_INTEGER)).toString();
2020-04-10 02:49:49 +05:30
}
2020-04-18 22:46:16 +05:30
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-10 02:49:49 +05:30
}
2020-04-18 22:46:16 +05:30
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);
} else if (err instanceof NetworkError) {
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-10 02:49:49 +05:30
}
}
2020-04-18 22:46:16 +05:30
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._status.set(LoadStatus.InitialSync);
}
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);
}
});
try {
await this._sync.start();
} catch (err) {
// swallow NetworkError here and continue,
// as the reconnector above will call
// sync.start again to retry in this case
if (!(err instanceof NetworkError)) {
throw err;
}
}
// only transition into Ready once the first sync has succeeded
await this._sync.status.waitFor(s => s === SyncStatus.Syncing);
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);
}
get loadStatus() {
return this._status;
}
get loadError() {
return this._error;
}
/** only set at loadStatus InitialSync, CatchupSync or Ready */
2020-04-10 02:49:49 +05:30
get sync() {
return this._sync;
}
2020-04-18 22:46:16 +05:30
/** only set at loadStatus InitialSync, CatchupSync or Ready */
2020-04-10 02:49:49 +05:30
get session() {
return this._session;
}
2020-04-18 22:46:16 +05:30
stop() {
this._reconnectSubscription();
this._reconnectSubscription = null;
this._sync.stop();
this._session.stop();
2020-04-10 02:49:49 +05:30
}
2020-04-18 22:46:16 +05:30
}
2020-04-10 02:49:49 +05:30
2020-04-18 22:46:16 +05:30
/*
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-10 02:49:49 +05:30
2020-04-18 22:46:16 +05:30
// 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-10 02:49:49 +05:30
}
2020-04-18 22:46:16 +05:30
*/