async handleCallEvent()

in chat/protocols/matrix/lib/matrix-sdk/webrtc/callEventHandler.js [155:335]


  async handleCallEvent(event) {
    this.client.emit(_client.ClientEvent.ReceivedVoipEvent, event);
    const content = event.getContent();
    const callRoomId = event.getRoomId() || this.client.groupCallEventHandler.getGroupCallById(content.conf_id)?.room?.roomId;
    const groupCallId = content.conf_id;
    const type = event.getType();
    const senderId = event.getSender();
    let call = content.call_id ? this.calls.get(content.call_id) : undefined;
    let opponentDeviceId;
    let groupCall;
    if (groupCallId) {
      groupCall = this.client.groupCallEventHandler.getGroupCallById(groupCallId);
      if (!groupCall) {
        _logger.logger.warn(`CallEventHandler handleCallEvent() could not find a group call - ignoring event (groupCallId=${groupCallId}, type=${type})`);
        return;
      }
      opponentDeviceId = content.device_id;
      if (!opponentDeviceId) {
        _logger.logger.warn(`CallEventHandler handleCallEvent() could not find a device id - ignoring event (senderId=${senderId})`);
        groupCall.emit(_groupCall.GroupCallEvent.Error, new _groupCall.GroupCallUnknownDeviceError(senderId));
        return;
      }
      if (content.dest_session_id !== this.client.getSessionId()) {
        _logger.logger.warn("CallEventHandler handleCallEvent() call event does not match current session id - ignoring");
        return;
      }
    }
    const weSentTheEvent = senderId === this.client.credentials.userId && (opponentDeviceId === undefined || opponentDeviceId === this.client.getDeviceId());
    if (!callRoomId) return;
    if (type === _event.EventType.CallInvite) {
      // ignore invites you send
      if (weSentTheEvent) return;
      // expired call
      if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) return;
      // stale/old invite event
      if (call && call.state === _call.CallState.Ended) return;
      if (call) {
        _logger.logger.warn(`CallEventHandler handleCallEvent() already has a call but got an invite - clobbering (callId=${content.call_id})`);
      }
      if (content.invitee && content.invitee !== this.client.getUserId()) {
        return; // This invite was meant for another user in the room
      }
      const timeUntilTurnCresExpire = (this.client.getTurnServersExpiry() ?? 0) - Date.now();
      _logger.logger.info("CallEventHandler handleCallEvent() current turn creds expire in " + timeUntilTurnCresExpire + " ms");
      call = (0, _call.createNewMatrixCall)(this.client, callRoomId, {
        forceTURN: this.client.forceTURN,
        opponentDeviceId,
        groupCallId,
        opponentSessionId: content.sender_session_id
      }) ?? undefined;
      if (!call) {
        _logger.logger.log(`CallEventHandler handleCallEvent() this client does not support WebRTC (callId=${content.call_id})`);
        // don't hang up the call: there could be other clients
        // connected that do support WebRTC and declining the
        // the call on their behalf would be really annoying.
        return;
      }
      call.callId = content.call_id;
      const stats = groupCall?.getGroupCallStats();
      if (stats) {
        call.initStats(stats);
      }
      try {
        await call.initWithInvite(event);
      } catch (e) {
        if (e instanceof _call.CallError) {
          if (e.code === _groupCall.GroupCallErrorCode.UnknownDevice) {
            groupCall?.emit(_groupCall.GroupCallEvent.Error, e);
          } else {
            _logger.logger.error(e);
          }
        }
      }
      this.calls.set(call.callId, call);

      // if we stashed candidate events for that call ID, play them back now
      if (this.candidateEventsByCall.get(call.callId)) {
        for (const ev of this.candidateEventsByCall.get(call.callId)) {
          call.onRemoteIceCandidatesReceived(ev);
        }
      }

      // Were we trying to call that user (room)?
      let existingCall;
      for (const thisCall of this.calls.values()) {
        const isCalling = [_call.CallState.WaitLocalMedia, _call.CallState.CreateOffer, _call.CallState.InviteSent].includes(thisCall.state);
        if (call.roomId === thisCall.roomId && thisCall.direction === _call.CallDirection.Outbound && call.getOpponentMember()?.userId === thisCall.invitee && isCalling) {
          existingCall = thisCall;
          break;
        }
      }
      if (existingCall) {
        if (existingCall.callId > call.callId) {
          _logger.logger.log(`CallEventHandler handleCallEvent() detected glare - answering incoming call and canceling outgoing call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`);
          existingCall.replacedBy(call);
        } else {
          _logger.logger.log(`CallEventHandler handleCallEvent() detected glare - hanging up incoming call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`);
          call.hangup(_call.CallErrorCode.Replaced, true);
        }
      } else {
        this.client.emit(CallEventHandlerEvent.Incoming, call);
      }
      return;
    } else if (type === _event.EventType.CallCandidates) {
      if (weSentTheEvent) return;
      if (!call) {
        // store the candidates; we may get a call eventually.
        if (!this.candidateEventsByCall.has(content.call_id)) {
          this.candidateEventsByCall.set(content.call_id, []);
        }
        this.candidateEventsByCall.get(content.call_id).push(event);
      } else {
        call.onRemoteIceCandidatesReceived(event);
      }
      return;
    } else if ([_event.EventType.CallHangup, _event.EventType.CallReject].includes(type)) {
      // Note that we also observe our own hangups here so we can see
      // if we've already rejected a call that would otherwise be valid
      if (!call) {
        // if not live, store the fact that the call has ended because
        // we're probably getting events backwards so
        // the hangup will come before the invite
        call = (0, _call.createNewMatrixCall)(this.client, callRoomId, {
          opponentDeviceId,
          opponentSessionId: content.sender_session_id
        }) ?? undefined;
        if (call) {
          call.callId = content.call_id;
          call.initWithHangup(event);
          this.calls.set(content.call_id, call);
        }
      } else {
        if (call.state !== _call.CallState.Ended) {
          if (type === _event.EventType.CallHangup) {
            call.onHangupReceived(content);
          } else {
            call.onRejectReceived(content);
          }

          // @ts-expect-error typescript thinks the state can't be 'ended' because we're
          // inside the if block where it wasn't, but it could have changed because
          // on[Hangup|Reject]Received are side-effecty.
          if (call.state === _call.CallState.Ended) this.calls.delete(content.call_id);
        }
      }
      return;
    }

    // The following events need a call and a peer connection
    if (!call || !call.hasPeerConnection) {
      _logger.logger.info(`CallEventHandler handleCallEvent() discarding possible call event as we don't have a call (type=${type})`);
      return;
    }
    // Ignore remote echo
    if (event.getContent().party_id === call.ourPartyId) return;
    switch (type) {
      case _event.EventType.CallAnswer:
        if (weSentTheEvent) {
          if (call.state === _call.CallState.Ringing) {
            call.onAnsweredElsewhere(content);
          }
        } else {
          call.onAnswerReceived(event);
        }
        break;
      case _event.EventType.CallSelectAnswer:
        call.onSelectAnswerReceived(event);
        break;
      case _event.EventType.CallNegotiate:
        call.onNegotiateReceived(event);
        break;
      case _event.EventType.CallAssertedIdentity:
      case _event.EventType.CallAssertedIdentityPrefix:
        call.onAssertedIdentityReceived(event);
        break;
      case _event.EventType.CallSDPStreamMetadataChanged:
      case _event.EventType.CallSDPStreamMetadataChangedPrefix:
        call.onSDPStreamMetadataChangedReceived(event);
        break;
    }
  }