@adobe/pdftools-extract-node-sdk
Version:
The Document Services PDF Tools Extract Node.js SDK provides APIs for extracting elements and renditions from PDF
290 lines (267 loc) • 9.12 kB
JavaScript
/*
* Copyright 2019 Adobe
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in
* accordance with the terms of the Adobe license agreement accompanying
* it. If you have received this file from a source other than Adobe,
* then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*/
const ServiceApiError = require('../../error/service-api-error'),
ServiceUsageError = require('../../error/service-usage-error'),
http = require('http'),
https = require('https'),
zlib = require('zlib'),
_ = require('lodash/core'),
Url = require('url'),
DefaultHeaders = require('./default-dc-request-options'),
logger = require('./../logger'),
specialHttpErrorCodes = require('./../config/dc-services-default-config').specialHttpErrorCodes,
apiGatewayErrorCodes = require('./../config/dc-services-default-config').apiGatewayErrorCodes,
cpfErrorCodes = require('./../config/dc-services-default-config').cpfErrorCodes,
imsErrorCodes = require('./../config/dc-services-default-config').imsErrorCodes;
// IMS error handling specific constants
const IMS_CERTIFICATE_EXPIRY_ERROR_DESCRIPTION_STRING = 'Could not match JWT signature to any of the bindings';
function rejectWithServiceApiError(requestOptions, promiseReject, httpResponse) {
if (requestOptions.authenticate === true) {
promiseReject(new ServiceApiError(
specialHttpErrorCodes[httpResponse.status],
requestOptions.headers[DefaultHeaders.DC_REQUEST_ID_HEADER_KEY], httpResponse.status
));
}
promiseReject(new ServiceApiError(
specialHttpErrorCodes[httpResponse.status],
requestOptions.headers[DefaultHeaders.SESSION_TOKEN_REQUEST_ID_HEADER_KEY], httpResponse.status
));
}
function handleJsonResponse(result, reject, options) {
try {
result.content = JSON.parse(result.content);
if(result.content.error){//for checking cpf platform related errors of wrong credentials.
result.content.message = result.content.error.message;
}
else
// Merging different types of possible error message fields into a single one
result.content.message = result.content.message || result.content.report || result.content.title || result.content['cpf:status'].title;
} catch (e) {
rejectWithServiceApiError(options, reject, result);
}
}
function getApiGatewayError(result) {
let apiGatewaySubErrorCodesForStatus = apiGatewayErrorCodes[result.status];
/* additional PAPI handling (for result.content.error.details.error_code)
{
"error": {
"code": "<code>",
"message": "<message>",
"details": {
"error_code": "429001/429002"
}
}
}
*/
if (apiGatewaySubErrorCodesForStatus && (apiGatewaySubErrorCodesForStatus[result.content.error_code] || apiGatewaySubErrorCodesForStatus[result.content.error.details.error_code])) {
return apiGatewaySubErrorCodesForStatus[result.content.error_code].errorMessage;
}
}
/*
* Sample message for quota exhaustion for PAPI based engine.
{ status: 429,
statusText: 429,
headers:<some headers>,
content:
{ 'cpf:status':
{ completed: true,
type:
'{ "error_code": "429001", "message": " Exceeded your Quota to call this functionality " }',
title: 'Client exhausted the Quota ',
status: 429,
report: '' },
'cpf:engine':
{ 'repo:assetId': 'urn:aaid:cpf:Service-4735fcf3cf924b25879e6fcf7aa84ad4' },
'cpf:inputs': { documentIn: [Object], params: [Object] },
message: 'Client exhausted the Quota ' } }
*/
function getCPFError(result) {
let cpfSubErrorCodesForStatus = cpfErrorCodes[result.status];
if (cpfSubErrorCodesForStatus) {
let errorBody = JSON.parse(result.content.type || result.content['cpf:status'].type);
if (cpfSubErrorCodesForStatus[errorBody.error_code]) {
return cpfSubErrorCodesForStatus[errorBody.error_code].errorMessage;
}
}
}
function getIMSError(result) {
let imsErrorCode = imsErrorCodes[result.status];
if (imsErrorCode) {
let errorSubCode = imsErrorCode[result.content.error];
if (errorSubCode) {
// Special handling for invalid token and certificate expiry cases from IMS (status code 400)
if (result.status === 400) {
if (result.content.error_description === IMS_CERTIFICATE_EXPIRY_ERROR_DESCRIPTION_STRING) {
return errorSubCode.imsCertificateExpiredErrorMessage;
} else {
return errorSubCode.imsInvalidTokenGenericErrorMessage
}
}
}
}
}
function handleErrorResponse(result, options, reject) {
handleJsonResponse(result, reject, options);
let customErrorMessage = getCustomErrorMessage(result);
// Reject with ServiceUsageError for Service Usage Errors with status code 429
if(result.status == 429) {
reject(new ServiceUsageError(
customErrorMessage || result.content.message,
result.headers[DefaultHeaders.DC_REQUEST_ID_HEADER_KEY], result.status
));
}
reject(
new ServiceApiError(
customErrorMessage || result.content.error_description || result.content.message,
result.headers[DefaultHeaders.SESSION_TOKEN_REQUEST_ID_HEADER_KEY]
|| result.headers[DefaultHeaders.DC_REQUEST_ID_HEADER_KEY],
result.status
));
}
function isTextContentType(type) {
return (/^text\/|json;|json$|xml;|xml$|svg;|svg$/).test(type);
}
function onData(chunk, ary) {
if (chunk) {
ary.push(chunk);
}
return ary;
}
function onEnd(chunk, res, ary, options, fulfill, reject) {
if (chunk) {
ary.push(chunk);
}
const resType = res.headers['content-type'],
result = {
status: res.statusCode,
statusText: res.statusText || res.statusCode,
headers: res.headers,
content: isTextContentType(resType) ?
ary.join('') :
(ary.length === 1 ? ary[0] : Buffer.concat(ary))
};
if (specialHttpErrorCodes[result.status]) {
rejectWithServiceApiError(options, reject, result);
}
// Allow 401 status response to resolve for request retry
if (result.status >= 400 && result.status != 401) {
handleErrorResponse(result, options, reject);
} else {
fulfill(result);
}
fulfill(result);
}
function createHttpRequest(options, fulfill, reject) {
let res;
let ary = [],
httpLib = options.protocol === 'http:' ? http : https;
return httpLib.request(options, response => {
res = response;
// response_content_open is set to true for download file cases
if (options.response_content_open) {
if(res.statusCode >= 400) {
res.on('data', function (chunk) {
ary = onData(chunk, ary);
});
res.on('end', function (chunk) {
onEnd(chunk, res, ary, options, fulfill, reject);
});
} else {
fulfill(response);
}
} else if (/gzip/.test(res.headers['content-encoding'])) {
const gunzip = zlib.createGunzip();
res.pipe(gunzip);
gunzip.on('data', data => {
ary.push(data.toString());
})
.on('end', function (chunk) {
onEnd(chunk, res, ary, options, fulfill, reject);
});
} else {
res.on('data', function (chunk) {
ary = onData(chunk, ary);
});
res.on('end', function (chunk) {
onEnd(chunk, res, ary, options, fulfill, reject);
});
}
});
}
function onError(err, reject) {
logger.error(`Unexpected Error, request could not be completed ${err}`);
reject(new Error(`Unexpected error encountered while executing request ${err}`));
}
/**
* Returns custom error messages corresponding to specific error bound codes and conditions
*/
function getCustomErrorMessage(result) {
if (result.content.error_code || result.content.error) { // API gateway errors
return getApiGatewayError(result);
} else if (result.content.type || result.content['cpf:status']) { // CPF errors
return getCPFError(result);
} else if (result.content.error) { // IMS errors
return getIMSError(result);
}
}
module.exports = {
http: {
getHeader(map, name) {
if (!map || !name) {
return null;
}
const result = map[name];
return result !== undefined ? result : map[String(name)
.toLowerCase()];
},
getRequestId(options) {
return options[DefaultHeaders.DC_REQUEST_ID_HEADER_KEY] ||
options[DefaultHeaders.SESSION_TOKEN_REQUEST_ID_HEADER_KEY];
},
call(requestParams, content, multiPartData) {
const self = this,
url = requestParams.uri,
options = _.extend(requestParams || {}, Url.parse(url));
if (content) {
if (_.isString(content)) {
options.headers['Content-Length'] = content.length;
}
if (!self.getHeader(options.headers, 'Content-Type')) {
options.headers['Content-Type'] =
'application/x-www-form-urlencoded; charset=UTF-8';
}
}
return new Promise((fulfill, reject) => {
const req = createHttpRequest(options, fulfill, reject);
req.setTimeout(options.readTimeout);
req.on('error', function (error) {
onError(error, reject);
});
req.on('timeout', () => {
req.destroy();
reject(new ServiceApiError(
`request timed out, ${options.timeout / 1000} seconds expired`,
self.getRequestId(options), null
));
});
if (options.request_content_open) {
multiPartData.pipe(req);
req.end();
} else {
if (content) {
req.write(content);
}
req.end();
}
});
}
}
};