Merge pull request #174 from vector-im/bwindels/fix-offline-session-backup-init
Fix session backup init breaking offline usage
This commit is contained in:
commit
8125173420
5 changed files with 61 additions and 25 deletions
|
@ -23,6 +23,9 @@ export class SessionBackupViewModel extends ViewModel {
|
|||
this._showKeySetup = true;
|
||||
this._error = null;
|
||||
this._isBusy = false;
|
||||
this.track(this._session.hasSecretStorageKey.subscribe(() => {
|
||||
this.emitChange("status");
|
||||
}));
|
||||
}
|
||||
|
||||
get isBusy() {
|
||||
|
@ -37,7 +40,11 @@ export class SessionBackupViewModel extends ViewModel {
|
|||
if (this._session.sessionBackup) {
|
||||
return "enabled";
|
||||
} else {
|
||||
return this._showKeySetup ? "setupKey" : "setupPhrase";
|
||||
if (this._session.hasSecretStorageKey.get() === false) {
|
||||
return this._showKeySetup ? "setupKey" : "setupPhrase";
|
||||
} else {
|
||||
return "pending";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ export class Session {
|
|||
this._getSyncToken = () => this.syncToken;
|
||||
this._olmWorker = olmWorker;
|
||||
this._cryptoDriver = cryptoDriver;
|
||||
this._sessionBackup = null;
|
||||
this._hasSecretStorageKey = new ObservableValue(null);
|
||||
|
||||
if (olm) {
|
||||
this._olmUtil = new olm.Utility();
|
||||
|
@ -81,6 +83,10 @@ export class Session {
|
|||
return this._e2eeAccount?.identityKeys.ed25519;
|
||||
}
|
||||
|
||||
get hasSecretStorageKey() {
|
||||
return this._hasSecretStorageKey;
|
||||
}
|
||||
|
||||
get deviceId() {
|
||||
return this._sessionInfo.deviceId;
|
||||
}
|
||||
|
@ -92,6 +98,8 @@ export class Session {
|
|||
// called once this._e2eeAccount is assigned
|
||||
_setupEncryption() {
|
||||
console.log("loaded e2ee account with keys", this._e2eeAccount.identityKeys);
|
||||
// TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account
|
||||
// and can create RoomEncryption objects and handle encrypted to_device messages and device list changes.
|
||||
const senderKeyLock = new LockMap();
|
||||
const olmDecryption = new OlmDecryption({
|
||||
account: this._e2eeAccount,
|
||||
|
@ -174,6 +182,9 @@ export class Session {
|
|||
if (!this._olm) {
|
||||
throw new Error("olm required");
|
||||
}
|
||||
if (this._sessionBackup) {
|
||||
return false;
|
||||
}
|
||||
const key = await ssssKeyFromCredential(type, credential, this._storage, this._cryptoDriver, this._olm);
|
||||
// and create session backup, which needs to read from accountData
|
||||
const readTxn = this._storage.readTxn([
|
||||
|
@ -192,6 +203,7 @@ export class Session {
|
|||
throw err;
|
||||
}
|
||||
await writeTxn.complete();
|
||||
this._hasSecretStorageKey.set(true);
|
||||
}
|
||||
|
||||
async _createSessionBackup(ssssKey, txn) {
|
||||
|
@ -211,12 +223,9 @@ export class Session {
|
|||
return this._sessionBackup;
|
||||
}
|
||||
|
||||
// called after load
|
||||
async beforeFirstSync(isNewLogin) {
|
||||
/** @internal */
|
||||
async createIdentity() {
|
||||
if (this._olm) {
|
||||
if (isNewLogin && this._e2eeAccount) {
|
||||
throw new Error("there should not be an e2ee account already on a fresh login");
|
||||
}
|
||||
if (!this._e2eeAccount) {
|
||||
this._e2eeAccount = await E2EEAccount.create({
|
||||
hsApi: this._hsApi,
|
||||
|
@ -231,21 +240,10 @@ export class Session {
|
|||
}
|
||||
await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
|
||||
await this._e2eeAccount.uploadKeys(this._storage);
|
||||
await this._deviceMessageHandler.decryptPending(this.rooms);
|
||||
|
||||
const txn = this._storage.readTxn([
|
||||
this._storage.storeNames.session,
|
||||
this._storage.storeNames.accountData,
|
||||
]);
|
||||
// try set up session backup if we stored the ssss key
|
||||
const ssssKey = await ssssReadKey(txn);
|
||||
if (ssssKey) {
|
||||
// txn will end here as this does a network request
|
||||
await this._createSessionBackup(ssssKey, txn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async load() {
|
||||
const txn = this._storage.readTxn([
|
||||
this._storage.storeNames.session,
|
||||
|
@ -289,6 +287,12 @@ export class Session {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal called when coming back online
|
||||
* @param {Object} lastVersionResponse a response from /versions, which is polled while offline,
|
||||
* and useful to store so we can later tell what capabilities
|
||||
* our homeserver has.
|
||||
*/
|
||||
async start(lastVersionResponse) {
|
||||
if (lastVersionResponse) {
|
||||
// store /versions response
|
||||
|
@ -299,7 +303,21 @@ export class Session {
|
|||
// TODO: what can we do if this throws?
|
||||
await txn.complete();
|
||||
}
|
||||
|
||||
// enable session backup, this requests the latest backup version
|
||||
if (!this._sessionBackup) {
|
||||
const txn = this._storage.readTxn([
|
||||
this._storage.storeNames.session,
|
||||
this._storage.storeNames.accountData,
|
||||
]);
|
||||
// try set up session backup if we stored the ssss key
|
||||
const ssssKey = await ssssReadKey(txn);
|
||||
if (ssssKey) {
|
||||
// txn will end here as this does a network request
|
||||
await this._createSessionBackup(ssssKey, txn);
|
||||
}
|
||||
this._hasSecretStorageKey.set(!!ssssKey);
|
||||
}
|
||||
// restore unfinished operations, like sending out room keys
|
||||
const opsTxn = this._storage.readWriteTxn([
|
||||
this._storage.storeNames.operations
|
||||
]);
|
||||
|
@ -333,6 +351,7 @@ export class Session {
|
|||
return this._rooms;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
createRoom(roomId, pendingEvents) {
|
||||
const room = new Room({
|
||||
roomId,
|
||||
|
@ -350,6 +369,7 @@ export class Session {
|
|||
return room;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async writeSync(syncResponse, syncFilterId, txn) {
|
||||
const changes = {
|
||||
syncInfo: null,
|
||||
|
@ -394,6 +414,7 @@ export class Session {
|
|||
return changes;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
afterSync({syncInfo, e2eeAccountChanges}) {
|
||||
if (syncInfo) {
|
||||
// sync transaction succeeded, modify object state now
|
||||
|
@ -404,6 +425,7 @@ export class Session {
|
|||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async afterSyncCompleted(changes, isCatchupSync) {
|
||||
const promises = [];
|
||||
if (changes.deviceMessageDecryptionPending) {
|
||||
|
@ -425,10 +447,12 @@ export class Session {
|
|||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get syncToken() {
|
||||
return this._syncInfo?.token;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get syncFilterId() {
|
||||
return this._syncInfo?.filterId;
|
||||
}
|
||||
|
|
|
@ -179,8 +179,10 @@ export class SessionContainer {
|
|||
mediaRepository: new MediaRepository(sessionInfo.homeServer)
|
||||
});
|
||||
await this._session.load();
|
||||
this._status.set(LoadStatus.SessionSetup);
|
||||
await this._session.beforeFirstSync(isNewLogin);
|
||||
if (isNewLogin) {
|
||||
this._status.set(LoadStatus.SessionSetup);
|
||||
await this._session.createIdentity();
|
||||
}
|
||||
|
||||
this._sync = new Sync({hsApi: this._requestScheduler.hsApi, storage: this._storage, session: this._session});
|
||||
// notify sync and session when back online
|
||||
|
|
|
@ -84,16 +84,17 @@ function isCacheableThumbnail(url) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const baseURL = new URL(self.registration.scope);
|
||||
async function handleRequest(request) {
|
||||
const baseURL = self.registration.scope;
|
||||
if (request.url === baseURL) {
|
||||
request = new Request(new URL("index.html", baseURL));
|
||||
const url = new URL(request.url);
|
||||
if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) {
|
||||
request = new Request(new URL("index.html", baseURL.href));
|
||||
}
|
||||
let response = await readCache(request);
|
||||
if (!response) {
|
||||
// use cors so the resource in the cache isn't opaque and uses up to 7mb
|
||||
// https://developers.google.com/web/tools/chrome-devtools/progressive-web-apps?utm_source=devtools#opaque-responses
|
||||
if (isCacheableThumbnail(new URL(request.url))) {
|
||||
if (isCacheableThumbnail(url)) {
|
||||
response = await fetch(request, {mode: "cors", credentials: "omit"});
|
||||
} else {
|
||||
response = await fetch(request);
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {TemplateView} from "../../general/TemplateView.js";
|
||||
import {StaticView} from "../../general/StaticView.js";
|
||||
|
||||
export class SessionBackupSettingsView extends TemplateView {
|
||||
render(t, vm) {
|
||||
|
@ -23,6 +24,7 @@ export class SessionBackupSettingsView extends TemplateView {
|
|||
case "enabled": return new TemplateView(vm, renderEnabled)
|
||||
case "setupKey": return new TemplateView(vm, renderEnableFromKey)
|
||||
case "setupPhrase": return new TemplateView(vm, renderEnableFromPhrase)
|
||||
case "pending": return new StaticView(vm, t => t.p(vm.i18n`Waiting to go online…`))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Reference in a new issue