UNPKG

sip.js

Version:

A SIP library for JavaScript

230 lines (229 loc) 8.65 kB
import { NameAddrHeader } from "../../grammar/name-addr-header.js"; import { createRandomToken, headerize, newTag, utf8Length } from "./utils.js"; /** * Outgoing SIP request message. * @public */ export class OutgoingRequestMessage { constructor(method, ruri, fromURI, toURI, options, extraHeaders, body) { this.headers = {}; this.extraHeaders = []; // Initialize default options this.options = OutgoingRequestMessage.getDefaultOptions(); // Options - merge a deep copy if (options) { this.options = Object.assign(Object.assign({}, this.options), options); if (this.options.optionTags && this.options.optionTags.length) { this.options.optionTags = this.options.optionTags.slice(); } if (this.options.routeSet && this.options.routeSet.length) { this.options.routeSet = this.options.routeSet.slice(); } } // Extra headers - deep copy if (extraHeaders && extraHeaders.length) { this.extraHeaders = extraHeaders.slice(); } // Body - deep copy if (body) { // TODO: internal representation should be Body // this.body = { ...body }; this.body = { body: body.content, contentType: body.contentType }; } // Method this.method = method; // RURI this.ruri = ruri.clone(); // From this.fromURI = fromURI.clone(); this.fromTag = this.options.fromTag ? this.options.fromTag : newTag(); this.from = OutgoingRequestMessage.makeNameAddrHeader(this.fromURI, this.options.fromDisplayName, this.fromTag); // To this.toURI = toURI.clone(); this.toTag = this.options.toTag; this.to = OutgoingRequestMessage.makeNameAddrHeader(this.toURI, this.options.toDisplayName, this.toTag); // Call-ID this.callId = this.options.callId ? this.options.callId : this.options.callIdPrefix + createRandomToken(15); // CSeq this.cseq = this.options.cseq; // The relative order of header fields with different field names is not // significant. However, it is RECOMMENDED that header fields which are // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, // Max-Forwards, and Proxy-Authorization, for example) appear towards // the top of the message to facilitate rapid parsing. // https://tools.ietf.org/html/rfc3261#section-7.3.1 this.setHeader("route", this.options.routeSet); this.setHeader("via", ""); this.setHeader("to", this.to.toString()); this.setHeader("from", this.from.toString()); this.setHeader("cseq", this.cseq + " " + this.method); this.setHeader("call-id", this.callId); this.setHeader("max-forwards", "70"); } /** Get a copy of the default options. */ static getDefaultOptions() { return { callId: "", callIdPrefix: "", cseq: 1, toDisplayName: "", toTag: "", fromDisplayName: "", fromTag: "", forceRport: false, hackViaTcp: false, optionTags: ["outbound"], routeSet: [], userAgentString: "sip.js", viaHost: "" }; } static makeNameAddrHeader(uri, displayName, tag) { const parameters = {}; if (tag) { parameters.tag = tag; } return new NameAddrHeader(uri, displayName, parameters); } /** * Get the value of the given header name at the given position. * @param name - header name * @returns Returns the specified header, undefined if header doesn't exist. */ getHeader(name) { const header = this.headers[headerize(name)]; if (header) { if (header[0]) { return header[0]; } } else { const regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); for (const exHeader of this.extraHeaders) { if (regexp.test(exHeader)) { return exHeader.substring(exHeader.indexOf(":") + 1).trim(); } } } return; } /** * Get the header/s of the given name. * @param name - header name * @returns Array with all the headers of the specified name. */ getHeaders(name) { const result = []; const headerArray = this.headers[headerize(name)]; if (headerArray) { for (const headerPart of headerArray) { result.push(headerPart); } } else { const regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); for (const exHeader of this.extraHeaders) { if (regexp.test(exHeader)) { result.push(exHeader.substring(exHeader.indexOf(":") + 1).trim()); } } } return result; } /** * Verify the existence of the given header. * @param name - header name * @returns true if header with given name exists, false otherwise */ hasHeader(name) { if (this.headers[headerize(name)]) { return true; } else { const regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); for (const extraHeader of this.extraHeaders) { if (regexp.test(extraHeader)) { return true; } } } return false; } /** * Replace the the given header by the given value. * @param name - header name * @param value - header value */ setHeader(name, value) { this.headers[headerize(name)] = value instanceof Array ? value : [value]; } /** * The Via header field indicates the transport used for the transaction * and identifies the location where the response is to be sent. A Via * header field value is added only after the transport that will be * used to reach the next hop has been selected (which may involve the * usage of the procedures in [4]). * * When the UAC creates a request, it MUST insert a Via into that * request. The protocol name and protocol version in the header field * MUST be SIP and 2.0, respectively. The Via header field value MUST * contain a branch parameter. This parameter is used to identify the * transaction created by that request. This parameter is used by both * the client and the server. * https://tools.ietf.org/html/rfc3261#section-8.1.1.7 * @param branchParameter - The branch parameter. * @param transport - The sent protocol transport. */ setViaHeader(branch, transport) { // FIXME: Hack if (this.options.hackViaTcp) { transport = "TCP"; } let via = "SIP/2.0/" + transport; via += " " + this.options.viaHost + ";branch=" + branch; if (this.options.forceRport) { via += ";rport"; } this.setHeader("via", via); this.branch = branch; } toString() { let msg = ""; msg += this.method + " " + this.ruri.toRaw() + " SIP/2.0\r\n"; for (const header in this.headers) { if (this.headers[header]) { for (const headerPart of this.headers[header]) { msg += header + ": " + headerPart + "\r\n"; } } } for (const header of this.extraHeaders) { msg += header.trim() + "\r\n"; } msg += "Supported: " + this.options.optionTags.join(", ") + "\r\n"; msg += "User-Agent: " + this.options.userAgentString + "\r\n"; if (this.body) { if (typeof this.body === "string") { msg += "Content-Length: " + utf8Length(this.body) + "\r\n\r\n"; msg += this.body; } else { if (this.body.body && this.body.contentType) { msg += "Content-Type: " + this.body.contentType + "\r\n"; msg += "Content-Length: " + utf8Length(this.body.body) + "\r\n\r\n"; msg += this.body.body; } else { msg += "Content-Length: " + 0 + "\r\n\r\n"; } } } else { msg += "Content-Length: " + 0 + "\r\n\r\n"; } return msg; } }