{"version":3,"file":"index.mjs","names":[],"sources":["../../src/call-session/index.ts"],"sourcesContent":["import sdpTransform from \"sdp-transform\";\n\nimport EventEmitter from \"../event-emitter.js\";\nimport type WebPhone from \"../index.js\";\nimport type InboundMessage from \"../sip-message/inbound.js\";\nimport RequestMessage from \"../sip-message/outbound/request.js\";\nimport ResponseMessage from \"../sip-message/outbound/response.js\";\nimport {\n  branch,\n  extractAddress,\n  extractNumber,\n  extractTag,\n  fakeDomain,\n  uuid,\n} from \"../utils.js\";\nimport type OutboundCallSession from \"./outbound.js\";\n\ninterface CommandResult {\n  code: number;\n  description: string;\n}\ntype ParkResult = CommandResult & {\n  \"park extension\": string;\n};\ntype FlipResult = CommandResult & {\n  number: string;\n  target: string;\n};\nconst DEFAULT_TRANSFER_TIMEOUT_MS = 10000;\n\nclass CallSession extends EventEmitter {\n  public webPhone: WebPhone;\n  public sipMessage!: InboundMessage;\n  public localPeer!: string;\n  public remotePeer!: string;\n  public rtcPeerConnection!: RTCPeerConnection;\n  public _mediaStream?: MediaStream;\n  public audioElement!: HTMLAudioElement;\n  public state: \"init\" | \"ringing\" | \"answered\" | \"disposed\" | \"failed\" =\n    \"init\";\n  public direction!: \"inbound\" | \"outbound\";\n  public inputDeviceId!: string;\n  public outputDeviceId: string | undefined;\n\n  private reqid = 1;\n  private sdpVersion = 1;\n\n  public constructor(webPhone: WebPhone) {\n    super();\n    this.webPhone = webPhone;\n  }\n\n  public get mediaStream(): MediaStream | undefined {\n    return this._mediaStream;\n  }\n  public set mediaStream(stream: MediaStream) {\n    this._mediaStream = stream;\n    this.emit(\"mediaStreamSet\", stream);\n  }\n\n  // for inbound call, this.sipMessage?.headers[\"Call-Id\"] will be the call id\n  // for outbound call, this._callId will be the call id. Once the call session is out of \"init\" state, this.sipMessage will be set\n  private _callId = uuid();\n  public get callId() {\n    return this.sipMessage?.headers[\"Call-Id\"] ?? this._callId;\n  }\n\n  public get sessionId() {\n    return this.sipMessage?.headers[\"p-rc-api-ids\"].match(\n      /session-id=(s-[0-9a-fz]+?)$/,\n    )?.[1];\n  }\n\n  public get partyId() {\n    return this.sipMessage?.headers[\"p-rc-api-ids\"].match(\n      /party-id=(p-[0-9a-fz]+?-\\d);/,\n    )?.[1];\n  }\n\n  public get remoteNumber(): string {\n    return extractNumber(this.remotePeer);\n  }\n\n  public get localNumber(): string {\n    return this.localPeer\n      ? extractNumber(this.localPeer)\n      : this.webPhone.sipInfo.username;\n  }\n\n  public get remoteTag() {\n    return extractTag(this.remotePeer);\n  }\n\n  public get localTag() {\n    return extractTag(this.localPeer);\n  }\n\n  public get isConference() {\n    return this.remotePeer\n      ? extractNumber(this.remotePeer).startsWith(\"conf_\")\n      : false;\n  }\n\n  public async init() {\n    this.rtcPeerConnection = new RTCPeerConnection({\n      iceServers:\n        this.webPhone.sipInfo.stunServers?.map((url) => ({\n          urls: `stun:${url}`,\n        })) ?? [],\n    });\n\n    // line below is to make sure that you have the permission to access the microphone\n    const tempStream = await navigator.mediaDevices.getUserMedia({\n      audio: true,\n      video: false,\n    });\n    tempStream.getTracks().forEach((track) => track.stop()); // 🔥 Stop immediately!\n\n    this.inputDeviceId = await this.webPhone.deviceManager.getInputDeviceId();\n    this.mediaStream = await navigator.mediaDevices.getUserMedia({\n      video: false,\n      audio: { deviceId: { exact: this.inputDeviceId } },\n    });\n    this.mediaStream.getAudioTracks().forEach((track) => {\n      const rtcRtpSender = this.rtcPeerConnection.addTrack(track);\n\n      // ref: https://github.com/ringcentral/ringcentral-web-phone/issues/257\n      const params = rtcRtpSender.getParameters();\n      if (!params.encodings || params.encodings.length === 0) {\n        params.encodings = [{}];\n      }\n      params.encodings.forEach((encoding) => {\n        encoding.priority = \"high\";\n      });\n      rtcRtpSender.setParameters(params);\n    });\n    this.rtcPeerConnection.ontrack = async (event) => {\n      const remoteStream = event.streams[0];\n      this.audioElement = document.createElement(\"audio\") as HTMLAudioElement;\n      this.audioElement.hidden = true;\n      this.audioElement.autoplay = true;\n      this.audioElement.srcObject = remoteStream;\n\n      // this code should be run last\n      this.outputDeviceId =\n        await this.webPhone.deviceManager.getOutputDeviceId();\n      if (this.outputDeviceId) {\n        this.audioElement.setSinkId(this.outputDeviceId);\n      }\n    };\n  }\n\n  public async changeInputDevice(deviceId: string) {\n    this.inputDeviceId = deviceId;\n    this.mediaStream?.getAudioTracks().forEach((track) => track.stop());\n    this.mediaStream = await navigator.mediaDevices.getUserMedia({\n      video: false,\n      audio: { deviceId: { exact: deviceId } },\n    });\n    const newAudioTrack = this.mediaStream.getAudioTracks()[0];\n    const sender = this.rtcPeerConnection\n      .getSenders()\n      .find((sender) => sender.track?.kind === \"audio\");\n    if (sender) {\n      sender.replaceTrack(newAudioTrack);\n    }\n  }\n\n  public async changeOutputDevice(deviceId: string) {\n    this.outputDeviceId = deviceId;\n    if (deviceId) {\n      await this.audioElement.setSinkId(deviceId);\n    }\n  }\n\n  public async transfer(target: string, timeout = DEFAULT_TRANSFER_TIMEOUT_MS) {\n    return await this._transfer(`sip:${target}@sip.ringcentral.com`, timeout);\n  }\n\n  public async warmTransfer(\n    target: string,\n    options?: { callerId?: string; timeout?: number },\n  ): Promise<{\n    complete: () => Promise<void>;\n    cancel: () => Promise<void>;\n    newSession: OutboundCallSession;\n  }> {\n    await this.hold();\n    // create a new session and user needs to talk to the target before transfer\n    const newSession = await this.webPhone.call(target, options?.callerId);\n    return {\n      // complete the transfer\n      complete: async () => {\n        await this.completeWarmTransfer(\n          newSession,\n          options?.timeout ?? DEFAULT_TRANSFER_TIMEOUT_MS,\n        );\n      },\n      // cancel the transfer\n      cancel: async () => {\n        await newSession.hangup();\n        await this.unhold();\n      },\n      newSession,\n    };\n  }\n\n  public async completeWarmTransfer(\n    existingSession: CallSession,\n    timeout = DEFAULT_TRANSFER_TIMEOUT_MS,\n  ) {\n    const target = existingSession.remoteNumber;\n    await this._transfer(\n      `\"${target}@sip.ringcentral.com\" <sip:${target}@sip.ringcentral.com;transport=wss?Replaces=${existingSession.callId}%3Bto-tag%3D${existingSession.remoteTag}%3Bfrom-tag%3D${existingSession.localTag}>`,\n      timeout,\n    );\n  }\n\n  public async hangup() {\n    const requestMessage = new RequestMessage(\n      `BYE sip:${this.webPhone.sipInfo.domain} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n      },\n    );\n    await this.webPhone.sipClient.request(requestMessage);\n  }\n\n  public async startRecording(): Promise<CommandResult> {\n    return await this.sendJsonMessage(\"startcallrecord\");\n  }\n\n  public async stopRecording(): Promise<CommandResult> {\n    return await this.sendJsonMessage(\"stopcallrecord\");\n  }\n\n  public async flip(target: string): Promise<FlipResult> {\n    const flipResult = await this.sendJsonMessage<FlipResult>(\"callflip\", {\n      target,\n    });\n    // note: we can't dispose the call session here\n    // otherwise the caller will not be able to talk to the flip target\n    // after the flip target answers the call, manually dispose the call session\n    // todo: review this part\n    return flipResult;\n  }\n\n  public async park(): Promise<ParkResult> {\n    const parkResult = await this.sendJsonMessage<ParkResult>(\"callpark\");\n    if (parkResult.code === 0) {\n      await this.hangup();\n    }\n    return parkResult;\n  }\n\n  public async hold() {\n    await this.toggleReceive(false);\n  }\n  public async unhold() {\n    await this.toggleReceive(true);\n  }\n\n  public mute() {\n    this.toggleTrack(false);\n  }\n  public unmute() {\n    this.toggleTrack(true);\n  }\n\n  public sendDtmf(tones: string, duration?: number, interToneGap?: number) {\n    for (const sender of this.rtcPeerConnection.getSenders()) {\n      if (sender.dtmf?.canInsertDTMF) {\n        sender.dtmf?.insertDTMF(tones, duration, interToneGap);\n      }\n    }\n  }\n\n  public dispose() {\n    this.rtcPeerConnection?.close();\n    this.mediaStream?.getTracks().forEach((track) => track.stop());\n    if (this.audioElement) {\n      this.audioElement.srcObject = null;\n    }\n    this.state = \"disposed\";\n    this.emit(\"disposed\");\n    this.removeAllListeners();\n  }\n\n  // for mute/unmute\n  protected toggleTrack(enabled: boolean) {\n    this.rtcPeerConnection.getSenders().forEach((sender) => {\n      if (sender.track) {\n        sender.track.enabled = enabled;\n      }\n    });\n  }\n\n  protected async waitForIceGatheringComplete(timeoutMs = 2000) {\n    if (this.rtcPeerConnection.iceGatheringState === \"complete\") {\n      return;\n    }\n    await new Promise<void>((resolve) => {\n      const timeout = setTimeout(() => {\n        cleanup();\n        resolve();\n      }, timeoutMs);\n      const onIceCandidate = (event: RTCPeerConnectionIceEvent) => {\n        if (event.candidate === null) {\n          cleanup();\n          resolve();\n        }\n      };\n      const cleanup = () => {\n        clearTimeout(timeout);\n        this.rtcPeerConnection.removeEventListener(\n          \"icecandidate\",\n          onIceCandidate,\n        );\n      };\n      this.rtcPeerConnection.addEventListener(\"icecandidate\", onIceCandidate);\n    });\n  }\n\n  // send re-INVITE.\n  // If the call is on hold and you don't want to unhold it, set toReceive to false\n  public async reInvite(toReceive: boolean = true) {\n    const offer = await this.rtcPeerConnection.createOffer({\n      iceRestart: true,\n    });\n    await this.rtcPeerConnection.setLocalDescription(offer);\n    await this.waitForIceGatheringComplete();\n    let sdp = this.rtcPeerConnection.localDescription!.sdp;\n    // default value is `a=sendrecv`\n    if (!toReceive) {\n      sdp = sdp.replace(/a=sendrecv/g, \"a=sendonly\");\n    }\n    const requestMessage = new RequestMessage(\n      `INVITE ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n        \"Content-Type\": \"application/sdp\",\n      },\n      sdp,\n    );\n    const replyMessage = await this.webPhone.sipClient.request(requestMessage);\n    await this.rtcPeerConnection.setRemoteDescription({\n      type: \"answer\",\n      sdp: replyMessage.body,\n    });\n    const ackMessage = new RequestMessage(\n      `ACK ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: replyMessage.headers.Via,\n        CSeq: replyMessage.headers.CSeq.replace(\" INVITE\", \" ACK\"),\n      },\n    );\n    await this.webPhone.sipClient.reply(ackMessage);\n  }\n\n  // handle re-INVITE from SIP server\n  public async handleReInvite(reInviteMessage: InboundMessage) {\n    this.sipMessage = reInviteMessage;\n    await this.rtcPeerConnection.setRemoteDescription({\n      type: \"offer\",\n      sdp: reInviteMessage.body,\n    });\n    const answer = await this.rtcPeerConnection.createAnswer();\n    await this.rtcPeerConnection.setLocalDescription(answer);\n    await this.waitForIceGatheringComplete();\n\n    const newMessage = new ResponseMessage(this.sipMessage, {\n      responseCode: 200,\n      headers: {\n        \"Content-Type\": \"application/sdp\",\n      },\n      body: this.rtcPeerConnection.localDescription!.sdp,\n    });\n    await this.webPhone.sipClient.reply(newMessage);\n\n    // note: no need to wait for the final SIP message (refer to inbound call answer function)\n    // because nobody is supposed to proactively invoke this function.\n  }\n\n  // for hold/unhold\n  // toggle between a=sendrecv and a=sendonly\n  protected async toggleReceive(toReceive: boolean) {\n    if (!this.rtcPeerConnection?.localDescription) {\n      return;\n    }\n    let sdp = this.rtcPeerConnection.localDescription!.sdp;\n    // default value is `a=sendrecv`\n    if (!toReceive) {\n      sdp = sdp.replace(/a=sendrecv/g, \"a=sendonly\");\n    }\n    // increase the sdp version\n    const res = sdpTransform.parse(sdp);\n    this.sdpVersion = Math.max(this.sdpVersion, res.origin!.sessionVersion + 1);\n    res.origin!.sessionVersion = this.sdpVersion++;\n    sdp = sdpTransform.write(res);\n    const requestMessage = new RequestMessage(\n      `INVITE ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n        \"Content-Type\": \"application/sdp\",\n      },\n      sdp,\n    );\n    const replyMessage = await this.webPhone.sipClient.request(requestMessage);\n    const ackMessage = new RequestMessage(\n      `ACK ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: replyMessage.headers.Via,\n        CSeq: replyMessage.headers.CSeq.replace(\" INVITE\", \" ACK\"),\n      },\n    );\n    await this.webPhone.sipClient.reply(ackMessage);\n  }\n\n  protected async sendJsonMessage<T>(\n    command: \"callpark\" | \"callflip\" | \"startcallrecord\" | \"stopcallrecord\",\n    args: { [key: string]: string } = {},\n  ) {\n    const reqid = this.reqid++;\n    const jsonBody = JSON.stringify({ request: { reqid, command, ...args } });\n    const requestMessage = new RequestMessage(\n      `INFO sip:${this.webPhone.sipInfo.domain} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n        \"Content-Type\": \"application/json;charset=utf-8\",\n      },\n      jsonBody,\n    );\n    await this.webPhone.sipClient.request(requestMessage);\n    return new Promise<T>((resolve) => {\n      const resultHandler = (inboundMessage: InboundMessage) => {\n        if (!inboundMessage.subject.startsWith(\"INFO sip:\")) {\n          return;\n        }\n        const response = JSON.parse(inboundMessage.body).response;\n        if (\n          !response ||\n          response.reqid !== reqid ||\n          response.command !== command\n        ) {\n          return;\n        }\n        this.webPhone.sipClient.off(\"inboundMessage\", resultHandler);\n        resolve(response.result);\n      };\n      this.webPhone.sipClient.on(\"inboundMessage\", resultHandler);\n    });\n  }\n\n  protected async _transfer(\n    uri: string,\n    timeout = DEFAULT_TRANSFER_TIMEOUT_MS,\n  ) {\n    const requestMessage = new RequestMessage(\n      `REFER ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: this.remotePeer,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n        \"Refer-To\": uri,\n        \"Referred-By\": `<${extractAddress(this.localPeer)}>`,\n      },\n    );\n    await this.webPhone.sipClient.request(requestMessage);\n\n    // wait for the final SIP message\n    let timeoutId: ReturnType<typeof setTimeout>;\n    return new Promise<void>((resolve, reject) => {\n      const handler = (inboundMessage: InboundMessage) => {\n        if (\n          inboundMessage.subject.startsWith(\"BYE sip:\") &&\n          inboundMessage.headers[\"Call-Id\"] === this.callId\n        ) {\n          clearTimeout(timeoutId);\n          this.webPhone.sipClient.off(\"inboundMessage\", handler);\n          resolve();\n        }\n      };\n      timeoutId = setTimeout(() => {\n        this.webPhone.sipClient.off(\"inboundMessage\", handler);\n        reject(\n          new Error(\n            `\"REFER ${extractAddress(\n              this.remotePeer,\n            )} SIP/2.0\" request timed out. It often means either you don't have permission or the call is not in a correct state.`,\n          ),\n        );\n      }, timeout);\n      this.webPhone.sipClient.on(\"inboundMessage\", handler);\n    });\n  }\n}\n\nexport default CallSession;\n"],"mappings":"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"}