feat: complete Ancestor quote bot with production-ready Docker support
This commit is contained in:
234
node_modules/matrix-js-sdk/lib/models/read-receipt.js
generated
vendored
234
node_modules/matrix-js-sdk/lib/models/read-receipt.js
generated
vendored
@@ -1,5 +1,18 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
||||
"use strict";
|
||||
|
||||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.ReadReceipt = void 0;
|
||||
exports.synthesizeReceipt = synthesizeReceipt;
|
||||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
||||
var _read_receipts = require("../@types/read_receipts");
|
||||
var _typedEventEmitter = require("./typed-event-emitter");
|
||||
var _utils = require("../utils");
|
||||
var _event = require("./event");
|
||||
var _event2 = require("../@types/event");
|
||||
var _room = require("./room");
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -13,44 +26,26 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptType } from "../@types/read_receipts.js";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter.js";
|
||||
import { isSupportedReceiptType } from "../utils.js";
|
||||
import { MatrixEvent } from "./event.js";
|
||||
import { EventType } from "../@types/event.js";
|
||||
import { MapWithDefault } from "../utils.js";
|
||||
import { NotificationCountType } from "./room.js";
|
||||
import { logger } from "../logger.js";
|
||||
import { inMainTimelineForReceipt, threadIdForReceipt } from "../client.js";
|
||||
|
||||
/**
|
||||
* Create a synthetic receipt for the given event
|
||||
* @param userId - The user ID if the receipt sender
|
||||
* @param event - The event that is to be acknowledged
|
||||
* @param receiptType - The type of receipt
|
||||
* @param unthreaded - the receipt is unthreaded
|
||||
* @returns a new event with the synthetic receipt in it
|
||||
*/
|
||||
export function synthesizeReceipt(userId, event, receiptType, unthreaded = false) {
|
||||
return new MatrixEvent({
|
||||
function synthesizeReceipt(userId, event, receiptType) {
|
||||
var _event$threadRootId;
|
||||
return new _event.MatrixEvent({
|
||||
content: {
|
||||
[event.getId()]: {
|
||||
[receiptType]: {
|
||||
[userId]: _objectSpread({
|
||||
ts: event.getTs()
|
||||
}, !unthreaded && {
|
||||
thread_id: threadIdForReceipt(event)
|
||||
})
|
||||
[userId]: {
|
||||
ts: event.getTs(),
|
||||
thread_id: (_event$threadRootId = event.threadRootId) !== null && _event$threadRootId !== void 0 ? _event$threadRootId : _read_receipts.MAIN_ROOM_TIMELINE
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: EventType.Receipt,
|
||||
type: _event2.EventType.Receipt,
|
||||
room_id: event.getRoomId()
|
||||
});
|
||||
}
|
||||
const ReceiptPairRealIndex = 0;
|
||||
const ReceiptPairSyntheticIndex = 1;
|
||||
export class ReadReceipt extends TypedEventEmitter {
|
||||
class ReadReceipt extends _typedEventEmitter.TypedEventEmitter {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
// receipts should clobber based on receipt_type and user_id pairs hence
|
||||
@@ -58,8 +53,9 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
// which pass in an event ID and get back some receipts, so we also store
|
||||
// a pre-cached list for this purpose.
|
||||
// Map: receipt type → user Id → receipt
|
||||
_defineProperty(this, "receipts", new MapWithDefault(() => new Map()));
|
||||
_defineProperty(this, "receiptCacheByEventId", new Map());
|
||||
(0, _defineProperty2.default)(this, "receipts", new _utils.MapWithDefault(() => new Map()));
|
||||
(0, _defineProperty2.default)(this, "receiptCacheByEventId", new Map());
|
||||
(0, _defineProperty2.default)(this, "timeline", void 0);
|
||||
}
|
||||
/**
|
||||
* Gets the latest receipt for a given user in the room
|
||||
@@ -68,142 +64,55 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
* @param receiptType - Optional. The type of the receipt we want to get
|
||||
* @returns the latest receipts of the chosen type for the chosen user
|
||||
*/
|
||||
getReadReceiptForUserId(userId, ignoreSynthesized = false, receiptType = ReceiptType.Read) {
|
||||
const [realReceipt, syntheticReceipt] = this.receipts.get(receiptType)?.get(userId) ?? [null, null];
|
||||
getReadReceiptForUserId(userId, ignoreSynthesized = false, receiptType = _read_receipts.ReceiptType.Read) {
|
||||
var _this$receipts$get$ge, _this$receipts$get;
|
||||
const [realReceipt, syntheticReceipt] = (_this$receipts$get$ge = (_this$receipts$get = this.receipts.get(receiptType)) === null || _this$receipts$get === void 0 ? void 0 : _this$receipts$get.get(userId)) !== null && _this$receipts$get$ge !== void 0 ? _this$receipts$get$ge : [null, null];
|
||||
if (ignoreSynthesized) {
|
||||
return realReceipt;
|
||||
}
|
||||
return syntheticReceipt ?? realReceipt;
|
||||
return syntheticReceipt !== null && syntheticReceipt !== void 0 ? syntheticReceipt : realReceipt;
|
||||
}
|
||||
compareReceipts(a, b) {
|
||||
var _this$getUnfilteredTi;
|
||||
// Try compare them in our unfiltered timeline set order, falling back to receipt timestamp which should be
|
||||
// relatively sane as receipts are set only by the originating homeserver so as long as its clock doesn't
|
||||
// jump around then it should be valid.
|
||||
return this.getUnfilteredTimelineSet().compareEventOrdering(a.eventId, b.eventId) ?? a.data.ts - b.data.ts;
|
||||
return (_this$getUnfilteredTi = this.getUnfilteredTimelineSet().compareEventOrdering(a.eventId, b.eventId)) !== null && _this$getUnfilteredTi !== void 0 ? _this$getUnfilteredTi : a.data.ts - b.data.ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the event that a given user has read up to, or null if:
|
||||
* - we have received no read receipts for them, or
|
||||
* - the receipt we have points at an event we don't have, or
|
||||
* - the thread ID in the receipt does not match the thread root of the
|
||||
* referenced event.
|
||||
*
|
||||
* (The event might not exist if it is not loaded, and the thread ID might
|
||||
* not match if the event has moved thread because it was redacted.)
|
||||
*
|
||||
* Get the ID of the event that a given user has read up to, or null if we
|
||||
* have received no read receipts from them.
|
||||
* @param userId - The user ID to get read receipt event ID for
|
||||
* @param ignoreSynthesized - If true, return only receipts that have been
|
||||
* sent by the server, not implicit ones generated
|
||||
* by the JS SDK.
|
||||
* @returns ID of the latest existing event that the given user has read, or null.
|
||||
* sent by the server, not implicit ones generated
|
||||
* by the JS SDK.
|
||||
* @returns ID of the latest event that the given user has read, or null.
|
||||
*/
|
||||
getEventReadUpTo(userId, ignoreSynthesized = false) {
|
||||
// Find what the latest receipt says is the latest event we have read
|
||||
const latestReceipt = this.getLatestReceipt(userId, ignoreSynthesized);
|
||||
if (!latestReceipt) {
|
||||
return null;
|
||||
}
|
||||
return this.receiptPointsAtConsistentEvent(latestReceipt) ? latestReceipt.eventId : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the event pointed at by this receipt exists, and its
|
||||
* threadRootId is consistent with the thread information in the receipt.
|
||||
*/
|
||||
receiptPointsAtConsistentEvent(receipt) {
|
||||
const event = this.findEventById(receipt.eventId);
|
||||
if (!event) {
|
||||
// If the receipt points at a non-existent event, we have multiple
|
||||
// possibilities:
|
||||
//
|
||||
// 1. We don't have the event because it's not loaded yet - probably
|
||||
// it's old and we're best off ignoring the receipt - we can just
|
||||
// send a new one when we read a new event.
|
||||
//
|
||||
// 2. We have a bug e.g. we misclassified this event into the wrong
|
||||
// thread.
|
||||
//
|
||||
// 3. The referenced event moved out of this thread (e.g. because it
|
||||
// was deleted.)
|
||||
//
|
||||
// 4. The receipt had the incorrect thread ID (due to a bug in a
|
||||
// client, or malicious behaviour).
|
||||
|
||||
// This receipt is not "valid" because it doesn't point at an event
|
||||
// we have. We want to pretend it doesn't exist.
|
||||
return false;
|
||||
}
|
||||
if (!receipt.data?.thread_id) {
|
||||
// If this is an unthreaded receipt, it could point at any event, so
|
||||
// there is no need to validate further - this receipt is valid.
|
||||
return true;
|
||||
}
|
||||
// Otherwise it is a threaded receipt...
|
||||
|
||||
if (receipt.data.thread_id === MAIN_ROOM_TIMELINE) {
|
||||
// The receipt is for the main timeline: we check that the event is
|
||||
// in the main timeline.
|
||||
|
||||
// Check if the event is in the main timeline
|
||||
const eventIsInMainTimeline = inMainTimelineForReceipt(event);
|
||||
if (eventIsInMainTimeline) {
|
||||
// The receipt is for the main timeline, and so is the event, so
|
||||
// the receipt is valid.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// The receipt is for a different thread (not the main timeline)
|
||||
|
||||
if (event.threadRootId === receipt.data.thread_id) {
|
||||
// If the receipt and event agree on the thread ID, the receipt
|
||||
// is valid.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The receipt thread ID disagrees with the event thread ID. There are 2
|
||||
// possibilities:
|
||||
//
|
||||
// 1. The event moved to a different thread after the receipt was
|
||||
// created. This can happen if the event was redacted because that
|
||||
// moves it to the main timeline.
|
||||
//
|
||||
// 2. There is a bug somewhere - either we put the event into the wrong
|
||||
// thread, or someone sent an incorrect receipt.
|
||||
//
|
||||
// In many cases, we won't get here because the call to findEventById
|
||||
// would have already returned null. We include this check to cover
|
||||
// cases when `this` is a room, meaning findEventById will find events
|
||||
// in any thread, and to be defensive against unforeseen code paths.
|
||||
logger.warn(`Ignoring receipt because its thread_id (${receipt.data.thread_id}) disagrees ` + `with the thread root (${event.threadRootId}) of the referenced event ` + `(event ID = ${receipt.eventId})`);
|
||||
|
||||
// This receipt is not "valid" because it disagrees with us about what
|
||||
// thread the event is in. We want to pretend it doesn't exist.
|
||||
return false;
|
||||
}
|
||||
getLatestReceipt(userId, ignoreSynthesized) {
|
||||
var _ref, _privateReadReceipt$e, _ref2;
|
||||
// XXX: This is very very ugly and I hope I won't have to ever add a new
|
||||
// receipt type here again. IMHO this should be done by the server in
|
||||
// some more intelligent manner or the client should just use timestamps
|
||||
|
||||
const publicReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, ReceiptType.Read);
|
||||
const privateReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, ReceiptType.ReadPrivate);
|
||||
const publicReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, _read_receipts.ReceiptType.Read);
|
||||
const privateReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, _read_receipts.ReceiptType.ReadPrivate);
|
||||
|
||||
// If we have both, compare them
|
||||
let comparison;
|
||||
if (publicReadReceipt?.eventId && privateReadReceipt?.eventId) {
|
||||
if (publicReadReceipt !== null && publicReadReceipt !== void 0 && publicReadReceipt.eventId && privateReadReceipt !== null && privateReadReceipt !== void 0 && privateReadReceipt.eventId) {
|
||||
comparison = this.compareReceipts(publicReadReceipt, privateReadReceipt);
|
||||
}
|
||||
|
||||
// The public receipt is more likely to drift out of date so the private
|
||||
// one has precedence
|
||||
if (!comparison) return privateReadReceipt ?? publicReadReceipt ?? null;
|
||||
if (!comparison) return (_ref = (_privateReadReceipt$e = privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId) !== null && _privateReadReceipt$e !== void 0 ? _privateReadReceipt$e : publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId) !== null && _ref !== void 0 ? _ref : null;
|
||||
|
||||
// If public read receipt is older, return the private one
|
||||
return (comparison < 0 ? privateReadReceipt : publicReadReceipt) ?? null;
|
||||
return (_ref2 = comparison < 0 ? privateReadReceipt === null || privateReadReceipt === void 0 ? void 0 : privateReadReceipt.eventId : publicReadReceipt === null || publicReadReceipt === void 0 ? void 0 : publicReadReceipt.eventId) !== null && _ref2 !== void 0 ? _ref2 : null;
|
||||
}
|
||||
addReceiptToStructure(eventId, receiptType, userId, receipt, synthetic) {
|
||||
var _pair$ReceiptPairSynt2, _pair$ReceiptPairSynt3;
|
||||
const receiptTypesMap = this.receipts.getOrCreate(receiptType);
|
||||
let pair = receiptTypesMap.get(userId);
|
||||
if (!pair) {
|
||||
@@ -212,7 +121,8 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
}
|
||||
let existingReceipt = pair[ReceiptPairRealIndex];
|
||||
if (synthetic) {
|
||||
existingReceipt = pair[ReceiptPairSyntheticIndex] ?? pair[ReceiptPairRealIndex];
|
||||
var _pair$ReceiptPairSynt;
|
||||
existingReceipt = (_pair$ReceiptPairSynt = pair[ReceiptPairSyntheticIndex]) !== null && _pair$ReceiptPairSynt !== void 0 ? _pair$ReceiptPairSynt : pair[ReceiptPairRealIndex];
|
||||
}
|
||||
const wrappedReceipt = {
|
||||
eventId,
|
||||
@@ -236,7 +146,7 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
|
||||
// we don't bother caching just real receipts by event ID as there's nothing that would read it.
|
||||
// Take the current cached receipt before we overwrite the pair elements.
|
||||
const cachedReceipt = pair[ReceiptPairSyntheticIndex] ?? pair[ReceiptPairRealIndex];
|
||||
const cachedReceipt = (_pair$ReceiptPairSynt2 = pair[ReceiptPairSyntheticIndex]) !== null && _pair$ReceiptPairSynt2 !== void 0 ? _pair$ReceiptPairSynt2 : pair[ReceiptPairRealIndex];
|
||||
if (synthetic && preferSynthetic) {
|
||||
pair[ReceiptPairSyntheticIndex] = wrappedReceipt;
|
||||
} else if (!synthetic) {
|
||||
@@ -245,7 +155,7 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
pair[ReceiptPairSyntheticIndex] = null;
|
||||
}
|
||||
}
|
||||
const newCachedReceipt = pair[ReceiptPairSyntheticIndex] ?? pair[ReceiptPairRealIndex];
|
||||
const newCachedReceipt = (_pair$ReceiptPairSynt3 = pair[ReceiptPairSyntheticIndex]) !== null && _pair$ReceiptPairSynt3 !== void 0 ? _pair$ReceiptPairSynt3 : pair[ReceiptPairRealIndex];
|
||||
if (cachedReceipt === newCachedReceipt) return;
|
||||
|
||||
// clean up any previous cache entry
|
||||
@@ -280,13 +190,6 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
getReceiptsForEvent(event) {
|
||||
return this.receiptCacheByEventId.get(event.getId()) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Look in this room/thread's timeline to find an event. If `this` is a
|
||||
* room, we look in all threads, but if `this` is a thread, we look only
|
||||
* inside this thread.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This issue should also be addressed on synapse's side and is tracked as part
|
||||
* of https://github.com/matrix-org/synapse/issues/14837
|
||||
@@ -301,9 +204,9 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
fixupNotifications(userId) {
|
||||
const receipt = this.getReadReceiptForUserId(userId, false);
|
||||
const lastEvent = this.timeline[this.timeline.length - 1];
|
||||
if (lastEvent && receipt?.eventId === lastEvent.getId() && userId === lastEvent.getSender()) {
|
||||
this.setUnread(NotificationCountType.Total, 0);
|
||||
this.setUnread(NotificationCountType.Highlight, 0);
|
||||
if (lastEvent && (receipt === null || receipt === void 0 ? void 0 : receipt.eventId) === lastEvent.getId() && userId === lastEvent.getSender()) {
|
||||
this.setUnread(_room.NotificationCountType.Total, 0);
|
||||
this.setUnread(_room.NotificationCountType.Highlight, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,10 +216,9 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
* @param userId - The user ID if the receipt sender
|
||||
* @param e - The event that is to be acknowledged
|
||||
* @param receiptType - The type of receipt
|
||||
* @param unthreaded - the receipt is unthreaded
|
||||
*/
|
||||
addLocalEchoReceipt(userId, e, receiptType, unthreaded = false) {
|
||||
this.addReceipt(synthesizeReceipt(userId, e, receiptType, unthreaded), true);
|
||||
addLocalEchoReceipt(userId, e, receiptType) {
|
||||
this.addReceipt(synthesizeReceipt(userId, e, receiptType), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,7 +228,7 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
*/
|
||||
getUsersReadUpTo(event) {
|
||||
return this.getReceiptsForEvent(event).filter(function (receipt) {
|
||||
return isSupportedReceiptType(receipt.type);
|
||||
return (0, _utils.isSupportedReceiptType)(receipt.type);
|
||||
}).map(function (receipt) {
|
||||
return receipt.userId;
|
||||
});
|
||||
@@ -340,6 +242,29 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
* @param eventId - The event ID to check if the user read.
|
||||
* @returns True if the user has read the event, false otherwise.
|
||||
*/
|
||||
hasUserReadEvent(userId, eventId) {
|
||||
var _this$timeline;
|
||||
const readUpToId = this.getEventReadUpTo(userId, false);
|
||||
if (readUpToId === eventId) return true;
|
||||
if ((_this$timeline = this.timeline) !== null && _this$timeline !== void 0 && _this$timeline.length && this.timeline[this.timeline.length - 1].getSender() && this.timeline[this.timeline.length - 1].getSender() === userId) {
|
||||
// It doesn't matter where the event is in the timeline, the user has read
|
||||
// it because they've sent the latest event.
|
||||
return true;
|
||||
}
|
||||
for (let i = ((_this$timeline2 = this.timeline) === null || _this$timeline2 === void 0 ? void 0 : _this$timeline2.length) - 1; i >= 0; --i) {
|
||||
var _this$timeline2;
|
||||
const ev = this.timeline[i];
|
||||
|
||||
// If we encounter the target event first, the user hasn't read it
|
||||
// however if we encounter the readUpToId first then the user has read
|
||||
// it. These rules apply because we're iterating bottom-up.
|
||||
if (ev.getId() === eventId) return false;
|
||||
if (ev.getId() === readUpToId) return true;
|
||||
}
|
||||
|
||||
// We don't know if the user has read it, so assume not.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent unthreaded receipt for a given user
|
||||
@@ -347,8 +272,7 @@ export class ReadReceipt extends TypedEventEmitter {
|
||||
* @returns an unthreaded Receipt. Can be undefined if receipts have been disabled
|
||||
* or a user chooses to use private read receipts (or we have simply not received
|
||||
* a receipt from this user yet).
|
||||
*
|
||||
* @deprecated use `hasUserReadEvent` or `getEventReadUpTo` instead
|
||||
*/
|
||||
}
|
||||
exports.ReadReceipt = ReadReceipt;
|
||||
//# sourceMappingURL=read-receipt.js.map
|
||||
Reference in New Issue
Block a user