1 | const Emitter = require('events').EventEmitter ;
|
2 | const sip = require('drachtio-sip') ;
|
3 | const delegate = require('delegates') ;
|
4 | const status_codes = require('sip-status') ;
|
5 | const only = require('only') ;
|
6 | const noop = require('node-noop').noop;
|
7 | const assert = require('assert') ;
|
8 | const debug = require('debug')('drachtio:response');
|
9 | class Response extends Emitter {
|
10 |
|
11 | constructor(agent) {
|
12 | super() ;
|
13 | this._agent = agent ;
|
14 | this.msg = new sip.SipMessage() ;
|
15 | this.finished = false ;
|
16 | }
|
17 |
|
18 | get req() {
|
19 | return this._req ;
|
20 | }
|
21 | set req(req) {
|
22 | this._req = req ;
|
23 |
|
24 |
|
25 | ['call-id', 'cseq', 'from', 'to'].forEach((hdr) => {
|
26 | if (req.has(hdr) && !this.has(hdr)) { this.msg.set(hdr, req.get(hdr)) ; }
|
27 | }) ;
|
28 | return this ;
|
29 | }
|
30 |
|
31 | get agent() {
|
32 | return this._agent ;
|
33 | }
|
34 |
|
35 | set agent(agent) {
|
36 | debug('setting agent');
|
37 | this._agent = agent ;
|
38 | return this ;
|
39 | }
|
40 |
|
41 | set meta(meta) {
|
42 | this.source = meta.source ;
|
43 | this.source_address = meta.address ;
|
44 | this.source_port = meta.port ? parseInt(meta.port) : 5060 ;
|
45 | this.protocol = meta.protocol ;
|
46 | this.stackTime = meta.time ;
|
47 | this.stackTxnId = meta.transactionId ;
|
48 | this.stackDialogId = meta.dialogId ;
|
49 | }
|
50 |
|
51 | get meta() {
|
52 | return {
|
53 | source: this.source,
|
54 | source_address: this.source_address,
|
55 | source_port: this.source_port,
|
56 | protocol: this.protocol,
|
57 | time: this.stackTime,
|
58 | transactionId: this.stackTxnId,
|
59 | dialogId: this.stackDialogId
|
60 | } ;
|
61 | }
|
62 |
|
63 | set statusCode(code) {
|
64 | this.status = code ;
|
65 | return this ;
|
66 | }
|
67 | get statusCode() {
|
68 | return this.status ;
|
69 | }
|
70 |
|
71 | get finalResponseSent() {
|
72 | return this.finished ;
|
73 | }
|
74 | get headersSent() {
|
75 | return this.finished ;
|
76 | }
|
77 |
|
78 | send(status, reason, opts, callback, fnPrack) {
|
79 | if (typeof status !== 'number' || !(status in status_codes)) {
|
80 | throw new Error('Response#send: status is required and must be a valid sip response code') ;
|
81 | }
|
82 |
|
83 | if (typeof reason === 'function') {
|
84 |
|
85 | fnPrack = callback ;
|
86 | callback = reason ;
|
87 | reason = undefined ;
|
88 | }
|
89 | else if (typeof reason === 'object') {
|
90 |
|
91 | fnPrack = callback ;
|
92 | callback = opts ;
|
93 | opts = reason ;
|
94 | reason = undefined ;
|
95 | }
|
96 |
|
97 | opts = opts || {} ;
|
98 |
|
99 | this.msg.status = status ;
|
100 | this.msg.reason = reason || status_codes[status];
|
101 |
|
102 |
|
103 | debug(`Res#send opts ${JSON.stringify(opts)}`);
|
104 | if (opts.headers && (opts.headers.to || opts.headers['To'])) {
|
105 | const to = opts.headers.to || opts.headers['To'];
|
106 | delete opts.headers.to;
|
107 | delete opts.headers['To'];
|
108 | debug(`app wants to set To on response ${to}`);
|
109 | const arr = /tag=(.*)/.exec(to);
|
110 | if (arr) {
|
111 | const tag = arr[1];
|
112 | debug(`app is setting tag on To: ${tag}`);
|
113 | if (this.msg.headers.to && !this.msg.headers.to.includes('tag=')) {
|
114 | this.msg.headers.to += `;tag=${tag}`;
|
115 | }
|
116 | }
|
117 | }
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | debug(`Response#send: msg: ${JSON.stringify(this.msg)}`);
|
124 | this._agent.sendResponse(this, opts, callback, fnPrack) ;
|
125 |
|
126 | if (status >= 200) {
|
127 | this.finished = true ;
|
128 | }
|
129 | }
|
130 |
|
131 | sendAck(dialogId, opts, callback) {
|
132 | this._agent.sendAck('ACK', dialogId, this.req, this, opts, callback) ;
|
133 | }
|
134 | sendPrack(dialogId, opts, callback) {
|
135 | const rack = `${this.get('rseq').toString()} ${this.req.get('cseq')}` ;
|
136 | opts = opts || {} ;
|
137 | opts.headers = opts.headers || {} ;
|
138 | Object.assign(opts.headers, {'RAck': rack }) ;
|
139 | this._agent.sendAck('PRACK', dialogId, this.req, this, opts, callback) ;
|
140 | }
|
141 | toJSON() {
|
142 | return only(this, 'msg source source_address source_port protocol stackTime stackDialogId stackTxnId') ;
|
143 | }
|
144 |
|
145 |
|
146 | removeHeader(hdrName) {
|
147 | noop() ;
|
148 | }
|
149 | getHeader(hdrName) {
|
150 | return this.msg.get(hdrName) ;
|
151 | }
|
152 | setHeader(hdrName, hdrValue) {
|
153 | return this.msg.set(hdrName, hdrValue) ;
|
154 | }
|
155 |
|
156 | end(data, encoding, callback) {
|
157 | assert(!this.finished, 'call to Response#end after response is finished') ;
|
158 |
|
159 | if (typeof encoding === 'function') {
|
160 | callback = encoding ;
|
161 | encoding = null ;
|
162 | }
|
163 | else if (typeof data === 'function') {
|
164 | callback = data ;
|
165 | encoding = null ;
|
166 | data = null ;
|
167 | }
|
168 | callback = callback || noop ;
|
169 |
|
170 | this.send(this.status, data, () => {
|
171 | callback() ;
|
172 | }) ;
|
173 | this.finished = true ;
|
174 | }
|
175 | }
|
176 |
|
177 | module.exports = Response ;
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | delegate(Response.prototype, 'msg')
|
183 | .method('get')
|
184 | .method('has')
|
185 | .method('getParsedHeader')
|
186 | .method('set')
|
187 | .access('headers')
|
188 | .access('body')
|
189 | .access('payload')
|
190 | .access('status')
|
191 | .access('reason')
|
192 | .getter('raw')
|
193 | .getter('type') ;
|