UNPKG

5.21 kBJavaScriptView Raw
1const Emitter = require('events').EventEmitter ;
2const sip = require('drachtio-sip') ;
3const delegate = require('delegates') ;
4const status_codes = require('sip-status') ;
5const only = require('only') ;
6const noop = require('node-noop').noop;
7const assert = require('assert') ;
8const debug = require('debug')('drachtio:response');
9class 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 //copy over the dialog-specific headers from the associated request
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 // i.e. res.send(180, fn)
85 fnPrack = callback ;
86 callback = reason ;
87 reason = undefined ;
88 }
89 else if (typeof reason === 'object') {
90 //i.e. res.send(180, {}, fn)
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 // allow app to set the tag in To header
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 //TODO: should really not be attaching the headers and body to the response object
120 //because we don't necessarily want them to go out on the final if this is a 1xx
121 //pass them to sendResponse instead -- caller can explicitly 'set' headers or body on the response if
122 //they want them to be sticky
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 // for compatibility with expressjs res object so we can use passport etc and other frameworks
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
177module.exports = Response ;
178
179
180// end compatibility
181
182delegate(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') ;