docusign
Version:
A DocuSign API helper library with promise support
265 lines (238 loc) • 7.47 kB
JavaScript
// Utility functions used across DocuSign NPM package
var fs = require('fs'); // core
var util = require('util');
var Bluebird = require('bluebird');
var request = require('request');
var temp = require('temp');
var stream = require('stream');
var uuid = require('uuid');
var assign = require('lodash.assign');
var isPlainObject = require('lodash.isplainobject');
exports.DocuSignError = DocuSignError;
function DocuSignError (message, errorDetails) {
errorDetails = errorDetails || {};
/* istanbul ignore if */
if (message instanceof DocuSignError) {
return message;
}
this.message = message;
this.name = 'DocuSignError';
assign(this, errorDetails);
Error.captureStackTrace(this, DocuSignError);
}
DocuSignError.prototype = Object.create(Error.prototype);
DocuSignError.prototype.constrcutor = DocuSignError;
/**
* Simple space to share internal state
*
* @memberOf Utils
* @private
* @type {Object}
*/
exports.internalState = {};
/**
* General logging function for debugging use
*
* @memberOf Utils
* @private
* @function
*/
exports.log = debugLog;
function debugLog () {
var isDebugLogEnabled = exports.internalState.dsDebug === 'true' || /docusign/ig.test(process.env.DEBUG);
/* istanbul ignore if */
if (isDebugLogEnabled) {
var timestamp = '[' + new Date().toISOString() + ']';
console.log.apply(console, [timestamp].concat(arguments));
}
}
/**
* Creates guids for use with when creating new users
*
* @memberOf Utils
* @private
* @function
* @returns {string}
*/
exports.generateNewGuid = uuid;
/**
* Provides an environment specific URL for the highest level API calls
*
* @memberOf Utils
* @private
* @function
* @returns {string}
*/
exports.getApiUrl = function () {
var env = exports.internalState.targetEnv;
return 'https://' + env + '.docusign.net/restapi/v2';
};
/**
* Formats and return an authorization header with the given oAuth token
*
* @memberOf Utils
* @private
* @function
* @param token
* @returns {{Authorization: string}}
*/
exports.getHeaders = function (token) {
return {
'X-DocuSign-SDK': 'Node-legacy',
Authorization: 'bearer ' + token
};
};
/**
* Helper function for making web requests to DocuSign
*
* @memberOf Utils
* @private
* @function
* @param apiName - name of the API to be requested
* @param options - options for the request
* @param callback
* @returns {Promise} - A thenable bluebird Promise; If callback is given it is called before the promise is resolved
*/
exports.makeRequest = function (apiName, options, callback) {
var makeRequestAsync = new Bluebird(function (resolve, reject) {
var data;
if ('json' in options) {
data = options.json;
} else {
data = '';
}
exports.log(util.format('DS API %s Request:\n %s %s\t %s\nHeaders: %s', apiName, options.method, options.url, util.inspect(data, {depth: null}), util.inspect(options.headers, {depth: null})));
request(options, function (error, response, body) {
if (error) {
return reject(error);
}
var errorDetails = {};
if (response.statusCode >= 400) {
errorDetails.statusCode = response.statusCode;
}
var json, err, errMsg;
try {
json = JSON.parse(body);
} catch (_) {
if (isPlainObject(body)) {
json = body;
} else {
json = null;
}
}
if (json === null) { // successful request; no json in response body
resolve(body);
} else if ('errorCode' in json) {
errMsg = util.format('DS API %s (Error Code: %s) Error:\n %s', apiName, json.errorCode, json.message);
errorDetails.errorCode = json.errorCode;
err = new DocuSignError(errMsg, errorDetails);
reject(err);
} else if ('error' in json) {
errMsg = util.format('DS API %s Error:\n %s \n\nDescription: %s', apiName, json.error, json.error_description);
errorDetails.errorCode = json.error;
err = new DocuSignError(errMsg, errorDetails);
reject(err);
} else { // no error
exports.log(util.format('DS API %s Response:\n %s', apiName, JSON.stringify(json)));
resolve(json);
}
});
});
return makeRequestAsync.asCallback(callback).catch(DocuSignError, function (error) {
exports.log(error.message);
throw error;
});
};
/**
*
* This function constructs and sends a multipart request.
*
* `mpUrl` is a string containing the URL where the multipart request
* will be made to.
*
* `mpHeaders` is an object that contains HTTP headers for the whole
* request. These will be combined with the appropriate Content-Type (CT)
* header, so providing the CT header is not required.
*
* `parts` is a list of objects of structure { headers, body }
* -> where `headers` is an object of HTTP headers and values.
* -> where `body` is a string or Node.js stream object.
*
* Returns into `callback` with the expected `request` parameters of
* structure { error, request, body }.
*
*
* @memberOf Utils
* @private
* @function
* @param mpUrl
* @param mpHeaders
* @param parts
* @param callback
*/
exports.sendMultipart = function (mpUrl, mpHeaders, parts, callback) {
var crlf = '\r\n';
var tempPath = temp.path('docusign');
var multipart = fs.createWriteStream(tempPath);
var boundary = exports.generateNewGuid();
mpHeaders['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
exports.log('mpHeaders', mpHeaders);
var buildMultipartBody = parts.reduce(function (lastPromise, part) {
return lastPromise.then(function () {
return new Bluebird(function (resolve, reject) {
var headers = Object.keys(part.headers).map(function (key) {
return key + ': ' + part.headers[key];
}).join(crlf);
exports.log('part.headers', part.headers);
multipart.write('--' + boundary + crlf);
multipart.write(headers);
multipart.write(crlf + crlf);
var body = (Buffer.isBuffer(part.body) || typeof part.body === 'string')
? _createStringStream(part.body) : part.body;
body.on('data', function (chunk) {
multipart.write(chunk);
});
body.on('end', function () {
multipart.write(crlf);
resolve();
});
body.resume();
});
});
}, Bluebird.resolve());
return new Bluebird(function (resolve, reject) {
buildMultipartBody.then(function () { // called when all is done
multipart.write('--' + boundary + '--');
var endAsync = Bluebird.promisify(multipart.end, {context: multipart});
return endAsync().then(function () {
fs.createReadStream(tempPath).pipe(request({
method: 'POST',
url: mpUrl,
headers: mpHeaders
}, function (error, response, body) {
try {
fs.unlinkSync(tempPath);
} catch (e) {
exports.log('Failed to fs.unlink', e);
}
if (error) {
return reject(error);
}
exports.log('sendMultipart response body', body);
resolve([response, body]);
}));
})
.catch(function (err) {
exports.log('sendMultipart error', err);
reject(err);
});
});
}).asCallback(callback, { spread: true });
};
function _createStringStream (str) {
var s = new stream.Readable();
s.pause();
s.push(str);
s.push(null);
return s;
}