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