UNPKG

@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
/* * 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(); } }); } } };