add support for redactions (and relations) local echo
This commit is contained in:
parent
b55efb7f11
commit
af45810582
5 changed files with 136 additions and 20 deletions
|
@ -216,12 +216,12 @@ export class SendQueue {
|
|||
let relatedEventId;
|
||||
if (isTxnId(eventIdOrTxnId)) {
|
||||
relatedTxnId = eventIdOrTxnId;
|
||||
log.set("relatedTxnId", eventIdOrTxnId);
|
||||
const txnId = eventIdOrTxnId;
|
||||
const pe = this._pendingEvents.array.find(pe => pe.txnId === txnId);
|
||||
if (pe && !pe.remoteId && pe.status !== SendStatus.Sending) {
|
||||
// haven't started sending this event yet,
|
||||
// just remove it from the queue
|
||||
log.set("remove", relatedTxnId);
|
||||
await pe.abort();
|
||||
return;
|
||||
} else if (pe) {
|
||||
|
@ -236,8 +236,15 @@ export class SendQueue {
|
|||
}
|
||||
} else {
|
||||
relatedEventId = eventIdOrTxnId;
|
||||
log.set("relatedEventId", relatedEventId);
|
||||
const pe = this._pendingEvents.array.find(pe => pe.remoteId === relatedEventId);
|
||||
if (pe) {
|
||||
// also set the txn id just in case that an event id was passed
|
||||
// for relating to a pending event that is still waiting for the remote echo
|
||||
relatedTxnId = pe.txnId;
|
||||
}
|
||||
}
|
||||
log.set("relatedTxnId", eventIdOrTxnId);
|
||||
log.set("relatedEventId", relatedEventId);
|
||||
await this._enqueueEvent(REDACTION_TYPE, {reason}, null, relatedTxnId, relatedEventId, log);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -28,7 +29,9 @@ export class Timeline {
|
|||
this._closeCallback = closeCallback;
|
||||
this._fragmentIdComparer = fragmentIdComparer;
|
||||
this._disposables = new Disposables();
|
||||
this._remoteEntries = new SortedArray((a, b) => a.compare(b));
|
||||
this._pendingEvents = pendingEvents;
|
||||
this._clock = clock;
|
||||
this._remoteEntries = null;
|
||||
this._ownMember = null;
|
||||
this._timelineReader = new TimelineReader({
|
||||
roomId: this._roomId,
|
||||
|
@ -36,17 +39,7 @@ export class Timeline {
|
|||
fragmentIdComparer: this._fragmentIdComparer
|
||||
});
|
||||
this._readerRequest = null;
|
||||
let localEntries;
|
||||
if (pendingEvents) {
|
||||
localEntries = new MappedList(pendingEvents, pe => {
|
||||
return new PendingEventEntry({pendingEvent: pe, member: this._ownMember, clock});
|
||||
}, (pee, params) => {
|
||||
pee.notifyUpdate(params);
|
||||
});
|
||||
} else {
|
||||
localEntries = new ObservableArray();
|
||||
}
|
||||
this._allEntries = new ConcatList(this._remoteEntries, localEntries);
|
||||
this._allEntries = null;
|
||||
}
|
||||
|
||||
/** @package */
|
||||
|
@ -69,12 +62,58 @@ export class Timeline {
|
|||
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(30, txn, log));
|
||||
try {
|
||||
const entries = await readerRequest.complete();
|
||||
this._remoteEntries.setManySorted(entries);
|
||||
this._setupEntries(entries);
|
||||
} finally {
|
||||
this._disposables.disposeTracked(readerRequest);
|
||||
}
|
||||
}
|
||||
|
||||
_setupEntries(timelineEntries) {
|
||||
this._remoteEntries = new SortedArray((a, b) => a.compare(b));
|
||||
this._remoteEntries.setManySorted(timelineEntries);
|
||||
if (this._pendingEvents) {
|
||||
this._localEntries = new MappedList(this._pendingEvents, pe => {
|
||||
const pee = new PendingEventEntry({pendingEvent: pe, member: this._ownMember, clock: this._clock});
|
||||
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => target.addLocalRelation(pee));
|
||||
return pee;
|
||||
}, (pee, params) => {
|
||||
// is sending but redacted, who do we detect that here to remove the relation?
|
||||
pee.notifyUpdate(params);
|
||||
}, pee => {
|
||||
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => target.removeLocalRelation(pee));
|
||||
});
|
||||
// we need a hook for when a pee is removed, so we can remove the local relation
|
||||
} else {
|
||||
this._localEntries = new ObservableArray();
|
||||
}
|
||||
this._allEntries = new ConcatList(this._remoteEntries, this._localEntries);
|
||||
}
|
||||
|
||||
_applyAndEmitLocalRelationChange(pe, updater) {
|
||||
// first, look in local entries (separately, as it has its own update mechanism)
|
||||
const foundInLocalEntries = this._localEntries.findAndUpdate(
|
||||
e => e.id === pe.relatedTxnId,
|
||||
e => {
|
||||
const params = updater(e);
|
||||
return params ? params : false;
|
||||
},
|
||||
);
|
||||
// now look in remote entries
|
||||
if (!foundInLocalEntries && pe.relatedEventId) {
|
||||
// TODO: ideally iterate in reverse as target is likely to be most recent,
|
||||
// but not easy through ObservableList contract
|
||||
for (const entry of this._allEntries) {
|
||||
if (pe.relatedEventId === entry.id) {
|
||||
const params = updater(entry);
|
||||
if (params) {
|
||||
this._remoteEntries.update(entry, params);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateOwnMember(member) {
|
||||
this._ownMember = member;
|
||||
}
|
||||
|
@ -89,6 +128,15 @@ export class Timeline {
|
|||
|
||||
/** @package */
|
||||
addOrReplaceEntries(newEntries) {
|
||||
// find any local relations to this new remote event
|
||||
for (const pee of this._localEntries) {
|
||||
// this will work because we set relatedEventId when removing remote echos
|
||||
if (pee.relatedEventId) {
|
||||
const relationTarget = newEntries.find(e => e.id === pee.relatedEventId);
|
||||
// no need to emit here as this entry is about to be added
|
||||
relationTarget?.addLocalRelation(pee);
|
||||
}
|
||||
}
|
||||
this._remoteEntries.setManySorted(newEntries);
|
||||
}
|
||||
|
||||
|
@ -128,6 +176,7 @@ export class Timeline {
|
|||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
55
src/matrix/room/timeline/entries/BaseEventEntry.js
Normal file
55
src/matrix/room/timeline/entries/BaseEventEntry.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {BaseEntry} from "./BaseEntry.js";
|
||||
import {REDACTION_TYPE} from "../../common.js";
|
||||
|
||||
export class BaseEventEntry extends BaseEntry {
|
||||
constructor(fragmentIdComparer) {
|
||||
super(fragmentIdComparer);
|
||||
this._localRedactCount = 0;
|
||||
}
|
||||
|
||||
get isRedacted() {
|
||||
return this._localRedactCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
aggregates local relation.
|
||||
@return [string] returns the name of the field that has changed, if any
|
||||
*/
|
||||
addLocalRelation(entry) {
|
||||
if (entry.eventType === REDACTION_TYPE) {
|
||||
this._localRedactCount += 1;
|
||||
if (this._localRedactCount === 1) {
|
||||
return "isRedacted";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
deaggregates local relation.
|
||||
@return [string] returns the name of the field that has changed, if any
|
||||
*/
|
||||
removeLocalRelation(entry) {
|
||||
if (entry.eventType === REDACTION_TYPE) {
|
||||
this._localRedactCount -= 1;
|
||||
if (this._localRedactCount === 0) {
|
||||
return "isRedacted";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {BaseEntry} from "./BaseEntry.js";
|
||||
import {BaseEventEntry} from "./BaseEventEntry.js";
|
||||
import {getPrevContentFromStateEvent} from "../../common.js";
|
||||
|
||||
export class EventEntry extends BaseEntry {
|
||||
export class EventEntry extends BaseEventEntry {
|
||||
constructor(eventEntry, fragmentIdComparer) {
|
||||
super(fragmentIdComparer);
|
||||
this._eventEntry = eventEntry;
|
||||
|
@ -114,7 +114,7 @@ export class EventEntry extends BaseEntry {
|
|||
}
|
||||
|
||||
get isRedacted() {
|
||||
return !!this._eventEntry.event.unsigned?.redacted_because;
|
||||
return super.isRedacted || !!this._eventEntry.event.unsigned?.redacted_because;
|
||||
}
|
||||
|
||||
get redactionReason() {
|
||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {BaseEntry, PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
||||
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
||||
import {BaseEventEntry} from "./BaseEventEntry.js";
|
||||
|
||||
export class PendingEventEntry extends BaseEntry {
|
||||
export class PendingEventEntry extends BaseEventEntry {
|
||||
constructor({pendingEvent, member, clock}) {
|
||||
super(null);
|
||||
this._pendingEvent = pendingEvent;
|
||||
|
@ -80,4 +81,8 @@ export class PendingEventEntry extends BaseEntry {
|
|||
notifyUpdate() {
|
||||
|
||||
}
|
||||
|
||||
get relatedEventId() {
|
||||
return this._pendingEvent.relatedEventId;
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue