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;
|
let relatedEventId;
|
||||||
if (isTxnId(eventIdOrTxnId)) {
|
if (isTxnId(eventIdOrTxnId)) {
|
||||||
relatedTxnId = eventIdOrTxnId;
|
relatedTxnId = eventIdOrTxnId;
|
||||||
log.set("relatedTxnId", eventIdOrTxnId);
|
|
||||||
const txnId = eventIdOrTxnId;
|
const txnId = eventIdOrTxnId;
|
||||||
const pe = this._pendingEvents.array.find(pe => pe.txnId === txnId);
|
const pe = this._pendingEvents.array.find(pe => pe.txnId === txnId);
|
||||||
if (pe && !pe.remoteId && pe.status !== SendStatus.Sending) {
|
if (pe && !pe.remoteId && pe.status !== SendStatus.Sending) {
|
||||||
// haven't started sending this event yet,
|
// haven't started sending this event yet,
|
||||||
// just remove it from the queue
|
// just remove it from the queue
|
||||||
|
log.set("remove", relatedTxnId);
|
||||||
await pe.abort();
|
await pe.abort();
|
||||||
return;
|
return;
|
||||||
} else if (pe) {
|
} else if (pe) {
|
||||||
|
@ -236,8 +236,15 @@ export class SendQueue {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
relatedEventId = eventIdOrTxnId;
|
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);
|
await this._enqueueEvent(REDACTION_TYPE, {reason}, null, relatedTxnId, relatedEventId, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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._closeCallback = closeCallback;
|
||||||
this._fragmentIdComparer = fragmentIdComparer;
|
this._fragmentIdComparer = fragmentIdComparer;
|
||||||
this._disposables = new Disposables();
|
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._ownMember = null;
|
||||||
this._timelineReader = new TimelineReader({
|
this._timelineReader = new TimelineReader({
|
||||||
roomId: this._roomId,
|
roomId: this._roomId,
|
||||||
|
@ -36,17 +39,7 @@ export class Timeline {
|
||||||
fragmentIdComparer: this._fragmentIdComparer
|
fragmentIdComparer: this._fragmentIdComparer
|
||||||
});
|
});
|
||||||
this._readerRequest = null;
|
this._readerRequest = null;
|
||||||
let localEntries;
|
this._allEntries = null;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
|
@ -69,12 +62,58 @@ export class Timeline {
|
||||||
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(30, txn, log));
|
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(30, txn, log));
|
||||||
try {
|
try {
|
||||||
const entries = await readerRequest.complete();
|
const entries = await readerRequest.complete();
|
||||||
this._remoteEntries.setManySorted(entries);
|
this._setupEntries(entries);
|
||||||
} finally {
|
} finally {
|
||||||
this._disposables.disposeTracked(readerRequest);
|
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) {
|
updateOwnMember(member) {
|
||||||
this._ownMember = member;
|
this._ownMember = member;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +128,15 @@ export class Timeline {
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
addOrReplaceEntries(newEntries) {
|
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);
|
this._remoteEntries.setManySorted(newEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +176,7 @@ export class Timeline {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseEntry} from "./BaseEntry.js";
|
import {BaseEventEntry} from "./BaseEventEntry.js";
|
||||||
import {getPrevContentFromStateEvent} from "../../common.js";
|
import {getPrevContentFromStateEvent} from "../../common.js";
|
||||||
|
|
||||||
export class EventEntry extends BaseEntry {
|
export class EventEntry extends BaseEventEntry {
|
||||||
constructor(eventEntry, fragmentIdComparer) {
|
constructor(eventEntry, fragmentIdComparer) {
|
||||||
super(fragmentIdComparer);
|
super(fragmentIdComparer);
|
||||||
this._eventEntry = eventEntry;
|
this._eventEntry = eventEntry;
|
||||||
|
@ -114,7 +114,7 @@ export class EventEntry extends BaseEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isRedacted() {
|
get isRedacted() {
|
||||||
return !!this._eventEntry.event.unsigned?.redacted_because;
|
return super.isRedacted || !!this._eventEntry.event.unsigned?.redacted_because;
|
||||||
}
|
}
|
||||||
|
|
||||||
get redactionReason() {
|
get redactionReason() {
|
||||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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}) {
|
constructor({pendingEvent, member, clock}) {
|
||||||
super(null);
|
super(null);
|
||||||
this._pendingEvent = pendingEvent;
|
this._pendingEvent = pendingEvent;
|
||||||
|
@ -80,4 +81,8 @@ export class PendingEventEntry extends BaseEntry {
|
||||||
notifyUpdate() {
|
notifyUpdate() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get relatedEventId() {
|
||||||
|
return this._pendingEvent.relatedEventId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue