UNPKG

sip.js

Version:

A SIP library for JavaScript

219 lines (218 loc) 10.3 kB
import { Timers } from "../timers.js"; import { ServerTransaction } from "./server-transaction.js"; import { TransactionState } from "./transaction-state.js"; /** * Non-INVITE Server Transaction. * @remarks * https://tools.ietf.org/html/rfc3261#section-17.2.2 * @public */ export class NonInviteServerTransaction extends ServerTransaction { /** * Constructor. * After construction the transaction will be in the "trying": state and the transaction * `id` will equal the branch parameter set in the Via header of the incoming request. * https://tools.ietf.org/html/rfc3261#section-17.2.2 * @param request - Incoming Non-INVITE request from the transport. * @param transport - The transport. * @param user - The transaction user. */ constructor(request, transport, user) { super(request, transport, user, TransactionState.Trying, "sip.transaction.nist"); } /** * Destructor. */ dispose() { if (this.J) { clearTimeout(this.J); this.J = undefined; } super.dispose(); } /** Transaction kind. Deprecated. */ get kind() { return "nist"; } /** * Receive requests from transport matching this transaction. * @param request - Request matching this transaction. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars receiveRequest(request) { switch (this.state) { case TransactionState.Trying: // Once in the "Trying" state, any further request retransmissions are discarded. // https://tools.ietf.org/html/rfc3261#section-17.2.2 break; case TransactionState.Proceeding: // If a retransmission of the request is received while in the "Proceeding" state, // the most recently sent provisional response MUST be passed to the transport layer for retransmission. // https://tools.ietf.org/html/rfc3261#section-17.2.2 if (!this.lastResponse) { throw new Error("Last response undefined."); } this.send(this.lastResponse).catch((error) => { this.logTransportError(error, "Failed to send retransmission of provisional response."); }); break; case TransactionState.Completed: // While in the "Completed" state, the server transaction MUST pass the final response to the transport // layer for retransmission whenever a retransmission of the request is received. Any other final responses // passed by the TU to the server transaction MUST be discarded while in the "Completed" state. // https://tools.ietf.org/html/rfc3261#section-17.2.2 if (!this.lastResponse) { throw new Error("Last response undefined."); } this.send(this.lastResponse).catch((error) => { this.logTransportError(error, "Failed to send retransmission of final response."); }); break; case TransactionState.Terminated: break; default: throw new Error(`Invalid state ${this.state}`); } } /** * Receive responses from TU for this transaction. * @param statusCode - Status code of response. 101-199 not allowed per RFC 4320. * @param response - Response to send. */ receiveResponse(statusCode, response) { if (statusCode < 100 || statusCode > 699) { throw new Error(`Invalid status code ${statusCode}`); } // An SIP element MUST NOT send any provisional response with a // Status-Code other than 100 to a non-INVITE request. // An SIP element MUST NOT respond to a non-INVITE request with a // Status-Code of 100 over any unreliable transport, such as UDP, // before the amount of time it takes a client transaction's Timer E to be reset to T2. // An SIP element MAY respond to a non-INVITE request with a // Status-Code of 100 over a reliable transport at any time. // https://tools.ietf.org/html/rfc4320#section-4.1 if (statusCode > 100 && statusCode <= 199) { throw new Error("Provisional response other than 100 not allowed."); } switch (this.state) { case TransactionState.Trying: // While in the "Trying" state, if the TU passes a provisional response // to the server transaction, the server transaction MUST enter the "Proceeding" state. // The response MUST be passed to the transport layer for transmission. // https://tools.ietf.org/html/rfc3261#section-17.2.2 this.lastResponse = response; if (statusCode >= 100 && statusCode < 200) { this.stateTransition(TransactionState.Proceeding); this.send(response).catch((error) => { this.logTransportError(error, "Failed to send provisional response."); }); return; } if (statusCode >= 200 && statusCode <= 699) { this.stateTransition(TransactionState.Completed); this.send(response).catch((error) => { this.logTransportError(error, "Failed to send final response."); }); return; } break; case TransactionState.Proceeding: // Any further provisional responses that are received from the TU while // in the "Proceeding" state MUST be passed to the transport layer for transmission. // If the TU passes a final response (status codes 200-699) to the server while in // the "Proceeding" state, the transaction MUST enter the "Completed" state, and // the response MUST be passed to the transport layer for transmission. // https://tools.ietf.org/html/rfc3261#section-17.2.2 this.lastResponse = response; if (statusCode >= 200 && statusCode <= 699) { this.stateTransition(TransactionState.Completed); this.send(response).catch((error) => { this.logTransportError(error, "Failed to send final response."); }); return; } break; case TransactionState.Completed: // Any other final responses passed by the TU to the server // transaction MUST be discarded while in the "Completed" state. // https://tools.ietf.org/html/rfc3261#section-17.2.2 return; case TransactionState.Terminated: break; default: throw new Error(`Invalid state ${this.state}`); } const message = `Non-INVITE server transaction received unexpected ${statusCode} response from TU while in state ${this.state}.`; this.logger.error(message); throw new Error(message); } /** * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD * inform the TU that a failure has occurred, and SHOULD transition to the terminated state. * https://tools.ietf.org/html/rfc3261#section-17.2.4 */ onTransportError(error) { if (this.user.onTransportError) { this.user.onTransportError(error); } this.stateTransition(TransactionState.Terminated, true); } /** For logging. */ typeToString() { return "non-INVITE server transaction"; } stateTransition(newState, dueToTransportError = false) { // Assert valid state transitions. const invalidStateTransition = () => { throw new Error(`Invalid state transition from ${this.state} to ${newState}`); }; switch (newState) { case TransactionState.Trying: invalidStateTransition(); break; case TransactionState.Proceeding: if (this.state !== TransactionState.Trying) { invalidStateTransition(); } break; case TransactionState.Completed: if (this.state !== TransactionState.Trying && this.state !== TransactionState.Proceeding) { invalidStateTransition(); } break; case TransactionState.Terminated: if (this.state !== TransactionState.Proceeding && this.state !== TransactionState.Completed) { if (!dueToTransportError) { invalidStateTransition(); } } break; default: invalidStateTransition(); } // When the server transaction enters the "Completed" state, it MUST set Timer J to fire // in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. // https://tools.ietf.org/html/rfc3261#section-17.2.2 if (newState === TransactionState.Completed) { this.J = setTimeout(() => this.timerJ(), Timers.TIMER_J); } // The server transaction MUST be destroyed the instant it enters the "Terminated" state. // https://tools.ietf.org/html/rfc3261#section-17.2.2 if (newState === TransactionState.Terminated) { this.dispose(); } this.setState(newState); } /** * The server transaction remains in this state until Timer J fires, * at which point it MUST transition to the "Terminated" state. * https://tools.ietf.org/html/rfc3261#section-17.2.2 */ timerJ() { this.logger.debug(`Timer J expired for NON-INVITE server transaction ${this.id}.`); if (this.state === TransactionState.Completed) { this.stateTransition(TransactionState.Terminated); } } }