diff --git a/src/client.ts b/src/client.ts index fd162128d..917b4215c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -758,6 +758,7 @@ export class MatrixClient extends EventEmitter { protected exportedOlmDeviceToImport: IOlmDevice; protected txnCtr = 0; protected mediaHandler = new MediaHandler(this); + protected sessionId: string; constructor(opts: IMatrixClientCreateOpts) { super(); @@ -771,6 +772,7 @@ export class MatrixClient extends EventEmitter { this.usingExternalCrypto = opts.usingExternalCrypto; this.store = opts.store || new StubStore(); this.deviceId = opts.deviceId || null; + this.sessionId = randomString(10); const userId = opts.userId || null; this.credentials = { userId }; @@ -1259,6 +1261,14 @@ export class MatrixClient extends EventEmitter { return this.deviceId; } + /** + * Get the session ID of this client + * @return {string} session ID + */ + public getSessionId(): string { + return this.sessionId; + } + /** * Check if the runtime environment supports VoIP calling. * @return {boolean} True if VoIP is supported. diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 97c4a3ddb..bbce1343b 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -319,6 +319,7 @@ export class MatrixCall extends EventEmitter { private callLength = 0; private opponentDeviceId: string; + private opponentSessionId: string; public groupCallId: string; constructor(opts: CallOpts) { @@ -374,6 +375,10 @@ export class MatrixCall extends EventEmitter { return this.opponentMember; } + public getOpponentSessionId(): string { + return this.opponentSessionId; + } + public opponentCanBeTransferred(): boolean { return Boolean(this.opponentCaps && this.opponentCaps["m.call.transferee"]); } @@ -2002,7 +2007,7 @@ export class MatrixCall extends EventEmitter { eventType, userId: this.invitee || this.getOpponentMember().userId, opponentDeviceId: this.opponentDeviceId, - content: { ...realContent, device_id: this.client.deviceId }, + content: { ...realContent, device_id: this.client.deviceId, session_id: this.client.getSessionId() }, }); return this.client.sendToDevice(eventType, { @@ -2010,6 +2015,7 @@ export class MatrixCall extends EventEmitter { [this.opponentDeviceId]: { ...realContent, device_id: this.client.deviceId, + session_id: this.client.getSessionId(), }, }, }); @@ -2339,6 +2345,7 @@ export class MatrixCall extends EventEmitter { } this.opponentCaps = msg.capabilities || {} as CallCapabilities; this.opponentMember = this.client.getRoom(this.roomId).getMember(ev.getSender()); + this.opponentSessionId = msg.session_id; } private async addBufferedIceCandidates(): Promise { diff --git a/src/webrtc/callEventTypes.ts b/src/webrtc/callEventTypes.ts index c5b0063f9..28eeb6d94 100644 --- a/src/webrtc/callEventTypes.ts +++ b/src/webrtc/callEventTypes.ts @@ -36,6 +36,7 @@ export interface MCallBase { call_id: string; version: string | number; party_id?: string; + session_id?: string; } export interface MCallAnswer extends MCallBase { @@ -54,6 +55,7 @@ export interface MCallInviteNegotiate extends MCallBase { lifetime: number; capabilities?: CallCapabilities; invitee?: string; + session_id?: string; [SDPStreamMetadataKey]: SDPStreamMetadata; } diff --git a/src/webrtc/groupCall.ts b/src/webrtc/groupCall.ts index 19c7ae77b..cdd1d6bc9 100644 --- a/src/webrtc/groupCall.ts +++ b/src/webrtc/groupCall.ts @@ -73,6 +73,7 @@ export interface IGroupCallRoomMemberFeed { export interface IGroupCallRoomMemberDevice { "device_id": string; + "session_id": string; "feeds": IGroupCallRoomMemberFeed[]; } @@ -532,6 +533,7 @@ export class GroupCall extends EventEmitter { "m.devices": [ { "device_id": deviceId, + "session_id": this.client.getSessionId(), "feeds": this.getLocalFeeds().map((feed) => ({ purpose: feed.purpose, })), @@ -632,12 +634,6 @@ export class GroupCall extends EventEmitter { return; } - const existingCall = this.getCallByUserId(member.userId); - - if (existingCall) { - return; - } - const opponentDevice = this.getDeviceForMember(member.userId); if (!opponentDevice) { @@ -652,6 +648,12 @@ export class GroupCall extends EventEmitter { return; } + const existingCall = this.getCallByUserId(member.userId); + + if (existingCall && existingCall.getOpponentSessionId() === opponentDevice.session_id) { + return; + } + const newCall = createNewMatrixCall( this.client, this.room.roomId, @@ -668,7 +670,11 @@ export class GroupCall extends EventEmitter { newCall.createDataChannel("datachannel", this.dataChannelOptions); } - this.addCall(newCall); + if (existingCall) { + this.replaceCall(existingCall, newCall, true); + } else { + this.addCall(newCall); + } }; public getDeviceForMember(userId: string): IGroupCallRoomMemberDevice { @@ -728,7 +734,7 @@ export class GroupCall extends EventEmitter { this.emit(GroupCallEvent.CallsChanged, this.calls); } - private replaceCall(existingCall: MatrixCall, replacementCall: MatrixCall) { + private replaceCall(existingCall: MatrixCall, replacementCall: MatrixCall, forceHangup = false) { const existingCallIndex = this.calls.indexOf(existingCall); if (existingCallIndex === -1) { @@ -737,7 +743,7 @@ export class GroupCall extends EventEmitter { this.calls.splice(existingCallIndex, 1, replacementCall); - this.disposeCall(existingCall, CallErrorCode.Replaced); + this.disposeCall(existingCall, CallErrorCode.Replaced, forceHangup); this.initCall(replacementCall); this.emit(GroupCallEvent.CallsChanged, this.calls); @@ -787,7 +793,7 @@ export class GroupCall extends EventEmitter { onCallFeedsChanged(); } - private disposeCall(call: MatrixCall, hangupReason: CallErrorCode) { + private disposeCall(call: MatrixCall, hangupReason: CallErrorCode, forceHangup = false) { const opponentMemberId = getCallUserId(call); if (!opponentMemberId) { @@ -808,7 +814,7 @@ export class GroupCall extends EventEmitter { this.callHandlers.delete(opponentMemberId); - if (call.hangupReason === CallErrorCode.Replaced) { + if (call.hangupReason === CallErrorCode.Replaced && !forceHangup) { return; }