summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js343
1 files changed, 343 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js
new file mode 100644
index 0000000..238d6c1
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/relations.js
@@ -0,0 +1,343 @@
+"use strict";
+
+var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RelationsEvent = exports.Relations = void 0;
+var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
+var _event = require("./event");
+var _logger = require("../logger");
+var _event2 = require("../@types/event");
+var _typedEventEmitter = require("./typed-event-emitter");
+var _room = require("./room");
+/*
+Copyright 2019, 2021, 2023 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.
+*/
+let RelationsEvent;
+exports.RelationsEvent = RelationsEvent;
+(function (RelationsEvent) {
+ RelationsEvent["Add"] = "Relations.add";
+ RelationsEvent["Remove"] = "Relations.remove";
+ RelationsEvent["Redaction"] = "Relations.redaction";
+})(RelationsEvent || (exports.RelationsEvent = RelationsEvent = {}));
+const matchesEventType = (eventType, targetEventType, altTargetEventTypes = []) => [targetEventType, ...altTargetEventTypes].includes(eventType);
+
+/**
+ * A container for relation events that supports easy access to common ways of
+ * aggregating such events. Each instance holds events that of a single relation
+ * type and event type. All of the events also relate to the same original event.
+ *
+ * The typical way to get one of these containers is via
+ * EventTimelineSet#getRelationsForEvent.
+ */
+class Relations extends _typedEventEmitter.TypedEventEmitter {
+ /**
+ * @param relationType - The type of relation involved, such as "m.annotation", "m.reference", "m.replace", etc.
+ * @param eventType - The relation event's type, such as "m.reaction", etc.
+ * @param client - The client which created this instance. For backwards compatibility also accepts a Room.
+ * @param altEventTypes - alt event types for relation events, for example to support unstable prefixed event types
+ */
+ constructor(relationType, eventType, client, altEventTypes) {
+ super();
+ this.relationType = relationType;
+ this.eventType = eventType;
+ this.altEventTypes = altEventTypes;
+ (0, _defineProperty2.default)(this, "relationEventIds", new Set());
+ (0, _defineProperty2.default)(this, "relations", new Set());
+ (0, _defineProperty2.default)(this, "annotationsByKey", {});
+ (0, _defineProperty2.default)(this, "annotationsBySender", {});
+ (0, _defineProperty2.default)(this, "sortedAnnotationsByKey", []);
+ (0, _defineProperty2.default)(this, "targetEvent", null);
+ (0, _defineProperty2.default)(this, "creationEmitted", false);
+ (0, _defineProperty2.default)(this, "client", void 0);
+ (0, _defineProperty2.default)(this, "onEventStatus", (event, status) => {
+ if (!event.isSending()) {
+ // Sending is done, so we don't need to listen anymore
+ event.removeListener(_event.MatrixEventEvent.Status, this.onEventStatus);
+ return;
+ }
+ if (status !== _event.EventStatus.CANCELLED) {
+ return;
+ }
+ // Event was cancelled, remove from the collection
+ event.removeListener(_event.MatrixEventEvent.Status, this.onEventStatus);
+ this.removeEvent(event);
+ });
+ (0, _defineProperty2.default)(this, "onBeforeRedaction", async redactedEvent => {
+ if (!this.relations.has(redactedEvent)) {
+ return;
+ }
+ this.relations.delete(redactedEvent);
+ if (this.relationType === _event2.RelationType.Annotation) {
+ // Remove the redacted annotation from aggregation by key
+ this.removeAnnotationFromAggregation(redactedEvent);
+ } else if (this.relationType === _event2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
+ const lastReplacement = await this.getLastReplacement();
+ this.targetEvent.makeReplaced(lastReplacement);
+ }
+ redactedEvent.removeListener(_event.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
+ this.emit(RelationsEvent.Redaction, redactedEvent);
+ });
+ this.client = client instanceof _room.Room ? client.client : client;
+ }
+
+ /**
+ * Add relation events to this collection.
+ *
+ * @param event - The new relation event to be added.
+ */
+ async addEvent(event) {
+ if (this.relationEventIds.has(event.getId())) {
+ return;
+ }
+ const relation = event.getRelation();
+ if (!relation) {
+ _logger.logger.error("Event must have relation info");
+ return;
+ }
+ const relationType = relation.rel_type;
+ const eventType = event.getType();
+ if (this.relationType !== relationType || !matchesEventType(eventType, this.eventType, this.altEventTypes)) {
+ _logger.logger.error("Event relation info doesn't match this container");
+ return;
+ }
+
+ // If the event is in the process of being sent, listen for cancellation
+ // so we can remove the event from the collection.
+ if (event.isSending()) {
+ event.on(_event.MatrixEventEvent.Status, this.onEventStatus);
+ }
+ this.relations.add(event);
+ this.relationEventIds.add(event.getId());
+ if (this.relationType === _event2.RelationType.Annotation) {
+ this.addAnnotationToAggregation(event);
+ } else if (this.relationType === _event2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
+ const lastReplacement = await this.getLastReplacement();
+ this.targetEvent.makeReplaced(lastReplacement);
+ }
+ event.on(_event.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
+ this.emit(RelationsEvent.Add, event);
+ this.maybeEmitCreated();
+ }
+
+ /**
+ * Remove relation event from this collection.
+ *
+ * @param event - The relation event to remove.
+ */
+ async removeEvent(event) {
+ if (!this.relations.has(event)) {
+ return;
+ }
+ this.relations.delete(event);
+ if (this.relationType === _event2.RelationType.Annotation) {
+ this.removeAnnotationFromAggregation(event);
+ } else if (this.relationType === _event2.RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
+ const lastReplacement = await this.getLastReplacement();
+ this.targetEvent.makeReplaced(lastReplacement);
+ }
+ this.emit(RelationsEvent.Remove, event);
+ }
+
+ /**
+ * Listens for event status changes to remove cancelled events.
+ *
+ * @param event - The event whose status has changed
+ * @param status - The new status
+ */
+
+ /**
+ * Get all relation events in this collection.
+ *
+ * These are currently in the order of insertion to this collection, which
+ * won't match timeline order in the case of scrollback.
+ * TODO: Tweak `addEvent` to insert correctly for scrollback.
+ *
+ * Relation events in insertion order.
+ */
+ getRelations() {
+ return [...this.relations];
+ }
+ addAnnotationToAggregation(event) {
+ var _event$getRelation;
+ const {
+ key
+ } = (_event$getRelation = event.getRelation()) !== null && _event$getRelation !== void 0 ? _event$getRelation : {};
+ if (!key) return;
+ let eventsForKey = this.annotationsByKey[key];
+ if (!eventsForKey) {
+ eventsForKey = this.annotationsByKey[key] = new Set();
+ this.sortedAnnotationsByKey.push([key, eventsForKey]);
+ }
+ // Add the new event to the set for this key
+ eventsForKey.add(event);
+ // Re-sort the [key, events] pairs in descending order of event count
+ this.sortedAnnotationsByKey.sort((a, b) => {
+ const aEvents = a[1];
+ const bEvents = b[1];
+ return bEvents.size - aEvents.size;
+ });
+ const sender = event.getSender();
+ let eventsFromSender = this.annotationsBySender[sender];
+ if (!eventsFromSender) {
+ eventsFromSender = this.annotationsBySender[sender] = new Set();
+ }
+ // Add the new event to the set for this sender
+ eventsFromSender.add(event);
+ }
+ removeAnnotationFromAggregation(event) {
+ var _event$getRelation2;
+ const {
+ key
+ } = (_event$getRelation2 = event.getRelation()) !== null && _event$getRelation2 !== void 0 ? _event$getRelation2 : {};
+ if (!key) return;
+ const eventsForKey = this.annotationsByKey[key];
+ if (eventsForKey) {
+ eventsForKey.delete(event);
+
+ // Re-sort the [key, events] pairs in descending order of event count
+ this.sortedAnnotationsByKey.sort((a, b) => {
+ const aEvents = a[1];
+ const bEvents = b[1];
+ return bEvents.size - aEvents.size;
+ });
+ }
+ const sender = event.getSender();
+ const eventsFromSender = this.annotationsBySender[sender];
+ if (eventsFromSender) {
+ eventsFromSender.delete(event);
+ }
+ }
+
+ /**
+ * For relations that have been redacted, we want to remove them from
+ * aggregation data sets and emit an update event.
+ *
+ * To do so, we listen for `Event.beforeRedaction`, which happens:
+ * - after the server accepted the redaction and remote echoed back to us
+ * - before the original event has been marked redacted in the client
+ *
+ * @param redactedEvent - The original relation event that is about to be redacted.
+ */
+
+ /**
+ * Get all events in this collection grouped by key and sorted by descending
+ * event count in each group.
+ *
+ * This is currently only supported for the annotation relation type.
+ *
+ * An array of [key, events] pairs sorted by descending event count.
+ * The events are stored in a Set (which preserves insertion order).
+ */
+ getSortedAnnotationsByKey() {
+ if (this.relationType !== _event2.RelationType.Annotation) {
+ // Other relation types are not grouped currently.
+ return null;
+ }
+ return this.sortedAnnotationsByKey;
+ }
+
+ /**
+ * Get all events in this collection grouped by sender.
+ *
+ * This is currently only supported for the annotation relation type.
+ *
+ * An object with each relation sender as a key and the matching Set of
+ * events for that sender as a value.
+ */
+ getAnnotationsBySender() {
+ if (this.relationType !== _event2.RelationType.Annotation) {
+ // Other relation types are not grouped currently.
+ return null;
+ }
+ return this.annotationsBySender;
+ }
+
+ /**
+ * Returns the most recent (and allowed) m.replace relation, if any.
+ *
+ * This is currently only supported for the m.replace relation type,
+ * once the target event is known, see `addEvent`.
+ */
+ async getLastReplacement() {
+ if (this.relationType !== _event2.RelationType.Replace) {
+ // Aggregating on last only makes sense for this relation type
+ return null;
+ }
+ if (!this.targetEvent) {
+ // Don't know which replacements to accept yet.
+ // This method shouldn't be called before the original
+ // event is known anyway.
+ return null;
+ }
+
+ // the all-knowning server tells us that the event at some point had
+ // this timestamp for its replacement, so any following replacement should definitely not be less
+ const replaceRelation = this.targetEvent.getServerAggregatedRelation(_event2.RelationType.Replace);
+ const minTs = replaceRelation === null || replaceRelation === void 0 ? void 0 : replaceRelation.origin_server_ts;
+ const lastReplacement = this.getRelations().reduce((last, event) => {
+ if (event.getSender() !== this.targetEvent.getSender()) {
+ return last;
+ }
+ if (minTs && minTs > event.getTs()) {
+ return last;
+ }
+ if (last && last.getTs() > event.getTs()) {
+ return last;
+ }
+ return event;
+ }, null);
+ if (lastReplacement !== null && lastReplacement !== void 0 && lastReplacement.shouldAttemptDecryption() && this.client.isCryptoEnabled()) {
+ await lastReplacement.attemptDecryption(this.client.crypto);
+ } else if (lastReplacement !== null && lastReplacement !== void 0 && lastReplacement.isBeingDecrypted()) {
+ await lastReplacement.getDecryptionPromise();
+ }
+ return lastReplacement;
+ }
+
+ /*
+ * @param targetEvent - the event the relations are related to.
+ */
+ async setTargetEvent(event) {
+ if (this.targetEvent) {
+ return;
+ }
+ this.targetEvent = event;
+ if (this.relationType === _event2.RelationType.Replace && !this.targetEvent.isState()) {
+ const replacement = await this.getLastReplacement();
+ // this is the initial update, so only call it if we already have something
+ // to not emit Event.replaced needlessly
+ if (replacement) {
+ this.targetEvent.makeReplaced(replacement);
+ }
+ }
+ this.maybeEmitCreated();
+ }
+ maybeEmitCreated() {
+ if (this.creationEmitted) {
+ return;
+ }
+ // Only emit we're "created" once we have a target event instance _and_
+ // at least one related event.
+ if (!this.targetEvent || !this.relations.size) {
+ return;
+ }
+ this.creationEmitted = true;
+ this.targetEvent.emit(_event.MatrixEventEvent.RelationsCreated, this.relationType, this.eventType);
+ }
+}
+exports.Relations = Relations;
+//# sourceMappingURL=relations.js.map \ No newline at end of file