diff --git a/lib/Client.js b/lib/Client.js index a618926..7788da0 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -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, diff --git a/lib/orders/H004/response.js b/lib/orders/H004/response.js index 36b6604..ed91800 100644 --- a/lib/orders/H004/response.js +++ b/lib/orders/H004/response.js @@ -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')); }, diff --git a/lib/orders/H004/serializers/download.js b/lib/orders/H004/serializers/download.js index 1fbd8be..7bb754d 100644 --- a/lib/orders/H004/serializers/download.js +++ b/lib/orders/H004/serializers/download.js @@ -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,35 +25,46 @@ 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, - Nonce: Crypto.nonce(), - Timestamp: Crypto.timestamp(), - PartnerID: ebicsAccount.partnerId, - UserID: ebicsAccount.userId, - Product: { - '@': { Language: 'en' }, - '#': productString, - }, - OrderDetails: orderDetails, - BankPubKeyDigests: { - Authentication: { - '@': { Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' }, - '#': Crypto.digestPublicKey(keys.bankX()), + ...!transactionId && { + Nonce: Crypto.nonce(), + Timestamp: Crypto.timestamp(), + PartnerID: ebicsAccount.partnerId, + UserID: ebicsAccount.userId, + Product: { + '@': { Language: 'en' }, + '#': productString, }, - Encryption: { - '@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' }, - '#': Crypto.digestPublicKey(keys.bankE()), + OrderDetails: orderDetails, + BankPubKeyDigests: { + Authentication: { + '@': { Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' }, + '#': Crypto.digestPublicKey(keys.bankX()), + }, + Encryption: { + '@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' }, + '#': Crypto.digestPublicKey(keys.bankE()), + }, }, + SecurityMedium: '0000', + }, + ...transactionId && { + TransactionID: transactionId, }, - SecurityMedium: '0000', }, mutable: { - TransactionPhase: 'Initialisation', + TransactionPhase: segmentNumber ? 'Transfer' : 'Initialisation', + ...segmentNumber && { + SegmentNumber: { + '@': { lastSegment: false }, + '#': segmentNumber, + }, + }, }, }; diff --git a/package-lock.json b/package-lock.json index 7dbfc70..6e6a171 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": {