mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Sticky event handling fixes
This commit is contained in:
@@ -141,7 +141,7 @@ const roomHasCallMembershipEvents = (room: Room): boolean => {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for *active* calls using sticky events.
|
||||
// Check for recent calls using sticky events.
|
||||
for (const sticky of room._unstable_getStickyEvents()) {
|
||||
if (sticky.getType() === EventType.RTCMembership) {
|
||||
return true;
|
||||
@@ -150,11 +150,9 @@ const roomHasCallMembershipEvents = (room: Room): boolean => {
|
||||
|
||||
// Otherwise, check recent event history to see if anyone had
|
||||
// sent a call membership in here.
|
||||
return timeline.getEvents().some(
|
||||
(e) =>
|
||||
// Membership events only count if both of these are true
|
||||
e.unstableStickyInfo && e.getType() === EventType.GroupCallMemberPrefix,
|
||||
);
|
||||
return timeline
|
||||
.getEvents()
|
||||
.some((e) => e.getType() === EventType.RTCMembership);
|
||||
// Otherwise, it's *unlikely* this room was ever a call.
|
||||
};
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test } from "vitest";
|
||||
import EventEmitter from "events";
|
||||
import { expect, test } from "vitest";
|
||||
import {
|
||||
EventType,
|
||||
type IRoomTimelineData,
|
||||
MatrixEvent,
|
||||
type Room,
|
||||
RoomEvent,
|
||||
RoomStickyEventsEvent,
|
||||
} from "matrix-js-sdk";
|
||||
import { type RTCCallIntent } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { map, mergeMap, NEVER, type Observable, startWith } from "rxjs";
|
||||
@@ -25,6 +27,7 @@ import {
|
||||
import {
|
||||
type CallNotificationWrapper,
|
||||
createCallNotificationLifecycle$,
|
||||
createReceivedDecline$,
|
||||
type RingAttempt,
|
||||
} from "./CallNotificationLifecycle";
|
||||
import { constant } from "../Behavior";
|
||||
@@ -181,22 +184,16 @@ test("ring attempt can be declined", () => {
|
||||
a: mockRingEvent("$notif1", 30),
|
||||
}),
|
||||
receivedDecline$: hot("--d", {
|
||||
d: [
|
||||
new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
d: new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
}),
|
||||
{} as Room,
|
||||
undefined,
|
||||
false,
|
||||
{} as IRoomTimelineData,
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -216,22 +213,16 @@ test("ring attempt times out if recipient declines too late", () => {
|
||||
a: mockRingEvent("$notif1", 30),
|
||||
}),
|
||||
receivedDecline$: hot("100ms d", {
|
||||
d: [
|
||||
new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
d: new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
}),
|
||||
{} as Room,
|
||||
undefined,
|
||||
false,
|
||||
{} as IRoomTimelineData,
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -251,22 +242,16 @@ test("decline event relating to wrong event is ignored (times out)", () => {
|
||||
a: mockRingEvent("$notif1", 30),
|
||||
}),
|
||||
receivedDecline$: hot("--d", {
|
||||
d: [
|
||||
new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$other", // <---- WRONG
|
||||
},
|
||||
d: new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: alice.userId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$other", // <---- WRONG
|
||||
},
|
||||
}),
|
||||
{} as Room,
|
||||
undefined,
|
||||
false,
|
||||
{} as IRoomTimelineData,
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -286,22 +271,16 @@ test("decline event from wrong sender is ignored (times out)", () => {
|
||||
a: mockRingEvent("$notif1", 30),
|
||||
}),
|
||||
receivedDecline$: hot("--d", {
|
||||
d: [
|
||||
new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: local.userId, // <---- WRONG
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
d: new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
sender: local.userId, // <---- WRONG
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$notif1",
|
||||
},
|
||||
}),
|
||||
{} as Room,
|
||||
undefined,
|
||||
false,
|
||||
{} as IRoomTimelineData,
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -311,3 +290,32 @@ test("decline event from wrong sender is ignored (times out)", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("declines are received from the timeline and the sticky section", () => {
|
||||
const room = new EventEmitter();
|
||||
const received: string[] = [];
|
||||
const subscription = createReceivedDecline$(
|
||||
room as unknown as Room,
|
||||
).subscribe((event) => received.push(event.getId()!));
|
||||
const event = (eventId: string, type = EventType.RTCDecline): MatrixEvent =>
|
||||
new MatrixEvent({ event_id: eventId, type, sender: alice.userId });
|
||||
|
||||
const timelineArgs = [room, false, false, {}];
|
||||
room.emit(RoomEvent.Timeline, event("$timeline"), ...timelineArgs);
|
||||
room.emit(
|
||||
RoomEvent.Timeline,
|
||||
event("$message", EventType.RoomMessage),
|
||||
...timelineArgs,
|
||||
);
|
||||
// A gappy sync delivers the decline only in the sticky section
|
||||
room.emit(RoomStickyEventsEvent.Update, [event("$sticky")], [], []);
|
||||
room.emit(
|
||||
RoomStickyEventsEvent.Update,
|
||||
[],
|
||||
[{ previous: event("$old"), current: event("$replacement") }],
|
||||
[event("$removed")],
|
||||
);
|
||||
subscription.unsubscribe();
|
||||
|
||||
expect(received).toEqual(["$timeline", "$sticky", "$replacement"]);
|
||||
});
|
||||
|
||||
@@ -25,13 +25,17 @@ import {
|
||||
EMPTY,
|
||||
race,
|
||||
take,
|
||||
mergeMap,
|
||||
} from "rxjs";
|
||||
import {
|
||||
type EventTimelineSetHandlerMap,
|
||||
EventType,
|
||||
type MatrixEvent,
|
||||
type Room as MatrixRoom,
|
||||
RoomEvent,
|
||||
RoomStickyEventsEvent,
|
||||
} from "matrix-js-sdk";
|
||||
import { type RoomStickyEventsMap } from "matrix-js-sdk/lib/models/room-sticky-events";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import { type Behavior, type ObservableScope } from "@element-hq/matrixrtc-sdk";
|
||||
@@ -68,12 +72,28 @@ export function createSentCallNotification$(
|
||||
|
||||
export function createReceivedDecline$(
|
||||
matrixRoom: MatrixRoom,
|
||||
): Observable<Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]>> {
|
||||
return (
|
||||
): Observable<MatrixEvent> {
|
||||
// A decline is sticky, so after a gappy sync it arrives only in the sticky
|
||||
// section, which never reaches the timeline. Racing for the first outcome
|
||||
// makes a decline seen in both places harmless.
|
||||
const timeline$ = (
|
||||
fromEvent(matrixRoom, RoomEvent.Timeline) as Observable<
|
||||
Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]>
|
||||
>
|
||||
).pipe(filter(([event]) => event.getType() === EventType.RTCDecline));
|
||||
).pipe(map(([event]) => event));
|
||||
const sticky$ = (
|
||||
fromEvent(matrixRoom, RoomStickyEventsEvent.Update) as Observable<
|
||||
Parameters<RoomStickyEventsMap[RoomStickyEventsEvent.Update]>
|
||||
>
|
||||
).pipe(
|
||||
mergeMap(([added, updated]) => [
|
||||
...added,
|
||||
...updated.map(({ current }) => current),
|
||||
]),
|
||||
);
|
||||
return merge(timeline$, sticky$).pipe(
|
||||
filter((event) => event.getType() === EventType.RTCDecline),
|
||||
);
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
@@ -83,9 +103,7 @@ export interface Props {
|
||||
/** The user ids of the room's members, ours included. */
|
||||
roomMemberUserIds$: Behavior<string[]>;
|
||||
sentCallNotification$: Observable<CallNotificationWrapper | null>;
|
||||
receivedDecline$: Observable<
|
||||
Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]>
|
||||
>;
|
||||
receivedDecline$: Observable<MatrixEvent>;
|
||||
options: { waitForCallPickup?: boolean; autoLeaveWhenOthersLeft?: boolean };
|
||||
localUser: { deviceId: string; userId: string };
|
||||
}
|
||||
@@ -146,7 +164,7 @@ export function createCallNotificationLifecycle$({
|
||||
// Call is declined when we receive a decline event
|
||||
const decline$ = receivedDecline$.pipe(
|
||||
filter(
|
||||
([event]) =>
|
||||
(event) =>
|
||||
event.getRelation()?.rel_type === "m.reference" &&
|
||||
event.getRelation()?.event_id === notificationEvent.event_id &&
|
||||
event.getSender() === recipient,
|
||||
|
||||
Reference in New Issue
Block a user