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-05-04 22:14:11 +05:30
|
|
|
/** @internal */
|
|
|
|
class MemberConnection {
|
|
|
|
public retryCount: number = 0;
|
|
|
|
public peerCall?: PeerCall;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
public localMedia: LocalMedia,
|
|
|
|
public localMuteSettings: MuteSettings,
|
|
|
|
public readonly logItem: ILogItem
|
|
|
|
) {}
|
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
export class Member {
|
2022-05-04 22:14:11 +05:30
|
|
|
private connection?: MemberConnection;
|
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-04-21 05:05:11 +05:30
|
|
|
private _deviceIndex: number,
|
|
|
|
private _eventTimestamp: number,
|
2022-03-25 19:13:02 +05:30
|
|
|
private readonly options: Options,
|
2022-03-30 18:48:46 +05:30
|
|
|
) {}
|
2022-03-10 19:23:31 +05:30
|
|
|
|
2022-05-06 20:36:56 +05:30
|
|
|
/**
|
|
|
|
* Gives access the log item for this item once joined to the group call.
|
|
|
|
* The signalling for this member will be log in this item.
|
|
|
|
* Can be used for call diagnostics while in the call.
|
|
|
|
**/
|
|
|
|
get logItem(): ILogItem | undefined {
|
|
|
|
return this.connection?.logItem;
|
|
|
|
}
|
|
|
|
|
2022-04-13 22:04:01 +05:30
|
|
|
get remoteMedia(): RemoteMedia | undefined {
|
2022-05-04 22:14:11 +05:30
|
|
|
return this.connection?.peerCall?.remoteMedia;
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
2022-04-26 17:50:44 +05:30
|
|
|
get remoteMuteSettings(): MuteSettings | undefined {
|
2022-05-04 22:14:11 +05:30
|
|
|
return this.connection?.peerCall?.remoteMuteSettings;
|
2022-04-26 17:50:44 +05:30
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
get isConnected(): boolean {
|
2022-05-04 22:14:11 +05:30
|
|
|
return this.connection?.peerCall?.state === CallState.Connected;
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
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 {
|
2022-05-04 22:14:11 +05:30
|
|
|
return this.connection?.peerCall?.dataChannel;
|
2022-04-11 19:23:34 +05:30
|
|
|
}
|
|
|
|
|
2022-04-21 05:05:11 +05:30
|
|
|
get deviceIndex(): number {
|
|
|
|
return this._deviceIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
get eventTimestamp(): number {
|
|
|
|
return this._eventTimestamp;
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:10:37 +05:30
|
|
|
/** @internal */
|
2022-05-06 20:29:26 +05:30
|
|
|
connect(localMedia: LocalMedia, localMuteSettings: MuteSettings, memberLogItem: ILogItem): ILogItem | undefined {
|
2022-05-04 22:14:11 +05:30
|
|
|
if (this.connection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const connection = new MemberConnection(localMedia, localMuteSettings, memberLogItem);
|
|
|
|
this.connection = connection;
|
2022-05-06 20:29:26 +05:30
|
|
|
let connectLogItem;
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.logItem.wrap("connect", async log => {
|
2022-05-06 20:29:26 +05:30
|
|
|
connectLogItem = log;
|
2022-05-04 22:14:11 +05:30
|
|
|
await this.callIfNeeded(log);
|
|
|
|
});
|
2022-05-06 20:29:26 +05:30
|
|
|
return connectLogItem;
|
2022-05-04 22:14:11 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
private callIfNeeded(log: ILogItem): Promise<void> {
|
|
|
|
return log.wrap("callIfNeeded", async log => {
|
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) {
|
2022-05-04 22:14:11 +05:30
|
|
|
const connection = this.connection!;
|
|
|
|
connection.peerCall = this._createPeerCall(makeId("c"));
|
|
|
|
await connection.peerCall.call(
|
|
|
|
connection.localMedia,
|
|
|
|
connection.localMuteSettings,
|
|
|
|
log
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.set("wait_for_invite", true);
|
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-05-06 20:29:26 +05:30
|
|
|
disconnect(hangup: boolean): ILogItem | undefined {
|
2022-05-04 22:14:11 +05:30
|
|
|
const {connection} = this;
|
|
|
|
if (!connection) {
|
|
|
|
return;
|
|
|
|
}
|
2022-05-06 20:29:26 +05:30
|
|
|
let disconnectLogItem;
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.logItem.wrap("disconnect", async log => {
|
2022-05-06 20:29:26 +05:30
|
|
|
disconnectLogItem = log;
|
2022-04-14 17:15:21 +05:30
|
|
|
if (hangup) {
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.peerCall?.hangup(CallErrorCode.UserHangup, log);
|
2022-04-14 17:15:21 +05:30
|
|
|
} else {
|
2022-05-04 22:14:11 +05:30
|
|
|
await connection.peerCall?.close(undefined, log);
|
2022-04-14 17:15:21 +05:30
|
|
|
}
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.peerCall?.dispose();
|
|
|
|
connection.localMedia?.dispose();
|
|
|
|
this.connection = undefined;
|
2022-03-29 15:30:54 +05:30
|
|
|
});
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.logItem.finish();
|
2022-05-06 20:29:26 +05:30
|
|
|
return disconnectLogItem;
|
2022-03-24 18:22:19 +05:30
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
/** @internal */
|
2022-05-05 23:32:23 +05:30
|
|
|
updateCallInfo(
|
|
|
|
callDeviceMembership: CallDeviceMembership,
|
|
|
|
deviceIndex: number,
|
|
|
|
eventTimestamp: number,
|
|
|
|
causeItem: ILogItem
|
|
|
|
) {
|
2022-05-04 22:14:11 +05:30
|
|
|
this.callDeviceMembership = callDeviceMembership;
|
2022-05-05 23:32:23 +05:30
|
|
|
this._deviceIndex = deviceIndex;
|
|
|
|
this._eventTimestamp = eventTimestamp;
|
|
|
|
|
2022-05-04 22:14:11 +05:30
|
|
|
if (this.connection) {
|
|
|
|
this.connection.logItem.refDetached(causeItem);
|
|
|
|
}
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
2022-05-04 22:14:11 +05:30
|
|
|
emitUpdateFromPeerCall = (peerCall: PeerCall, params: any, log: ILogItem): void => {
|
|
|
|
const connection = this.connection!;
|
2022-03-10 19:23:31 +05:30
|
|
|
if (peerCall.state === CallState.Ringing) {
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.logItem.wrap("ringing, answer peercall", answerLog => {
|
|
|
|
log.refDetached(answerLog);
|
|
|
|
return peerCall.answer(connection.localMedia, connection.localMuteSettings, answerLog);
|
|
|
|
});
|
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();
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.peerCall = undefined;
|
2022-04-12 17:32:13 +05:30
|
|
|
if (hangupReason && !errorCodesWithoutRetry.includes(hangupReason)) {
|
2022-05-04 22:14:11 +05:30
|
|
|
connection.retryCount += 1;
|
|
|
|
const {retryCount} = connection;
|
|
|
|
connection.logItem.wrap({l: "retry connection", retryCount}, async retryLog => {
|
|
|
|
log.refDetached(retryLog);
|
|
|
|
if (retryCount <= 3) {
|
|
|
|
await this.callIfNeeded(retryLog);
|
|
|
|
} else {
|
2022-05-06 20:29:26 +05:30
|
|
|
const disconnectLogItem = this.disconnect(false);
|
|
|
|
if (disconnectLogItem) {
|
|
|
|
retryLog.refDetached(disconnectLogItem);
|
|
|
|
}
|
2022-05-04 22:14:11 +05:30
|
|
|
}
|
|
|
|
});
|
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-05-04 22:14:11 +05:30
|
|
|
handleDeviceMessage(message: SignallingMessage<MGroupCallBase>, syncLog: ILogItem): void{
|
|
|
|
const {connection} = this;
|
|
|
|
if (connection) {
|
|
|
|
const destSessionId = message.content.dest_session_id;
|
|
|
|
if (destSessionId !== this.options.sessionId) {
|
|
|
|
const logItem = connection.logItem.log({l: "ignoring to_device event with wrong session_id", destSessionId, type: message.type});
|
|
|
|
syncLog.refDetached(logItem);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (message.type === EventType.Invite && !connection.peerCall) {
|
|
|
|
connection.peerCall = this._createPeerCall(message.content.call_id);
|
|
|
|
}
|
|
|
|
if (connection.peerCall) {
|
|
|
|
const item = connection.peerCall.handleIncomingSignallingMessage(message, this.deviceId, connection.logItem);
|
|
|
|
syncLog.refDetached(item);
|
|
|
|
} else {
|
|
|
|
// TODO: need to buffer events until invite comes?
|
|
|
|
}
|
2022-03-10 19:23:31 +05:30
|
|
|
} else {
|
2022-05-04 22:14:11 +05:30
|
|
|
syncLog.log({l: "member not connected", userId: this.userId, deviceId: this.deviceId});
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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> {
|
2022-05-04 22:14:11 +05:30
|
|
|
const {connection} = this;
|
|
|
|
if (connection) {
|
|
|
|
connection.localMedia = connection.localMedia.replaceClone(connection.localMedia, previousMedia);
|
|
|
|
await connection.peerCall?.setMedia(connection.localMedia, connection.logItem);
|
|
|
|
}
|
2022-04-15 02:49:44 +05:30
|
|
|
}
|
|
|
|
|
2022-05-04 22:14:11 +05:30
|
|
|
async setMuted(muteSettings: MuteSettings): Promise<void> {
|
|
|
|
const {connection} = this;
|
|
|
|
if (connection) {
|
|
|
|
connection.localMuteSettings = muteSettings;
|
|
|
|
await connection.peerCall?.setMuted(muteSettings, connection.logItem);
|
|
|
|
}
|
2022-04-22 19:18:14 +05:30
|
|
|
}
|
|
|
|
|
2022-03-10 19:23:31 +05:30
|
|
|
private _createPeerCall(callId: string): PeerCall {
|
|
|
|
return new PeerCall(callId, Object.assign({}, this.options, {
|
2022-05-04 22:14:11 +05:30
|
|
|
emitUpdate: this.emitUpdateFromPeerCall,
|
2022-03-10 19:23:31 +05:30
|
|
|
sendSignallingMessage: this.sendSignallingMessage
|
2022-05-04 22:14:11 +05:30
|
|
|
}), this.connection!.logItem);
|
2022-03-10 19:23:31 +05:30
|
|
|
}
|
|
|
|
}
|