prevent decryption result getting lost after reaction updates entry
This commit is contained in:
parent
48588687a5
commit
e125599a47
6 changed files with 77 additions and 21 deletions
|
@ -299,7 +299,7 @@ export class BaseRoom extends EventEmitter {
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
// these should not be added if not already there
|
// these should not be added if not already there
|
||||||
this._timeline.replaceEntries(gapResult.updatedEntries);
|
this._timeline.replaceEntries(gapResult.updatedEntries);
|
||||||
this._timeline.addOrReplaceEntries(gapResult.entries);
|
this._timeline.addEntries(gapResult.entries);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ export class Room extends BaseRoom {
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
// these should not be added if not already there
|
// these should not be added if not already there
|
||||||
this._timeline.replaceEntries(updatedEntries);
|
this._timeline.replaceEntries(updatedEntries);
|
||||||
this._timeline.addOrReplaceEntries(newEntries);
|
this._timeline.addEntries(newEntries);
|
||||||
}
|
}
|
||||||
if (this._observedEvents) {
|
if (this._observedEvents) {
|
||||||
this._observedEvents.updateEvents(updatedEntries);
|
this._observedEvents.updateEvents(updatedEntries);
|
||||||
|
|
|
@ -182,17 +182,11 @@ export class Timeline {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @package */
|
||||||
updateOwnMember(member) {
|
updateOwnMember(member) {
|
||||||
this._ownMember = member;
|
this._ownMember = member;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceEntries(entries) {
|
|
||||||
this._addLocalRelationsToNewRemoteEntries(entries);
|
|
||||||
for (const entry of entries) {
|
|
||||||
this._remoteEntries.update(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_addLocalRelationsToNewRemoteEntries(entries) {
|
_addLocalRelationsToNewRemoteEntries(entries) {
|
||||||
// because it is not safe to iterate a derived observable collection
|
// because it is not safe to iterate a derived observable collection
|
||||||
// before it has any subscriptions, we bail out if this isn't
|
// before it has any subscriptions, we bail out if this isn't
|
||||||
|
@ -223,8 +217,22 @@ export class Timeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used in replaceEntries
|
||||||
|
static _entryUpdater(existingEntry, entry) {
|
||||||
|
entry.updateFrom(existingEntry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
addOrReplaceEntries(newEntries) {
|
replaceEntries(entries) {
|
||||||
|
this._addLocalRelationsToNewRemoteEntries(entries);
|
||||||
|
for (const entry of entries) {
|
||||||
|
this._remoteEntries.getAndUpdate(entry, Timeline._entryUpdater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @package */
|
||||||
|
addEntries(newEntries) {
|
||||||
this._addLocalRelationsToNewRemoteEntries(newEntries);
|
this._addLocalRelationsToNewRemoteEntries(newEntries);
|
||||||
this._remoteEntries.setManySorted(newEntries);
|
this._remoteEntries.setManySorted(newEntries);
|
||||||
}
|
}
|
||||||
|
@ -251,7 +259,7 @@ export class Timeline {
|
||||||
));
|
));
|
||||||
try {
|
try {
|
||||||
const entries = await readerRequest.complete();
|
const entries = await readerRequest.complete();
|
||||||
this.addOrReplaceEntries(entries);
|
this.addEntries(entries);
|
||||||
return entries.length < amount;
|
return entries.length < amount;
|
||||||
} finally {
|
} finally {
|
||||||
this._disposables.disposeTracked(readerRequest);
|
this._disposables.disposeTracked(readerRequest);
|
||||||
|
@ -366,13 +374,13 @@ export function tests() {
|
||||||
closeCallback: () => {}, fragmentIdComparer, pendingEvents, clock: new MockClock()});
|
closeCallback: () => {}, fragmentIdComparer, pendingEvents, clock: new MockClock()});
|
||||||
// 1. load timeline
|
// 1. load timeline
|
||||||
await timeline.load(new User(alice), "join", new NullLogItem());
|
await timeline.load(new User(alice), "join", new NullLogItem());
|
||||||
// 2. test replaceEntries and addOrReplaceEntries don't fail
|
// 2. test replaceEntries and addEntries don't fail
|
||||||
const event1 = withTextBody("hi!", withSender(bob, createEvent("m.room.message", "!abc")));
|
const event1 = withTextBody("hi!", withSender(bob, createEvent("m.room.message", "!abc")));
|
||||||
const entry1 = new EventEntry({event: event1, fragmentId: 1, eventIndex: 1}, fragmentIdComparer);
|
const entry1 = new EventEntry({event: event1, fragmentId: 1, eventIndex: 1}, fragmentIdComparer);
|
||||||
timeline.replaceEntries([entry1]);
|
timeline.replaceEntries([entry1]);
|
||||||
const event2 = withTextBody("hi bob!", withSender(alice, createEvent("m.room.message", "!def")));
|
const event2 = withTextBody("hi bob!", withSender(alice, createEvent("m.room.message", "!def")));
|
||||||
const entry2 = new EventEntry({event: event2, fragmentId: 1, eventIndex: 2}, fragmentIdComparer);
|
const entry2 = new EventEntry({event: event2, fragmentId: 1, eventIndex: 2}, fragmentIdComparer);
|
||||||
timeline.addOrReplaceEntries([entry2]);
|
timeline.addEntries([entry2]);
|
||||||
// 3. add local relation (redaction)
|
// 3. add local relation (redaction)
|
||||||
pendingEvents.append(new PendingEvent({data: {
|
pendingEvents.append(new PendingEvent({data: {
|
||||||
roomId,
|
roomId,
|
||||||
|
@ -396,7 +404,7 @@ export function tests() {
|
||||||
await timeline.load(new User(bob), "join", new NullLogItem());
|
await timeline.load(new User(bob), "join", new NullLogItem());
|
||||||
timeline.entries.subscribe(new ListObserver());
|
timeline.entries.subscribe(new ListObserver());
|
||||||
const event = withTextBody("hi bob!", withSender(alice, createEvent("m.room.message", "!abc")));
|
const event = withTextBody("hi bob!", withSender(alice, createEvent("m.room.message", "!abc")));
|
||||||
timeline.addOrReplaceEntries([new EventEntry({event, fragmentId: 1, eventIndex: 2}, fragmentIdComparer)]);
|
timeline.addEntries([new EventEntry({event, fragmentId: 1, eventIndex: 2}, fragmentIdComparer)]);
|
||||||
let entry = getIndexFromIterable(timeline.entries, 0);
|
let entry = getIndexFromIterable(timeline.entries, 0);
|
||||||
// 2. add local reaction
|
// 2. add local reaction
|
||||||
pendingEvents.append(new PendingEvent({data: {
|
pendingEvents.append(new PendingEvent({data: {
|
||||||
|
@ -467,7 +475,7 @@ export function tests() {
|
||||||
await timeline.load(new User(bob), "join", new NullLogItem());
|
await timeline.load(new User(bob), "join", new NullLogItem());
|
||||||
timeline.entries.subscribe(new ListObserver());
|
timeline.entries.subscribe(new ListObserver());
|
||||||
// 3. add message to timeline
|
// 3. add message to timeline
|
||||||
timeline.addOrReplaceEntries([messageEntry]);
|
timeline.addEntries([messageEntry]);
|
||||||
const entry = getIndexFromIterable(timeline.entries, 0);
|
const entry = getIndexFromIterable(timeline.entries, 0);
|
||||||
assert.equal(entry, messageEntry);
|
assert.equal(entry, messageEntry);
|
||||||
assert.equal(entry.annotations["👋"].count, 1);
|
assert.equal(entry.annotations["👋"].count, 1);
|
||||||
|
@ -488,7 +496,7 @@ export function tests() {
|
||||||
event: withContent(createAnnotation(messageEntry.id, "👋"), createEvent("m.reaction", "!def", bob)),
|
event: withContent(createAnnotation(messageEntry.id, "👋"), createEvent("m.reaction", "!def", bob)),
|
||||||
fragmentId: 1, eventIndex: 3, roomId
|
fragmentId: 1, eventIndex: 3, roomId
|
||||||
}, fragmentIdComparer);
|
}, fragmentIdComparer);
|
||||||
timeline.addOrReplaceEntries([messageEntry, reactionEntry]);
|
timeline.addEntries([messageEntry, reactionEntry]);
|
||||||
// 3. redact reaction
|
// 3. redact reaction
|
||||||
pendingEvents.append(new PendingEvent({data: {
|
pendingEvents.append(new PendingEvent({data: {
|
||||||
roomId,
|
roomId,
|
||||||
|
@ -521,7 +529,7 @@ export function tests() {
|
||||||
}}));
|
}}));
|
||||||
await poll(() => timeline.entries.length === 1);
|
await poll(() => timeline.entries.length === 1);
|
||||||
// 3. add remote reaction target
|
// 3. add remote reaction target
|
||||||
timeline.addOrReplaceEntries([messageEntry]);
|
timeline.addEntries([messageEntry]);
|
||||||
await poll(() => timeline.entries.length === 2);
|
await poll(() => timeline.entries.length === 2);
|
||||||
const entry = getIndexFromIterable(timeline.entries, 0);
|
const entry = getIndexFromIterable(timeline.entries, 0);
|
||||||
assert.equal(entry, messageEntry);
|
assert.equal(entry, messageEntry);
|
||||||
|
@ -555,7 +563,7 @@ export function tests() {
|
||||||
}}));
|
}}));
|
||||||
await poll(() => timeline.entries.length === 1);
|
await poll(() => timeline.entries.length === 1);
|
||||||
// 4. add reaction target
|
// 4. add reaction target
|
||||||
timeline.addOrReplaceEntries([new EventEntry({
|
timeline.addEntries([new EventEntry({
|
||||||
event: withTextBody("hi bob!", createEvent("m.room.message", messageId, alice)),
|
event: withTextBody("hi bob!", createEvent("m.room.message", messageId, alice)),
|
||||||
fragmentId: 1, eventIndex: 2}, fragmentIdComparer)
|
fragmentId: 1, eventIndex: 2}, fragmentIdComparer)
|
||||||
]);
|
]);
|
||||||
|
@ -564,5 +572,32 @@ export function tests() {
|
||||||
const entry = getIndexFromIterable(timeline.entries, 0);
|
const entry = getIndexFromIterable(timeline.entries, 0);
|
||||||
assert.equal(entry.pendingAnnotations.get("👋"), -1);
|
assert.equal(entry.pendingAnnotations.get("👋"), -1);
|
||||||
},
|
},
|
||||||
|
"decrypted entry preserves content when receiving other update without decryption": async assert => {
|
||||||
|
// 1. create encrypted and decrypted entry
|
||||||
|
const encryptedEntry = new EventEntry({
|
||||||
|
event: withContent({ciphertext: "abc"}, createEvent("m.room.encrypted", "!abc", alice)),
|
||||||
|
fragmentId: 1, eventIndex: 1, roomId
|
||||||
|
}, fragmentIdComparer);
|
||||||
|
const decryptedEntry = encryptedEntry.clone();
|
||||||
|
decryptedEntry.setDecryptionResult({
|
||||||
|
event: withTextBody("hi bob!", createEvent("m.room.message", encryptedEntry.id, encryptedEntry.sender))
|
||||||
|
});
|
||||||
|
// 2. setup the timeline
|
||||||
|
const timeline = new Timeline({roomId, storage: await createMockStorage(), closeCallback: () => {},
|
||||||
|
fragmentIdComparer, pendingEvents: new ObservableArray(), clock: new MockClock()});
|
||||||
|
await timeline.load(new User(alice), "join", new NullLogItem());
|
||||||
|
timeline.addEntries([decryptedEntry]);
|
||||||
|
const observer = new ListObserver();
|
||||||
|
timeline.entries.subscribe(observer);
|
||||||
|
// 3. replace the entry with one that is not decrypted
|
||||||
|
// (as would happen when receiving a reaction,
|
||||||
|
// as it does not rerun the decryption)
|
||||||
|
// and check that the decrypted content is preserved
|
||||||
|
timeline.replaceEntries([encryptedEntry]);
|
||||||
|
const {value, type} = await observer.next();
|
||||||
|
assert.equal(type, "update");
|
||||||
|
assert.equal(value.eventType, "m.room.message");
|
||||||
|
assert.equal(value.content.body, "hi bob!");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,4 +47,6 @@ export class BaseEntry {
|
||||||
asEventKey() {
|
asEventKey() {
|
||||||
return new EventKey(this.fragmentId, this.entryIndex);
|
return new EventKey(this.fragmentId, this.entryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFrom() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,20 @@ export class EventEntry extends BaseEventEntry {
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
const clone = new EventEntry(this._eventEntry, this._fragmentIdComparer);
|
const clone = new EventEntry(this._eventEntry, this._fragmentIdComparer);
|
||||||
clone._decryptionResult = this._decryptionResult;
|
clone.updateFrom(this);
|
||||||
clone._decryptionError = this._decryptionError;
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFrom(other) {
|
||||||
|
super.updateFrom(other);
|
||||||
|
if (other._decryptionResult && !this._decryptionResult) {
|
||||||
|
this._decryptionResult = other._decryptionResult;
|
||||||
|
}
|
||||||
|
if (other._decryptionError && !this._decryptionError) {
|
||||||
|
this._decryptionError = other._decryptionError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get event() {
|
get event() {
|
||||||
return this._eventEntry.event;
|
return this._eventEntry.event;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,16 @@ export class SortedArray extends BaseObservableList {
|
||||||
return findAndUpdateInArray(predicate, this._items, this, updater);
|
return findAndUpdateInArray(predicate, this._items, this, updater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAndUpdate(item, updater, updateParams = null) {
|
||||||
|
const idx = this.indexOf(item);
|
||||||
|
if (idx !== -1) {
|
||||||
|
const existingItem = this._items[idx];
|
||||||
|
const newItem = updater(existingItem, item);
|
||||||
|
this._items[idx] = newItem;
|
||||||
|
this.emitUpdate(idx, newItem, updateParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update(item, updateParams = null) {
|
update(item, updateParams = null) {
|
||||||
const idx = this.indexOf(item);
|
const idx = this.indexOf(item);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
|
|
Reference in a new issue