You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					173 lines
				
				5.9 KiB
			
		
		
			
		
	
	
					173 lines
				
				5.9 KiB
			| 
											3 years ago
										 | //Dependencies
 | ||
|  | var Q = require('q'); | ||
|  | var querystring = require('querystring'); | ||
|  | var request = require('request'); | ||
|  | var moduleinfo = require('../package.json'); | ||
|  | var _ = require('underscore'); | ||
|  | 
 | ||
|  | //REST API Config Defaults
 | ||
|  | var defaultHost = 'api.twilio.com'; | ||
|  | var defaultApiVersion = '2010-04-01'; | ||
|  | 
 | ||
|  | function Client(sid, tkn, host, api_version, timeout) { | ||
|  |     //Required client config
 | ||
|  |     if (!sid || !tkn) { | ||
|  |         if (process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN) { | ||
|  |             this.accountSid = process.env.TWILIO_ACCOUNT_SID; | ||
|  |             this.authToken = process.env.TWILIO_AUTH_TOKEN; | ||
|  |         } | ||
|  |         else { | ||
|  |             throw 'Client requires an Account SID and Auth Token set explicitly ' + | ||
|  |                 'or via the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables'; | ||
|  |         } | ||
|  |     } | ||
|  |     else { | ||
|  |         //if auth token/SID passed in manually, trim spaces
 | ||
|  |         this.accountSid = sid.replace(/ /g, ''); | ||
|  |         this.authToken = tkn.replace(/ /g, ''); | ||
|  |     } | ||
|  | 
 | ||
|  |     //Optional client config
 | ||
|  |     this.host = host || defaultHost; | ||
|  |     this.apiVersion = api_version || defaultApiVersion; | ||
|  |     this.timeout = timeout || 31000; // request timeout in milliseconds
 | ||
|  | } | ||
|  | 
 | ||
|  | //process data and make available in a more JavaScripty format
 | ||
