UNPKG

sip.js

Version:

A SIP library for JavaScript

234 lines (233 loc) 13 kB
import { Dialog } from "../dialogs/dialog.js"; import { SessionDialog } from "../dialogs/session-dialog.js"; import { TransactionStateError } from "../exceptions/transaction-state-error.js"; import { SignalingState } from "../session/session.js"; import { InviteServerTransaction } from "../transactions/invite-server-transaction.js"; import { AllowedMethods } from "../user-agent-core/allowed-methods.js"; import { UserAgentServer } from "./user-agent-server.js"; /** * INVITE UAS. * @remarks * 13 Initiating a Session * https://tools.ietf.org/html/rfc3261#section-13 * 13.1 Overview * https://tools.ietf.org/html/rfc3261#section-13.1 * 13.3 UAS Processing * https://tools.ietf.org/html/rfc3261#section-13.3 * @public */ export class InviteUserAgentServer extends UserAgentServer { constructor(core, message, delegate) { super(InviteServerTransaction, core, message, delegate); this.core = core; } dispose() { if (this.earlyDialog) { this.earlyDialog.dispose(); } super.dispose(); } /** * 13.3.1.4 The INVITE is Accepted * The UAS core generates a 2xx response. This response establishes a * dialog, and therefore follows the procedures of Section 12.1.1 in * addition to those of Section 8.2.6. * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 * @param options - Accept options bucket. */ accept(options = { statusCode: 200 }) { if (!this.acceptable) { throw new TransactionStateError(`${this.message.method} not acceptable in state ${this.transaction.state}.`); } // This response establishes a dialog... // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 if (!this.confirmedDialog) { if (this.earlyDialog) { this.earlyDialog.confirm(); this.confirmedDialog = this.earlyDialog; this.earlyDialog = undefined; } else { const transaction = this.transaction; if (!(transaction instanceof InviteServerTransaction)) { throw new Error("Transaction not instance of InviteClientTransaction."); } const state = Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag); this.confirmedDialog = new SessionDialog(transaction, this.core, state); } } // When a UAS responds to a request with a response that establishes a // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route // header field values from the request into the response (including the // URIs, URI parameters, and any Record-Route header field parameters, // whether they are known or unknown to the UAS) and MUST maintain the // order of those values. The UAS MUST add a Contact header field to // the response. The Contact header field contains an address where the // UAS would like to be contacted for subsequent requests in the dialog // (which includes the ACK for a 2xx response in the case of an INVITE). // Generally, the host portion of this URI is the IP address or FQDN of // the host. The URI provided in the Contact header field MUST be a SIP // or SIPS URI. If the request that initiated the dialog contained a // SIPS URI in the Request-URI or in the top Record-Route header field // value, if there was any, or the Contact header field if there was no // Record-Route header field, the Contact header field in the response // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the // same URI can be used in messages outside this dialog). The same way, // the scope of the URI in the Contact header field of the INVITE is not // limited to this dialog either. It can therefore be used in messages // to the UAC even outside this dialog. // https://tools.ietf.org/html/rfc3261#section-12.1.1 const recordRouteHeader = this.message.getHeaders("record-route").map((header) => `Record-Route: ${header}`); const contactHeader = `Contact: ${this.core.configuration.contact.toString()}`; // A 2xx response to an INVITE SHOULD contain the Allow header field and // the Supported header field, and MAY contain the Accept header field. // Including these header fields allows the UAC to determine the // features and extensions supported by the UAS for the duration of the // call, without probing. // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 // FIXME: TODO: This should not be hard coded. const allowHeader = "Allow: " + AllowedMethods.toString(); // FIXME: TODO: Supported header (see reply()) // FIXME: TODO: Accept header // If the INVITE request contained an offer, and the UAS had not yet // sent an answer, the 2xx MUST contain an answer. If the INVITE did // not contain an offer, the 2xx MUST contain an offer if the UAS had // not yet sent an offer. // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 if (!options.body) { if (this.confirmedDialog.signalingState === SignalingState.Stable) { options.body = this.confirmedDialog.answer; // resend the answer sent in provisional response } else if (this.confirmedDialog.signalingState === SignalingState.Initial || this.confirmedDialog.signalingState === SignalingState.HaveRemoteOffer) { throw new Error("Response must have a body."); } } options.statusCode = options.statusCode || 200; options.extraHeaders = options.extraHeaders || []; options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); options.extraHeaders.push(allowHeader); options.extraHeaders.push(contactHeader); const response = super.accept(options); const session = this.confirmedDialog; const result = Object.assign(Object.assign({}, response), { session }); // Update dialog signaling state if (options.body) { // Once the UAS has sent or received an answer to the initial // offer, it MUST NOT generate subsequent offers in any responses // to the initial INVITE. This means that a UAS based on this // specification alone can never generate subsequent offers until // completion of the initial transaction. // https://tools.ietf.org/html/rfc3261#section-13.2.1 if (this.confirmedDialog.signalingState !== SignalingState.Stable) { this.confirmedDialog.signalingStateTransition(options.body); } } return result; } /** * 13.3.1.1 Progress * If the UAS is not able to answer the invitation immediately, it can * choose to indicate some kind of progress to the UAC (for example, an * indication that a phone is ringing). This is accomplished with a * provisional response between 101 and 199. These provisional * responses establish early dialogs and therefore follow the procedures * of Section 12.1.1 in addition to those of Section 8.2.6. A UAS MAY * send as many provisional responses as it likes. Each of these MUST * indicate the same dialog ID. However, these will not be delivered * reliably. * * If the UAS desires an extended period of time to answer the INVITE, * it will need to ask for an "extension" in order to prevent proxies * from canceling the transaction. A proxy has the option of canceling * a transaction when there is a gap of 3 minutes between responses in a * transaction. To prevent cancellation, the UAS MUST send a non-100 * provisional response at every minute, to handle the possibility of * lost provisional responses. * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 * @param options - Progress options bucket. */ progress(options = { statusCode: 180 }) { if (!this.progressable) { throw new TransactionStateError(`${this.message.method} not progressable in state ${this.transaction.state}.`); } // This response establishes a dialog... // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 if (!this.earlyDialog) { const transaction = this.transaction; if (!(transaction instanceof InviteServerTransaction)) { throw new Error("Transaction not instance of InviteClientTransaction."); } const state = Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag, true); this.earlyDialog = new SessionDialog(transaction, this.core, state); } // When a UAS responds to a request with a response that establishes a // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route // header field values from the request into the response (including the // URIs, URI parameters, and any Record-Route header field parameters, // whether they are known or unknown to the UAS) and MUST maintain the // order of those values. The UAS MUST add a Contact header field to // the response. The Contact header field contains an address where the // UAS would like to be contacted for subsequent requests in the dialog // (which includes the ACK for a 2xx response in the case of an INVITE). // Generally, the host portion of this URI is the IP address or FQDN of // the host. The URI provided in the Contact header field MUST be a SIP // or SIPS URI. If the request that initiated the dialog contained a // SIPS URI in the Request-URI or in the top Record-Route header field // value, if there was any, or the Contact header field if there was no // Record-Route header field, the Contact header field in the response // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the // same URI can be used in messages outside this dialog). The same way, // the scope of the URI in the Contact header field of the INVITE is not // limited to this dialog either. It can therefore be used in messages // to the UAC even outside this dialog. // https://tools.ietf.org/html/rfc3261#section-12.1.1 const recordRouteHeader = this.message.getHeaders("record-route").map((header) => `Record-Route: ${header}`); const contactHeader = `Contact: ${this.core.configuration.contact}`; options.extraHeaders = options.extraHeaders || []; options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); options.extraHeaders.push(contactHeader); const response = super.progress(options); const session = this.earlyDialog; const result = Object.assign(Object.assign({}, response), { session }); // Update dialog signaling state if (options.body) { // Once the UAS has sent or received an answer to the initial // offer, it MUST NOT generate subsequent offers in any responses // to the initial INVITE. This means that a UAS based on this // specification alone can never generate subsequent offers until // completion of the initial transaction. // https://tools.ietf.org/html/rfc3261#section-13.2.1 if (this.earlyDialog.signalingState !== SignalingState.Stable) { this.earlyDialog.signalingStateTransition(options.body); } } return result; } /** * 13.3.1.2 The INVITE is Redirected * If the UAS decides to redirect the call, a 3xx response is sent. A * 300 (Multiple Choices), 301 (Moved Permanently) or 302 (Moved * Temporarily) response SHOULD contain a Contact header field * containing one or more URIs of new addresses to be tried. The * response is passed to the INVITE server transaction, which will deal * with its retransmissions. * https://tools.ietf.org/html/rfc3261#section-13.3.1.2 * @param contacts - Contacts to redirect to. * @param options - Redirect options bucket. */ redirect(contacts, options = { statusCode: 302 }) { return super.redirect(contacts, options); } /** * 13.3.1.3 The INVITE is Rejected * A common scenario occurs when the callee is currently not willing or * able to take additional calls at this end system. A 486 (Busy Here) * SHOULD be returned in such a scenario. * https://tools.ietf.org/html/rfc3261#section-13.3.1.3 * @param options - Reject options bucket. */ reject(options = { statusCode: 486 }) { return super.reject(options); } }