1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-23 17:02:25 +03:00

Make a MatrixRTCSession emit once the RTCNotification is sent (#4976)

* MatrixRTCSession emits once the rtc notification is sent.

Signed-off-by: Timo K <toger5@hotmail.de>

* update correct type description

Signed-off-by: Timo K <toger5@hotmail.de>

* Add test

Signed-off-by: Timo K <toger5@hotmail.de>

* fix imports

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-08-26 17:16:02 +02:00
committed by GitHub
parent 1fac06e223
commit c4c7f94514
2 changed files with 80 additions and 13 deletions

View File

@@ -309,8 +309,19 @@ describe("MatrixRTCSession", () => {
expect(sess!.isJoined()).toEqual(true); expect(sess!.isJoined()).toEqual(true);
}); });
it("sends a notification when starting a call", async () => { it("sends a notification when starting a call and emit DidSendCallNotification", async () => {
// Simulate a join, including the update to the room state // Simulate a join, including the update to the room state
// Ensure sendEvent returns event IDs so the DidSendCallNotification payload includes them
sendEventMock
.mockResolvedValueOnce({ event_id: "legacy-evt" })
.mockResolvedValueOnce({ event_id: "new-evt" });
const didSendEventFn = jest.fn();
sess!.once(MatrixRTCSessionEvent.DidSendCallNotification, didSendEventFn);
// Create an additional listener to create a promise that resolves after the emission.
const didSendNotification = new Promise((resolve) => {
sess!.once(MatrixRTCSessionEvent.DidSendCallNotification, resolve);
});
sess!.joinRoomSession([mockFocus], mockFocus, { notificationType: "ring" }); sess!.joinRoomSession([mockFocus], mockFocus, { notificationType: "ring" });
await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 5000))]); await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 5000))]);
mockRoomState(mockRoom, [{ ...membershipTemplate, user_id: client.getUserId()! }]); mockRoomState(mockRoom, [{ ...membershipTemplate, user_id: client.getUserId()! }]);
@@ -335,6 +346,28 @@ describe("MatrixRTCSession", () => {
"notify_type": "ring", "notify_type": "ring",
"call_id": "", "call_id": "",
}); });
await didSendNotification;
// And ensure we emitted the DidSendCallNotification event with both payloads
expect(didSendEventFn).toHaveBeenCalledWith(
{
"event_id": "new-evt",
"lifetime": 30000,
"m.mentions": { room: true, user_ids: [] },
"m.relates_to": {
event_id: expect.any(String),
rel_type: "org.matrix.msc4075.rtc.notification.parent",
},
"notification_type": "ring",
"sender_ts": expect.any(Number),
},
{
"application": "m.call",
"call_id": "",
"event_id": "legacy-evt",
"m.mentions": { room: true, user_ids: [] },
"notify_type": "ring",
},
);
}); });
it("doesn't send a notification when joining an existing call", async () => { it("doesn't send a notification when joining an existing call", async () => {

View File

@@ -20,14 +20,21 @@ import { EventTimeline } from "../models/event-timeline.ts";
import { type Room } from "../models/room.ts"; import { type Room } from "../models/room.ts";
import { type MatrixClient } from "../client.ts"; import { type MatrixClient } from "../client.ts";
import { EventType, RelationType } from "../@types/event.ts"; import { EventType, RelationType } from "../@types/event.ts";
import { KnownMembership } from "../@types/membership.ts";
import { type ISendEventResponse } from "../@types/requests.ts";
import { CallMembership } from "./CallMembership.ts"; import { CallMembership } from "./CallMembership.ts";
import { RoomStateEvent } from "../models/room-state.ts"; import { RoomStateEvent } from "../models/room-state.ts";
import { type Focus } from "./focus.ts"; import { type Focus } from "./focus.ts";
import { KnownMembership } from "../@types/membership.ts";
import { MembershipManager } from "./MembershipManager.ts"; import { MembershipManager } from "./MembershipManager.ts";
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts"; import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
import { deepCompare, logDurationSync } from "../utils.ts"; import { deepCompare, logDurationSync } from "../utils.ts";
import { type Statistics, type RTCNotificationType, type Status } from "./types.ts"; import {
type Statistics,
type RTCNotificationType,
type Status,
type IRTCNotificationContent,
type ICallNotifyContent,
} from "./types.ts";
import { RoomKeyTransport } from "./RoomKeyTransport.ts"; import { RoomKeyTransport } from "./RoomKeyTransport.ts";
import { import {
MembershipManagerEvent, MembershipManagerEvent,
@@ -43,6 +50,9 @@ import {
import { TypedReEmitter } from "../ReEmitter.ts"; import { TypedReEmitter } from "../ReEmitter.ts";
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts"; import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
/**
* Events emitted by MatrixRTCSession
*/
export enum MatrixRTCSessionEvent { export enum MatrixRTCSessionEvent {
// A member joined, left, or updated a property of their membership. // A member joined, left, or updated a property of their membership.
MembershipsChanged = "memberships_changed", MembershipsChanged = "memberships_changed",
@@ -54,6 +64,8 @@ export enum MatrixRTCSessionEvent {
EncryptionKeyChanged = "encryption_key_changed", EncryptionKeyChanged = "encryption_key_changed",
/** The membership manager had to shut down caused by an unrecoverable error */ /** The membership manager had to shut down caused by an unrecoverable error */
MembershipManagerError = "membership_manager_error", MembershipManagerError = "membership_manager_error",
/** The RTCSession did send a call notification caused by joining the call as the first member */
DidSendCallNotification = "did_send_call_notification",
} }
export type MatrixRTCSessionEventHandlerMap = { export type MatrixRTCSessionEventHandlerMap = {
@@ -68,6 +80,10 @@ export type MatrixRTCSessionEventHandlerMap = {
participantId: string, participantId: string,
) => void; ) => void;
[MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void; [MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void;
[MatrixRTCSessionEvent.DidSendCallNotification]: (
notificationContentNew: { event_id: string } & IRTCNotificationContent,
notificationContentLegacy: { event_id: string } & ICallNotifyContent,
) => void;
}; };
export interface SessionConfig { export interface SessionConfig {
@@ -652,19 +668,24 @@ export class MatrixRTCSession extends TypedEventEmitter<
* Sends a notification corresponding to the configured notify type. * Sends a notification corresponding to the configured notify type.
*/ */
private sendCallNotify(parentEventId: string, notificationType: RTCNotificationType): void { private sendCallNotify(parentEventId: string, notificationType: RTCNotificationType): void {
// Send legacy event: const sendLegacyNotificationEvent = async (): Promise<{
this.client response: ISendEventResponse;
.sendEvent(this.roomSubset.roomId, EventType.CallNotify, { content: ICallNotifyContent;
}> => {
const content: ICallNotifyContent = {
"application": "m.call", "application": "m.call",
"m.mentions": { user_ids: [], room: true }, "m.mentions": { user_ids: [], room: true },
"notify_type": notificationType === "notification" ? "notify" : notificationType, "notify_type": notificationType === "notification" ? "notify" : notificationType,
"call_id": this.callId!, "call_id": this.callId!,
}) };
.catch((e) => this.logger.error("Failed to send call notification", e)); const response = await this.client.sendEvent(this.roomSubset.roomId, EventType.CallNotify, content);
return { response, content };
// Send new event: };
this.client const sendNewNotificationEvent = async (): Promise<{
.sendEvent(this.roomSubset.roomId, EventType.RTCNotification, { response: ISendEventResponse;
content: IRTCNotificationContent;
}> => {
const content: IRTCNotificationContent = {
"m.mentions": { user_ids: [], room: true }, "m.mentions": { user_ids: [], room: true },
"notification_type": notificationType, "notification_type": notificationType,
"m.relates_to": { "m.relates_to": {
@@ -673,8 +694,21 @@ export class MatrixRTCSession extends TypedEventEmitter<
}, },
"sender_ts": Date.now(), "sender_ts": Date.now(),
"lifetime": 30_000, // 30 seconds "lifetime": 30_000, // 30 seconds
};
const response = await this.client.sendEvent(this.roomSubset.roomId, EventType.RTCNotification, content);
return { response, content };
};
void Promise.all([sendLegacyNotificationEvent(), sendNewNotificationEvent()])
.then(([legacy, newNotification]) => {
// Join event_id and origin event content
const legacyResult = { ...legacy.response, ...legacy.content };
const newResult = { ...newNotification.response, ...newNotification.content };
this.emit(MatrixRTCSessionEvent.DidSendCallNotification, newResult, legacyResult);
}) })
.catch((e) => this.logger.error("Failed to send call notification", e)); .catch(([errorLegacy, errorNew]) =>
this.logger.error("Failed to send call notification", errorLegacy, errorNew),
);
} }
/** /**