24
1
Fork 0

use actual file size in dl progress. detect cancelled stream

This commit is contained in:
Danny Coates 2018-07-23 15:12:58 -07:00
parent 2afe79c941
commit 5483dc2506
No known key found for this signature in database
GPG Key ID: 4C442633C62E00CB
6 changed files with 35 additions and 30 deletions

View File

@ -74,7 +74,7 @@ export async function metadata(id, keychain) {
const data = await result.response.json();
const meta = await keychain.decryptMetadata(b64ToArray(data.metadata));
return {
size: data.size,
size: meta.size,
ttl: data.ttl,
iv: meta.iv,
name: meta.name,

View File

@ -172,6 +172,7 @@ export default class Keychain {
JSON.stringify({
iv: arrayToB64(this.iv),
name: metadata.name,
size: metadata.size,
type: metadata.type || 'application/octet-stream'
})
)

View File

@ -14,8 +14,7 @@ self.addEventListener('activate', event => {
self.clients.claim();
});
async function decryptStream(request) {
const id = request.url.split('/')[5];
async function decryptStream(id) {
try {
const file = map.get(id);
const keychain = new Keychain(file.key, file.nonce);
@ -27,20 +26,29 @@ async function decryptStream(request) {
const body = await file.download.result;
const readStream = transformStream(body, {
transform: (chunk, controller) => {
file.progress += chunk.length;
controller.enqueue(chunk);
const decrypted = keychain.decryptStream(body);
const readStream = transformStream(
decrypted,
{
transform(chunk, controller) {
file.progress += chunk.length;
controller.enqueue(chunk);
}
},
function oncancel() {
// NOTE: cancel doesn't currently fire on chrome
// https://bugs.chromium.org/p/chromium/issues/detail?id=638494
file.download.cancel();
map.delete(id);
}
});
const decrypted = keychain.decryptStream(readStream);
);
const headers = {
'Content-Disposition': contentDisposition(file.filename),
'Content-Type': file.type,
'Content-Length': file.size
};
return new Response(decrypted, { headers });
return new Response(readStream, { headers });
} catch (e) {
if (noSave) {
return new Response(null, { status: e.message });
@ -48,16 +56,14 @@ async function decryptStream(request) {
const redirectRes = await fetch(`/download/${id}`);
return new Response(redirectRes.body, { status: 302 });
} finally {
// TODO: need to clean up, but not break progress
// map.delete(id)
}
}
self.onfetch = event => {
const req = event.request.clone();
const req = event.request;
if (req.url.includes('/api/download')) {
event.respondWith(decryptStream(req));
const id = req.url.split('/')[5];
event.respondWith(decryptStream(id));
}
};
@ -73,8 +79,7 @@ self.onmessage = event => {
url: event.data.url,
type: event.data.type,
size: event.data.size,
progress: 0,
cancelled: false
progress: 0
};
map.set(event.data.id, info);
@ -82,19 +87,20 @@ self.onmessage = event => {
} else if (event.data.request === 'progress') {
const file = map.get(event.data.id);
if (!file) {
event.ports[0].postMessage({ progress: 0 });
} else if (file.cancelled) {
event.ports[0].postMessage({ error: 'cancelled' });
} else {
if (file.progress === file.size) {
map.delete(event.data.id);
}
event.ports[0].postMessage({ progress: file.progress });
}
} else if (event.data.request === 'cancel') {
const file = map.get(event.data.id);
if (file) {
file.cancelled = true;
if (file.download) {
file.download.cancel();
}
map.delete(event.data.id);
}
event.ports[0].postMessage('download cancelled');
}

View File

@ -1,6 +1,6 @@
/* global ReadableStream TransformStream */
export function transformStream(readable, transformer) {
export function transformStream(readable, transformer, oncancel) {
if (typeof TransformStream === 'function') {
return readable.pipeThrough(new TransformStream(transformer));
}
@ -30,8 +30,11 @@ export function transformStream(readable, transformer) {
await transformer.transform(data.value, wrappedController);
}
},
cancel() {
readable.cancel();
cancel(reason) {
readable.cancel(reason);
if (oncancel) {
oncancel(reason);
}
}
});
}

View File

@ -4,12 +4,10 @@ module.exports = async function(req, res) {
const id = req.params.id;
const meta = req.meta;
try {
const size = await storage.length(id);
const ttl = await storage.ttl(id);
res.send({
metadata: meta.metadata,
finalDownload: meta.dl + 1 === meta.dlimit,
size,
ttl
});
} catch (e) {

View File

@ -30,16 +30,15 @@ describe('/api/metadata', function() {
storage.length.reset();
});
it('calls storage.[ttl|length] with the id parameter', async function() {
it('calls storage.ttl with the id parameter', async function() {
const req = request('x');
const res = response();
await metadataRoute(req, res);
sinon.assert.calledWith(storage.ttl, 'x');
sinon.assert.calledWith(storage.length, 'x');
});
it('sends a 404 on failure', async function() {
storage.length.returns(Promise.reject(new Error()));
storage.ttl.returns(Promise.reject(new Error()));
const res = response();
await metadataRoute(request('x'), res);
sinon.assert.calledWith(res.sendStatus, 404);
@ -47,7 +46,6 @@ describe('/api/metadata', function() {
it('returns a json object', async function() {
storage.ttl.returns(Promise.resolve(123));
storage.length.returns(Promise.resolve(987));
const meta = {
dlimit: 1,
dl: 0,
@ -58,7 +56,6 @@ describe('/api/metadata', function() {
sinon.assert.calledWithMatch(res.send, {
metadata: 'foo',
finalDownload: true,
size: 987,
ttl: 123
});
});