{"version":3,"file":"outbound.mjs","names":[],"sources":["../../src/call-session/outbound.ts"],"sourcesContent":["import type WebPhone from \"../index.js\";\nimport type InboundMessage from \"../sip-message/inbound.js\";\nimport RequestMessage from \"../sip-message/outbound/request.js\";\nimport {\n  branch,\n  extractAddress,\n  fakeDomain,\n  fakeEmail,\n  generateAuthorization,\n  uuid,\n  withoutTag,\n} from \"../utils.js\";\nimport CallSession from \"./index.js\";\n\nclass OutboundCallSession extends CallSession {\n  public constructor(webPhone: WebPhone, callee: string) {\n    super(webPhone);\n    this.callee = callee;\n    this.direction = \"outbound\";\n  }\n\n  private callee: string;\n  public get remoteNumber(): string {\n    return this.remotePeer ? super.remoteNumber : this.callee;\n  }\n\n  public async call(\n    callerId?: string,\n    options?: { headers?: Record<string, string> },\n  ) {\n    const offer = await this.rtcPeerConnection.createOffer({\n      iceRestart: true,\n    });\n    await this.rtcPeerConnection.setLocalDescription(offer);\n    await this.waitForIceGatheringComplete();\n\n    const inviteMessage = new RequestMessage(\n      `INVITE sip:${this.callee}@${this.webPhone.sipInfo.domain} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        Contact: `<sip:${fakeEmail};transport=wss>;expires=60`,\n        From: `<sip:${this.webPhone.sipInfo.username}@${this.webPhone.sipInfo.domain}>;tag=${uuid()}`,\n        To: `<sip:${this.callee}@${this.webPhone.sipInfo.domain}>`,\n        Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,\n        \"Content-Type\": \"application/sdp\",\n      },\n      this.rtcPeerConnection.localDescription!.sdp,\n    );\n    if (callerId) {\n      inviteMessage.headers[\"P-Asserted-Identity\"] =\n        `sip:${callerId}@${this.webPhone.sipInfo.domain}`;\n    }\n\n    if (options?.headers) {\n      for (const [key, value] of Object.entries(options.headers)) {\n        inviteMessage.headers[key] = value;\n      }\n    }\n\n    const inboundMessage = await this.webPhone.sipClient.request(inviteMessage);\n    if (inboundMessage.subject.startsWith(\"SIP/2.0 403 \")) {\n      // for exmaple, webPhone.sipRegister(0) has been called\n      return;\n    }\n    const proxyAuthenticate = inboundMessage.headers[\"Proxy-Authenticate\"];\n    const nonce = proxyAuthenticate.match(/, nonce=\"(.+?)\"/)![1];\n    const newMessage = inviteMessage.fork();\n    newMessage.headers[\"Proxy-Authorization\"] = generateAuthorization(\n      this.webPhone.sipInfo,\n      nonce,\n      \"INVITE\",\n    );\n    const progressMessage = await this.webPhone.sipClient.request(newMessage);\n    this.sipMessage = progressMessage;\n    this.state = \"ringing\";\n    this.emit(\"ringing\");\n    this.localPeer = progressMessage.headers.From;\n    this.remotePeer = progressMessage.headers.To;\n\n    // wait for the call to be answered\n    // by SIP server design, this happens immediately, even if the callee has not received the INVITE\n    return new Promise<boolean>((resolve) => {\n      const answerHandler = async (message: InboundMessage) => {\n        if (message.headers.CSeq === this.sipMessage.headers.CSeq) {\n          this.webPhone.sipClient.off(\"inboundMessage\", answerHandler);\n\n          // outbound call failed, for example, invalid number\n          // or emergency address is not configured properly\n          if (message.subject !== \"SIP/2.0 200 OK\") {\n            this.state = \"failed\";\n            this.emit(\"failed\", message.subject);\n            const index = this.webPhone.callSessions.findIndex(\n              (callSession) =>\n                callSession.callId === message.headers[\"Call-Id\"],\n            );\n            if (index !== -1) {\n              this.webPhone.callSessions.splice(index, 1);\n            }\n            this.dispose();\n            resolve(false);\n            return;\n          }\n\n          this.state = \"answered\";\n          this.emit(\"answered\");\n          this.rtcPeerConnection.setRemoteDescription({\n            type: \"answer\",\n            sdp: message.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: this.sipMessage.headers.Via,\n              CSeq: this.sipMessage.headers.CSeq.replace(\" INVITE\", \" ACK\"),\n            },\n          );\n          await this.webPhone.sipClient.reply(ackMessage);\n          resolve(true);\n        }\n      };\n      this.webPhone.sipClient.on(\"inboundMessage\", answerHandler);\n    });\n  }\n\n  public async cancel() {\n    const requestMessage = new RequestMessage(\n      `CANCEL ${extractAddress(this.remotePeer)} SIP/2.0`,\n      {\n        \"Call-Id\": this.callId,\n        From: this.localPeer,\n        To: withoutTag(this.remotePeer),\n        Via: this.sipMessage.headers.Via,\n        CSeq: this.sipMessage.headers.CSeq.replace(\" INVITE\", \" CANCEL\"),\n      },\n    );\n    await this.webPhone.sipClient.request(requestMessage);\n  }\n}\n\nexport default OutboundCallSession;\n"],"mappings":"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"}