diff --git a/src/matrix/Session.js b/src/matrix/Session.js index d48e0d00..63b954af 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -125,6 +125,7 @@ export class Session { olmEncryption: this._olmEncryption, megolmEncryption: this._megolmEncryption, megolmDecryption: this._megolmDecryption, + storage: this._storage, encryptionParams }); } diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index cd918292..3538381f 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -128,6 +128,22 @@ export class RoomEncryption { }; } + needsToShareKeys(memberChanges) { + for (const m of memberChanges.values()) { + if (m.member.needsRoomKey) { + return true; + } + } + return false; + } + + async shareRoomKeyToPendingMembers(hsApi) { + // sucks to call this for all encrypted rooms on startup? + const txn = await this._storage.readTxn([this._storage.storeNames.roomMembers]); + const pendingUserIds = await txn.roomMembers.getUserIdsNeedingRoomKey(this._room.id); + return await this._shareRoomKey(pendingUserIds, hsApi); + } + async shareRoomKeyForMemberChanges(memberChanges, hsApi) { const pendingUserIds = []; for (const m of memberChanges.values()) { diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index b4e19a80..55bb1ccb 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -157,7 +157,8 @@ export class Room extends EventEmitter { newLiveKey, removedPendingEvents, memberChanges, - heroChanges + heroChanges, + needsAfterSyncCompleted: this._roomEncryption?.needsToShareKeys(memberChanges) }; } @@ -204,6 +205,17 @@ export class Room extends EventEmitter { } } + /** + * Only called if the result of writeSync had `needsAfterSyncCompleted` set. + * Can be used to do longer running operations that resulted from the last sync, + * like network operations. + */ + async afterSyncCompleted({memberChanges}) { + if (this._roomEncryption) { + await this._roomEncryption.shareRoomKeyForMemberChanges(memberChanges, this._hsApi); + } + } + /** @package */ resumeSending() { this._sendQueue.resumeSending(); diff --git a/src/matrix/storage/idb/QueryTarget.js b/src/matrix/storage/idb/QueryTarget.js index fd3050bd..6d11932f 100644 --- a/src/matrix/storage/idb/QueryTarget.js +++ b/src/matrix/storage/idb/QueryTarget.js @@ -187,6 +187,14 @@ export class QueryTarget { return results; } + async iterateWhile(range, predicate) { + const cursor = this._openCursor(range, "next"); + await iterateCursor(cursor, (value) => { + const passesPredicate = predicate(value); + return {done: !passesPredicate}; + }); + } + async _find(range, predicate, direction) { const cursor = this._openCursor(range, direction); let result; diff --git a/src/matrix/storage/idb/stores/RoomMemberStore.js b/src/matrix/storage/idb/stores/RoomMemberStore.js index be2b16ec..6d354dd9 100644 --- a/src/matrix/storage/idb/stores/RoomMemberStore.js +++ b/src/matrix/storage/idb/stores/RoomMemberStore.js @@ -60,4 +60,19 @@ export class RoomMemberStore { }); return userIds; } + + async getUserIdsNeedingRoomKey(roomId) { + const userIds = []; + const range = IDBKeyRange.lowerBound(encodeKey(roomId, "")); + await this._roomMembersStore.iterateWhile(range, member => { + if (member.roomId !== roomId) { + return false; + } + if (member.needsRoomKey) { + userIds.push(member.userId); + } + return true; + }); + return userIds; + } }