amazon-sp-api
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -19,5 +19,7 @@ const https = require('https'); | ||
let req = https.request(options, (res) => { | ||
let chunks = []; | ||
let body = ''; | ||
res.on('data', (chunk) => { | ||
body += chunk; | ||
chunks.push(chunk); | ||
}); | ||
@@ -27,3 +29,5 @@ res.on('end', () => { | ||
body:body, | ||
statusCode:res.statusCode | ||
chunks:chunks, | ||
statusCode:res.statusCode, | ||
headers:res.headers | ||
}); | ||
@@ -30,0 +34,0 @@ }); |
@@ -7,2 +7,6 @@ const CustomError = require('./CustomError'); | ||
const operations = require('./operations'); | ||
const crypto = require('crypto'); | ||
const csv = require('csvtojson'); | ||
const fs = require('fs/promises'); | ||
const zlib = require('zlib'); | ||
@@ -69,2 +73,13 @@ // Provide credentials as environment variables OR create a path and file ~/.amzspapi/credentials (located in your user folder) | ||
async _unzip(buffer){ | ||
return new Promise((resolve, reject) => { | ||
zlib.gunzip(buffer, (err, unzipped_buffer) => { | ||
if (err){ | ||
reject(err); | ||
} | ||
resolve(unzipped_buffer); | ||
}); | ||
}); | ||
} | ||
async refreshAccessToken(){ | ||
@@ -211,4 +226,99 @@ let res = await request({ | ||
// Will be a tab-delimited flat file or an xml document | ||
// Options object: | ||
// * json: true/false, whether or not the content should be transformed to json before returning it (from tab delimited flat-file or XML). Defaults to false. | ||
// --> IMPORTANT: is ignored when unzip is set to false. | ||
// * unzip: true/false, whether or not the content should be unzipped before returning it. Defaults to true. | ||
// * file: absolute file path to save the report to. Defaults to not saving to disk. | ||
// --> IMPORTANT: Even when saved to disk the report content is still returned. | ||
async download(details, options = {}){ | ||
options = Object.assign({ | ||
unzip:true | ||
}, options); | ||
if (!details || !details.encryptionDetails || !details.url){ | ||
throw new CustomError({ | ||
code:'DOWNLOAD_INFORMATION_MISSING', | ||
message:'Please provide encryptionDetails and url' | ||
}); | ||
} | ||
// Docs state that no other encryption standards should be possible, but check if its correct anyway | ||
if (details.encryptionDetails.standard !== 'AES'){ | ||
throw new CustomError({ | ||
code:'UNKNOWN_ENCRYPTION_STANDARD', | ||
message:'Cannot decrypt ' + details.encryptionDetails.standard + ', expecting AES' | ||
}); | ||
} | ||
let compression = details.compressionAlgorithm; | ||
// Docs state that no other zip standards should be possible, but check if its correct anyway | ||
if (compression && compression !== 'GZIP'){ | ||
throw new CustomError({ | ||
code:'UNKNOWN_ZIP_STANDARD', | ||
message:'Cannot unzip ' + compression + ', expecting GZIP' | ||
}); | ||
} | ||
let res = await request({ | ||
url:details.url | ||
}); | ||
if (res.statusCode !== 200){ | ||
let json_res; | ||
try { | ||
json_res = xml_parser.parse(res.body); | ||
} catch(e){ | ||
throw new CustomError({ | ||
code:'DOWNLOAD_ERROR', | ||
message:res.body | ||
}); | ||
} | ||
if (json_res && json_res.Error){ | ||
throw new CustomError({ | ||
code:json_res.Error.Code, | ||
message:json_res.Error.Message | ||
}); | ||
} else { | ||
throw new CustomError({ | ||
code:'DOWNLOAD_ERROR', | ||
message:json_res | ||
}); | ||
} | ||
} else { | ||
// Decrypt buffer | ||
let encrypted_buffer = Buffer.concat(res.chunks); | ||
let decipher = crypto.createDecipheriv( | ||
'aes-256-cbc', | ||
Buffer.from(details.encryptionDetails.key, 'base64'), | ||
Buffer.from(details.encryptionDetails.initializationVector, 'base64') | ||
); | ||
let decrypted_buffer = Buffer.concat([decipher.update(encrypted_buffer), decipher.final()]); | ||
// Decompress if content is compressed and unzip option is true | ||
if (compression && options.unzip){ | ||
decrypted_buffer = await this._unzip(decrypted_buffer); | ||
} | ||
let decrypted = (!compression || options.unzip) ? decrypted_buffer.toString() : decrypted_buffer; | ||
if ((options.unzip || !compression) && options.json){ | ||
// Transform content to json --> take content type from which to transform to json from result header | ||
try { | ||
if (res.headers['content-type'].includes('xml')){ | ||
decrypted = xml_parser.parse(decrypted); | ||
} else if (res.headers['content-type'].includes('plain')){ | ||
decrypted = await csv({ | ||
delimiter:'\t' | ||
}).fromString(decrypted); | ||
} | ||
} catch(e){ | ||
throw new CustomError({ | ||
code:'PARSE_ERROR', | ||
message:'Could not parse result to JSON.', | ||
details:decrypted | ||
}); | ||
} | ||
} | ||
if (options.file){ | ||
options.json ? await fs.writeFile(options.file, JSON.stringify(decrypted)) : await fs.writeFile(options.file, decrypted); | ||
} | ||
return decrypted; | ||
} | ||
} | ||
}; | ||
module.exports = SellingPartner; |
{ | ||
"name": "amazon-sp-api", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "Amazon Selling Partner API client", | ||
@@ -24,2 +24,3 @@ "main": "index.js", | ||
"crypto-js": "^4.0.0", | ||
"csvtojson": "^2.0.10", | ||
"fast-xml-parser": "^3.17.4", | ||
@@ -26,0 +27,0 @@ "qs": "^6.9.4" |
@@ -71,3 +71,3 @@ # amazon-sp-api (client for the Amazon Selling Partner API) | ||
auto_request_tokens:true, // Optional, whether or not the client should retrieve new access and role credentials if non given or expired. Default is true | ||
auto_request_throttled:true // Optional: Whether or not the client should automatically retry a request when throttled. Default is true | ||
auto_request_throttled:true // Optional, whether or not the client should automatically retry a request when throttled. Default is true | ||
} | ||
@@ -107,3 +107,3 @@ } | ||
The .callAPI() function takes an object as input: | ||
The **.callAPI()** function takes an object as input: | ||
* operation: Required, the operation you want to request [see SP API References](https://github.com/amzn/selling-partner-api-docs/tree/main/references) | ||
@@ -114,3 +114,3 @@ * path: The input paramaters added to the path of the operation | ||
## Examples | ||
#### Examples | ||
```javascript | ||
@@ -147,3 +147,46 @@ let res = await sellingPartner.callAPI({ | ||
### Download, decrypt and unzip reports | ||
The **.download()** function takes the download details (url and encryption details) received from a "getReportDocument" operation as input, downloads the content, unzips it (if result is compressed), decrypts it and returns it. | ||
You may also include an options object to enable a json result or to additionally save the report to a file. | ||
Retrieve the download details with a "getReportDocument" operation: | ||
```javascript | ||
let report_document = await sellingPartner.callAPI({ | ||
operation:'getReportDocument', | ||
path:{ | ||
reportDocumentId:'<REPORT_DOCUMENT_ID>' // retrieve the reportDocumentId from a "getReport" operation (when processingStatus of report is "DONE") | ||
} | ||
}); | ||
``` | ||
The structure of the returned report_document should look like this: | ||
```javascript | ||
{ | ||
reportDocumentId:'<REPORT_DOCUMENT_ID>', | ||
compressionAlgorithm:'GZIP', // Only included if report is compressed | ||
encryptionDetails:{ | ||
standard:'AES', | ||
initializationVector:'<INITIALIZATION_VECTOR>', | ||
key:'<KEY>' | ||
}, | ||
url: '<REPORT_DOWNLOAD_URL>' // Expires after 5 minutes! | ||
} | ||
``` | ||
Call the .download() function to receive the content of the report. The default without any config options will download, decrypt and unzip the content and return it without reformatting or saving it to the disk: | ||
```javascript | ||
let report = await sellingPartner.download(report_document); | ||
``` | ||
The options object has three optional parameters: | ||
* json: true/false, whether or not the content should be transformed to json before returning it (from tab delimited flat-file or XML). Defaults to false. IMPORTANT: is ignored when unzip is set to false. | ||
* unzip: true/false, whether or not the content should be unzipped before returning it. Defaults to true. | ||
* file: absolute file path to save the report to. Defaults to not saving to disk. IMPORTANT: Even when saved to disk the report content is still returned. | ||
The following call will download the report, transform it to json and save it to disk: | ||
```javascript | ||
let report = await sellingPartner.download(report_document, { | ||
json:true, | ||
file:'<YOUR_ABSOLUTE_FILE_PATH>/report.json' | ||
}); | ||
``` | ||
## Known Issues | ||
Since the Selling Partner API is still pretty new, not all API paths and endpoints have been tested for full functionality. If you find any calls not working please open up a new issue. |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
90865
3090
188
1
4
4
+ Addedcsvtojson@^2.0.10
+ Addedbluebird@3.7.2(transitive)
+ Addedcsvtojson@2.0.10(transitive)
+ Addedis-utf8@0.2.1(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedstrip-bom@2.0.0(transitive)