Refactor + put redaction in NonPersistedEventEntry

This commit is contained in:
RMidhunSuresh 2022-01-07 19:38:57 +05:30
parent 8cc04e4c25
commit 9d161a0bcf
5 changed files with 78 additions and 56 deletions

View file

@ -254,7 +254,7 @@ export class Timeline {
/** @package */ /** @package */
addEntries(newEntries) { addEntries(newEntries) {
this._addLocalRelationsToNewRemoteEntries(newEntries); this._addLocalRelationsToNewRemoteEntries(newEntries);
this._updateEntriesNotInTimeline(newEntries); this._updateEntriesFetchedFromHomeserver(newEntries);
this._moveEntryToRemoteEntries(newEntries); this._moveEntryToRemoteEntries(newEntries);
this._remoteEntries.setManySorted(newEntries); this._remoteEntries.setManySorted(newEntries);
this._loadContextEntriesWhereNeeded(newEntries); this._loadContextEntriesWhereNeeded(newEntries);
@ -262,13 +262,17 @@ export class Timeline {
/** /**
* Update entries based on newly received events. * Update entries based on newly received events.
* eg: a newly received redaction event may mark an existing event in contextEntriesNotInTimeline as being redacted * This is specific to events that are not in the timeline but had to be fetched from the homeserver.
* This is only for the events that are not in the timeline but had to fetched from elsewhere to render reply previews.
*/ */
_updateEntriesNotInTimeline(entries) { _updateEntriesFetchedFromHomeserver(entries) {
for (const entry of entries) { for (const entry of entries) {
const relatedEntry = this._contextEntriesNotInTimeline.get(entry.relatedEventId); const relatedEntry = this._contextEntriesNotInTimeline.get(entry.relatedEventId);
if (!relatedEntry) { if (!relatedEntry || !relatedEntry.isNonPersisted) {
/**
* Updates for entries in timeline is handled by remoteEntries observable collection
* Updates for entries not in timeline but fetched from storage is handled in this.replaceEntries()
* This code is specific to entries fetched from HomeServer i.e NonPersistedEventEntry
*/
continue; continue;
} }
const newEntry = this._createEntryFromRelatedEntries(entry, relatedEntry); const newEntry = this._createEntryFromRelatedEntries(entry, relatedEntry);
@ -292,7 +296,7 @@ export class Timeline {
_createEntryFromRelatedEntries(entry, relatedEntry) { _createEntryFromRelatedEntries(entry, relatedEntry) {
if (entry.isRedaction) { if (entry.isRedaction) {
const newEntry = relatedEntry.clone(); const newEntry = relatedEntry.clone();
newEntry.setAsRedacted(); newEntry.redact(entry);
return newEntry; return newEntry;
} }
} }

View file

@ -17,3 +17,50 @@ limitations under the License.
export function isValidFragmentId(id) { export function isValidFragmentId(id) {
return typeof id === "number"; return typeof id === "number";
} }
// copied over from matrix-js-sdk, copyright 2016 OpenMarket Ltd
/* _REDACT_KEEP_KEY_MAP gives the keys we keep when an event is redacted
*
* This is specified here:
* http://matrix.org/speculator/spec/HEAD/client_server/latest.html#redactions
*
* Also:
* - We keep 'unsigned' since that is created by the local server
* - We keep user_id for backwards-compat with v1
*/
const _REDACT_KEEP_KEY_MAP = [
'event_id', 'type', 'room_id', 'user_id', 'sender', 'state_key', 'prev_state',
'content', 'unsigned', 'origin_server_ts',
].reduce(function(ret, val) {
ret[val] = 1; return ret;
}, {});
// a map from event type to the .content keys we keep when an event is redacted
const _REDACT_KEEP_CONTENT_MAP = {
'm.room.member': {'membership': 1},
'm.room.create': {'creator': 1},
'm.room.join_rules': {'join_rule': 1},
'm.room.power_levels': {'ban': 1, 'events': 1, 'events_default': 1,
'kick': 1, 'redact': 1, 'state_default': 1,
'users': 1, 'users_default': 1,
},
'm.room.aliases': {'aliases': 1},
};
// end of matrix-js-sdk code
export function redactEvent(redactionEvent, redactedEvent) {
for (const key of Object.keys(redactedEvent)) {
if (!_REDACT_KEEP_KEY_MAP[key]) {
delete redactedEvent[key];
}
}
const { content } = redactedEvent;
const keepMap = _REDACT_KEEP_CONTENT_MAP[redactedEvent.type];
for (const key of Object.keys(content)) {
if (!keepMap?.[key]) {
delete content[key];
}
}
redactedEvent.unsigned = redactedEvent.unsigned || {};
redactedEvent.unsigned.redacted_because = redactionEvent;
}

View file

@ -26,7 +26,6 @@ export class EventEntry extends BaseEventEntry {
this._decryptionResult = null; this._decryptionResult = null;
this._contextEntry = null; this._contextEntry = null;
this._contextForEntries = null; this._contextForEntries = null;
this._markedAsRedacted = false;
} }
clone() { clone() {
@ -58,10 +57,6 @@ export class EventEntry extends BaseEventEntry {
this._contextForEntries.push(entry); this._contextForEntries.push(entry);
} }
setAsRedacted() {
this._markedAsRedacted = true;
}
get contextForEntries() { get contextForEntries() {
return this._contextForEntries; return this._contextForEntries;
} }
@ -160,7 +155,7 @@ export class EventEntry extends BaseEventEntry {
} }
get isRedacted() { get isRedacted() {
return this._markedAsRedacted || super.isRedacted || isRedacted(this._eventEntry.event); return super.isRedacted || isRedacted(this._eventEntry.event);
} }
get redactionReason() { get redactionReason() {

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import {EventEntry} from "./EventEntry.js"; import {EventEntry} from "./EventEntry.js";
import {redactEvent} from "../common.js";
// EventEntry but without the two properties that are populated via SyncWriter // EventEntry but without the two properties that are populated via SyncWriter
// Useful if you want to create an EventEntry that is ephemeral // Useful if you want to create an EventEntry that is ephemeral
@ -27,4 +28,21 @@ export class NonPersistedEventEntry extends EventEntry {
get entryIndex() { get entryIndex() {
throw new Error("Cannot access entryIndex for non-persisted EventEntry"); throw new Error("Cannot access entryIndex for non-persisted EventEntry");
} }
/**
* This method is needed because NonPersistedEventEntry cannot rely on RelationWriter to handle redactions
*/
redact(redactionEvent) {
redactEvent(redactionEvent.event, this.event);
}
clone() {
const clone = new NonPersistedEventEntry(this._eventEntry, this._fragmentIdComparer);
clone.updateFrom(this);
return clone;
}
get isNonPersisted() {
return true;
}
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
import {EventEntry} from "../entries/EventEntry.js"; import {EventEntry} from "../entries/EventEntry.js";
import {REDACTION_TYPE, isRedacted} from "../../common.js"; import {REDACTION_TYPE, isRedacted} from "../../common.js";
import {ANNOTATION_RELATION_TYPE, getRelation} from "../relations.js"; import {ANNOTATION_RELATION_TYPE, getRelation} from "../relations.js";
import {redactEvent} from "../common.js";
export class RelationWriter { export class RelationWriter {
constructor({roomId, ownUserId, fragmentIdComparer}) { constructor({roomId, ownUserId, fragmentIdComparer}) {
@ -127,21 +128,7 @@ export class RelationWriter {
// check if we're the target of a relation and remove all relations then as well // check if we're the target of a relation and remove all relations then as well
txn.timelineRelations.removeAllForTarget(this._roomId, redactedEvent.event_id); txn.timelineRelations.removeAllForTarget(this._roomId, redactedEvent.event_id);
for (const key of Object.keys(redactedEvent)) { redactEvent(redactionEvent, redactedEvent);
if (!_REDACT_KEEP_KEY_MAP[key]) {
delete redactedEvent[key];
}
}
const {content} = redactedEvent;
const keepMap = _REDACT_KEEP_CONTENT_MAP[redactedEvent.type];
for (const key of Object.keys(content)) {
if (!keepMap?.[key]) {
delete content[key];
}
}
redactedEvent.unsigned = redactedEvent.unsigned || {};
redactedEvent.unsigned.redacted_because = redactionEvent;
delete redactedStorageEntry.annotations; delete redactedStorageEntry.annotations;
return true; return true;
@ -223,35 +210,6 @@ function isObjectEmpty(obj) {
return true; return true;
} }
// copied over from matrix-js-sdk, copyright 2016 OpenMarket Ltd
/* _REDACT_KEEP_KEY_MAP gives the keys we keep when an event is redacted
*
* This is specified here:
* http://matrix.org/speculator/spec/HEAD/client_server/latest.html#redactions
*
* Also:
* - We keep 'unsigned' since that is created by the local server
* - We keep user_id for backwards-compat with v1
*/
const _REDACT_KEEP_KEY_MAP = [
'event_id', 'type', 'room_id', 'user_id', 'sender', 'state_key', 'prev_state',
'content', 'unsigned', 'origin_server_ts',
].reduce(function(ret, val) {
ret[val] = 1; return ret;
}, {});
// a map from event type to the .content keys we keep when an event is redacted
const _REDACT_KEEP_CONTENT_MAP = {
'm.room.member': {'membership': 1},
'm.room.create': {'creator': 1},
'm.room.join_rules': {'join_rule': 1},
'm.room.power_levels': {'ban': 1, 'events': 1, 'events_default': 1,
'kick': 1, 'redact': 1, 'state_default': 1,
'users': 1, 'users_default': 1,
},
'm.room.aliases': {'aliases': 1},
};
// end of matrix-js-sdk code
import {createMockStorage} from "../../../../mocks/Storage"; import {createMockStorage} from "../../../../mocks/Storage";
import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js"; import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js";