async run()

in src/task/FinishGatheringICECandidatesTask.ts [67:175]


  async run(): Promise<void> {
    if (!this.context.peer) {
      this.logAndThrow(`session does not have peer connection; bypass ice gathering`);
    }
    if (this.context.browserBehavior.requiresCheckForSdpConnectionAttributes()) {
      if (new SDP(this.context.peer.localDescription.sdp).hasCandidatesForAllMLines()) {
        this.context.logger.info(
          `ice gathering already complete; bypass gathering, current local description ${this.context.peer.localDescription.sdp}`
        );
        return;
      }
    } else {
      this.context.logger.info(
        `iOS device does not require checking for connection attributes in SDP, current local description ${this.context.peer.localDescription.sdp}`
      );
    }

    /*
     * To bypass waiting for events, it is required that "icegatheringstate" to be complete and sdp to have candidate
     * For Firefox, it takes long for iceGatheringState === 'complete'
     * Ref: https://github.com/aws/amazon-chime-sdk-js/issues/609
     */
    if (
      (this.context.browserBehavior.hasFirefoxWebRTC() ||
        this.context.peer.iceGatheringState === 'complete') &&
      new SDP(this.context.peer.localDescription.sdp).hasCandidates()
    ) {
      this.context.logger.info(
        'ice gathering state is complete and candidates are in SDP; bypass gathering'
      );
      return;
    }
    try {
      await new Promise<void>((resolve, reject) => {
        this.cancelPromise = (error: Error) => {
          this.removeEventListener();
          reject(error);
        };

        if (!this.context.turnCredentials) {
          // if one day, we found a case where a FinishGatheringICECandidate did not resolve but ice gathering state is complete and SDP answer has ice candidates
          // we may need to enable this
          this.context.iceGatheringStateEventHandler = () => {
            if (this.context.peer.iceGatheringState === 'complete') {
              this.removeEventListener();
              resolve();
              delete this.cancelPromise;
              return;
            }
          };
          this.context.peer.addEventListener(
            'icegatheringstatechange',
            this.context.iceGatheringStateEventHandler
          );
        }

        this.context.iceCandidateHandler = (event: RTCPeerConnectionIceEvent) => {
          this.context.logger.info(
            `ice candidate: ${event.candidate ? event.candidate.candidate : '(null)'} state: ${
              this.context.peer.iceGatheringState
            }`
          );
          // Ice candidate arrives, do not need to wait anymore.
          // https://webrtcglossary.com/trickle-ice/
          if (event.candidate) {
            if (SDP.isRTPCandidate(event.candidate.candidate)) {
              this.context.iceCandidates.push(event.candidate);
            }

            // Could there be a case the candidate is not written to SDP ?
            if (this.context.turnCredentials && this.context.iceCandidates.length >= 1) {
              this.context.logger.info('gathered at least one relay candidate');
              this.removeEventListener();
              resolve();
              delete this.cancelPromise;
              return;
            }
          }

          // Ice candidate gathering is complete, additional barrier to make sure sdp contain an ice candidate.
          // TODO: Could there be a race where iceGatheringState is flipped after this task is run ? This could only be handled if ice state is monitored persistently.
          if (this.context.peer.iceGatheringState === 'complete') {
            this.context.logger.info('done gathering ice candidates');
            this.removeEventListener();
            if (
              !new SDP(this.context.peer.localDescription.sdp).hasCandidates() ||
              this.context.iceCandidates.length === 0
            ) {
              reject(new Error('no ice candidates were gathered'));
              delete this.cancelPromise;
            } else {
              resolve();
              delete this.cancelPromise;
            }
          }
        };
        // SDK does not catch candidate itself and send to sever. Rather, WebRTC handles candidate events and writes candidate to SDP.
        this.context.peer.addEventListener('icecandidate', this.context.iceCandidateHandler);
        this.startTimestampMs = Date.now();
      });
    } catch (error) {
      throw error;
    } finally {
      /* istanbul ignore else */
      if (this.startTimestampMs) {
        this.context.iceGatheringDurationMs = Math.round(Date.now() - this.startTimestampMs);
      }
    }
  }