text: import * as Config from "./config.js";
  import * as Logger from "./logger.js";
  
  export default class Peer extends EventTarget {
    constructor(connectionId, polite, resendIntervalMsec = 5000) {
      super();
      const _this = this;
      this.connectionId = connectionId;
      this.polite = polite;
      this.config = Config.getRTCConfiguration();
      this.pc = new RTCPeerConnection(this.config);
      this.makingOffer = false;
      this.waitingAnswer = false;
      this.ignoreOffer = false;
      this.srdAnswerPending = false;
      this.log = str => void Logger.log(`[${_this.polite ? 'POLITE' : 'IMPOLITE'}] ${str}`);
      this.assert_equals = window.assert_equals ? window.assert_equals : (a, b, msg) => { if (a === b) { return; } throw new Error(`${msg} expected ${b} but got ${a}`); };
      this.interval = resendIntervalMsec;
      this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
  
      this.pc.ontrack = e => {
        _this.log(`ontrack:${e}`);
        _this.dispatchEvent(new CustomEvent('trackevent', { detail: e }));
      };
      this.pc.onicecandidate = ({ candidate }) => {
        _this.log(`send candidate:${candidate}`);
        if (candidate == null) {
          return;
        }
        _this.dispatchEvent(new CustomEvent('sendcandidate', { detail: { connectionId: _this.connectionId, candidate: candidate.candidate, sdpMLineIndex: candidate.sdpMLineIndex, sdpMid: candidate.sdpMid } }));
      };
  
      this.pc.onnegotiationneeded = async () => {
        try {
          _this.log(`SLD due to negotiationneeded`);
          _this.assert_equals(_this.pc.signalingState, 'stable', 'negotiationneeded always fires in stable state');
          _this.assert_equals(_this.makingOffer, false, 'negotiationneeded not already in progress');
          _this.makingOffer = true;
          await _this.pc.setLocalDescription();
          _this.assert_equals(_this.pc.signalingState, 'have-local-offer', 'negotiationneeded not racing with onmessage');
          _this.assert_equals(_this.pc.localDescription.type, 'offer', 'negotiationneeded SLD worked');
          _this.waitingAnswer = true;
          _this.dispatchEvent(new CustomEvent('sendoffer', { detail: { connectionId: _this.connectionId, sdp: _this.pc.localDescription.sdp } }));
        } catch (e) {
          _this.log(e);
        } finally {
          _this.makingOffer = false;
        }
      };
  
      this.pc.onsignalingstatechange = e => {
        _this.log(`signalingState changed:${e}`);
      };
  
      this.pc.oniceconnectionstatechange = e => {
        _this.log(`iceConnectionState changed:${e}`);
        if (_this.pc.iceConnectionState === 'disconnected') {
          this.dispatchEvent(new Event('disconnect'));
        }
      };
  
      this.pc.onicegatheringstatechange = e => {
        _this.log(`iceGatheringState changed:${e}'`);
      };
  
      this.loopResendOffer();
    }
  
    async loopResendOffer() {
      while (this.connectionId) {
        if (this.pc != null && this.waitingAnswer) {
          this.dispatchEvent(new CustomEvent('sendoffer', { detail: { connectionId: this.connectionId, sdp: this.pc.localDescription.sdp } }));
        }
        await this.sleep(this.interval);
      }
    }
  
    close() {
      this.connectionId = null;
      if (this.pc != null) {
        this.pc.close();
        this.pc = null;
      }
    }
  
    getTransceivers(connectionId) {
      if (this.connectionId != connectionId) {
        return null;
      }
  
      return this.pc.getTransceivers();
    }
  
    addTrack(connectionId, track) {
      if (this.connectionId != connectionId) {
        return null;
      }
  
      const sender = this.pc.addTrack(track);
      const transceiver = this.pc.getTransceivers().find((t) => t.sender == sender);
      transceiver.direction = "sendonly";
      return sender;
    }
  
    addTransceiver(connectionId, trackOrKind, init) {
      if (this.connectionId != connectionId) {
        return null;
      }
  
      return this.pc.addTransceiver(trackOrKind, init);
    }
  
    createDataChannel(connectionId, label) {
      if (this.connectionId != connectionId) {
        return null;
      }
  
      return this.pc.createDataChannel(label);
    }
  
    async onGotDescription(connectionId, description) {
      if (this.connectionId != connectionId) {
        return;
      }
  
      const _this = this;
      const isStable =
        this.pc.signalingState == 'stable' ||
        (this.pc.signalingState == 'have-local-offer' && this.srdAnswerPending);
      this.ignoreOffer =
        description.type == 'offer' && !this.polite && (this.makingOffer || !isStable);
  
      if (this.ignoreOffer) {
        _this.log(`glare - ignoring offer`);
        return;
      }
  
      this.waitingAnswer = false;
      this.srdAnswerPending = description.type == 'answer';
      _this.log(`SRD(${description.type})`);
      await this.pc.setRemoteDescription(description);
      this.srdAnswerPending = false;
  
      if (description.type == 'offer') {
        _this.assert_equals(this.pc.signalingState, 'have-remote-offer', 'Remote offer');
        _this.assert_equals(this.pc.remoteDescription.type, 'offer', 'SRD worked');
        _this.log('SLD to get back to stable');
        await this.pc.setLocalDescription();
        _this.assert_equals(this.pc.signalingState, 'stable', 'onmessage not racing with negotiationneeded');
        _this.assert_equals(this.pc.localDescription.type, 'answer', 'onmessage SLD worked');
        _this.dispatchEvent(new CustomEvent('sendanswer', { detail: { connectionId: _this.connectionId, sdp: _this.pc.localDescription.sdp } }));
  
      } else {
        _this.assert_equals(this.pc.remoteDescription.type, 'answer', 'Answer was set');
        _this.assert_equals(this.pc.signalingState, 'stable', 'answered');
        this.pc.dispatchEvent(new Event('negotiated'));
      }
    }
  
    async onGotCandidate(connectionId, candidate) {
      if (this.connectionId != connectionId) {
        return;
      }
  
      try {
        await this.pc.addIceCandidate(candidate);
      } catch (e) {
        if (!this.ignoreOffer) this.log(e);
      }
    }
  }
  