also decrypt messages in the sync response that enabled encryption
like initial sync
This commit is contained in:
parent
241176d6fb
commit
a8392dc684
5 changed files with 111 additions and 192 deletions
|
@ -41,17 +41,14 @@ function timelineIsEmpty(roomResponse) {
|
||||||
/**
|
/**
|
||||||
* Sync steps in js-pseudocode:
|
* Sync steps in js-pseudocode:
|
||||||
* ```js
|
* ```js
|
||||||
* let preparation;
|
|
||||||
* if (room.needsPrepareSync) {
|
|
||||||
* // can only read some stores
|
* // can only read some stores
|
||||||
* preparation = await room.prepareSync(roomResponse, prepareTxn);
|
* const preparation = await room.prepareSync(roomResponse, membership, prepareTxn);
|
||||||
* // can do async work that is not related to storage (such as decryption)
|
* // can do async work that is not related to storage (such as decryption)
|
||||||
* preparation = await room.afterPrepareSync(preparation);
|
* await room.afterPrepareSync(preparation);
|
||||||
* }
|
|
||||||
* // writes and calculates changes
|
* // writes and calculates changes
|
||||||
* const changes = await room.writeSync(roomResponse, membership, isInitialSync, preparation, syncTxn);
|
* const changes = await room.writeSync(roomResponse, isInitialSync, preparation, syncTxn);
|
||||||
* // applies and emits changes once syncTxn is committed
|
* // applies and emits changes once syncTxn is committed
|
||||||
* room.afterSync(changes);
|
* room.afterSync(changes, preparation);
|
||||||
* if (room.needsAfterSyncCompleted(changes)) {
|
* if (room.needsAfterSyncCompleted(changes)) {
|
||||||
* // can do network requests
|
* // can do network requests
|
||||||
* await room.afterSyncCompleted(changes);
|
* await room.afterSyncCompleted(changes);
|
||||||
|
@ -173,14 +170,14 @@ export class Sync {
|
||||||
const isInitialSync = !syncToken;
|
const isInitialSync = !syncToken;
|
||||||
syncToken = response.next_batch;
|
syncToken = response.next_batch;
|
||||||
const roomStates = this._parseRoomsResponse(response.rooms, isInitialSync);
|
const roomStates = this._parseRoomsResponse(response.rooms, isInitialSync);
|
||||||
await this._prepareRooms(roomStates);
|
await this._prepareRooms(roomStates, isInitialSync);
|
||||||
let sessionChanges;
|
let sessionChanges;
|
||||||
const syncTxn = await this._openSyncTxn();
|
const syncTxn = await this._openSyncTxn();
|
||||||
try {
|
try {
|
||||||
await Promise.all(roomStates.map(async rs => {
|
await Promise.all(roomStates.map(async rs => {
|
||||||
console.log(` * applying sync response to room ${rs.room.id} ...`);
|
console.log(` * applying sync response to room ${rs.room.id} ...`);
|
||||||
rs.changes = await rs.room.writeSync(
|
rs.changes = await rs.room.writeSync(
|
||||||
rs.roomResponse, rs.membership, isInitialSync, rs.preparation, syncTxn);
|
rs.roomResponse, isInitialSync, rs.preparation, syncTxn);
|
||||||
}));
|
}));
|
||||||
sessionChanges = await this._session.writeSync(response, syncFilterId, syncTxn);
|
sessionChanges = await this._session.writeSync(response, syncFilterId, syncTxn);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
@ -219,16 +216,11 @@ export class Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareRooms(roomStates) {
|
async _prepareRooms(roomStates) {
|
||||||
const prepareRoomStates = roomStates.filter(rs => rs.room.needsPrepareSync);
|
|
||||||
if (prepareRoomStates.length) {
|
|
||||||
const prepareTxn = await this._openPrepareSyncTxn();
|
const prepareTxn = await this._openPrepareSyncTxn();
|
||||||
await Promise.all(prepareRoomStates.map(async rs => {
|
await Promise.all(roomStates.map(async rs => {
|
||||||
rs.preparation = await rs.room.prepareSync(rs.roomResponse, prepareTxn);
|
rs.preparation = await rs.room.prepareSync(rs.roomResponse, rs.membership, prepareTxn);
|
||||||
}));
|
}));
|
||||||
await Promise.all(prepareRoomStates.map(async rs => {
|
await Promise.all(roomStates.map(rs => rs.room.afterPrepareSync(rs.preparation)));
|
||||||
rs.preparation = await rs.room.afterPrepareSync(rs.preparation);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _openSyncTxn() {
|
async _openSyncTxn() {
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class Room extends EventEmitter {
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
this._hsApi = hsApi;
|
this._hsApi = hsApi;
|
||||||
this._mediaRepository = mediaRepository;
|
this._mediaRepository = mediaRepository;
|
||||||
this._summary = new RoomSummary(roomId, user.id);
|
this._summary = new RoomSummary(roomId);
|
||||||
this._fragmentIdComparer = new FragmentIdComparer([]);
|
this._fragmentIdComparer = new FragmentIdComparer([]);
|
||||||
this._syncWriter = new SyncWriter({roomId, fragmentIdComparer: this._fragmentIdComparer});
|
this._syncWriter = new SyncWriter({roomId, fragmentIdComparer: this._fragmentIdComparer});
|
||||||
this._emitCollectionChange = emitCollectionChange;
|
this._emitCollectionChange = emitCollectionChange;
|
||||||
|
@ -84,18 +84,17 @@ export class Room extends EventEmitter {
|
||||||
// _decryptEntries entries and could even know which events have been decrypted for the first
|
// _decryptEntries entries and could even know which events have been decrypted for the first
|
||||||
// time from DecryptionChanges.write and only pass those to the summary. As timeline changes
|
// time from DecryptionChanges.write and only pass those to the summary. As timeline changes
|
||||||
// are not essential to the room summary, it's fine to write this in a separate txn for now.
|
// are not essential to the room summary, it's fine to write this in a separate txn for now.
|
||||||
const changes = this._summary.processTimelineEntries(retryEntries, false, this._isTimelineOpen);
|
const changes = this._summary.data.applyTimelineEntries(retryEntries, false, this._isTimelineOpen);
|
||||||
if (changes) {
|
if (await this._summary.writeAndApplyData(changes, this._storage)) {
|
||||||
this._summary.writeAndApplyChanges(changes, this._storage);
|
|
||||||
this._emitUpdate();
|
this._emitUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableEncryption(encryptionParams) {
|
_setEncryption(roomEncryption) {
|
||||||
this._roomEncryption = this._createRoomEncryption(this, encryptionParams);
|
if (roomEncryption && !this._roomEncryption) {
|
||||||
if (this._roomEncryption) {
|
this._roomEncryption = roomEncryption;
|
||||||
this._sendQueue.enableEncryption(this._roomEncryption);
|
this._sendQueue.enableEncryption(this._roomEncryption);
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
this._timeline.enableEncryption(this._decryptEntries.bind(this, DecryptionSource.Timeline));
|
this._timeline.enableEncryption(this._decryptEntries.bind(this, DecryptionSource.Timeline));
|
||||||
|
@ -141,57 +140,62 @@ export class Room extends EventEmitter {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
get needsPrepareSync() {
|
async prepareSync(roomResponse, membership, txn) {
|
||||||
// only encrypted rooms need the prepare sync steps
|
const summaryChanges = this._summary.data.applySyncResponse(roomResponse, membership)
|
||||||
return !!this._roomEncryption;
|
let roomEncryption = this._roomEncryption;
|
||||||
|
// encryption is enabled in this sync
|
||||||
|
if (!roomEncryption && summaryChanges.encryption) {
|
||||||
|
roomEncryption = this._createRoomEncryption(this, summaryChanges.encryption);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepareSync(roomResponse, txn) {
|
let decryptPreparation;
|
||||||
if (this._roomEncryption) {
|
if (roomEncryption) {
|
||||||
const events = roomResponse?.timeline?.events;
|
const events = roomResponse?.timeline?.events;
|
||||||
if (Array.isArray(events)) {
|
if (Array.isArray(events)) {
|
||||||
const eventsToDecrypt = events.filter(event => {
|
const eventsToDecrypt = events.filter(event => {
|
||||||
return event?.type === EVENT_ENCRYPTED_TYPE;
|
return event?.type === EVENT_ENCRYPTED_TYPE;
|
||||||
});
|
});
|
||||||
const preparation = await this._roomEncryption.prepareDecryptAll(
|
decryptPreparation = await roomEncryption.prepareDecryptAll(
|
||||||
eventsToDecrypt, DecryptionSource.Sync, this._isTimelineOpen, txn);
|
eventsToDecrypt, DecryptionSource.Sync, this._isTimelineOpen, txn);
|
||||||
return preparation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
roomEncryption,
|
||||||
|
summaryChanges,
|
||||||
|
decryptPreparation,
|
||||||
|
decryptChanges: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async afterPrepareSync(preparation) {
|
async afterPrepareSync(preparation) {
|
||||||
if (preparation) {
|
if (preparation.decryptPreparation) {
|
||||||
const decryptChanges = await preparation.decrypt();
|
preparation.decryptChanges = await preparation.decryptPreparation.decrypt();
|
||||||
return decryptChanges;
|
preparation.decryptPreparation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
async writeSync(roomResponse, membership, isInitialSync, decryptChanges, txn) {
|
async writeSync(roomResponse, isInitialSync, {summaryChanges, decryptChanges, roomEncryption}, txn) {
|
||||||
let decryption;
|
|
||||||
if (this._roomEncryption && decryptChanges) {
|
|
||||||
decryption = await decryptChanges.write(txn);
|
|
||||||
}
|
|
||||||
const {entries, newLiveKey, memberChanges} =
|
const {entries, newLiveKey, memberChanges} =
|
||||||
await this._syncWriter.writeSync(roomResponse, txn);
|
await this._syncWriter.writeSync(roomResponse, txn);
|
||||||
if (decryption) {
|
if (decryptChanges) {
|
||||||
|
const decryption = await decryptChanges.write(txn);
|
||||||
decryption.applyToEntries(entries);
|
decryption.applyToEntries(entries);
|
||||||
}
|
}
|
||||||
// pass member changes to device tracker
|
// pass member changes to device tracker
|
||||||
if (this._roomEncryption && this.isTrackingMembers && memberChanges?.size) {
|
if (roomEncryption && this.isTrackingMembers && memberChanges?.size) {
|
||||||
await this._roomEncryption.writeMemberChanges(memberChanges, txn);
|
await roomEncryption.writeMemberChanges(memberChanges, txn);
|
||||||
}
|
}
|
||||||
const summaryChanges = this._summary.writeSync(
|
// also apply (decrypted) timeline entries to the summary changes
|
||||||
roomResponse,
|
summaryChanges = summaryChanges.applyTimelineEntries(
|
||||||
entries,
|
entries, isInitialSync, this._isTimelineOpen, this._user.id);
|
||||||
membership,
|
// write summary changes, and unset if nothing was actually changed
|
||||||
isInitialSync, this._isTimelineOpen,
|
summaryChanges = this._summary.writeData(summaryChanges, txn);
|
||||||
txn);
|
|
||||||
// fetch new members while we have txn open,
|
// fetch new members while we have txn open,
|
||||||
// but don't make any in-memory changes yet
|
// but don't make any in-memory changes yet
|
||||||
let heroChanges;
|
let heroChanges;
|
||||||
if (summaryChanges && needsHeroes(summaryChanges)) {
|
if (summaryChanges?.needsHeroes) {
|
||||||
// room name disappeared, open heroes
|
// room name disappeared, open heroes
|
||||||
if (!this._heroes) {
|
if (!this._heroes) {
|
||||||
this._heroes = new Heroes(this._roomId);
|
this._heroes = new Heroes(this._roomId);
|
||||||
|
@ -204,6 +208,7 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
summaryChanges,
|
summaryChanges,
|
||||||
|
roomEncryption,
|
||||||
newTimelineEntries: entries,
|
newTimelineEntries: entries,
|
||||||
newLiveKey,
|
newLiveKey,
|
||||||
removedPendingEvents,
|
removedPendingEvents,
|
||||||
|
@ -217,11 +222,9 @@ export class Room extends EventEmitter {
|
||||||
* Called with the changes returned from `writeSync` to apply them and emit changes.
|
* Called with the changes returned from `writeSync` to apply them and emit changes.
|
||||||
* No storage or network operations should be done here.
|
* No storage or network operations should be done here.
|
||||||
*/
|
*/
|
||||||
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
|
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges, roomEncryption}) {
|
||||||
this._syncWriter.afterSync(newLiveKey);
|
this._syncWriter.afterSync(newLiveKey);
|
||||||
if (!this._summary.encryption && summaryChanges.encryption && !this._roomEncryption) {
|
this._setEncryption(roomEncryption);
|
||||||
this._enableEncryption(summaryChanges.encryption);
|
|
||||||
}
|
|
||||||
if (memberChanges.size) {
|
if (memberChanges.size) {
|
||||||
if (this._changedMembersDuringSync) {
|
if (this._changedMembersDuringSync) {
|
||||||
for (const [userId, memberChange] of memberChanges.entries()) {
|
for (const [userId, memberChange] of memberChanges.entries()) {
|
||||||
|
@ -235,14 +238,14 @@ export class Room extends EventEmitter {
|
||||||
let emitChange = false;
|
let emitChange = false;
|
||||||
if (summaryChanges) {
|
if (summaryChanges) {
|
||||||
this._summary.applyChanges(summaryChanges);
|
this._summary.applyChanges(summaryChanges);
|
||||||
if (!this._summary.needsHeroes) {
|
if (!this._summary.data.needsHeroes) {
|
||||||
this._heroes = null;
|
this._heroes = null;
|
||||||
}
|
}
|
||||||
emitChange = true;
|
emitChange = true;
|
||||||
}
|
}
|
||||||
if (this._heroes && heroChanges) {
|
if (this._heroes && heroChanges) {
|
||||||
const oldName = this.name;
|
const oldName = this.name;
|
||||||
this._heroes.applyChanges(heroChanges, this._summary);
|
this._heroes.applyChanges(heroChanges, this._summary.data);
|
||||||
if (oldName !== this.name) {
|
if (oldName !== this.name) {
|
||||||
emitChange = true;
|
emitChange = true;
|
||||||
}
|
}
|
||||||
|
@ -294,14 +297,15 @@ export class Room extends EventEmitter {
|
||||||
async load(summary, txn) {
|
async load(summary, txn) {
|
||||||
try {
|
try {
|
||||||
this._summary.load(summary);
|
this._summary.load(summary);
|
||||||
if (this._summary.encryption) {
|
if (this._summary.data.encryption) {
|
||||||
this._enableEncryption(this._summary.encryption);
|
const roomEncryption = this._createRoomEncryption(this, this._summary.data.encryption);
|
||||||
|
this._setEncryption(roomEncryption);
|
||||||
}
|
}
|
||||||
// need to load members for name?
|
// need to load members for name?
|
||||||
if (this._summary.needsHeroes) {
|
if (this._summary.data.needsHeroes) {
|
||||||
this._heroes = new Heroes(this._roomId);
|
this._heroes = new Heroes(this._roomId);
|
||||||
const changes = await this._heroes.calculateChanges(this._summary.heroes, [], txn);
|
const changes = await this._heroes.calculateChanges(this._summary.data.heroes, [], txn);
|
||||||
this._heroes.applyChanges(changes, this._summary);
|
this._heroes.applyChanges(changes, this._summary.data);
|
||||||
}
|
}
|
||||||
return this._syncWriter.load(txn);
|
return this._syncWriter.load(txn);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -397,7 +401,14 @@ export class Room extends EventEmitter {
|
||||||
if (this._heroes) {
|
if (this._heroes) {
|
||||||
return this._heroes.roomName;
|
return this._heroes.roomName;
|
||||||
}
|
}
|
||||||
return this._summary.name;
|
const summaryData = this._summary.data;
|
||||||
|
if (summaryData.name) {
|
||||||
|
return summaryData.name;
|
||||||
|
}
|
||||||
|
if (summaryData.canonicalAlias) {
|
||||||
|
return summaryData.canonicalAlias;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -406,8 +417,8 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
get avatarUrl() {
|
get avatarUrl() {
|
||||||
if (this._summary.avatarUrl) {
|
if (this._summary.data.avatarUrl) {
|
||||||
return this._summary.avatarUrl;
|
return this._summary.data.avatarUrl;
|
||||||
} else if (this._heroes) {
|
} else if (this._heroes) {
|
||||||
return this._heroes.roomAvatarUrl;
|
return this._heroes.roomAvatarUrl;
|
||||||
}
|
}
|
||||||
|
@ -415,28 +426,28 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessageTimestamp() {
|
get lastMessageTimestamp() {
|
||||||
return this._summary.lastMessageTimestamp;
|
return this._summary.data.lastMessageTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isUnread() {
|
get isUnread() {
|
||||||
return this._summary.isUnread;
|
return this._summary.data.isUnread;
|
||||||
}
|
}
|
||||||
|
|
||||||
get notificationCount() {
|
get notificationCount() {
|
||||||
return this._summary.notificationCount;
|
return this._summary.data.notificationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
get highlightCount() {
|
get highlightCount() {
|
||||||
return this._summary.highlightCount;
|
return this._summary.data.highlightCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLowPriority() {
|
get isLowPriority() {
|
||||||
const tags = this._summary.tags;
|
const tags = this._summary.data.tags;
|
||||||
return !!(tags && tags['m.lowpriority']);
|
return !!(tags && tags['m.lowpriority']);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEncrypted() {
|
get isEncrypted() {
|
||||||
return !!this._summary.encryption;
|
return !!this._summary.data.encryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
enableSessionBackup(sessionBackup) {
|
enableSessionBackup(sessionBackup) {
|
||||||
|
@ -444,7 +455,7 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTrackingMembers() {
|
get isTrackingMembers() {
|
||||||
return this._summary.isTrackingMembers;
|
return this._summary.data.isTrackingMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getLastEventId() {
|
async _getLastEventId() {
|
||||||
|
|
|
@ -39,16 +39,17 @@ function applySyncResponse(data, roomResponse, membership) {
|
||||||
if (roomResponse.account_data) {
|
if (roomResponse.account_data) {
|
||||||
data = roomResponse.account_data.events.reduce(processRoomAccountData, data);
|
data = roomResponse.account_data.events.reduce(processRoomAccountData, data);
|
||||||
}
|
}
|
||||||
|
const stateEvents = roomResponse?.state?.events;
|
||||||
// state comes before timeline
|
// state comes before timeline
|
||||||
if (roomResponse.state) {
|
if (Array.isArray(stateEvents)) {
|
||||||
data = roomResponse.state.events.reduce(processStateEvent, data);
|
data = stateEvents.reduce(processStateEvent, data);
|
||||||
}
|
}
|
||||||
const {timeline} = roomResponse;
|
const timelineEvents = roomResponse?.timeline?.events;
|
||||||
// process state events in timeline
|
// process state events in timeline
|
||||||
// non-state events are handled by applyTimelineEntries
|
// non-state events are handled by applyTimelineEntries
|
||||||
// so decryption is handled properly
|
// so decryption is handled properly
|
||||||
if (timeline && Array.isArray(timeline.events)) {
|
if (Array.isArray(timelineEvents)) {
|
||||||
data = timeline.events.reduce((data, event) => {
|
data = timelineEvents.reduce((data, event) => {
|
||||||
if (typeof event.state_key === "string") {
|
if (typeof event.state_key === "string") {
|
||||||
return processStateEvent(data, event);
|
return processStateEvent(data, event);
|
||||||
}
|
}
|
||||||
|
@ -200,87 +201,27 @@ class SummaryData {
|
||||||
const {cloned, ...serializedProps} = this;
|
const {cloned, ...serializedProps} = this;
|
||||||
return serializedProps;
|
return serializedProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTimelineEntries(timelineEntries, isInitialSync, isTimelineOpen, ownUserId) {
|
||||||
|
return applyTimelineEntries(this, timelineEntries, isInitialSync, isTimelineOpen, ownUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function needsHeroes(data) {
|
applySyncResponse(roomResponse, membership) {
|
||||||
return !data.name && !data.canonicalAlias && data.heroes && data.heroes.length > 0;
|
return applySyncResponse(this, roomResponse, membership);
|
||||||
|
}
|
||||||
|
|
||||||
|
get needsHeroes() {
|
||||||
|
return !this.name && !this.canonicalAlias && this.heroes && this.heroes.length > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RoomSummary {
|
export class RoomSummary {
|
||||||
constructor(roomId, ownUserId) {
|
constructor(roomId) {
|
||||||
this._ownUserId = ownUserId;
|
|
||||||
this._data = new SummaryData(null, roomId);
|
this._data = new SummaryData(null, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get data() {
|
||||||
if (this._data.name) {
|
return this._data;
|
||||||
return this._data.name;
|
|
||||||
}
|
|
||||||
if (this._data.canonicalAlias) {
|
|
||||||
return this._data.canonicalAlias;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get heroes() {
|
|
||||||
return this._data.heroes;
|
|
||||||
}
|
|
||||||
|
|
||||||
get encryption() {
|
|
||||||
return this._data.encryption;
|
|
||||||
}
|
|
||||||
|
|
||||||
// whether the room name should be determined with Heroes
|
|
||||||
get needsHeroes() {
|
|
||||||
return needsHeroes(this._data);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isUnread() {
|
|
||||||
return this._data.isUnread;
|
|
||||||
}
|
|
||||||
|
|
||||||
get notificationCount() {
|
|
||||||
return this._data.notificationCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get highlightCount() {
|
|
||||||
return this._data.highlightCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastMessage() {
|
|
||||||
return this._data.lastMessageBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastMessageTimestamp() {
|
|
||||||
return this._data.lastMessageTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
get inviteCount() {
|
|
||||||
return this._data.inviteCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get joinCount() {
|
|
||||||
return this._data.joinCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get avatarUrl() {
|
|
||||||
return this._data.avatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasFetchedMembers() {
|
|
||||||
return this._data.hasFetchedMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTrackingMembers() {
|
|
||||||
return this._data.isTrackingMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tags() {
|
|
||||||
return this._data.tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastDecryptedEventKey() {
|
|
||||||
return this._data.lastDecryptedEventKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeClearUnread(txn) {
|
writeClearUnread(txn) {
|
||||||
|
@ -306,45 +247,17 @@ export class RoomSummary {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
writeData(data, txn) {
|
||||||
* after retrying decryption
|
|
||||||
*/
|
|
||||||
processTimelineEntries(timelineEntries, isInitialSync, isTimelineOpen) {
|
|
||||||
// clear cloned flag, so cloneIfNeeded makes a copy and
|
|
||||||
// this._data is not modified if any field is changed.
|
|
||||||
this._data.cloned = false;
|
|
||||||
const data = applyTimelineEntries(
|
|
||||||
this._data,
|
|
||||||
timelineEntries,
|
|
||||||
isInitialSync, isTimelineOpen,
|
|
||||||
this._ownUserId);
|
|
||||||
if (data !== this._data) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSync(roomResponse, timelineEntries, membership, isInitialSync, isTimelineOpen, txn) {
|
|
||||||
// clear cloned flag, so cloneIfNeeded makes a copy and
|
|
||||||
// this._data is not modified if any field is changed.
|
|
||||||
this._data.cloned = false;
|
|
||||||
let data = applySyncResponse(this._data, roomResponse, membership);
|
|
||||||
data = applyTimelineEntries(
|
|
||||||
data,
|
|
||||||
timelineEntries,
|
|
||||||
isInitialSync, isTimelineOpen,
|
|
||||||
this._ownUserId);
|
|
||||||
if (data !== this._data) {
|
if (data !== this._data) {
|
||||||
txn.roomSummary.set(data.serialize());
|
txn.roomSummary.set(data.serialize());
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async writeAndApplyData(data, storage) {
|
||||||
* Only to be used with processTimelineEntries,
|
if (data === this._data) {
|
||||||
* other methods like writeSync, writeHasFetchedMembers,
|
return;
|
||||||
* writeIsTrackingMembers, ... take a txn directly.
|
}
|
||||||
*/
|
|
||||||
async writeAndApplyChanges(data, storage) {
|
|
||||||
const txn = await storage.readWriteTxn([
|
const txn = await storage.readWriteTxn([
|
||||||
storage.storeNames.roomSummary,
|
storage.storeNames.roomSummary,
|
||||||
]);
|
]);
|
||||||
|
@ -360,6 +273,9 @@ export class RoomSummary {
|
||||||
|
|
||||||
applyChanges(data) {
|
applyChanges(data) {
|
||||||
this._data = data;
|
this._data = data;
|
||||||
|
// clear cloned flag, so cloneIfNeeded makes a copy and
|
||||||
|
// this._data is not modified if any field is changed.
|
||||||
|
this._data.cloned = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(summary) {
|
async load(summary) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
import {RoomMember} from "./RoomMember.js";
|
import {RoomMember} from "./RoomMember.js";
|
||||||
|
|
||||||
function calculateRoomName(sortedMembers, summary) {
|
function calculateRoomName(sortedMembers, summaryData) {
|
||||||
const countWithoutMe = summary.joinCount + summary.inviteCount - 1;
|
const countWithoutMe = summaryData.joinCount + summaryData.inviteCount - 1;
|
||||||
if (sortedMembers.length >= countWithoutMe) {
|
if (sortedMembers.length >= countWithoutMe) {
|
||||||
if (sortedMembers.length > 1) {
|
if (sortedMembers.length > 1) {
|
||||||
const lastMember = sortedMembers[sortedMembers.length - 1];
|
const lastMember = sortedMembers[sortedMembers.length - 1];
|
||||||
|
@ -74,7 +74,7 @@ export class Heroes {
|
||||||
return {updatedHeroMembers: updatedHeroMembers.values(), removedUserIds};
|
return {updatedHeroMembers: updatedHeroMembers.values(), removedUserIds};
|
||||||
}
|
}
|
||||||
|
|
||||||
applyChanges({updatedHeroMembers, removedUserIds}, summary) {
|
applyChanges({updatedHeroMembers, removedUserIds}, summaryData) {
|
||||||
for (const userId of removedUserIds) {
|
for (const userId of removedUserIds) {
|
||||||
this._members.delete(userId);
|
this._members.delete(userId);
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ export class Heroes {
|
||||||
this._members.set(member.userId, member);
|
this._members.set(member.userId, member);
|
||||||
}
|
}
|
||||||
const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.name.localeCompare(b.name));
|
const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
this._roomName = calculateRoomName(sortedMembers, summary);
|
this._roomName = calculateRoomName(sortedMembers, summaryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
get roomName() {
|
get roomName() {
|
||||||
|
|
|
@ -82,7 +82,7 @@ async function fetchMembers({summary, syncToken, roomId, hsApi, storage, setChan
|
||||||
|
|
||||||
export async function fetchOrLoadMembers(options) {
|
export async function fetchOrLoadMembers(options) {
|
||||||
const {summary} = options;
|
const {summary} = options;
|
||||||
if (!summary.hasFetchedMembers) {
|
if (!summary.data.hasFetchedMembers) {
|
||||||
return fetchMembers(options);
|
return fetchMembers(options);
|
||||||
} else {
|
} else {
|
||||||
return loadMembers(options);
|
return loadMembers(options);
|
||||||
|
|
Reference in a new issue