|  | function processKeys(source) { | ||
|  |     if (_.isObject(source)) { | ||
|  |         Object.keys(source).forEach(function(key) { | ||
|  | 
 | ||
|  |             if (key === 'total' || key === 'last_page_uri' || key === 'num_pages') { | ||
|  |                 delete source[key]; | ||
|  |             } | ||
|  | 
 | ||
|  |             //Supplement underscore values with camel-case
 | ||
|  |             if (key.indexOf('_') > 0) { | ||
|  |                 var cc = key.replace(/_([a-z])/g, function (g) { | ||
|  |                     return g[1].toUpperCase() | ||
|  |                 }); | ||
|  |                 source[cc] = source[key]; | ||
|  |             } | ||
|  | 
 | ||
|  |             //process any nested arrays...
 | ||
|  |             if (Array.isArray(source[key])) { | ||
|  |                 source[key].forEach(processKeys); | ||
|  |             } | ||
|  |             else if (_.isObject(source[key])) { | ||
|  |                 processKeys(source[key]); | ||
|  |             } | ||
|  |         }); | ||
|  | 
 | ||
|  |         //Look for and convert date strings for specific keys
 | ||
|  |         ['startDate', 'endDate', 'dateCreated', 'dateUpdated', 'startTime', 'endTime', 'dateSent'].forEach(function(dateKey) { | ||
|  |             if (source[dateKey]) { | ||
|  |                 source[dateKey] = new Date(source[dateKey]); | ||
|  |             } | ||
|  |         }); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  Get the base URL which we'll use for all requests with this client | ||
|  | 
 | ||
|  |  @returns {string} - the API base URL | ||
|  |  */ | ||
|  | Client.prototype.getBaseUrl = function () { | ||
|  |     return 'https://' + this.accountSid + ':' + this.authToken + '@' + this.host + '/' + this.apiVersion; | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  Make an authenticated request against the Twilio backend. Uses the request | ||
|  |  library, and largely passes through to its API for options: | ||
|  | 
 | ||
|  |  https://github.com/mikeal/request
 | ||
|  | 
 | ||
|  |  @param {object} options - options for HTTP request | ||
|  |  @param {function} callback - callback function for when request is complete | ||
|  |  - @param {object} error - an error object if there was a problem processing the request | ||
|  |  - @param {object} data - the JSON-parsed data | ||
|  |  - @param {http.ClientResponse} response - the raw node http.ClientResponse object | ||
|  |  */ | ||
|  | Client.prototype.request = function (options, callback) { | ||
|  |     var client = this; | ||
|  |     var deferred = Q.defer(); | ||
|  | 
 | ||
|  |     //Prepare request options
 | ||
|  |     // Add base URL if we weren't given an absolute one
 | ||
|  |     if (!options.url.indexOf('http') !== 0) { | ||
|  |         options.url = client.getBaseUrl() + options.url; | ||
|  |     } | ||
|  |     options.headers = { | ||
|  |         'Accept':'application/json', | ||
|  |         'Accept-Charset': 'utf-8', | ||
|  |         'User-Agent':'twilio-node/' + moduleinfo.version | ||
|  |     }; | ||
|  |     options.timeout = client.timeout; | ||
|  | 
 | ||
|  |     // Manually create POST body if there's a form object. Sadly, request
 | ||
|  |     // turns multiple key parameters into array-ified queries, like this:
 | ||
|  |     // MediaUrl[0]=foo&MediaUrl[1]=bar. Node querystring does the right thing so
 | ||
|  |     // we use that here. Also see https://github.com/mikeal/request/issues/644
 | ||
|  |     if (options.form) { | ||
|  |         options.body = querystring.stringify(options.form).toString('utf-8'); | ||
|  |         options.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'; | ||
|  |         options.form = null; | ||
|  |     } | ||
|  | 
 | ||
|  |     //Initiate HTTP request
 | ||
|  |     request(options, function (err, response, body) { | ||
|  |         var data; | ||
|  |         try { | ||
|  |             if (err) { | ||
|  |                 data = err; | ||
|  |             } else { | ||
|  |                 data = body ? JSON.parse(body) : null; | ||
|  |             } | ||
|  |         } catch (e) { | ||
|  |             data = { status: 500, message: (e.message || 'Invalid JSON body') }; | ||
|  |         } | ||
|  | 
 | ||
|  |         //request doesn't think 4xx is an error - we want an error for any non-2xx status codes
 | ||
|  |         var error = null; | ||
|  |         if (err || (response && (response.statusCode < 200 || response.statusCode > 206))) { | ||
|  |             error = {}; | ||
|  |             // response is null if server is unreachable
 | ||
|  |             if (response) { | ||
|  |                 error.status = response.statusCode; | ||
|  |                 error.message = data ? data.message : 'Unable to complete HTTP request'; | ||
|  |                 error.code = data && data.code; | ||
|  |                 error.moreInfo = data && data.more_info; | ||
|  |             } else { | ||
|  |                 error.status = err.code; | ||
|  |                 error.message = 'Unable to reach host: "'+client.host+'"'; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // JavaScriptify properties of response if it exists
 | ||
|  |         data && processKeys(data); | ||
|  | 
 | ||
|  |         //hang response off the JSON-serialized data, as unenumerable to allow for stringify.
 | ||
|  |         data && Object.defineProperty(data, 'nodeClientResponse', { | ||
|  |             value: response, | ||
|  |             configurable: true, | ||
|  |             writeable: true, | ||
|  |             enumerable: false | ||
|  |         }); | ||
|  | 
 | ||
|  |         // Resolve promise
 | ||
|  |         if (error) { | ||
|  |             deferred.reject(error); | ||
|  |         } else { | ||
|  |             deferred.resolve(data); | ||
|  |         } | ||
|  | 
 | ||
|  |     }); | ||
|  | 
 | ||
|  |     // Return promise, but also support original node callback style
 | ||
|  |     return deferred.promise.nodeify(callback); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports = Client; |