You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Allow sending notification events when starting a call (#4826)
* Make it easier to mock call memberships for specific user IDs * Allow sending notification events when starting a call * rename notify -> notification * replace `joining` concept with `ownMembership` * introduce new `m.rtc.notification` event alongside `m.call.notify` * send new notification event alongside the deprecated one * Test for new notification event type * update relation string to match msc * review * fix doc errors * fix tests + format * remove anything decline related --------- Co-authored-by: Timo <toger5@hotmail.de>
This commit is contained in:
@@ -51,7 +51,11 @@ import {
|
||||
type SDPStreamMetadata,
|
||||
type SDPStreamMetadataKey,
|
||||
} from "../webrtc/callEventTypes.ts";
|
||||
import { type EncryptionKeysEventContent, type ICallNotifyContent } from "../matrixrtc/types.ts";
|
||||
import {
|
||||
type IRTCNotificationContent,
|
||||
type EncryptionKeysEventContent,
|
||||
type ICallNotifyContent,
|
||||
} from "../matrixrtc/types.ts";
|
||||
import { type M_POLL_END, type M_POLL_START, type PollEndEventContent, type PollStartEventContent } from "./polls.ts";
|
||||
import { type SessionMembershipData } from "../matrixrtc/CallMembership.ts";
|
||||
import { type LocalNotificationSettings } from "./local_notifications.ts";
|
||||
@@ -147,6 +151,7 @@ export enum EventType {
|
||||
|
||||
// MatrixRTC events
|
||||
CallNotify = "org.matrix.msc4075.call.notify",
|
||||
RTCNotification = "org.matrix.msc4075.rtc.notification",
|
||||
}
|
||||
|
||||
export enum RelationType {
|
||||
@@ -158,6 +163,7 @@ export enum RelationType {
|
||||
// moreover, our tests currently use the unstable prefix. Use THREAD_RELATION_TYPE.name.
|
||||
// Once we support *only* the stable prefix, THREAD_RELATION_TYPE can die and we can switch to this.
|
||||
Thread = "m.thread",
|
||||
unstable_RTCNotificationParent = "org.matrix.msc4075.rtc.notification.parent",
|
||||
}
|
||||
|
||||
export enum MsgType {
|
||||
@@ -325,6 +331,7 @@ export interface TimelineEvents {
|
||||
[EventType.CallSDPStreamMetadataChangedPrefix]: MCallBase & { [SDPStreamMetadataKey]: SDPStreamMetadata };
|
||||
[EventType.CallEncryptionKeysPrefix]: EncryptionKeysEventContent;
|
||||
[EventType.CallNotify]: ICallNotifyContent;
|
||||
[EventType.RTCNotification]: IRTCNotificationContent;
|
||||
[M_BEACON.name]: MBeaconEventContent;
|
||||
[M_POLL_START.name]: PollStartEventContent;
|
||||
[M_POLL_END.name]: PollEndEventContent;
|
||||
|
@@ -55,9 +55,13 @@ export interface IMembershipManager {
|
||||
* Get the actual connection status of the manager.
|
||||
*/
|
||||
get status(): Status;
|
||||
|
||||
/**
|
||||
* The current status while the manager is activated
|
||||
* The Current own state event if the manger is connected.
|
||||
* `undefined` if not connected.
|
||||
*/
|
||||
get ownMembership(): CallMembership | undefined;
|
||||
|
||||
/**
|
||||
* Start sending all necessary events to make this user participate in the RTC session.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
|
@@ -19,7 +19,7 @@ import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { EventTimeline } from "../models/event-timeline.ts";
|
||||
import { type Room } from "../models/room.ts";
|
||||
import { type MatrixClient } from "../client.ts";
|
||||
import { EventType } from "../@types/event.ts";
|
||||
import { EventType, RelationType } from "../@types/event.ts";
|
||||
import { CallMembership } from "./CallMembership.ts";
|
||||
import { RoomStateEvent } from "../models/room-state.ts";
|
||||
import { type Focus } from "./focus.ts";
|
||||
@@ -27,7 +27,7 @@ import { KnownMembership } from "../@types/membership.ts";
|
||||
import { MembershipManager } from "./MembershipManager.ts";
|
||||
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
|
||||
import { logDurationSync } from "../utils.ts";
|
||||
import { type Statistics } from "./types.ts";
|
||||
import { type Statistics, type RTCNotificationType } from "./types.ts";
|
||||
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
||||
import type { IMembershipManager } from "./IMembershipManager.ts";
|
||||
import { RTCEncryptionManager } from "./RTCEncryptionManager.ts";
|
||||
@@ -65,6 +65,15 @@ export type MatrixRTCSessionEventHandlerMap = {
|
||||
) => void;
|
||||
[MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void;
|
||||
};
|
||||
|
||||
export interface SessionConfig {
|
||||
/**
|
||||
* What kind of notification to send when starting the session.
|
||||
* @default `undefined` (no notification)
|
||||
*/
|
||||
notificationType?: RTCNotificationType;
|
||||
}
|
||||
|
||||
// The names follow these principles:
|
||||
// - we use the technical term delay if the option is related to delayed events.
|
||||
// - we use delayedLeaveEvent if the option is related to the delayed leave event.
|
||||
@@ -168,7 +177,7 @@ export interface EncryptionConfig {
|
||||
*/
|
||||
useKeyDelay?: number;
|
||||
}
|
||||
export type JoinSessionConfig = MembershipConfig & EncryptionConfig;
|
||||
export type JoinSessionConfig = SessionConfig & MembershipConfig & EncryptionConfig;
|
||||
|
||||
/**
|
||||
* A MatrixRTCSession manages the membership & properties of a MatrixRTC session.
|
||||
@@ -182,7 +191,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
||||
private encryptionManager?: IEncryptionManager;
|
||||
// The session Id of the call, this is the call_id of the call Member event.
|
||||
private _callId: string | undefined;
|
||||
private joinConfig?: SessionConfig;
|
||||
private logger: Logger;
|
||||
|
||||
private pendingNotificationToSend: undefined | RTCNotificationType;
|
||||
/**
|
||||
* This timeout is responsible to track any expiration. We need to know when we have to start
|
||||
* to ignore other call members. There is no callback for this. This timeout will always be configured to
|
||||
@@ -447,6 +459,9 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
||||
}
|
||||
}
|
||||
|
||||
this.joinConfig = joinConfig;
|
||||
this.pendingNotificationToSend = this.joinConfig?.notificationType;
|
||||
|
||||
// Join!
|
||||
this.membershipManager!.join(fociPreferred, fociActive, (e) => {
|
||||
this.logger.error("MembershipManager encountered an unrecoverable error: ", e);
|
||||
@@ -547,6 +562,35 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification corresponding to the configured notify type.
|
||||
*/
|
||||
private sendCallNotify(parentEventId: string, notificationType: RTCNotificationType): void {
|
||||
// Send legacy event:
|
||||
this.client
|
||||
.sendEvent(this.roomSubset.roomId, EventType.CallNotify, {
|
||||
"application": "m.call",
|
||||
"m.mentions": { user_ids: [], room: true },
|
||||
"notify_type": notificationType === "notification" ? "notify" : notificationType,
|
||||
"call_id": this.callId!,
|
||||
})
|
||||
.catch((e) => this.logger.error("Failed to send call notification", e));
|
||||
|
||||
// Send new event:
|
||||
this.client
|
||||
.sendEvent(this.roomSubset.roomId, EventType.RTCNotification, {
|
||||
"m.mentions": { user_ids: [], room: true },
|
||||
"notification_type": notificationType,
|
||||
"m.relates_to": {
|
||||
event_id: parentEventId,
|
||||
rel_type: RelationType.unstable_RTCNotificationParent,
|
||||
},
|
||||
"sender_ts": Date.now(),
|
||||
"lifetime": 30_000, // 30 seconds
|
||||
})
|
||||
.catch((e) => this.logger.error("Failed to send call notification", e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when the Matrix room members have changed.
|
||||
*/
|
||||
@@ -587,6 +631,20 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
||||
});
|
||||
|
||||
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
|
||||
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
|
||||
const ownMembership = this.membershipManager?.ownMembership;
|
||||
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
|
||||
// If we're the first member in the call, we're responsible for
|
||||
// sending the notification event
|
||||
if (ownMembership.eventId && this.joinConfig?.notificationType) {
|
||||
this.sendCallNotify(ownMembership.eventId, this.joinConfig.notificationType);
|
||||
} else {
|
||||
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
|
||||
}
|
||||
}
|
||||
// If anyone else joins the session it is no longer our responsibility to send the notification.
|
||||
// (If we were the joiner we already did sent the notification in the block above.)
|
||||
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
|
||||
}
|
||||
// This also needs to be done if `changed` = false
|
||||
// A member might have updated their fingerprint (created_ts)
|
||||
|
@@ -221,7 +221,13 @@ export class MembershipManager
|
||||
public async onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void> {
|
||||
const userId = this.client.getUserId();
|
||||
const deviceId = this.client.getDeviceId();
|
||||
if (userId && deviceId && this.isJoined() && !memberships.some((m) => isMyMembership(m, userId, deviceId))) {
|
||||
if (!userId || !deviceId) {
|
||||
this.logger.error("MembershipManager.onRTCSessionMemberUpdate called without user or device id");
|
||||
return Promise.resolve();
|
||||
}
|
||||
this._ownMembership = memberships.find((m) => isMyMembership(m, userId, deviceId));
|
||||
|
||||
if (this.isActivated() && !this._ownMembership) {
|
||||
// If one of these actions are scheduled or are getting inserted in the next iteration, we should already
|
||||
// take care of our missing membership.
|
||||
const sendingMembershipActions = [
|
||||
@@ -310,6 +316,11 @@ export class MembershipManager
|
||||
}, this.logger);
|
||||
}
|
||||
|
||||
private _ownMembership?: CallMembership;
|
||||
public get ownMembership(): CallMembership | undefined {
|
||||
return this._ownMembership;
|
||||
}
|
||||
|
||||
// scheduler
|
||||
private oldStatus?: Status;
|
||||
private scheduler: ActionScheduler;
|
||||
|
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import type { IMentions } from "../matrix.ts";
|
||||
import type { RelationEvent } from "../types.ts";
|
||||
import type { CallMembership } from "./CallMembership.ts";
|
||||
|
||||
export type ParticipantId = string;
|
||||
@@ -80,9 +81,13 @@ export interface EncryptionKeysToDeviceEventContent {
|
||||
// Why is this needed?
|
||||
sent_ts?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `RTCNotificationType` instead.
|
||||
*/
|
||||
export type CallNotifyType = "ring" | "notify";
|
||||
|
||||
/**
|
||||
* @deprecated Use `IRTCNotificationContent` instead.
|
||||
*/
|
||||
export interface ICallNotifyContent {
|
||||
"application": string;
|
||||
"m.mentions": IMentions;
|
||||
@@ -90,6 +95,15 @@ export interface ICallNotifyContent {
|
||||
"call_id": string;
|
||||
}
|
||||
|
||||
export type RTCNotificationType = "ring" | "notification";
|
||||
export interface IRTCNotificationContent extends RelationEvent {
|
||||
"m.mentions": IMentions;
|
||||
"decline_reason"?: string;
|
||||
"notification_type": RTCNotificationType;
|
||||
"sender_ts": number;
|
||||
"lifetime": number;
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
Disconnected = "Disconnected",
|
||||
Connecting = "Connecting",
|
||||
|
Reference in New Issue
Block a user