UNPKG

sip.js

Version:

A SIP library for JavaScript

225 lines (224 loc) 10.5 kB
import { TransactionStateError } from "../exceptions/transaction-state-error.js"; import { constructOutgoingResponse } from "../messages/outgoing-response.js"; import { newTag } from "../messages/utils.js"; import { InviteServerTransaction } from "../transactions/invite-server-transaction.js"; import { NonInviteServerTransaction } from "../transactions/non-invite-server-transaction.js"; import { TransactionState } from "../transactions/transaction-state.js"; /** * User Agent Server (UAS). * @remarks * A user agent server is a logical entity * that generates a response to a SIP request. The response * accepts, rejects, or redirects the request. This role lasts * only for the duration of that transaction. In other words, if * a piece of software responds to a request, it acts as a UAS for * the duration of that transaction. If it generates a request * later, it assumes the role of a user agent client for the * processing of that transaction. * https://tools.ietf.org/html/rfc3261#section-6 * @public */ export class UserAgentServer { constructor(transactionConstructor, core, message, delegate) { this.transactionConstructor = transactionConstructor; this.core = core; this.message = message; this.delegate = delegate; this.logger = this.loggerFactory.getLogger("sip.user-agent-server"); this.toTag = message.toTag ? message.toTag : newTag(); this.init(); } dispose() { this.transaction.dispose(); } get loggerFactory() { return this.core.loggerFactory; } /** The transaction associated with this request. */ get transaction() { if (!this._transaction) { throw new Error("Transaction undefined."); } return this._transaction; } accept(options = { statusCode: 200 }) { if (!this.acceptable) { throw new TransactionStateError(`${this.message.method} not acceptable in state ${this.transaction.state}.`); } const statusCode = options.statusCode; if (statusCode < 200 || statusCode > 299) { throw new TypeError(`Invalid statusCode: ${statusCode}`); } const response = this.reply(options); return response; } progress(options = { statusCode: 180 }) { if (!this.progressable) { throw new TransactionStateError(`${this.message.method} not progressable in state ${this.transaction.state}.`); } const statusCode = options.statusCode; if (statusCode < 101 || statusCode > 199) { throw new TypeError(`Invalid statusCode: ${statusCode}`); } const response = this.reply(options); return response; } redirect(contacts, options = { statusCode: 302 }) { if (!this.redirectable) { throw new TransactionStateError(`${this.message.method} not redirectable in state ${this.transaction.state}.`); } const statusCode = options.statusCode; if (statusCode < 300 || statusCode > 399) { throw new TypeError(`Invalid statusCode: ${statusCode}`); } const contactHeaders = new Array(); contacts.forEach((contact) => contactHeaders.push(`Contact: ${contact.toString()}`)); options.extraHeaders = (options.extraHeaders || []).concat(contactHeaders); const response = this.reply(options); return response; } reject(options = { statusCode: 480 }) { if (!this.rejectable) { throw new TransactionStateError(`${this.message.method} not rejectable in state ${this.transaction.state}.`); } const statusCode = options.statusCode; if (statusCode < 400 || statusCode > 699) { throw new TypeError(`Invalid statusCode: ${statusCode}`); } const response = this.reply(options); return response; } // eslint-disable-next-line @typescript-eslint/no-unused-vars trying(options) { if (!this.tryingable) { throw new TransactionStateError(`${this.message.method} not tryingable in state ${this.transaction.state}.`); } const response = this.reply({ statusCode: 100 }); return response; } /** * If the UAS did not find a matching transaction for the CANCEL * according to the procedure above, it SHOULD respond to the CANCEL * with a 481 (Call Leg/Transaction Does Not Exist). If the transaction * for the original request still exists, the behavior of the UAS on * receiving a CANCEL request depends on whether it has already sent a * final response for the original request. If it has, the CANCEL * request has no effect on the processing of the original request, no * effect on any session state, and no effect on the responses generated * for the original request. If the UAS has not issued a final response * for the original request, its behavior depends on the method of the * original request. If the original request was an INVITE, the UAS * SHOULD immediately respond to the INVITE with a 487 (Request * Terminated). A CANCEL request has no impact on the processing of * transactions with any other method defined in this specification. * https://tools.ietf.org/html/rfc3261#section-9.2 * @param request - Incoming CANCEL request. */ receiveCancel(message) { // Note: Currently CANCEL is being handled as a special case. // No UAS is created to handle the CANCEL and the response to // it CANCEL is being handled statelessly by the user agent core. // As such, there is currently no way to externally impact the // response to the a CANCEL request. if (this.delegate && this.delegate.onCancel) { this.delegate.onCancel(message); } } get acceptable() { if (this.transaction instanceof InviteServerTransaction) { return (this.transaction.state === TransactionState.Proceeding || this.transaction.state === TransactionState.Accepted); } if (this.transaction instanceof NonInviteServerTransaction) { return (this.transaction.state === TransactionState.Trying || this.transaction.state === TransactionState.Proceeding); } throw new Error("Unknown transaction type."); } get progressable() { if (this.transaction instanceof InviteServerTransaction) { return this.transaction.state === TransactionState.Proceeding; } if (this.transaction instanceof NonInviteServerTransaction) { return false; // https://tools.ietf.org/html/rfc4320#section-4.1 } throw new Error("Unknown transaction type."); } get redirectable() { if (this.transaction instanceof InviteServerTransaction) { return this.transaction.state === TransactionState.Proceeding; } if (this.transaction instanceof NonInviteServerTransaction) { return (this.transaction.state === TransactionState.Trying || this.transaction.state === TransactionState.Proceeding); } throw new Error("Unknown transaction type."); } get rejectable() { if (this.transaction instanceof InviteServerTransaction) { return this.transaction.state === TransactionState.Proceeding; } if (this.transaction instanceof NonInviteServerTransaction) { return (this.transaction.state === TransactionState.Trying || this.transaction.state === TransactionState.Proceeding); } throw new Error("Unknown transaction type."); } get tryingable() { if (this.transaction instanceof InviteServerTransaction) { return this.transaction.state === TransactionState.Proceeding; } if (this.transaction instanceof NonInviteServerTransaction) { return this.transaction.state === TransactionState.Trying; } throw new Error("Unknown transaction type."); } /** * When a UAS wishes to construct a response to a request, it follows * the general procedures detailed in the following subsections. * Additional behaviors specific to the response code in question, which * are not detailed in this section, may also be required. * * Once all procedures associated with the creation of a response have * been completed, the UAS hands the response back to the server * transaction from which it received the request. * https://tools.ietf.org/html/rfc3261#section-8.2.6 * @param statusCode - Status code to reply with. * @param options - Reply options bucket. */ reply(options) { if (!options.toTag && options.statusCode !== 100) { options.toTag = this.toTag; } options.userAgent = options.userAgent || this.core.configuration.userAgentHeaderFieldValue; options.supported = options.supported || this.core.configuration.supportedOptionTagsResponse; const response = constructOutgoingResponse(this.message, options); this.transaction.receiveResponse(options.statusCode, response.message); return response; } init() { // We are the transaction user. const user = { loggerFactory: this.loggerFactory, onStateChange: (newState) => { if (newState === TransactionState.Terminated) { // Remove the terminated transaction from the core. // eslint-disable-next-line @typescript-eslint/no-use-before-define this.core.userAgentServers.delete(userAgentServerId); this.dispose(); } }, onTransportError: (error) => { this.logger.error(error.message); if (this.delegate && this.delegate.onTransportError) { this.delegate.onTransportError(error); } else { this.logger.error("User agent server response transport error."); } } }; // Create a new transaction with us as the user. const transaction = new this.transactionConstructor(this.message, this.core.transport, user); this._transaction = transaction; // Add the new transaction to the core. const userAgentServerId = transaction.id; this.core.userAgentServers.set(transaction.id, this); } }