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
						
					
					
				| //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;
 |