2022-03-10 19:23:31 +05:30
|
|
|
/*
|
|
|
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {PeerCall, CallState} from "../PeerCall";
|
|
|
|
import {makeTxnId, makeId} from "../../common";
|
2022-04-12 17:32:13 +05:30
|
|
|
import {EventType, CallErrorCode} from "../callEventTypes";
|
2022-03-23 16:53:10 +05:30
|
|
|
import {formatToDeviceMessagesPayload} from "../../common";
|
2022-03-10 19:23:31 +05:30
|
|
|
|
2022-04-22 19:18:14 +05:30
|
|
|
import type {MuteSettings} from "../common";
|
2022-04-13 22:04:01 +05:30
|
|
|
import type {Options as PeerCallOptions, RemoteMedia} from "../PeerCall";
|
2022-03-10 19:23:31 +05:30
|
|
|
import type {LocalMedia} from "../LocalMedia";
|
|
|
|
import type {HomeServerApi} from "../../net/HomeServerApi";
|
2022-03-29 20:43:33 +05:30
|
|
|
import type {MCallBase, MGroupCallBase, SignallingMessage, CallDeviceMembership} from "../callEventTypes";
|
2022-03-10 19:23:31 +05:30
|
|
|
import type {GroupCall} from "./GroupCall";
|
|
|
|
import type {RoomMember} from "../../room/members/RoomMember";
|
|
|
|
import type {EncryptedMessage} from "../../e2ee/olm/Encryption";
|
|
|
|
import type {ILogItem} from "../../../logging/types";
|
|
|
|
|
|
|
|
export type Options = Omit<PeerCallOptions, "emitUpdate" | "sendSignallingMessage"> & {
|
|
|
|
confId: string,
|
|
|
|
ownUserId: string,
|
2022-03-29 20:43:33 +05:30
|
|
|
ownDeviceId: string,
|
2022-04-27 00:41:41 +05:30
|
|
|
// local session id of our client
|
2022-04-07 20:23:37 +05:30
|
|
|
sessionId: string,
|
2022-03-10 19:23:31 +05:30
|
|
|
hsApi: HomeServerApi,
|
2022-03-23 16:53:10 +05:30
|
|
|
encryptDeviceMessage: (userId: string, message: SignallingMessage<MGroupCallBase>, log: ILogItem) => Promise<EncryptedMessage>,
|
2022-03-10 19:23:31 +05:30
|
|
|
emitUpdate: (participant: Member, params?: any) => void,
|
|
|
|
}
|
|
|
|
|
2022-04-12 17:32:13 +05:30
|
|
|
const errorCodesWithoutRetry = [
|
|
|
|
CallErrorCode.UserHangup,
|
|
|
|
CallErrorCode.AnsweredElsewhere,
|
|
|
|
CallErrorCode.Replaced,
|
|
|
|
CallErrorCode.UserBusy,
|
|
|
|
CallErrorCode.Transfered,
|
|
|
|
CallErrorCode.NewSession
|
|
|
|
];
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
export class Member {
|
|
|
|
private peerCall?: PeerCall;
|
2022-03-11 19:10:37 +05:30
|
|
|
private localMedia?: LocalMedia;
|
2022-04-22 19:18:14 +05:30
|
|
|
private localMuteSettings?: MuteSettings;
|
2022-04-12 17:32:13 +05:30
|
|
|
private retryCount: number = 0;
|
2022-03-10 19:23:31 +05:30
|
|
|
|
|
|
|
constructor(
|
|
|
|
public readonly member: RoomMember,
|
2022-03-29 20:43:33 +05:30
|
|
|
private callDeviceMembership: CallDeviceMembership,
|
2022-03-25 19:13:02 +05:30
|
|
|
private readonly options: Options,
|
|
|
|
private readonly logItem: ILogItem,
|
2022-03-30 18:48:46 +05:30
|
|
|
) {}
|
2022-03-10 19:23:31 +05:30
|
|
|
|
2022-04-13 22:04:01 +05:30
|
|
|
get remoteMedia(): RemoteMedia | undefined {
|
|
|
|
return this.peerCall?.remoteMedia;
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
2022-04-26 17:50:44 +05:30
|
|
|
get remoteMuteSettings(): MuteSettings | undefined {
|
|
|
|
return this.peerCall?.remoteMuteSettings;
|
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
get isConnected(): boolean {
|
|
|
|
return this.peerCall?.state === CallState.Connected;
|
|
|
|
}
|
|
|
|
|
2022-03-29 20:43:33 +05:30
|
|
|
get userId(): string {
|
|
|
|
return this.member.userId;
|
|
|
|
}
|
|
|
|
|
|
|
|
get deviceId(): string {
|
|
|
|
return this.callDeviceMembership.device_id;
|
|
|
|
}
|
|
|
|
|
2022-04-27 00:41:41 +05:30
|
|
|
/** session id of the member */
|
|
|
|
get sessionId(): string {
|
|
|
|
return this.callDeviceMembership.session_id;
|
|
|
|
}
|
|
|
|
|
2022-04-11 19:23:34 +05:30
|
|
|
get dataChannel(): any | undefined {
|
|
|
|
return this.peerCall?.dataChannel;
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:10:37 +05:30
|
|
|
/** @internal */
|
2022-04-22 19:18:14 +05:30
|
|
|
connect(localMedia: LocalMedia, localMuteSettings: MuteSettings) {
|
2022-03-29 20:43:33 +05:30
|
|
|
this.logItem.wrap("connect", () => {
|
|
|
|
this.localMedia = localMedia;
|
2022-04-22 19:18:14 +05:30
|
|
|
this.localMuteSettings = localMuteSettings;
|
2022-03-29 20:43:33 +05:30
|
|
|
// otherwise wait for it to connect
|
|
|
|
let shouldInitiateCall;
|
2022-04-07 20:23:57 +05:30
|
|
|
// the lexicographically lower side initiates the call
|
2022-03-29 20:43:33 +05:30
|
|
|
if (this.member.userId === this.options.ownUserId) {
|
2022-04-07 20:23:57 +05:30
|
|
|
shouldInitiateCall = this.deviceId > this.options.ownDeviceId;
|
2022-03-29 20:43:33 +05:30
|
|
|
} else {
|
2022-04-07 20:23:57 +05:30
|
|
|
shouldInitiateCall = this.member.userId > this.options.ownUserId;
|
2022-03-29 20:43:33 +05:30
|
|
|
}
|
|
|
|
if (shouldInitiateCall) {
|
|
|
|
this.peerCall = this._createPeerCall(makeId("c"));
|
2022-04-22 19:18:14 +05:30
|
|
|
this.peerCall.call(localMedia, localMuteSettings);
|
2022-03-29 20:43:33 +05:30
|
|
|
}
|
|
|
|
});
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
2022-03-24 18:22:19 +05:30
|
|
|
/** @internal */
|
2022-04-27 16:04:01 +05:30
|
|
|
disconnect(hangup: boolean) {
|
|
|
|
this.logItem.wrap("disconnect", log => {
|
2022-04-14 17:15:21 +05:30
|
|
|
if (hangup) {
|
|
|
|
this.peerCall?.hangup(CallErrorCode.UserHangup);
|
|
|
|
} else {
|
|
|
|
this.peerCall?.close(undefined, log);
|
|
|
|
}
|
2022-03-29 15:30:54 +05:30
|
|
|
this.peerCall?.dispose();
|
|
|
|
this.peerCall = undefined;
|
2022-04-14 17:14:11 +05:30
|
|
|
this.localMedia?.dispose();
|
2022-03-29 15:30:54 +05:30
|
|
|
this.localMedia = undefined;
|
2022-04-27 21:03:12 +05:30
|
|
|
this.retryCount = 0;
|
2022-03-29 15:30:54 +05:30
|
|
|
});
|
2022-03-24 18:22:19 +05:30
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
/** @internal */
|
2022-04-27 00:41:41 +05:30
|
|
|
updateCallInfo(callDeviceMembership: CallDeviceMembership, log: ILogItem) {
|
|
|
|
log.wrap({l: "updateing device membership", deviceId: this.deviceId}, log => {
|
|
|
|
// session id is changing, disconnect so we start with a new slate for the new session
|
|
|
|
if (callDeviceMembership.session_id !== this.sessionId) {
|
|
|
|
log.wrap({
|
|
|
|
l: "member event changes session id",
|
|
|
|
oldSessionId: this.sessionId,
|
|
|
|
newSessionId: callDeviceMembership.session_id
|
|
|
|
}, log => {
|
2022-04-27 16:04:01 +05:30
|
|
|
// prevent localMedia from being stopped
|
|
|
|
// as connect won't be called again when reconnecting
|
|
|
|
// to the new session
|
|
|
|
const localMedia = this.localMedia;
|
|
|
|
this.localMedia = undefined;
|
|
|
|
this.disconnect(false);
|
2022-04-27 23:11:02 +05:30
|
|
|
// connect again, as the other side might be waiting for our invite
|
|
|
|
// after refreshing
|
|
|
|
this.connect(localMedia!, this.localMuteSettings!);
|
2022-04-27 00:41:41 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
this.callDeviceMembership = callDeviceMembership;
|
|
|
|
});
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
emitUpdate = (peerCall: PeerCall, params: any) => {
|
|
|
|
if (peerCall.state === CallState.Ringing) {
|
2022-04-22 19:18:14 +05:30
|
|
|
peerCall.answer(this.localMedia!, this.localMuteSettings!);
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
2022-04-12 17:32:13 +05:30
|
|
|
else if (peerCall.state === CallState.Ended) {
|
|
|
|
const hangupReason = peerCall.hangupReason;
|
|
|
|
peerCall.dispose();
|
|
|
|
this.peerCall = undefined;
|
|
|
|
if (hangupReason && !errorCodesWithoutRetry.includes(hangupReason)) {
|
|
|
|
this.retryCount += 1;
|
|
|
|
if (this.retryCount <= 3) {
|
2022-04-22 19:18:14 +05:30
|
|
|
this.connect(this.localMedia!, this.localMuteSettings!);
|
2022-04-12 17:32:13 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-10 19:23:31 +05:30
|
|
|
this.options.emitUpdate(this, params);
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:10:37 +05:30
|
|
|
/** @internal */
|
2022-03-25 19:13:02 +05:30
|
|
|
sendSignallingMessage = async (message: SignallingMessage<MCallBase>, log: ILogItem): Promise<void> => {
|
2022-03-10 19:23:31 +05:30
|
|
|
const groupMessage = message as SignallingMessage<MGroupCallBase>;
|
|
|
|
groupMessage.content.conf_id = this.options.confId;
|
2022-04-07 20:20:16 +05:30
|
|
|
groupMessage.content.device_id = this.options.ownDeviceId;
|
|
|
|
groupMessage.content.party_id = this.options.ownDeviceId;
|
|
|
|
groupMessage.content.sender_session_id = this.options.sessionId;
|
2022-04-27 00:41:41 +05:30
|
|
|
groupMessage.content.dest_session_id = this.sessionId;
|
2022-04-07 20:20:16 +05:30
|
|
|
// const encryptedMessages = await this.options.encryptDeviceMessage(this.member.userId, groupMessage, log);
|
|
|
|
// const payload = formatToDeviceMessagesPayload(encryptedMessages);
|
|
|
|
const payload = {
|
|
|
|
messages: {
|
|
|
|
[this.member.userId]: {
|
2022-04-11 19:24:41 +05:30
|
|
|
[this.deviceId]: groupMessage.content
|
2022-04-07 20:20:16 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-04-20 14:27:07 +05:30
|
|
|
// TODO: remove this for release
|
|
|
|
log.set("payload", groupMessage.content);
|
2022-03-10 19:23:31 +05:30
|
|
|
const request = this.options.hsApi.sendToDevice(
|
2022-04-07 20:20:16 +05:30
|
|
|
message.type,
|
|
|
|
//"m.room.encrypted",
|
2022-03-23 16:53:10 +05:30
|
|
|
payload,
|
|
|
|
makeTxnId(),
|
|
|
|
{log}
|
|
|
|
);
|
2022-03-10 19:23:31 +05:30
|
|
|
await request.response();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
2022-04-27 00:41:41 +05:30
|
|
|
handleDeviceMessage(message: SignallingMessage<MGroupCallBase>, syncLog: ILogItem): void {
|
2022-03-25 19:13:02 +05:30
|
|
|
syncLog.refDetached(this.logItem);
|
2022-04-11 17:09:18 +05:30
|
|
|
const destSessionId = message.content.dest_session_id;
|
|
|
|
if (destSessionId !== this.options.sessionId) {
|
|
|
|
this.logItem.log({l: "ignoring to_device event with wrong session_id", destSessionId, type: message.type});
|
|
|
|
return;
|
|
|
|
}
|
2022-03-10 19:23:31 +05:30
|
|
|
if (message.type === EventType.Invite && !this.peerCall) {
|
|
|
|
this.peerCall = this._createPeerCall(message.content.call_id);
|
|
|
|
}
|
|
|
|
if (this.peerCall) {
|
2022-04-27 00:41:41 +05:30
|
|
|
this.peerCall.handleIncomingSignallingMessage(message, this.deviceId);
|
2022-03-10 19:23:31 +05:30
|
|
|
} else {
|
|
|
|
// TODO: need to buffer events until invite comes?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 02:49:44 +05:30
|
|
|
/** @internal */
|
2022-04-21 13:41:24 +05:30
|
|
|
async setMedia(localMedia: LocalMedia, previousMedia: LocalMedia): Promise<void> {
|
|
|
|
this.localMedia = localMedia.replaceClone(this.localMedia, previousMedia);
|
|
|
|
await this.peerCall?.setMedia(this.localMedia);
|
2022-04-15 02:49:44 +05:30
|
|
|
}
|
|
|
|
|
2022-04-22 19:18:14 +05:30
|
|
|
setMuted(muteSettings: MuteSettings) {
|
|
|
|
this.localMuteSettings = muteSettings;
|
|
|
|
this.peerCall?.setMuted(muteSettings);
|
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
private _createPeerCall(callId: string): PeerCall {
|
|
|
|
return new PeerCall(callId, Object.assign({}, this.options, {
|
|
|
|
emitUpdate: this.emitUpdate,
|
|
|
|
sendSignallingMessage: this.sendSignallingMessage
|
2022-03-25 19:13:02 +05:30
|
|
|
}), this.logItem);
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
}
|