You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Update thread info after MSC3440 updates (#2209)
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
import { RelationType, UNSTABLE_FILTER_RELATION_SENDERS, UNSTABLE_FILTER_RELATION_TYPES } from "../../src";
|
import {
|
||||||
|
RelationType,
|
||||||
|
UNSTABLE_FILTER_RELATED_BY_REL_TYPES,
|
||||||
|
UNSTABLE_FILTER_RELATED_BY_SENDERS,
|
||||||
|
} from "../../src";
|
||||||
import { FilterComponent } from "../../src/filter-component";
|
import { FilterComponent } from "../../src/filter-component";
|
||||||
import { mkEvent } from '../test-utils';
|
import { mkEvent } from '../test-utils';
|
||||||
|
|
||||||
@@ -35,7 +39,7 @@ describe("Filter Component", function() {
|
|||||||
it("should filter out events by relation participation", function() {
|
it("should filter out events by relation participation", function() {
|
||||||
const currentUserId = '@me:server.org';
|
const currentUserId = '@me:server.org';
|
||||||
const filter = new FilterComponent({
|
const filter = new FilterComponent({
|
||||||
[UNSTABLE_FILTER_RELATION_SENDERS.name]: currentUserId,
|
[UNSTABLE_FILTER_RELATED_BY_SENDERS.name]: [currentUserId],
|
||||||
}, currentUserId);
|
}, currentUserId);
|
||||||
|
|
||||||
const threadRootNotParticipated = mkEvent({
|
const threadRootNotParticipated = mkEvent({
|
||||||
@@ -60,7 +64,7 @@ describe("Filter Component", function() {
|
|||||||
it("should keep events by relation participation", function() {
|
it("should keep events by relation participation", function() {
|
||||||
const currentUserId = '@me:server.org';
|
const currentUserId = '@me:server.org';
|
||||||
const filter = new FilterComponent({
|
const filter = new FilterComponent({
|
||||||
[UNSTABLE_FILTER_RELATION_SENDERS.name]: currentUserId,
|
[UNSTABLE_FILTER_RELATED_BY_SENDERS.name]: [currentUserId],
|
||||||
}, currentUserId);
|
}, currentUserId);
|
||||||
|
|
||||||
const threadRootParticipated = mkEvent({
|
const threadRootParticipated = mkEvent({
|
||||||
@@ -84,7 +88,7 @@ describe("Filter Component", function() {
|
|||||||
|
|
||||||
it("should filter out events by relation type", function() {
|
it("should filter out events by relation type", function() {
|
||||||
const filter = new FilterComponent({
|
const filter = new FilterComponent({
|
||||||
[UNSTABLE_FILTER_RELATION_TYPES.name]: RelationType.Thread,
|
[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name]: [RelationType.Thread],
|
||||||
});
|
});
|
||||||
|
|
||||||
const referenceRelationEvent = mkEvent({
|
const referenceRelationEvent = mkEvent({
|
||||||
@@ -104,7 +108,7 @@ describe("Filter Component", function() {
|
|||||||
|
|
||||||
it("should keep events by relation type", function() {
|
it("should keep events by relation type", function() {
|
||||||
const filter = new FilterComponent({
|
const filter = new FilterComponent({
|
||||||
[UNSTABLE_FILTER_RELATION_TYPES.name]: RelationType.Thread,
|
[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name]: [RelationType.Thread],
|
||||||
});
|
});
|
||||||
|
|
||||||
const threadRootEvent = mkEvent({
|
const threadRootEvent = mkEvent({
|
@@ -1,3 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an internal module. See {@link MatrixClient} for the public class.
|
||||||
|
* @module client
|
||||||
|
*/
|
||||||
|
|
||||||
import * as utils from "../test-utils";
|
import * as utils from "../test-utils";
|
||||||
import { DuplicateStrategy, EventStatus, MatrixEvent, PendingEventOrdering, RoomEvent } from "../../src";
|
import { DuplicateStrategy, EventStatus, MatrixEvent, PendingEventOrdering, RoomEvent } from "../../src";
|
||||||
import { EventTimeline } from "../../src/models/event-timeline";
|
import { EventTimeline } from "../../src/models/event-timeline";
|
||||||
@@ -5,6 +26,7 @@ import { Room } from "../../src/models/room";
|
|||||||
import { RoomState } from "../../src/models/room-state";
|
import { RoomState } from "../../src/models/room-state";
|
||||||
import { RelationType, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
|
import { RelationType, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
|
||||||
import { TestClient } from "../TestClient";
|
import { TestClient } from "../TestClient";
|
||||||
|
import { Thread } from "../../src/models/thread";
|
||||||
|
|
||||||
describe("Room", function() {
|
describe("Room", function() {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@@ -1845,6 +1867,54 @@ describe("Room", function() {
|
|||||||
|
|
||||||
expect(() => room.createThread(rootEvent, [])).not.toThrow();
|
expect(() => room.createThread(rootEvent, [])).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not add events before server supports is known", function() {
|
||||||
|
Thread.hasServerSideSupport = undefined;
|
||||||
|
|
||||||
|
const rootEvent = new MatrixEvent({
|
||||||
|
event_id: "$666",
|
||||||
|
room_id: roomId,
|
||||||
|
content: {},
|
||||||
|
unsigned: {
|
||||||
|
"age": 1,
|
||||||
|
"m.relations": {
|
||||||
|
[RelationType.Thread]: {
|
||||||
|
latest_event: null,
|
||||||
|
count: 1,
|
||||||
|
current_user_participated: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let age = 1;
|
||||||
|
function mkEvt(id): MatrixEvent {
|
||||||
|
return new MatrixEvent({
|
||||||
|
event_id: id,
|
||||||
|
room_id: roomId,
|
||||||
|
content: {
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": RelationType.Thread,
|
||||||
|
"event_id": "$666",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
unsigned: {
|
||||||
|
"age": age++,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const thread = room.createThread(rootEvent, []);
|
||||||
|
expect(thread.length).toBe(0);
|
||||||
|
|
||||||
|
thread.addEvent(mkEvt("$1"));
|
||||||
|
expect(thread.length).toBe(0);
|
||||||
|
|
||||||
|
Thread.hasServerSideSupport = true;
|
||||||
|
|
||||||
|
thread.addEvent(mkEvt("$2"));
|
||||||
|
expect(thread.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,10 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RelationType } from "./@types/event";
|
import { RelationType } from "./@types/event";
|
||||||
import { UNSTABLE_FILTER_RELATION_SENDERS, UNSTABLE_FILTER_RELATION_TYPES } from "./filter";
|
import {
|
||||||
|
UNSTABLE_FILTER_RELATED_BY_REL_TYPES,
|
||||||
|
UNSTABLE_FILTER_RELATED_BY_SENDERS,
|
||||||
|
} from "./filter";
|
||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +51,8 @@ export interface IFilterComponent {
|
|||||||
not_senders?: string[];
|
not_senders?: string[];
|
||||||
contains_url?: boolean;
|
contains_url?: boolean;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
[UNSTABLE_FILTER_RELATION_TYPES.name]?: Array<RelationType | string>;
|
[UNSTABLE_FILTER_RELATED_BY_SENDERS.name]?: string[];
|
||||||
|
[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name]?: Array<RelationType | string>;
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
@@ -106,8 +110,8 @@ export class FilterComponent {
|
|||||||
senders: this.filterJson.senders || null,
|
senders: this.filterJson.senders || null,
|
||||||
not_senders: this.filterJson.not_senders || [],
|
not_senders: this.filterJson.not_senders || [],
|
||||||
contains_url: this.filterJson.contains_url || null,
|
contains_url: this.filterJson.contains_url || null,
|
||||||
[UNSTABLE_FILTER_RELATION_SENDERS.name]: UNSTABLE_FILTER_RELATION_SENDERS.findIn(this.filterJson),
|
[UNSTABLE_FILTER_RELATED_BY_SENDERS.name]: UNSTABLE_FILTER_RELATED_BY_SENDERS.findIn(this.filterJson),
|
||||||
[UNSTABLE_FILTER_RELATION_TYPES.name]: UNSTABLE_FILTER_RELATION_TYPES.findIn(this.filterJson),
|
[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name]: UNSTABLE_FILTER_RELATED_BY_REL_TYPES.findIn(this.filterJson),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,14 +165,14 @@ export class FilterComponent {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationTypesFilter = this.filterJson[UNSTABLE_FILTER_RELATION_TYPES.name];
|
const relationTypesFilter = this.filterJson[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name];
|
||||||
if (relationTypesFilter !== undefined) {
|
if (relationTypesFilter !== undefined) {
|
||||||
if (!this.arrayMatchesFilter(relationTypesFilter, relationTypes)) {
|
if (!this.arrayMatchesFilter(relationTypesFilter, relationTypes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationSendersFilter = this.filterJson[UNSTABLE_FILTER_RELATION_SENDERS.name];
|
const relationSendersFilter = this.filterJson[UNSTABLE_FILTER_RELATED_BY_SENDERS.name];
|
||||||
if (relationSendersFilter !== undefined) {
|
if (relationSendersFilter !== undefined) {
|
||||||
if (!this.arrayMatchesFilter(relationSendersFilter, relationSenders)) {
|
if (!this.arrayMatchesFilter(relationSendersFilter, relationSenders)) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -26,13 +26,13 @@ import { FilterComponent, IFilterComponent } from "./filter-component";
|
|||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
import { UnstableValue } from "./NamespacedValue";
|
import { UnstableValue } from "./NamespacedValue";
|
||||||
|
|
||||||
export const UNSTABLE_FILTER_RELATION_SENDERS = new UnstableValue(
|
export const UNSTABLE_FILTER_RELATED_BY_SENDERS = new UnstableValue(
|
||||||
"relation_senders",
|
"related_by_senders",
|
||||||
"io.element.relation_senders",
|
"io.element.relation_senders",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UNSTABLE_FILTER_RELATION_TYPES = new UnstableValue(
|
export const UNSTABLE_FILTER_RELATED_BY_REL_TYPES = new UnstableValue(
|
||||||
"relation_types",
|
"related_by_rel_types",
|
||||||
"io.element.relation_types",
|
"io.element.relation_types",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -66,8 +66,8 @@ export interface IRoomEventFilter extends IFilterComponent {
|
|||||||
lazy_load_members?: boolean;
|
lazy_load_members?: boolean;
|
||||||
include_redundant_members?: boolean;
|
include_redundant_members?: boolean;
|
||||||
types?: Array<EventType | string>;
|
types?: Array<EventType | string>;
|
||||||
[UNSTABLE_FILTER_RELATION_TYPES.name]?: Array<RelationType | string>;
|
[UNSTABLE_FILTER_RELATED_BY_REL_TYPES.name]?: Array<RelationType | string>;
|
||||||
[UNSTABLE_FILTER_RELATION_SENDERS.name]?: string[];
|
[UNSTABLE_FILTER_RELATED_BY_SENDERS.name]?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IStateFilter extends IRoomEventFilter {}
|
interface IStateFilter extends IRoomEventFilter {}
|
||||||
|
@@ -52,6 +52,9 @@ interface IThreadOpts {
|
|||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||||
|
public static hasServerSideSupport: boolean;
|
||||||
|
private static serverSupportPromise: Promise<boolean> | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to all the events ID at the bottom of the threads
|
* A reference to all the events ID at the bottom of the threads
|
||||||
*/
|
*/
|
||||||
@@ -91,6 +94,15 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
RoomEvent.TimelineReset,
|
RoomEvent.TimelineReset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (Thread.hasServerSideSupport === undefined) {
|
||||||
|
Thread.serverSupportPromise = this.client.doesServerSupportUnstableFeature("org.matrix.msc3440");
|
||||||
|
Thread.serverSupportPromise.then((serverSupportsThread) => {
|
||||||
|
Thread.hasServerSideSupport = serverSupportsThread;
|
||||||
|
}).catch(() => {
|
||||||
|
Thread.serverSupportPromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If we weren't able to find the root event, it's probably missing
|
// If we weren't able to find the root event, it's probably missing
|
||||||
// and we define the thread ID from one of the thread relation
|
// and we define the thread ID from one of the thread relation
|
||||||
if (!rootEvent) {
|
if (!rootEvent) {
|
||||||
@@ -107,11 +119,6 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
this.room.on(RoomEvent.Timeline, this.onEcho);
|
this.room.on(RoomEvent.Timeline, this.onEcho);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasServerSideSupport(): boolean {
|
|
||||||
return this.client.cachedCapabilities
|
|
||||||
?.capabilities?.[RelationType.Thread]?.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onEcho = (event: MatrixEvent) => {
|
private onEcho = (event: MatrixEvent) => {
|
||||||
if (this.timelineSet.eventIdToTimeline(event.getId())) {
|
if (this.timelineSet.eventIdToTimeline(event.getId())) {
|
||||||
this.emit(ThreadEvent.Update, this);
|
this.emit(ThreadEvent.Update, this);
|
||||||
@@ -152,8 +159,12 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
* to the start (and not the end) of the timeline.
|
* to the start (and not the end) of the timeline.
|
||||||
*/
|
*/
|
||||||
public async addEvent(event: MatrixEvent, toStartOfTimeline = false): Promise<void> {
|
public async addEvent(event: MatrixEvent, toStartOfTimeline = false): Promise<void> {
|
||||||
|
if (Thread.hasServerSideSupport === undefined) {
|
||||||
|
await Thread.serverSupportPromise;
|
||||||
|
}
|
||||||
|
|
||||||
// Add all incoming events to the thread's timeline set when there's no server support
|
// Add all incoming events to the thread's timeline set when there's no server support
|
||||||
if (!this.hasServerSideSupport) {
|
if (!Thread.hasServerSideSupport) {
|
||||||
// all the relevant membership info to hydrate events with a sender
|
// all the relevant membership info to hydrate events with a sender
|
||||||
// is held in the main room timeline
|
// is held in the main room timeline
|
||||||
// We want to fetch the room state from there and pass it down to this thread
|
// We want to fetch the room state from there and pass it down to this thread
|
||||||
@@ -165,7 +176,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
await this.client.decryptEventIfNeeded(event, {});
|
await this.client.decryptEventIfNeeded(event, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasServerSideSupport && this.initialEventsFetched) {
|
if (Thread.hasServerSideSupport && this.initialEventsFetched) {
|
||||||
if (event.localTimestamp > this.lastReply().localTimestamp) {
|
if (event.localTimestamp > this.lastReply().localTimestamp) {
|
||||||
this.addEventToTimeline(event, false);
|
this.addEventToTimeline(event, false);
|
||||||
}
|
}
|
||||||
@@ -178,7 +189,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
const isThreadReply = event.getRelation()?.rel_type === RelationType.Thread;
|
const isThreadReply = event.getRelation()?.rel_type === RelationType.Thread;
|
||||||
// If no thread support exists we want to count all thread relation
|
// If no thread support exists we want to count all thread relation
|
||||||
// added as a reply. We can't rely on the bundled relationships count
|
// added as a reply. We can't rely on the bundled relationships count
|
||||||
if (!this.hasServerSideSupport && isThreadReply) {
|
if (!Thread.hasServerSideSupport && isThreadReply) {
|
||||||
this.replyCount++;
|
this.replyCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +202,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
// This counting only works when server side support is enabled
|
// This counting only works when server side support is enabled
|
||||||
// as we started the counting from the value returned in the
|
// as we started the counting from the value returned in the
|
||||||
// bundled relationship
|
// bundled relationship
|
||||||
if (this.hasServerSideSupport) {
|
if (Thread.hasServerSideSupport) {
|
||||||
this.replyCount++;
|
this.replyCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,10 +214,17 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initialiseThread(rootEvent: MatrixEvent | undefined): void {
|
private initialiseThread(rootEvent: MatrixEvent | undefined): void {
|
||||||
|
if (Thread.hasServerSideSupport === undefined) {
|
||||||
|
Thread.serverSupportPromise.then(() => {
|
||||||
|
this.initialiseThread(rootEvent);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bundledRelationship = rootEvent
|
const bundledRelationship = rootEvent
|
||||||
?.getServerAggregatedRelation<IThreadBundledRelationship>(RelationType.Thread);
|
?.getServerAggregatedRelation<IThreadBundledRelationship>(RelationType.Thread);
|
||||||
|
|
||||||
if (this.hasServerSideSupport && bundledRelationship) {
|
if (Thread.hasServerSideSupport && bundledRelationship) {
|
||||||
this.replyCount = bundledRelationship.count;
|
this.replyCount = bundledRelationship.count;
|
||||||
this._currentUserParticipated = bundledRelationship.current_user_participated;
|
this._currentUserParticipated = bundledRelationship.current_user_participated;
|
||||||
|
|
||||||
@@ -221,6 +239,9 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchInitialEvents(): Promise<boolean> {
|
public async fetchInitialEvents(): Promise<boolean> {
|
||||||
|
if (Thread.hasServerSideSupport === undefined) {
|
||||||
|
await Thread.serverSupportPromise;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.fetchEvents();
|
await this.fetchEvents();
|
||||||
this.initialEventsFetched = true;
|
this.initialEventsFetched = true;
|
||||||
@@ -296,6 +317,10 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
nextBatch?: string;
|
nextBatch?: string;
|
||||||
prevBatch?: string;
|
prevBatch?: string;
|
||||||
}> {
|
}> {
|
||||||
|
if (Thread.serverSupportPromise) {
|
||||||
|
await Thread.serverSupportPromise;
|
||||||
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
originalEvent,
|
originalEvent,
|
||||||
events,
|
events,
|
||||||
|
Reference in New Issue
Block a user