UNPKG

9.2 kBJavaScriptView Raw
1const Emitter = require('events').EventEmitter ;
2const sip = require('drachtio-sip') ;
3const delegate = require('delegates') ;
4const assert = require('assert') ;
5const noop = require('node-noop').noop;
6const debug = require('debug')('drachtio:request');
7
8class Request extends Emitter {
9
10 constructor(msg, meta) {
11 super() ;
12
13 if (msg) {
14 assert(msg instanceof sip.SipMessage) ;
15 this.msg = msg ;
16 this.meta = meta ;
17 }
18 }
19
20 get res() {
21 return this._res ;
22 }
23 set res(res) {
24 this._res = res ;
25 return this ;
26 }
27
28 get isNewInvite() {
29 const to = this.getParsedHeader('to') ;
30 return this.method === 'INVITE' && !('tag' in to.params) ;
31 }
32
33 get url() {
34 return this.uri ;
35 }
36
37 set agent(agent) {
38 this._agent = agent ;
39 }
40 get agent() {
41 return this._agent ;
42 }
43
44 set meta(meta) {
45 debug(`Request#set meta ${JSON.stringify(meta)}`);
46 this.source = meta.source ;
47 this.source_address = meta.address ;
48 this.source_port = meta.port ? parseInt(meta.port) : 5060 ;
49 this.protocol = meta.protocol ;
50 this.stackTime = meta.time ;
51 this.stackTxnId = meta.transactionId ;
52 this.stackDialogId = meta.dialogId ;
53 if (meta.server) this.server = meta.server;
54 if (meta.receivedOn) this.receivedOn = meta.receivedOn;
55 }
56
57 get meta() {
58 return {
59 source: this.source,
60 source_address: this.source_address,
61 source_port: this.source_port,
62 protocol: this.protocol,
63 time: this.stackTime,
64 transactionId: this.stackTxnId,
65 dialogId: this.stackDialogId
66 } ;
67 }
68
69 /**
70 * Cancel a request that was sent by the application
71 * @param {Object} [opts.headers] optional headers to attach to the CANCEL request
72 * @param {Request~cancelCallback} callback - invoked with cancel operation completes
73 */
74 cancel(opts, callback) {
75 opts = opts || {};
76 if (typeof opts === 'function') {
77 callback = opts;
78 opts = {};
79 }
80 if (!this._agent || this.source !== 'application') {
81 throw new Error('Request#cancel can only be used for uac Request') ;
82 }
83 this._agent.request(Object.assign(
84 {
85 _socket: this.socket,
86 uri: this.uri,
87 method: 'CANCEL',
88 stackTxnId: this.stackTxnId
89 }, opts),
90 callback) ;
91 }
92 /**
93 * This callback is invoked when the application has sent a CANCEL for a request.
94 * @callback Request~cancelCallback
95 * @param {Error} err - if an error occurred while attempting to send the cancel
96 * @param {Request} req - the cancel request that was sent
97 */
98
99 /**
100 * Proxy an incoming request
101 * @param {Request~proxyOptions} opts - options governing the proxy operation
102 * @param {Request~proxyCallback} [callback] - callback invoked when proxy operation completes
103 * @returns {Promise|Request} returns a Promise if not callback is supplied, otherwise the Request object
104 */
105 proxy(opts, callback) {
106 if (this.source !== 'network') {
107 throw new Error('Request#proxy can only be used for incoming requests') ;
108 }
109 opts = opts || {} ;
110
111 //TODO: throw error if req.res.send has already been called (i.e. can't start off as UAS and then become a proxy)
112 const destination = opts.destination || this.uri ;
113 if (typeof destination === 'string') { opts.destination = [destination] ; }
114
115 Object.assign(opts, {
116 stackTxnId: this.stackTxnId,
117 remainInDialog: opts.remainInDialog || opts.path || opts.recordRoute || false,
118 provisionalTimeout: opts.provisionalTimeout || '',
119 finalTimeout: opts.finalTimeout || '',
120 followRedirects: opts.followRedirects || false,
121 simultaneous: opts.forking === 'simultaneous',
122 fullResponse: true
123 }) ;
124
125 //normalize sip uris
126 opts.destination.forEach((value, index, array) => {
127 const token = value.split(':') ;
128 if (token[0] !== 'sip' && token[0] !== 'tel') {
129 array[index] = 'sip:' + value ;
130 }
131 }) ;
132
133 const result = {
134 connected: false,
135 responses: []
136 } ;
137
138 const __x = (callback) => {
139 this._agent.proxy(this, opts, (token, rawMsg, meta) => {
140 if ('NOK' === token[0]) {
141 return callback(token[1]) ;
142 }
143 if ('done' === token[1]) {
144 result.connected = (200 === result.finalStatus) ;
145 return callback(null, result) ;
146 }
147 else {
148 //add a new response to the array
149 const address = meta.address ;
150 const port = +meta.port;
151 const msg = new sip.SipMessage(rawMsg) ;
152 const obj = {
153 time: meta.time,
154 status: msg.status,
155 msg: msg
156 } ;
157 let len = result.responses.length ;
158 if (len === 0 || address !== result.responses[len - 1].address || port === result.responses[len - 1].port) {
159 result.responses.push({
160 address: address,
161 port: port,
162 msgs:[]
163 }) ;
164 len++ ;
165 }
166 result.responses[len - 1].msgs.push(obj) ;
167 result.finalStatus = msg.status ;
168 result.finalResponse = obj ;
169 }
170 }) ;
171 };
172
173 if (callback) {
174 __x(callback);
175 return this;
176 }
177
178 return new Promise((resolve, reject) => {
179 __x((err, results) => {
180 if (err) return reject(err);
181 resolve(results);
182 });
183 });
184 }
185
186 /**
187 * Options governing a proxy operation
188 * @typedef {Object} Request~proxyOptions
189 * @property {string|Array} destination - an ordered list of one or more SIP URIs to proxy the request to
190 * @property {boolean} [remainInDialog=false] - if true add a Record-Route header and emain in the SIP dialog
191 * after the INVITE transaction.
192 * @property {boolean} [followRedirects=false] - if true respond to 3XX redirect responses by generating
193 * a new INVITE to the SIP URI in the Contact header of the response
194 * @property {string} [forking=sequential] - 'simultaneous' or 'sequential'; dicates whether the proxy waits
195 * for a failure response from one target before trying the next, or forks the request to all targets simultaneously
196 * @property {string} [provisionalTimeout] - amount of time to wait for a 100 Trying response from a target before
197 * trying the next target; valid syntax is '2s' or '1500ms' for example
198 * @property {string} [finalTimeout] - amount of time to wait for a final response from a target before trying
199 * the next target; syntax is as described above for provisionalTimeout
200 */
201 /**
202 * This callback is invoked when proxy operation has completed.
203 * @callback Request~proxyCallback
204 * @param {Error} err - if an error occurred while attempting to proxy the request
205 * @param {Request~proxyResults} results - results summarizing the proxy operation
206 */
207
208 // for compatibility with passport
209 logIn(user, options, done) {
210 if (typeof options === 'function') {
211 done = options;
212 options = {};
213 }
214 options = options || {};
215 done = done || noop ;
216
217 let property = 'user';
218 if (this._passport && this._passport.instance) {
219 property = this._passport.instance._userProperty || 'user';
220 }
221 const session = (options.session === undefined) ? true : options.session;
222
223 this[property] = user;
224 if (session) {
225 if (!this._passport) { throw new Error('passport.initialize() middleware not in use'); }
226 if (typeof done !== 'function') { throw new Error('req#login requires a callback function'); }
227
228 this._passport.instance.serializeUser(user, this, (err, obj) => {
229 if (err) { this[property] = null; return done(err); }
230 if (!this._passport.session) {
231 this._passport.session = {};
232 }
233 this._passport.session.user = obj;
234 this.session = this.session || {};
235 this.session[this._passport.instance._key] = this._passport.session;
236 done();
237 });
238 } else {
239 done();
240 }
241 }
242
243 // Terminate an existing login session.
244 logOut() {
245 let property = 'user';
246 if (this._passport && this._passport.instance) {
247 property = this._passport.instance._userProperty || 'user';
248 }
249
250 this[property] = null;
251 if (this._passport && this._passport.session) {
252 delete this._passport.session.user;
253 }
254 }
255 // Test if request is authenticated.
256 isAuthenticated() {
257 let property = 'user';
258 if (this._passport && this._passport.instance) {
259 property = this._passport.instance._userProperty || 'user';
260 }
261
262 return (this[property]) ? true : false;
263 }
264
265 // Test if request is unauthenticated.
266 isUnauthenticated() {
267 return !this.isAuthenticated();
268 }
269}
270
271module.exports = Request ;
272
273delegate(Request.prototype, 'msg')
274 .method('get')
275 .method('has')
276 .method('getParsedHeader')
277 .method('set')
278 .access('method')
279 .access('uri')
280 .access('headers')
281 .access('body')
282 .access('payload')
283 .getter('type')
284 .getter('raw')
285 .getter('callingNumber')
286 .getter('calledNumber')
287 .getter('canFormDialog') ;
288
289/**
290 * response event triggered when a Request sent by the application receives a response from the network
291 * @event Endpoint#destroy
292 * @param {Response} res - SIP response received as a result of sending a SIP request
293 */