ebay-api
Version:
eBay API for Node and Browser
176 lines (175 loc) • 5.24 kB
JavaScript
import debug from 'debug';
import { XMLBuilder, XMLParser } from 'fast-xml-parser';
import { checkEBayResponse, EbayNoCallError } from '../../errors/index.js';
const log = debug('ebay:xml:request');
export const defaultJSON2XMLOptions = {
attributeNamePrefix: '@_',
textNodeName: '#value',
ignoreAttributes: false,
cdataTagName: '__cdata',
cdataPositionChar: '\\c',
format: false,
indentBy: ' ',
suppressEmptyNode: false,
cdataPropName: '__cdata'
};
export const defaultXML2JSONParseOptions = {
attributeNamePrefix: '',
textNodeName: 'value',
ignoreAttributes: false,
parseAttributeValue: true,
parseNodeValue: true,
numberParseOptions: {
hex: false,
leadingZeros: false
},
removeNSPrefix: true,
isArray: (name, jpath) => {
return /Array$/.test(jpath.slice(0, -name.length - 1));
}
};
export const defaultApiConfig = {
raw: false,
parseOptions: defaultXML2JSONParseOptions,
useIaf: true,
sign: false,
headers: {},
returnResponse: false
};
export const defaultHeaders = {
'Content-Type': 'text/xml'
};
/**
* XML request for making eBay API call.
*/
export default class XMLRequest {
/**
* Creates the new Request object
*
* @private
* @param {string} callName the callname
* @param {Object} fields the fields
* @param {Object} req the request
* @param {XMLReqConfig} config
*/
constructor(callName, fields, config, req) {
if (!callName) {
throw new EbayNoCallError();
}
this.callName = callName;
this.fields = fields || {};
this.config = { ...defaultApiConfig, ...config };
this.req = req;
}
/**
* returns the expected name of XML node of a Request
*
* @private
* @return {String} callnameReponse
*/
getResponseWrapper() {
return `${this.callName}Response`;
}
/**
* returns the XML structure for the SOAP auth
*
* @private
* @return {Object} the RequesterCredentials
*/
getCredentials() {
return this.config.eBayAuthToken ? {
RequesterCredentials: {
eBayAuthToken: this.config.eBayAuthToken
}
} : {};
}
getParseOptions() {
return {
...defaultXML2JSONParseOptions,
...this.config.parseOptions
};
}
getHeaders() {
return {
...defaultHeaders,
...this.config.headers,
};
}
/**
* Converts an XML response to JSON
*
* @param {string} xml the xml
* @return {JSON} resolves to a JSON representation of the HTML
*/
toJSON(xml) {
const parseOptions = this.getParseOptions();
log('parseOption', parseOptions);
return new XMLParser(parseOptions).parse(xml);
}
/**
* returns the XML document for the request
*
* @private
* @param {Fields} fields the fields
* @return {String} The XML string of the Request
*/
toXML(fields) {
const HEADING = '<?xml version="1.0" encoding="utf-8"?>';
return HEADING + XMLRequest.j2x.build({
[this.callName + 'Request']: {
'@_xmlns': this.config.xmlns,
...this.getCredentials(),
...fields
}
});
}
/**
* Call the xml api endpoint.
*
* @private
* @return {Promise} resolves to the response
*
*/
async request() {
const xml = this.toXML(this.fields);
log('xml', xml);
try {
const { body, headers = {} } = this.config.hook?.(xml) ?? { body: xml };
const config = {
headers: {
...this.getHeaders(),
...this.config.digitalSignatureHeaders ? this.config.digitalSignatureHeaders(body) : {},
...(headers ? headers : {})
}
};
log('config', config);
const response = await this.req.post(this.config.endpoint, body, config);
log('response', response);
if (this.config.returnResponse) {
return response;
}
const { data } = response;
// return raw XML
if (this.config.raw) {
return data;
}
const json = this.xml2JSON(data);
checkEBayResponse(json);
return json;
}
catch (error) {
log('error', error);
if (error.response?.data) {
const json = this.toJSON(error.response.data);
checkEBayResponse(json);
}
throw error;
}
}
xml2JSON(xml) {
const json = this.toJSON(xml);
// Unwrap
return json[this.getResponseWrapper()] ?? json;
}
}
XMLRequest.j2x = new XMLBuilder(defaultJSON2XMLOptions);