chore(download): Add support for segmented download

This commit is contained in:
Vlad Gramuzov 2021-05-14 01:16:50 +03:00
parent fe0f585b27
commit 59515f21e3
No known key found for this signature in database
GPG Key ID: 6D48E708EDC4E583
4 changed files with 70 additions and 29 deletions

View File

@ -147,14 +147,35 @@ module.exports = class Client {
async download(order) {
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.DOWNLOAD');
const res = await this.ebicsRequest(order);
const orderData = [];
let res = await this.ebicsRequest(order);
let transactionKey = res.transactionKey();
order.transactionId = res.transactionId();
order.segmentNumber = res.segmentNumber();
if (res.orderData()) orderData.push(res.orderData());
// In case of multi-segment download transaction is
// usually supplied during INITIALISATION phase,
// whereas the actual data is delivered in following segments
while (res.isSegmented() && !res.isLastSegment()) {
order.segmentNumber = res.segmentNumber() + 1;
res = await this.ebicsRequest(order);
transactionKey = transactionKey || res.transactionKey();
res.obtainedTransactionKey = transactionKey;
if (res.orderData()) orderData.push(res.orderData());
}
if (res.isSegmented() && res.isLastSegment()) {
if (this.tracesStorage)
this.tracesStorage.connect().ofType('RECEIPT.ORDER.DOWNLOAD');
order.segmentNumber = null; // Clear segment number for receipt request
await this.ebicsRequest(order);
}
@ -162,7 +183,7 @@ module.exports = class Client {
const returnedBusinessCode = res.businessCode();
return {
orderData: res.orderData(),
orderData: orderData.length === 1 ? orderData[0] : orderData, // backward compatibility
orderId: res.orderId(),
technicalCode: returnedTechnicalCode,

View File

@ -22,6 +22,7 @@ const lastChild = (node) => {
module.exports = (xml, keys) => ({
keys,
doc: new DOMParser().parseFromString(xml, 'text/xml'),
obtainedTransactionKey: null,
isSegmented() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
@ -30,6 +31,13 @@ module.exports = (xml, keys) => ({
return !!node.length;
},
segmentNumber() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:header/xmlns:mutable/xmlns:SegmentNumber', this.doc);
return node.length ? Number.parseInt(node[0].textContent, 10) : null;
},
isLastSegment() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", this.doc);
@ -40,7 +48,7 @@ module.exports = (xml, keys) => ({
orderData() {
const orderDataNode = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'OrderData');
if (!orderDataNode.length) return {};
if (!orderDataNode.length) return null;
const orderData = orderDataNode[0].textContent;
const decipher = crypto.createDecipheriv('aes-128-cbc', this.transactionKey(), DEFAULT_IV).setAutoPadding(false);
@ -50,6 +58,7 @@ module.exports = (xml, keys) => ({
},
transactionKey() {
if (this.obtainedTransactionKey) return this.obtainedTransactionKey;
const keyNodeText = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'TransactionKey')[0].textContent;
return Crypto.privateDecrypt(this.keys.e(), Buffer.from(keyNodeText, 'base64'));
},

View File

@ -13,7 +13,7 @@ module.exports = {
userId: client.userId,
hostId: client.hostId,
};
const { orderDetails, transactionId } = order;
const { orderDetails, transactionId, segmentNumber } = order;
const {
rootName, xmlOptions, xmlSchema, receipt, transfer, productString,
} = genericSerializer(client.hostId, transactionId);
@ -25,12 +25,13 @@ module.exports = {
this.receipt = receipt;
this.transfer = transfer;
if (transactionId) return this.receipt();
if (!segmentNumber && transactionId) return this.receipt();
this.xmlSchema.header = {
'@': { authenticate: true },
static: {
HostID: ebicsAccount.hostId,
...!transactionId && {
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: ebicsAccount.partnerId,
@ -52,8 +53,18 @@ module.exports = {
},
SecurityMedium: '0000',
},
...transactionId && {
TransactionID: transactionId,
},
},
mutable: {
TransactionPhase: 'Initialisation',
TransactionPhase: segmentNumber ? 'Transfer' : 'Initialisation',
...segmentNumber && {
SegmentNumber: {
'@': { lastSegment: false },
'#': segmentNumber,
},
},
},
};

12
package-lock.json generated
View File

@ -1967,9 +1967,9 @@
"dev": true
},
"hosted-git-info": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"html-escaper": {
@ -2536,9 +2536,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.flattendeep": {