plivo
Version:
A Node.js SDK to make voice calls and send SMS using Plivo and to generate Plivo XML
428 lines (374 loc) • 16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
exports.Axios = Axios;
var _exceptions = require('../utils/exceptions');
var Exceptions = _interopRequireWildcard(_exceptions);
var _lodash = require('lodash');
var _ = _interopRequireWildcard(_lodash);
var _axios = require('axios');
var _axios2 = _interopRequireDefault(_axios);
var _querystring = require('querystring');
var _querystring2 = _interopRequireDefault(_querystring);
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _https = require('https');
var _https2 = _interopRequireDefault(_https);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
var HttpsProxyAgent = require('https-proxy-agent');
var httpAgent = new _http2.default.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 100, // Maximum sockets per host - requests queue when limit reached
maxFreeSockets: 20, // Maximum free sockets per host (increased proportionally)
timeout: 60000, // Socket timeout - requests fail if this timeout is reached
freeSocketTimeout: 30000 // Free socket timeout
});
var httpsAgent = new _https2.default.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 100, // Requests queue when limit reached, don't fail immediately
maxFreeSockets: 20, // Increased proportionally with maxSockets
timeout: 60000, // Only fail if timeout reached while waiting for socket
freeSocketTimeout: 30000
});
// Set default agents for axios
_axios2.default.defaults.httpAgent = httpAgent;
_axios2.default.defaults.httpsAgent = httpsAgent;
function Axios(config) {
var auth = 'Basic ' + new Buffer(config.authId + ':' + config.authToken).toString('base64');
var headers = {
Authorization: auth,
'User-Agent': config.userAgent,
'Content-Type': 'application/json'
};
var isGeoPermissionError = function isGeoPermissionError(response) {
var code = response.status;
var url = response.config.url;
var method = response.config.method;
var GeoPermissionEndpoints = ['/Message/', '/Call/', '/Session/'];
return code == 403 && method.toLowerCase() == "post" && GeoPermissionEndpoints.some(function (endpoint) {
return url.endsWith(endpoint);
});
};
var retryWrapper = function retryWrapper(axiosInstance, options) {
var max_time = options.retryTime;
var counter = 0;
// Create a dedicated axios instance for this request to avoid global interceptor pollution
var requestAxios = _axios2.default.create({
httpAgent: axiosInstance.defaults.httpAgent,
httpsAgent: axiosInstance.defaults.httpsAgent,
timeout: axiosInstance.defaults.timeout
});
// Add interceptor with proper cleanup
var interceptorId = requestAxios.interceptors.response.use(null, function (error) {
var config = error.config;
if (counter < max_time && error.response && error.response.status >= 500) {
counter++;
config.url = options.urls[counter] + options.authId + '/' + options.action;
return new Promise(function (resolve) {
resolve(requestAxios(config));
});
}
return Promise.reject(error);
});
return {
axios: requestAxios,
cleanup: function cleanup() {
// Clean up the interceptor when done
requestAxios.interceptors.response.eject(interceptorId);
}
};
};
return function (method, action, params) {
var configuration = config;
// Add support fot multipart requests.
if (typeof params != 'undefined' && params.multipart == true) {
return new Promise(function (resolve, reject) {
delete params.multipart;
var FormData = require('form-data');
var multipartParams = new FormData();
for (var key in params) {
if (key != 'file') {
multipartParams.append(key, params[key]);
} else {
// In case files are in array
if (Array.isArray(params.file)) {
for (var index = 0; index < params.file.length; index++) {
multipartParams.append(key, require('fs').createReadStream(params.file[index]));
}
} else {
multipartParams.append(key, require('fs').createReadStream(params[key]));
}
}
}
var headers = multipartParams.getHeaders();
var config = {
method: method,
url: configuration.url + '/' + action,
headers: {
'Authorization': 'Basic ' + new Buffer(configuration.authId + ':' + configuration.authToken).toString('base64'),
'User-Agent': configuration.userAgent,
'content-type': headers['content-type']
},
data: multipartParams
};
(0, _axios2.default)(config).then(function (response) {
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError
}[response.status] || Error;
if (isGeoPermissionError(response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(response.status, 200, 300)) {
var body = response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(JSON.stringify(body)));
} else {
reject(new exceptionClass(body));
}
}
resolve({
response: response,
body: response.data
});
}).catch(function (error) {
//client side exception like file not found case
if (error.response == undefined) {
return reject(error.stack);
}
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError
}[error.response.status] || Error;
if (isGeoPermissionError(error.response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(error.response.status, 200, 300)) {
var body = error.response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(error));
} else {
if (error.response.status >= 500) {
reject(new Exceptions.ServerError(error));
}
reject(new exceptionClass(error));
}
}
reject(error.stack + '\n' + JSON.stringify(error.response.data));
});
});
}
if (typeof params != 'undefined' && typeof params.file != 'undefined') {
var files = [];
if (Array.isArray(params.file)) {
for (var index = 0; index < params.file.length; index++) {
files[index] = require('fs').createReadStream(params.file[index]);
}
} else {
files[0] = require('fs').createReadStream(params.file);
}
params.file = files;
}
var options = {
url: config.url + '/' + action,
method: method,
data: params || '',
headers: headers,
json: true
};
var apiVoiceUris = ['https://api.plivo.com/v1/Account/', 'https://api.plivo.com/v1/Account/', 'https://api.plivo.com/v1/Account/'];
var isVoiceReq = false;
if (params) {
if (params.hasOwnProperty('is_call_insights_request')) {
options.url = params.call_insights_base_url + params.call_insights_request_path;
delete params.is_call_insights_request;
delete params.call_insights_base_url;
delete params.call_insights_request_path;
delete options.data;
options.json = params;
} else if (params.hasOwnProperty('is_voice_request')) {
options.url = apiVoiceUris[0] + config.authId + '/' + action;
delete params.is_voice_request;
isVoiceReq = true;
} else if (params.hasOwnProperty('override_url')) {
// currently used by Lookup API but is generic enough to be used
// by any product in future.
options.url = params.override_url;
delete params.override_url;
}
}
if (method === 'GET' && options.data !== '' && !isVoiceReq) {
var query = '?' + _querystring2.default.stringify(params);
options.url += query;
delete options.data;
}
if (typeof config.proxy !== 'undefined') {
// Create proxy agent with connection pooling settings
options.httpsAgent = new HttpsProxyAgent(config.proxy, {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 100, // Match the main configuration
maxFreeSockets: 20, // Match the main configuration
timeout: 60000,
freeSocketTimeout: 30000
});
}
if (typeof config.timeout !== 'undefined') {
options.timeout = config.timeout;
} else if (typeof config.timeout === 'undefined' && isVoiceReq) {
options.timeout = 5000;
}
return new Promise(function (resolve, reject) {
if (isVoiceReq) {
// Set up retry wrapper with proper cleanup
var retrySetup = retryWrapper(_axios2.default, { retryTime: 2, urls: apiVoiceUris, authId: config.authId, action: action });
var retryAxios = retrySetup.axios;
options.url = apiVoiceUris[0] + config.authId + '/' + action;
if (method === 'GET' && options.data !== '') {
var _query = '?' + _querystring2.default.stringify(params);
options.url += _query;
delete options.data;
}
retryAxios(options).then(function (response) {
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError,
409: Exceptions.InvalidRequestError,
422: Exceptions.InvalidRequestError,
207: Exceptions.InvalidRequestError
}[response.status] || Error;
if (isGeoPermissionError(response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(response.status, 200, 300)) {
var body = response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(JSON.stringify(body)));
} else {
reject(new exceptionClass(body));
}
}
resolve({
response: response,
body: response.data
});
}).catch(function (error) {
if (error.response == undefined) {
return reject(error.stack);
}
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError,
409: Exceptions.InvalidRequestError,
422: Exceptions.InvalidRequestError,
207: Exceptions.InvalidRequestError
}[error.response.status] || Error;
if (isGeoPermissionError(error.response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(error.response.status, 200, 300)) {
var body = error.response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(error));
} else {
if (error.response.status >= 500) {
reject(new Exceptions.ServerError(error));
}
reject(new exceptionClass(error));
}
}
reject(error.stack + '\n' + JSON.stringify(error.response.data));
}).finally(function () {
// Clean up interceptors in all cases - success, error, or unexpected exception
retrySetup.cleanup();
});
} else {
(0, _axios2.default)(options).then(function (response) {
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError,
409: Exceptions.InvalidRequestError,
422: Exceptions.InvalidRequestError,
207: Exceptions.InvalidRequestError
}[response.status] || Error;
if (isGeoPermissionError(response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(response.status, 200, 300)) {
var body = response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(JSON.stringify(body)));
} else {
reject(new exceptionClass(body));
}
} else {
var _body2 = response.data;
var isObj = (typeof _body === 'undefined' ? 'undefined' : _typeof(_body)) === 'object' && _body !== null && !(_body instanceof Array) && !(_body instanceof Date);
if (isObj) {
_body['statusCode'] = response.status;
}
resolve({
response: response,
body: _body2
});
}
}).catch(function (error) {
if (error.response == undefined) {
return reject(error.stack);
}
var exceptionClass = {
400: Exceptions.InvalidRequestError,
401: Exceptions.AuthenticationError,
404: Exceptions.ResourceNotFoundError,
405: Exceptions.InvalidRequestError,
406: Exceptions.NotAcceptableError,
500: Exceptions.ServerError,
409: Exceptions.InvalidRequestError,
422: Exceptions.InvalidRequestError,
207: Exceptions.InvalidRequestError
}[error.response.status] || Error;
if (isGeoPermissionError(error.response)) {
exceptionClass = Exceptions.GeoPermissionError;
}
if (!_.inRange(error.response.status, 200, 300)) {
var body = error.response.data;
if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object') {
reject(new exceptionClass(error));
} else {
if (error.response.status >= 500) {
reject(new Exceptions.ServerError(error));
}
reject(new exceptionClass(error));
}
}
reject(error.stack + '\n' + JSON.stringify(error.response.data));
});
}
});
};
}