diff --git a/app/api.js b/app/api.js index e314657d..7d9fb394 100644 --- a/app/api.js +++ b/app/api.js @@ -160,6 +160,7 @@ function download(id, keychain, onprogress, canceller) { resolve(this.result); }; }); + xhr.addEventListener('progress', function(event) { if (event.lengthComputable && event.target.status === 200) { onprogress([event.loaded, event.total]); @@ -168,8 +169,10 @@ function download(id, keychain, onprogress, canceller) { const auth = await keychain.authHeader(); xhr.open('get', `/api/download/${id}`); xhr.setRequestHeader('Authorization', auth); + xhr.setRequestHeader('Connection', 'close'); xhr.responseType = 'blob'; xhr.send(); + onprogress([0, 1]); }); } diff --git a/server/routes/download.js b/server/routes/download.js index b320d899..84ba715d 100644 --- a/server/routes/download.js +++ b/server/routes/download.js @@ -14,10 +14,15 @@ module.exports = async function(req, res) { 'WWW-Authenticate': `send-v1 ${req.nonce}` }); const file_stream = storage.get(id); - let sentBytes = 0; - file_stream.on('data', c => (sentBytes += c.length)); - file_stream.on('end', async () => { - if (sentBytes < contentLength) { + let cancelled = false; + + req.on('close', () => { + cancelled = true; + file_stream.destroy(); + }); + + file_stream.on('close', async () => { + if (cancelled) { return; } const dl = meta.dl + 1; @@ -32,6 +37,7 @@ module.exports = async function(req, res) { log.info('StorageError:', id); } }); + file_stream.pipe(res); } catch (e) { res.sendStatus(404); diff --git a/test/frontend/tests/workflow-tests.js b/test/frontend/tests/workflow-tests.js index 23bc8efc..69eba9dd 100644 --- a/test/frontend/tests/workflow-tests.js +++ b/test/frontend/tests/workflow-tests.js @@ -134,6 +134,27 @@ describe('Upload / Download flow', function() { } }); + it('can cancel and not increase download count', async function() { + const fs = new FileSender(blob); + const file = await fs.upload(); + const fr = new FileReceiver({ + secretKey: file.toJSON().secretKey, + id: file.id, + nonce: file.keychain.nonce, + requiresPassword: false + }); + await fr.getMetadata(); + fr.once('progress', () => fr.cancel()); + + try { + await fr.download(noSave); + assert.fail('not cancelled'); + } catch (e) { + await file.updateDownloadCount(); + assert.equal(file.dtotal, 0); + } + }); + it('can allow multiple downloads', async function() { const fs = new FileSender(blob); const file = await fs.upload();