Compare commits
7 Commits
v22.0.0-rc.1
...
v22.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| b6369cc2bd | |||
| e6e079f487 | |||
| 83a1e07380 | |||
| 7f3123ed65 | |||
| 5a88a6c62a | |||
| 12cecbdcf1 | |||
| c17deb0806 |
+5
-2
@@ -1,5 +1,5 @@
|
||||
Changes in [22.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v22.0.0-rc.1) (2022-11-29)
|
||||
============================================================================================================
|
||||
Changes in [22.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v22.0.0) (2022-12-06)
|
||||
==================================================================================================
|
||||
|
||||
## 🚨 BREAKING CHANGES
|
||||
* Enable users to join group calls from multiple devices ([\#2902](https://github.com/matrix-org/matrix-js-sdk/pull/2902)).
|
||||
@@ -19,6 +19,9 @@ Changes in [22.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/ta
|
||||
* Fix 3pid invite acceptance not working due to mxid being sent in body ([\#2907](https://github.com/matrix-org/matrix-js-sdk/pull/2907)). Fixes vector-im/element-web#23823.
|
||||
* Don't hang up calls that haven't started yet ([\#2898](https://github.com/matrix-org/matrix-js-sdk/pull/2898)).
|
||||
* Read receipt accumulation for threads ([\#2881](https://github.com/matrix-org/matrix-js-sdk/pull/2881)).
|
||||
* Make GroupCall work better with widgets ([\#2935](https://github.com/matrix-org/matrix-js-sdk/pull/2935)).
|
||||
* Fix highlight notifications increasing when total notification is zero ([\#2937](https://github.com/matrix-org/matrix-js-sdk/pull/2937)). Fixes vector-im/element-web#23885.
|
||||
* Fix synthesizeReceipt ([\#2916](https://github.com/matrix-org/matrix-js-sdk/pull/2916)). Fixes vector-im/element-web#23827 vector-im/element-web#23754 and vector-im/element-web#23847.
|
||||
|
||||
Changes in [21.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.2.0) (2022-11-22)
|
||||
==================================================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "22.0.0-rc.1",
|
||||
"version": "22.0.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
@@ -416,6 +416,9 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
|
||||
public getRooms = jest.fn<Room[], []>().mockReturnValue([]);
|
||||
public getRoom = jest.fn();
|
||||
|
||||
public supportsExperimentalThreads(): boolean { return true; }
|
||||
public async decryptEventIfNeeded(): Promise<void> {}
|
||||
|
||||
public typed(): MatrixClient { return this as unknown as MatrixClient; }
|
||||
|
||||
public emitRoomState(event: MatrixEvent, state: RoomState): void {
|
||||
|
||||
@@ -37,7 +37,7 @@ let event: MatrixEvent;
|
||||
let threadEvent: MatrixEvent;
|
||||
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
let THREAD_ID;
|
||||
let THREAD_ID: string;
|
||||
|
||||
function mkPushAction(notify, highlight): IActionsObject {
|
||||
return {
|
||||
@@ -76,7 +76,7 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
event: true,
|
||||
}, mockClient);
|
||||
|
||||
THREAD_ID = event.getId();
|
||||
THREAD_ID = event.getId()!;
|
||||
threadEvent = mkEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
@@ -108,6 +108,16 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("does not change the room count when there's no unread count", () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 0);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
|
||||
|
||||
fixNotificationCountOnDecryption(mockClient, event);
|
||||
|
||||
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Total)).toBe(1);
|
||||
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("changes the thread count to highlight on decryption", () => {
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(1);
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
|
||||
@@ -118,6 +128,16 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("does not change the room count when there's no unread count", () => {
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
|
||||
|
||||
fixNotificationCountOnDecryption(mockClient, event);
|
||||
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(0);
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
|
||||
});
|
||||
|
||||
it("emits events", () => {
|
||||
const cb = jest.fn();
|
||||
room.on(RoomEvent.UnreadNotifications, cb);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MAIN_ROOM_TIMELINE, ReceiptType } from '../../src/@types/read_receipts'
|
||||
import { MatrixClient } from "../../src/client";
|
||||
import { Feature, ServerSupport } from '../../src/feature';
|
||||
import { EventType } from '../../src/matrix';
|
||||
import { synthesizeReceipt } from '../../src/models/read-receipt';
|
||||
import { encodeUri } from '../../src/utils';
|
||||
import * as utils from "../test-utils/test-utils";
|
||||
|
||||
@@ -175,4 +176,20 @@ describe("Read receipt", () => {
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
describe("synthesizeReceipt", () => {
|
||||
it.each([
|
||||
{ event: roomEvent, destinationId: MAIN_ROOM_TIMELINE },
|
||||
{ event: threadEvent, destinationId: threadEvent.threadRootId! },
|
||||
])("adds the receipt to $destinationId", ({ event, destinationId }) => {
|
||||
const userId = "@bob:example.org";
|
||||
const receiptType = ReceiptType.Read;
|
||||
|
||||
const fakeReadReceipt = synthesizeReceipt(userId, event, receiptType);
|
||||
|
||||
const content = fakeReadReceipt.getContent()[event.getId()!][receiptType][userId];
|
||||
|
||||
expect(content.thread_id).toEqual(destinationId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '../../../src';
|
||||
import { RoomStateEvent } from "../../../src/models/room-state";
|
||||
import { GroupCall, GroupCallEvent, GroupCallState } from "../../../src/webrtc/groupCall";
|
||||
import { MatrixClient } from "../../../src/client";
|
||||
import { IMyDevice, MatrixClient } from "../../../src/client";
|
||||
import {
|
||||
installWebRTCMocks,
|
||||
MockCallFeed,
|
||||
@@ -180,13 +180,13 @@ describe('Group Call', function() {
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1);
|
||||
groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt);
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
});
|
||||
|
||||
it("does not initialize local call feed, if it already is", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.initLocalCallFeed();
|
||||
jest.spyOn(groupCall, "initLocalCallFeed");
|
||||
await groupCall.enter();
|
||||
@@ -216,10 +216,6 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends member state event to room on enter", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.create();
|
||||
|
||||
try {
|
||||
@@ -249,10 +245,6 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends member state event to room on leave", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.create();
|
||||
await groupCall.enter();
|
||||
mockSendState.mockClear();
|
||||
@@ -267,6 +259,18 @@ describe('Group Call', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it("includes local device in participants when entered via another session", async () => {
|
||||
const hasLocalParticipant = () => groupCall.participants.get(
|
||||
room.getMember(mockClient.getUserId()!)!,
|
||||
)?.has(mockClient.getDeviceId()!) ?? false;
|
||||
|
||||
expect(groupCall.enteredViaAnotherSession).toBe(false);
|
||||
expect(hasLocalParticipant()).toBe(false);
|
||||
|
||||
groupCall.enteredViaAnotherSession = true;
|
||||
expect(hasLocalParticipant()).toBe(true);
|
||||
});
|
||||
|
||||
it("starts with mic unmuted in regular calls", async () => {
|
||||
try {
|
||||
await groupCall.create();
|
||||
@@ -1270,4 +1274,155 @@ describe('Group Call', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleaning member state", () => {
|
||||
const bobWeb: IMyDevice = {
|
||||
device_id: "bobweb",
|
||||
last_seen_ts: 0,
|
||||
};
|
||||
const bobDesktop: IMyDevice = {
|
||||
device_id: "bobdesktop",
|
||||
last_seen_ts: 0,
|
||||
};
|
||||
const bobDesktopOffline: IMyDevice = {
|
||||
device_id: "bobdesktopoffline",
|
||||
last_seen_ts: 1000 * 60 * 60 * -2, // 2 hours ago
|
||||
};
|
||||
const bobDesktopNeverOnline: IMyDevice = {
|
||||
device_id: "bobdesktopneveronline",
|
||||
};
|
||||
|
||||
const mkContent = (devices: IMyDevice[]) => ({
|
||||
"m.calls": [{
|
||||
"m.call_id": groupCall.groupCallId,
|
||||
"m.devices": devices.map(d => ({
|
||||
device_id: d.device_id, session_id: "1", feeds: [], expires_ts: 1000 * 60 * 10,
|
||||
})),
|
||||
}],
|
||||
});
|
||||
|
||||
const expectDevices = (devices: IMyDevice[]) => expect(
|
||||
room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, FAKE_USER_ID_2)?.getContent(),
|
||||
).toEqual({
|
||||
"m.calls": [{
|
||||
"m.call_id": groupCall.groupCallId,
|
||||
"m.devices": devices.map(d => ({
|
||||
device_id: d.device_id, session_id: "1", feeds: [], expires_ts: expect.any(Number),
|
||||
})),
|
||||
}],
|
||||
});
|
||||
|
||||
let mockClient: MatrixClient;
|
||||
let room: Room;
|
||||
let groupCall: GroupCall;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterAll(() => jest.useRealTimers());
|
||||
|
||||
beforeEach(async () => {
|
||||
const typedMockClient = new MockCallMatrixClient(
|
||||
FAKE_USER_ID_2, bobWeb.device_id, FAKE_SESSION_ID_2,
|
||||
);
|
||||
jest.spyOn(typedMockClient, "sendStateEvent").mockImplementation(
|
||||
async (roomId, eventType, content, stateKey) => {
|
||||
const eventId = `$${Math.random()}`;
|
||||
if (roomId === room.roomId) {
|
||||
room.addLiveEvents([new MatrixEvent({
|
||||
event_id: eventId,
|
||||
type: eventType,
|
||||
room_id: roomId,
|
||||
sender: FAKE_USER_ID_2,
|
||||
content,
|
||||
state_key: stateKey,
|
||||
})]);
|
||||
}
|
||||
return { event_id: eventId };
|
||||
},
|
||||
);
|
||||
mockClient = typedMockClient as unknown as MatrixClient;
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_2);
|
||||
room.getMember = jest.fn().mockImplementation((userId) => ({ userId }));
|
||||
|
||||
groupCall = new GroupCall(
|
||||
mockClient,
|
||||
room,
|
||||
GroupCallType.Video,
|
||||
false,
|
||||
GroupCallIntent.Prompt,
|
||||
FAKE_CONF_ID,
|
||||
);
|
||||
await groupCall.create();
|
||||
|
||||
mockClient.getDevices = async () => ({
|
||||
devices: [
|
||||
bobWeb,
|
||||
bobDesktop,
|
||||
bobDesktopOffline,
|
||||
bobDesktopNeverOnline,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => groupCall.leave());
|
||||
|
||||
it("doesn't clean up valid devices", async () => {
|
||||
await groupCall.enter();
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb, bobDesktop]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobWeb, bobDesktop]);
|
||||
});
|
||||
|
||||
it("cleans up our own device if we're disconnected", async () => {
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb, bobDesktop]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobDesktop]);
|
||||
});
|
||||
|
||||
it("doesn't clean up the local device if entered via another session", async () => {
|
||||
groupCall.enteredViaAnotherSession = true;
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobWeb]);
|
||||
});
|
||||
|
||||
it("cleans up devices that have never been online", async () => {
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobDesktop, bobDesktopNeverOnline]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobDesktop]);
|
||||
});
|
||||
|
||||
it("no-ops if there are no state events", async () => {
|
||||
await groupCall.cleanMemberState();
|
||||
expect(room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, FAKE_USER_ID_2)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+11
-9
@@ -5830,8 +5830,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
).then(async (res) => {
|
||||
const mapper = this.getEventMapper();
|
||||
const matrixEvents = res.chunk.map(mapper);
|
||||
for (const event of matrixEvents) {
|
||||
await eventTimeline.getTimelineSet()?.thread?.processEvent(event);
|
||||
|
||||
// Process latest events first
|
||||
for (const event of matrixEvents.slice().reverse()) {
|
||||
await thread?.processEvent(event);
|
||||
const sender = event.getSender()!;
|
||||
if (!backwards || thread?.getEventReadUpTo(sender) === null) {
|
||||
room.addLocalEchoReceipt(sender, event, ReceiptType.Read);
|
||||
}
|
||||
}
|
||||
|
||||
const newToken = res.next_batch;
|
||||
@@ -9338,12 +9344,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
|
||||
if (!room || !cli.getUserId()) return;
|
||||
|
||||
const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;
|
||||
const currentCount = (isThreadEvent
|
||||
? room.getThreadUnreadNotificationCount(
|
||||
event.threadRootId,
|
||||
NotificationCountType.Highlight,
|
||||
)
|
||||
: room.getUnreadNotificationCount(NotificationCountType.Highlight)) ?? 0;
|
||||
|
||||
const currentCount = room.getUnreadCountForEventContext(NotificationCountType.Highlight, event);
|
||||
|
||||
// Ensure the unread counts are kept up to date if the event is encrypted
|
||||
// We also want to make sure that the notification count goes up if we already
|
||||
@@ -9375,7 +9377,7 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
|
||||
// Fix 'Mentions Only' rooms from not having the right badge count
|
||||
const totalCount = (isThreadEvent
|
||||
? room.getThreadUnreadNotificationCount(event.threadRootId, NotificationCountType.Total)
|
||||
: room.getUnreadNotificationCount(NotificationCountType.Total)) ?? 0;
|
||||
: room.getRoomUnreadNotificationCount(NotificationCountType.Total)) ?? 0;
|
||||
|
||||
if (totalCount < newCount) {
|
||||
if (isThreadEvent) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptTyp
|
||||
[receiptType]: {
|
||||
[userId]: {
|
||||
ts: event.getTs(),
|
||||
threadId: event.threadRootId ?? MAIN_ROOM_TIMELINE,
|
||||
thread_id: event.threadRootId ?? MAIN_ROOM_TIMELINE,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+27
-9
@@ -1183,7 +1183,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
* for this type.
|
||||
*/
|
||||
public getUnreadNotificationCount(type = NotificationCountType.Total): number {
|
||||
let count = this.notificationCounts[type] ?? 0;
|
||||
let count = this.getRoomUnreadNotificationCount(type);
|
||||
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
|
||||
for (const threadNotification of this.threadNotifications.values()) {
|
||||
count += threadNotification[type] ?? 0;
|
||||
@@ -1192,6 +1192,27 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification for the event context (room or thread timeline)
|
||||
*/
|
||||
public getUnreadCountForEventContext(type = NotificationCountType.Total, event: MatrixEvent): number {
|
||||
const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;
|
||||
|
||||
return (isThreadEvent
|
||||
? this.getThreadUnreadNotificationCount(event.threadRootId, type)
|
||||
: this.getRoomUnreadNotificationCount(type)) ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one of the notification counts for this room
|
||||
* @param {String} type The type of notification count to get. default: 'total'
|
||||
* @return {Number} The notification count, or undefined if there is no count
|
||||
* for this type.
|
||||
*/
|
||||
public getRoomUnreadNotificationCount(type = NotificationCountType.Total): number {
|
||||
return this.notificationCounts[type] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* Get one of the notification counts for a thread
|
||||
@@ -1675,7 +1696,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
Array.from(this.threads)
|
||||
.forEach(([, thread]) => {
|
||||
if (thread.length === 0) return;
|
||||
const currentUserParticipated = thread.events.some(event => {
|
||||
const currentUserParticipated = thread.timeline.some(event => {
|
||||
return event.getSender() === this.client.getUserId();
|
||||
});
|
||||
if (filterType !== ThreadFilterType.My || currentUserParticipated) {
|
||||
@@ -1758,20 +1779,17 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
let latestMyThreadsRootEvent: MatrixEvent | undefined;
|
||||
const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
for (const rootEvent of threadRoots) {
|
||||
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, {
|
||||
const opts = {
|
||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||
fromCache: false,
|
||||
roomState,
|
||||
});
|
||||
};
|
||||
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, opts);
|
||||
|
||||
const threadRelationship = rootEvent
|
||||
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
if (threadRelationship?.current_user_participated) {
|
||||
this.threadsTimelineSets[1]?.addLiveEvent(rootEvent, {
|
||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||
fromCache: false,
|
||||
roomState,
|
||||
});
|
||||
this.threadsTimelineSets[1]?.addLiveEvent(rootEvent, opts);
|
||||
latestMyThreadsRootEvent = rootEvent;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-8
@@ -81,6 +81,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
* A reference to all the events ID at the bottom of the threads
|
||||
*/
|
||||
public readonly timelineSet: EventTimelineSet;
|
||||
public timeline: MatrixEvent[] = [];
|
||||
|
||||
private _currentUserParticipated = false;
|
||||
|
||||
@@ -123,7 +124,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
this.room.on(RoomEvent.Redaction, this.onRedaction);
|
||||
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
|
||||
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
|
||||
this.timelineSet.on(RoomEvent.Timeline, this.onTimelineEvent);
|
||||
|
||||
// even if this thread is thought to be originating from this client, we initialise it as we may be in a
|
||||
// gappy sync and a thread around this event may already exist.
|
||||
@@ -181,7 +182,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
private onRedaction = async (event: MatrixEvent): Promise<void> => {
|
||||
if (event.threadRootId !== this.id) return; // ignore redactions for other timelines
|
||||
if (this.replyCount <= 0) {
|
||||
for (const threadEvent of this.events) {
|
||||
for (const threadEvent of this.timeline) {
|
||||
this.clearEventMetadata(threadEvent);
|
||||
}
|
||||
this.lastEvent = this.rootEvent;
|
||||
@@ -192,6 +193,18 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
};
|
||||
|
||||
private onTimelineEvent = (
|
||||
event: MatrixEvent,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean | undefined,
|
||||
): void => {
|
||||
// Add a synthesized receipt when paginating forward in the timeline
|
||||
if (!toStartOfTimeline) {
|
||||
room!.addLocalEchoReceipt(event.getSender()!, event, ReceiptType.Read);
|
||||
}
|
||||
this.onEcho(event);
|
||||
};
|
||||
|
||||
private onEcho = async (event: MatrixEvent): Promise<void> => {
|
||||
if (event.threadRootId !== this.id) return; // ignore echoes for other timelines
|
||||
if (this.lastEvent === event) return;
|
||||
@@ -216,6 +229,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
roomState: this.roomState,
|
||||
},
|
||||
);
|
||||
this.timeline = this.events;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +289,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.setEventMetadata(event);
|
||||
await this.fetchEditsWhereNeeded(event);
|
||||
}
|
||||
this.timeline = this.events;
|
||||
}
|
||||
|
||||
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
|
||||
@@ -362,8 +377,8 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
* Return last reply to the thread, if known.
|
||||
*/
|
||||
public lastReply(matches: (ev: MatrixEvent) => boolean = (): boolean => true): MatrixEvent | null {
|
||||
for (let i = this.events.length - 1; i >= 0; i--) {
|
||||
const event = this.events[i];
|
||||
for (let i = this.timeline.length - 1; i >= 0; i--) {
|
||||
const event = this.timeline[i];
|
||||
if (matches(event)) {
|
||||
return event;
|
||||
}
|
||||
@@ -411,10 +426,6 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
return this.timelineSet;
|
||||
}
|
||||
|
||||
public get timeline(): MatrixEvent[] {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
public addReceipt(event: MatrixEvent, synthetic: boolean): void {
|
||||
throw new Error("Unsupported function on the thread model");
|
||||
}
|
||||
|
||||
+21
-3
@@ -285,6 +285,21 @@ export class GroupCall extends TypedEventEmitter<
|
||||
this._creationTs = value;
|
||||
}
|
||||
|
||||
private _enteredViaAnotherSession = false;
|
||||
|
||||
/**
|
||||
* Whether the local device has entered this call via another session, such
|
||||
* as a widget.
|
||||
*/
|
||||
public get enteredViaAnotherSession(): boolean {
|
||||
return this._enteredViaAnotherSession;
|
||||
}
|
||||
|
||||
public set enteredViaAnotherSession(value: boolean) {
|
||||
this._enteredViaAnotherSession = value;
|
||||
this.updateParticipants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given callback on all calls in this group call.
|
||||
* @param f The callback.
|
||||
@@ -1170,7 +1185,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
|
||||
const participants = new Map<RoomMember, Map<string, ParticipantState>>();
|
||||
const now = Date.now();
|
||||
const entered = this.state === GroupCallState.Entered;
|
||||
const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession;
|
||||
let nextExpiration = Infinity;
|
||||
|
||||
for (const e of this.getMemberStateEvents()) {
|
||||
@@ -1344,8 +1359,11 @@ export class GroupCall extends TypedEventEmitter<
|
||||
await this.updateDevices(devices => {
|
||||
const newDevices = devices.filter(d => {
|
||||
const device = deviceMap.get(d.device_id);
|
||||
return device?.last_seen_ts !== undefined
|
||||
&& !(d.device_id === this.client.getDeviceId()! && this.state !== GroupCallState.Entered);
|
||||
return device?.last_seen_ts !== undefined && !(
|
||||
d.device_id === this.client.getDeviceId()!
|
||||
&& this.state !== GroupCallState.Entered
|
||||
&& !this.enteredViaAnotherSession
|
||||
);
|
||||
});
|
||||
|
||||
// Skip the update if the devices are unchanged
|
||||
|
||||
Reference in New Issue
Block a user