Merge pull request #457 from mozilla/refactor-progress
factored out progress into progress.js
This commit is contained in:
commit
d2b57039bf
|
@ -1,96 +1,49 @@
|
|||
const { Raven } = require('./common');
|
||||
const FileReceiver = require('./fileReceiver');
|
||||
const { notify, gcmCompliant } = require('./utils');
|
||||
const bytes = require('bytes');
|
||||
const { bytes, notify, gcmCompliant } = require('./utils');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
const links = require('./links');
|
||||
const metrics = require('./metrics');
|
||||
|
||||
const progress = require('./progress');
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
|
||||
$(() => {
|
||||
gcmCompliant()
|
||||
.then(() => {
|
||||
function onUnload(size) {
|
||||
metrics.cancelledDownload({ size });
|
||||
}
|
||||
|
||||
function download() {
|
||||
const $downloadBtn = $('#download-btn');
|
||||
const $dlProgress = $('#dl-progress');
|
||||
const $progressText = $('.progress-text');
|
||||
const $title = $('.title');
|
||||
|
||||
const filename = $('#dl-filename').text();
|
||||
const size = Number($('#dl-size').text());
|
||||
const ttl = Number($('#dl-ttl').text());
|
||||
|
||||
//initiate progress bar
|
||||
$dlProgress.circleProgress({
|
||||
value: 0.0,
|
||||
startAngle: -Math.PI / 2,
|
||||
fill: '#3B9DFF',
|
||||
size: 158,
|
||||
animation: { duration: 300 }
|
||||
});
|
||||
|
||||
const download = () => {
|
||||
// Disable the download button to avoid accidental double clicks.
|
||||
$downloadBtn.attr('disabled', 'disabled');
|
||||
links.setOpenInNewTab(true);
|
||||
|
||||
const $file = $('#dl-file');
|
||||
const size = Number($file.attr('data-size'));
|
||||
const ttl = Number($file.attr('data-ttl'));
|
||||
const unloadHandler = onUnload.bind(null, size);
|
||||
const startTime = Date.now();
|
||||
const fileReceiver = new FileReceiver();
|
||||
|
||||
fileReceiver.on('progress', progress => {
|
||||
window.onunload = function() {
|
||||
metrics.cancelledDownload({ size });
|
||||
};
|
||||
|
||||
$downloadBtn.attr('disabled', 'disabled');
|
||||
$('#download-page-one').attr('hidden', true);
|
||||
$('#download-progress').removeAttr('hidden');
|
||||
const percent = progress[0] / progress[1];
|
||||
// update progress bar
|
||||
$dlProgress.circleProgress('value', percent);
|
||||
$('.percent-number').text(`${Math.floor(percent * 100)}`);
|
||||
$progressText.text(
|
||||
`${filename} (${bytes(progress[0], {
|
||||
decimalPlaces: 1,
|
||||
fixedDecimals: true
|
||||
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
|
||||
);
|
||||
metrics.startedDownload({ size, ttl });
|
||||
links.setOpenInNewTab(true);
|
||||
window.addEventListener('unload', unloadHandler);
|
||||
|
||||
fileReceiver.on('progress', data => {
|
||||
progress.setProgress({ complete: data[0], total: data[1] });
|
||||
});
|
||||
|
||||
let downloadEnd;
|
||||
fileReceiver.on('decrypting', isStillDecrypting => {
|
||||
// The file is being decrypted
|
||||
if (isStillDecrypting) {
|
||||
fileReceiver.removeAllListeners('progress');
|
||||
window.onunload = null;
|
||||
document.l10n.formatValue('decryptingFile').then(decryptingFile => {
|
||||
$progressText.text(decryptingFile);
|
||||
});
|
||||
} else {
|
||||
fileReceiver.on('decrypting', () => {
|
||||
downloadEnd = Date.now();
|
||||
}
|
||||
window.removeEventListener('unload', unloadHandler);
|
||||
fileReceiver.removeAllListeners('progress');
|
||||
document.l10n.formatValue('decryptingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
fileReceiver.on('hashing', isStillHashing => {
|
||||
// The file is being hashed to make sure a malicious user hasn't tampered with it
|
||||
if (isStillHashing) {
|
||||
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
|
||||
$progressText.text(verifyingFile);
|
||||
fileReceiver.on('hashing', () => {
|
||||
document.l10n.formatValue('verifyingFile').then(progress.setText);
|
||||
});
|
||||
} else {
|
||||
$progressText.text(' ');
|
||||
document.l10n
|
||||
.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
$title.text(translated[1]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
metrics.startedDownload({ size, ttl });
|
||||
|
||||
fileReceiver
|
||||
.download()
|
||||
|
@ -115,6 +68,13 @@ $(() => {
|
|||
const speed = size / (downloadTime / 1000);
|
||||
storage.totalDownloads += 1;
|
||||
metrics.completedDownload({ size, time, speed });
|
||||
progress.setText(' ');
|
||||
document.l10n
|
||||
.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
$title.text(translated[1]);
|
||||
});
|
||||
|
||||
const dataView = new DataView(decrypted);
|
||||
const blob = new Blob([dataView]);
|
||||
|
@ -136,9 +96,23 @@ $(() => {
|
|||
return Promise.reject(err);
|
||||
})
|
||||
.then(() => links.setOpenInNewTab(false));
|
||||
};
|
||||
}
|
||||
|
||||
$downloadBtn.on('click', download);
|
||||
$(() => {
|
||||
const $file = $('#dl-file');
|
||||
const filename = $file.attr('data-filename');
|
||||
const b = Number($file.attr('data-size'));
|
||||
const size = bytes(b);
|
||||
document.l10n
|
||||
.formatValue('downloadFileSize', { size })
|
||||
.then(str => $('#dl-filesize').text(str));
|
||||
document.l10n
|
||||
.formatValue('downloadingPageProgress', { filename, size })
|
||||
.then(str => $('#dl-title').text(str));
|
||||
|
||||
gcmCompliant()
|
||||
.then(() => {
|
||||
$('#download-btn').on('click', download);
|
||||
})
|
||||
.catch(err => {
|
||||
metrics.unsupported({ err }).then(() => {
|
||||
|
|
|
@ -62,7 +62,7 @@ class FileReceiver extends EventEmitter {
|
|||
});
|
||||
})
|
||||
.then(([fdata, key]) => {
|
||||
this.emit('decrypting', true);
|
||||
this.emit('decrypting');
|
||||
return Promise.all([
|
||||
window.crypto.subtle
|
||||
.decrypt(
|
||||
|
@ -76,7 +76,6 @@ class FileReceiver extends EventEmitter {
|
|||
fdata.data
|
||||
)
|
||||
.then(decrypted => {
|
||||
this.emit('decrypting', false);
|
||||
return Promise.resolve(decrypted);
|
||||
}),
|
||||
fdata.filename,
|
||||
|
@ -84,11 +83,10 @@ class FileReceiver extends EventEmitter {
|
|||
]);
|
||||
})
|
||||
.then(([decrypted, fname, proposedHash]) => {
|
||||
this.emit('hashing', true);
|
||||
this.emit('hashing');
|
||||
return window.crypto.subtle
|
||||
.digest('SHA-256', decrypted)
|
||||
.then(calculatedHash => {
|
||||
this.emit('hashing', false);
|
||||
const integrity =
|
||||
new Uint8Array(calculatedHash).toString() ===
|
||||
proposedHash.toString();
|
||||
|
|
|
@ -34,7 +34,7 @@ class FileSender extends EventEmitter {
|
|||
|
||||
upload() {
|
||||
const self = this;
|
||||
self.emit('loading', true);
|
||||
self.emit('loading');
|
||||
return Promise.all([
|
||||
window.crypto.subtle.generateKey(
|
||||
{
|
||||
|
@ -48,12 +48,10 @@ class FileSender extends EventEmitter {
|
|||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(this.file);
|
||||
reader.onload = function(event) {
|
||||
self.emit('loading', false);
|
||||
self.emit('hashing', true);
|
||||
self.emit('hashing');
|
||||
const plaintext = new Uint8Array(this.result);
|
||||
window.crypto.subtle.digest('SHA-256', plaintext).then(hash => {
|
||||
self.emit('hashing', false);
|
||||
self.emit('encrypting', true);
|
||||
self.emit('encrypting');
|
||||
resolve({ plaintext: plaintext, hash: new Uint8Array(hash) });
|
||||
});
|
||||
};
|
||||
|
@ -64,8 +62,7 @@ class FileSender extends EventEmitter {
|
|||
])
|
||||
.then(([secretKey, file]) => {
|
||||
return Promise.all([
|
||||
window.crypto.subtle
|
||||
.encrypt(
|
||||
window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv,
|
||||
|
@ -74,13 +71,7 @@ class FileSender extends EventEmitter {
|
|||
},
|
||||
secretKey,
|
||||
file.plaintext
|
||||
)
|
||||
.then(encrypted => {
|
||||
self.emit('encrypting', false);
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(encrypted);
|
||||
});
|
||||
}),
|
||||
),
|
||||
window.crypto.subtle.exportKey('jwk', secretKey),
|
||||
new Promise((resolve, reject) => {
|
||||
resolve(file.hash);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
const { bytes } = require('./utils');
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
|
||||
let $progress = null;
|
||||
let $percent = null;
|
||||
let $text = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
$percent = $('.percent-number');
|
||||
$text = $('.progress-text');
|
||||
$progress = $('.progress-bar');
|
||||
$progress.circleProgress({
|
||||
value: 0.0,
|
||||
startAngle: -Math.PI / 2,
|
||||
fill: '#3B9DFF',
|
||||
size: 158,
|
||||
animation: { duration: 300 }
|
||||
});
|
||||
});
|
||||
|
||||
function setProgress(params) {
|
||||
const percent = params.complete / params.total;
|
||||
$progress.circleProgress('value', percent);
|
||||
$percent.text(`${Math.floor(percent * 100)}`);
|
||||
document.l10n
|
||||
.formatValue('fileSizeProgress', {
|
||||
partialSize: bytes(params.complete),
|
||||
totalSize: bytes(params.total)
|
||||
})
|
||||
.then(setText);
|
||||
}
|
||||
|
||||
function setText(str) {
|
||||
$text.text(str);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setProgress,
|
||||
setText
|
||||
};
|
|
@ -2,18 +2,18 @@
|
|||
const { Raven } = require('./common');
|
||||
const FileSender = require('./fileSender');
|
||||
const {
|
||||
bytes,
|
||||
copyToClipboard,
|
||||
notify,
|
||||
gcmCompliant,
|
||||
ONE_DAY_IN_MS
|
||||
} = require('./utils');
|
||||
const bytes = require('bytes');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
const metrics = require('./metrics');
|
||||
const progress = require('./progress');
|
||||
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
|
||||
const allowedCopy = () => {
|
||||
const support = !!document.queryCommandSupported;
|
||||
|
@ -27,10 +27,8 @@ $(() => {
|
|||
const $copyBtn = $('#copy-btn');
|
||||
const $link = $('#link');
|
||||
const $uploadWindow = $('.upload-window');
|
||||
const $ulProgress = $('#ul-progress');
|
||||
const $uploadError = $('#upload-error');
|
||||
const $uploadProgress = $('#upload-progress');
|
||||
const $progressText = $('.progress-text');
|
||||
const $fileList = $('#file-list');
|
||||
|
||||
$pageOne.removeAttr('hidden');
|
||||
|
@ -96,15 +94,6 @@ $(() => {
|
|||
$uploadWindow.removeClass('ondrag');
|
||||
});
|
||||
|
||||
//initiate progress bar
|
||||
$ulProgress.circleProgress({
|
||||
value: 0.0,
|
||||
startAngle: -Math.PI / 2,
|
||||
fill: '#3B9DFF',
|
||||
size: 158,
|
||||
animation: { duration: 300 }
|
||||
});
|
||||
|
||||
//link back to homepage
|
||||
$('.send-new').attr('href', window.location);
|
||||
|
||||
|
@ -152,9 +141,15 @@ $(() => {
|
|||
$pageOne.attr('hidden', true);
|
||||
$uploadError.attr('hidden', true);
|
||||
$uploadProgress.removeAttr('hidden');
|
||||
document.l10n.formatValue('importingFile').then(importingFile => {
|
||||
$progressText.text(importingFile);
|
||||
document.l10n
|
||||
.formatValue('uploadingPageProgress', {
|
||||
size: bytes(file.size),
|
||||
filename: file.name
|
||||
})
|
||||
.then(str => {
|
||||
$('#upload-filename').text(str);
|
||||
});
|
||||
document.l10n.formatValue('importingFile').then(progress.setText);
|
||||
//don't allow drag and drop when not on page-one
|
||||
$(document.body).off('drop', onUpload);
|
||||
|
||||
|
@ -168,40 +163,21 @@ $(() => {
|
|||
location.reload();
|
||||
});
|
||||
|
||||
fileSender.on('progress', progress => {
|
||||
const percent = progress[0] / progress[1];
|
||||
// update progress bar
|
||||
$ulProgress.circleProgress('value', percent);
|
||||
$ulProgress.circleProgress().on('circle-animation-end', function() {
|
||||
$('.percent-number').text(`${Math.floor(percent * 100)}`);
|
||||
});
|
||||
$progressText.text(
|
||||
`${file.name} (${bytes(progress[0], {
|
||||
decimalPlaces: 1,
|
||||
fixedDecimals: true
|
||||
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
|
||||
);
|
||||
});
|
||||
|
||||
fileSender.on('hashing', isStillHashing => {
|
||||
// The file is being hashed
|
||||
if (isStillHashing) {
|
||||
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
|
||||
$progressText.text(verifyingFile);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let uploadStart;
|
||||
fileSender.on('encrypting', isStillEncrypting => {
|
||||
// The file is being encrypted
|
||||
if (isStillEncrypting) {
|
||||
document.l10n.formatValue('encryptingFile').then(encryptingFile => {
|
||||
$progressText.text(encryptingFile);
|
||||
fileSender.on('progress', data => {
|
||||
uploadStart = uploadStart || Date.now();
|
||||
progress.setProgress({
|
||||
complete: data[0],
|
||||
total: data[1]
|
||||
});
|
||||
} else {
|
||||
uploadStart = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
fileSender.on('hashing', () => {
|
||||
document.l10n.formatValue('verifyingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
fileSender.on('encrypting', () => {
|
||||
document.l10n.formatValue('encryptingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
let t;
|
||||
|
@ -244,16 +220,11 @@ $(() => {
|
|||
};
|
||||
|
||||
storage.addFile(info.fileId, fileData);
|
||||
$('#upload-filename').attr(
|
||||
'data-l10n-id',
|
||||
'uploadSuccessConfirmHeader'
|
||||
);
|
||||
t = window.setTimeout(() => {
|
||||
|
||||
$pageOne.attr('hidden', true);
|
||||
$uploadProgress.attr('hidden', true);
|
||||
$uploadError.attr('hidden', true);
|
||||
$('#share-link').removeAttr('hidden');
|
||||
}, 1000);
|
||||
|
||||
populateFileList(fileData);
|
||||
document.l10n.formatValue('notifyUploadDone').then(str => {
|
||||
|
@ -331,7 +302,7 @@ $(() => {
|
|||
|
||||
$link.attr('value', url);
|
||||
$('#copy-text')
|
||||
.attr('data-l10n-args', `{"filename": "${file.name}"}`)
|
||||
.attr('data-l10n-args', JSON.stringify({ filename: file.name }))
|
||||
.attr('data-l10n-id', 'copyUrlFormLabelWithName');
|
||||
|
||||
$popupText.attr('tabindex', '-1');
|
||||
|
|
|
@ -104,9 +104,29 @@ function copyToClipboard(str) {
|
|||
return result;
|
||||
}
|
||||
|
||||
const LOCALIZE_NUMBERS = !!(
|
||||
typeof Intl === 'object' &&
|
||||
Intl &&
|
||||
typeof Intl.NumberFormat === 'function'
|
||||
);
|
||||
|
||||
const UNITS = ['B', 'kB', 'MB', 'GB'];
|
||||
function bytes(num) {
|
||||
const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
|
||||
const n = Number(num / Math.pow(1000, exponent));
|
||||
const nStr = LOCALIZE_NUMBERS
|
||||
? n.toLocaleString(navigator.languages, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})
|
||||
: n.toFixed(1);
|
||||
return `${nStr}${UNITS[exponent]}`;
|
||||
}
|
||||
|
||||
const ONE_DAY_IN_MS = 86400000;
|
||||
|
||||
module.exports = {
|
||||
bytes,
|
||||
copyToClipboard,
|
||||
arrayToHex,
|
||||
hexToArray,
|
||||
|
|
|
@ -663,11 +663,6 @@
|
|||
"readable-stream": "1.1.14"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.5.0.tgz",
|
||||
"integrity": "sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo="
|
||||
},
|
||||
"cached-path-relative": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz",
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"dependencies": {
|
||||
"aws-sdk": "^2.89.0",
|
||||
"body-parser": "^1.17.2",
|
||||
"bytes": "^2.5.0",
|
||||
"connect-busboy": "0.0.2",
|
||||
"convict": "^3.0.0",
|
||||
"express": "^4.15.3",
|
||||
|
|
|
@ -11,7 +11,7 @@ uploadPageBrowseButton = Select a file on your computer
|
|||
.title = Select a file on your computer
|
||||
uploadPageMultipleFilesAlert = Uploading multiple files or a folder is currently not supported.
|
||||
uploadPageBrowseButtonTitle = Upload file
|
||||
uploadingPageHeader = Uploading Your File
|
||||
uploadingPageProgress = Uploading { $filename } ({ $size })
|
||||
importingFile = Importing…
|
||||
verifyingFile = Verifying…
|
||||
encryptingFile = Encrypting…
|
||||
|
@ -50,6 +50,8 @@ downloadButtonLabel = Download
|
|||
.title = Download
|
||||
downloadNotification = Your download has completed.
|
||||
downloadFinish = Download Complete
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } of { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Try Firefox Send
|
||||
.title = Try Firefox Send
|
||||
|
|
|
@ -4,7 +4,6 @@ const busboy = require('connect-busboy');
|
|||
const path = require('path');
|
||||
const bodyParser = require('body-parser');
|
||||
const helmet = require('helmet');
|
||||
const bytes = require('bytes');
|
||||
const conf = require('./config.js');
|
||||
const storage = require('./storage.js');
|
||||
const Raven = require('raven');
|
||||
|
@ -141,13 +140,15 @@ app.get('/download/:id', async (req, res) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const filename = await storage.filename(id);
|
||||
const contentLength = await storage.length(id);
|
||||
const efilename = await storage.filename(id);
|
||||
const filename = decodeURIComponent(efilename);
|
||||
const filenameJson = JSON.stringify({ filename });
|
||||
const sizeInBytes = await storage.length(id);
|
||||
const ttl = await storage.ttl(id);
|
||||
res.render('download', {
|
||||
filename: decodeURIComponent(filename),
|
||||
filesize: bytes(contentLength),
|
||||
sizeInBytes: contentLength,
|
||||
filename,
|
||||
filenameJson,
|
||||
sizeInBytes,
|
||||
ttl
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<script src="/download.js"></script>
|
||||
<div id="download-page-one">
|
||||
<div class="title">
|
||||
<span id="dl-filename"
|
||||
<span id="dl-file"
|
||||
data-filename="{{filename}}"
|
||||
data-size="{{sizeInBytes}}"
|
||||
data-ttl="{{ttl}}"
|
||||
data-l10n-id="downloadFileName"
|
||||
data-l10n-args='{"filename": "{{filename}}"}'></span>
|
||||
<span data-l10n-id="downloadFileSize"
|
||||
data-l10n-args='{"size": "{{filesize}}"}'></span>
|
||||
<span id="dl-size" hidden="true">{{sizeInBytes}}</span>
|
||||
<span id="dl-ttl" hidden="true">{{ttl}}</span>
|
||||
data-l10n-args='{{filenameJson}}'></span>
|
||||
<span id="dl-filesize"></span>
|
||||
</div>
|
||||
<div class="description" data-l10n-id="downloadMessage"></div>
|
||||
<img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
|
||||
|
@ -18,10 +18,7 @@
|
|||
</div>
|
||||
|
||||
<div id="download-progress" hidden="true">
|
||||
<div class="title"
|
||||
data-l10n-id="downloadingPageProgress"
|
||||
data-l10n-args='{"filename": "{{filename}}", "size": "{{filesize}}"}'>
|
||||
</div>
|
||||
<div id="dl-title" class="title"></div>
|
||||
<div class="description" data-l10n-id="downloadingPageMessage"></div>
|
||||
<!-- progress bar here -->
|
||||
<div class="progress-bar" id="dl-progress">
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
|
||||
<div id="upload-progress" hidden="true">
|
||||
<div class="title" id="upload-filename" data-l10n-id="uploadingPageHeader"></div>
|
||||
<div class="title" id="upload-filename"></div>
|
||||
<div class="description"></div>
|
||||
<!-- progress bar here -->
|
||||
<div class="progress-bar" id="ul-progress">
|
||||
|
|
Loading…
Reference in New Issue