Merge pull request #515 from mozilla/refactor-upload

removed jquery from upload.js
This commit is contained in:
Danny Coates 2017-08-14 21:23:05 -07:00 committed by GitHub
commit 856b2cdc60
7 changed files with 440 additions and 511 deletions

View File

@ -2,17 +2,15 @@ import FileSender from './fileSender';
import Storage from './storage'; import Storage from './storage';
import * as metrics from './metrics'; import * as metrics from './metrics';
import { allowedCopy, copyToClipboard, ONE_DAY_IN_MS } from './utils'; import { allowedCopy, copyToClipboard, ONE_DAY_IN_MS } from './utils';
import $ from 'jquery/dist/jquery.slim'; import bel from 'bel';
const HOUR = 1000 * 60 * 60;
const storage = new Storage(); const storage = new Storage();
let fileList = null; let fileList = null;
let $link = null;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
$link = $('#link');
fileList = document.getElementById('file-list'); fileList = document.getElementById('file-list');
toggleHeader(); toggleHeader();
// eslint-disable-next-line prefer-const
Promise.all( Promise.all(
storage.files.map(file => { storage.files.map(file => {
const id = file.fileId; const id = file.fileId;
@ -33,180 +31,115 @@ function toggleHeader() {
fileList.hidden = storage.files.length === 0; fileList.hidden = storage.files.length === 0;
} }
function timeLeft(milliseconds) {
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
const seconds = Math.floor(milliseconds / 1000 % 60);
if (hours >= 1) {
return `${hours}h ${minutes % 60}m`;
} else if (hours === 0) {
return `${minutes}m ${seconds}s`;
}
return 'Expired';
}
function addFile(file) { function addFile(file) {
if (!file) { if (!file) {
return; return;
} }
const row = document.createElement('tr');
const name = document.createElement('td');
const link = document.createElement('td');
const $copyIcon = $('<img>', {
src: '/resources/copy-16.svg',
class: 'icon-copy',
'data-l10n-id': 'copyUrlHover',
disabled: !allowedCopy()
});
const expiry = document.createElement('td');
const del = document.createElement('td');
const $delIcon = $('<img>', {
src: '/resources/close-16.svg',
class: 'icon-delete',
'data-l10n-id': 'deleteButtonHover'
});
const popupDiv = document.createElement('div');
const $popupText = $('<div>', { class: 'popuptext' });
const cellText = document.createTextNode(file.name);
const url = file.url.trim() + `#${file.secretKey}`.trim();
$link.attr('value', url);
$('#copy-text')
.attr('data-l10n-args', `{"filename": "${file.name}"}`)
.attr('data-l10n-id', 'copyUrlFormLabelWithName');
$popupText.attr('tabindex', '-1');
name.appendChild(cellText);
// create delete button
const delSpan = document.createElement('span');
$(delSpan)
.addClass('icon-cancel-1')
.attr('data-l10n-id', 'deleteButtonHover');
del.appendChild(delSpan);
const linkSpan = document.createElement('span');
$(linkSpan).addClass('icon-docs').attr('data-l10n-id', 'copyUrlHover');
link.appendChild(linkSpan);
link.style.color = '#0A8DFF';
//copy link to clipboard when icon clicked
$copyIcon.on('click', () => {
// record copied event from upload list
metrics.copiedLink({ location: 'upload-list' });
copyToClipboard(url);
document.l10n.formatValue('copiedUrl').then(translated => {
link.innerHTML = translated;
});
setTimeout(() => {
const linkImg = document.createElement('img');
$(linkImg)
.addClass('icon-copy')
.attr('data-l10n-id', 'copyUrlHover')
.attr('src', '/resources/copy-16.svg');
$(link).html(linkImg);
}, 500);
});
file.creationDate = new Date(file.creationDate); file.creationDate = new Date(file.creationDate);
const url = `${file.url}#${file.secretKey}`;
const future = new Date(); const future = new Date();
future.setTime(file.creationDate.getTime() + file.expiry); future.setTime(file.creationDate.getTime() + file.expiry);
const countdown = future.getTime() - Date.now();
let countdown = 0; const row = bel`
countdown = future.getTime() - Date.now(); <tr>
let minutes = Math.floor(countdown / 1000 / 60); <td>${file.name}</td>
let hours = Math.floor(minutes / 60); <td>
let seconds = Math.floor(countdown / 1000 % 60); <span class="icon-docs" data-l10n-id="copyUrlHover"></span>
<img onclick=${copyClick} src="/resources/copy-16.svg" class="icon-copy" data-l10n-id="copyUrlHover">
const poll = () => { <span data-l10n-id="copiedUrl" class="text-copied" hidden="true"></span>
countdown = future.getTime() - Date.now(); </td>
minutes = Math.floor(countdown / 1000 / 60); <td>${timeLeft(countdown)}</td>
hours = Math.floor(minutes / 60); <td>
seconds = Math.floor(countdown / 1000 % 60); <span class="icon-cancel-1" data-l10n-id="deleteButtonHover" title="Delete"></span>
let t; <img onclick=${showPopup} src="/resources/close-16.svg" class="icon-delete" data-l10n-id="deleteButtonHover" title="Delete">
<div class="popup">
if (hours >= 1) { <div class="popuptext" onclick=${stopProp} onblur=${cancel} tabindex="-1">
expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm'; <div class="popup-message" data-l10n-id="deletePopupText"></div>
t = setTimeout(() => { <div class="popup-action">
poll(); <span class="popup-no" onclick=${cancel} data-l10n-id="deletePopupCancel"></span>
}, 60000); <span class="popup-yes" onclick=${deleteFile} data-l10n-id="deletePopupYes"></span>
} else if (hours === 0) { </div>
expiry.innerHTML = minutes + 'm ' + seconds + 's'; </div>
t = window.setTimeout(() => { </div>
poll(); </td>
}, 1000); </tr>
} `;
//remove from list when expired const popup = row.querySelector('.popuptext');
if (countdown <= 0) { const timeCol = row.querySelectorAll('td')[2];
storage.remove(file.fileId); if (!allowedCopy()) {
$(expiry).parents('tr').remove(); row.querySelector('.icon-copy').disabled = true;
window.clearTimeout(t); }
toggleHeader();
}
};
fileList.querySelector('tbody').appendChild(row);
toggleHeader();
poll(); poll();
// create popup function copyClick(e) {
popupDiv.classList.add('popup'); metrics.copiedLink({ location: 'upload-list' });
const $popupMessage = $('<div>', { class: 'popup-message' }); copyToClipboard(url);
$popupMessage.attr('data-l10n-id', 'deletePopupText'); const icon = e.target;
const $popupAction = $('<div>', { class: 'popup-action' }); const text = e.target.nextSibling;
const $popupNvmSpan = $('<span>', { class: 'popup-no' }); icon.hidden = true;
$popupNvmSpan.attr('data-l10n-id', 'deletePopupCancel'); text.hidden = false;
const $popupDelSpan = $('<span>', { class: 'popup-yes' }); setTimeout(() => {
$popupDelSpan.attr('data-l10n-id', 'deletePopupYes'); icon.hidden = false;
text.hidden = true;
}, 500);
}
$popupText.html([$popupMessage, $popupAction]); function poll() {
$popupAction.html([$popupNvmSpan, $popupDelSpan]); const countdown = future.getTime() - Date.now();
if (countdown <= 0) {
// add data cells to table row storage.remove(file.fileId);
row.appendChild(name); row.parentNode.removeChild(row);
$(link).append($copyIcon);
row.appendChild(link);
row.appendChild(expiry);
$(popupDiv).append($popupText);
$(del).append($delIcon);
del.appendChild(popupDiv);
row.appendChild(del);
$('tbody').append(row); //add row to table
// delete file
$popupText.find('.popup-yes').on('click', e => {
FileSender.delete(file.fileId, file.deleteToken).then(() => {
$(e.target).parents('tr').remove();
const ttl = ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
metrics
.deletedUpload({
size: file.size,
time: file.totalTime,
speed: file.uploadSpeed,
type: file.typeOfUpload,
location: 'upload-list',
ttl
})
.then(() => {
storage.remove(file.fileId);
});
toggleHeader(); toggleHeader();
}
timeCol.textContent = timeLeft(countdown);
setTimeout(poll, countdown >= HOUR ? 60000 : 1000);
}
function deleteFile() {
FileSender.delete(file.fileId, file.deleteToken);
const ttl = ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
metrics.deletedUpload({
size: file.size,
time: file.totalTime,
speed: file.uploadSpeed,
type: file.typeOfUpload,
location: 'upload-list',
ttl
}); });
}); row.parentNode.removeChild(row);
storage.remove(file.fileId);
toggleHeader();
}
// show popup function showPopup() {
$delIcon.on('click', () => { popup.classList.add('show');
$popupText.addClass('show').focus(); popup.focus();
}); }
// hide popup function cancel(e) {
$popupText.find('.popup-no').on('click', e => {
e.stopPropagation(); e.stopPropagation();
$popupText.removeClass('show'); popup.classList.remove('show');
}); }
$popupText.on('click', e => { function stopProp(e) {
e.stopPropagation(); e.stopPropagation();
}); }
//close when popup loses focus
$popupText.on('blur', () => {
$popupText.removeClass('show');
});
toggleHeader();
} }
async function checkExistence(id) { async function checkExistence(id) {

View File

@ -7,6 +7,14 @@ export default class FileSender extends EventEmitter {
this.file = file; this.file = file;
this.iv = window.crypto.getRandomValues(new Uint8Array(12)); this.iv = window.crypto.getRandomValues(new Uint8Array(12));
this.uploadXHR = new XMLHttpRequest(); this.uploadXHR = new XMLHttpRequest();
this.key = window.crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 128
},
true,
['encrypt']
);
} }
static delete(fileId, token) { static delete(fileId, token) {
@ -32,87 +40,79 @@ export default class FileSender extends EventEmitter {
this.uploadXHR.abort(); this.uploadXHR.abort();
} }
upload() { readFile() {
const self = this; return new Promise((resolve, reject) => {
self.emit('loading'); const reader = new FileReader();
return Promise.all([ reader.readAsArrayBuffer(this.file);
window.crypto.subtle.generateKey( reader.onload = function(event) {
{ const plaintext = new Uint8Array(this.result);
name: 'AES-GCM', resolve(plaintext);
length: 128 };
}, reader.onerror = function(err) {
true, reject(err);
['encrypt', 'decrypt'] };
), });
new Promise((resolve, reject) => { }
const reader = new FileReader();
reader.readAsArrayBuffer(this.file);
reader.onload = function(event) {
const plaintext = new Uint8Array(this.result);
resolve(plaintext);
};
reader.onerror = function(err) {
reject(err);
};
})
])
.then(([secretKey, plaintext]) => {
self.emit('encrypting');
return Promise.all([
window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
tagLength: 128
},
secretKey,
plaintext
),
window.crypto.subtle.exportKey('jwk', secretKey)
]);
})
.then(([encrypted, keydata]) => {
return new Promise((resolve, reject) => {
const file = this.file;
const fileId = arrayToHex(this.iv);
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: file.type });
const fd = new FormData();
fd.append('data', blob, file.name);
const xhr = self.uploadXHR; uploadFile(encrypted, keydata) {
return new Promise((resolve, reject) => {
const file = this.file;
const fileId = arrayToHex(this.iv);
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: file.type });
const fd = new FormData();
fd.append('data', blob, file.name);
xhr.upload.addEventListener('progress', e => { const xhr = this.uploadXHR;
if (e.lengthComputable) {
self.emit('progress', [e.loaded, e.total]);
}
});
xhr.onreadystatechange = () => { xhr.upload.addEventListener('progress', e => {
if (xhr.readyState === XMLHttpRequest.DONE) { if (e.lengthComputable) {
if (xhr.status === 200) { this.emit('progress', [e.loaded, e.total]);
const responseObj = JSON.parse(xhr.responseText); }
return resolve({
url: responseObj.url,
fileId: responseObj.id,
secretKey: keydata.k,
deleteToken: responseObj.delete
});
}
reject(xhr.status);
}
};
xhr.open('post', '/upload', true);
xhr.setRequestHeader(
'X-File-Metadata',
JSON.stringify({
id: fileId,
filename: encodeURIComponent(file.name)
})
);
xhr.send(fd);
});
}); });
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const responseObj = JSON.parse(xhr.responseText);
return resolve({
url: responseObj.url,
fileId: responseObj.id,
secretKey: keydata.k,
deleteToken: responseObj.delete
});
}
reject(xhr.status);
}
};
xhr.open('post', '/upload', true);
xhr.setRequestHeader(
'X-File-Metadata',
JSON.stringify({
id: fileId,
filename: encodeURIComponent(file.name)
})
);
xhr.send(fd);
});
}
async upload() {
this.emit('loading');
const key = await this.key;
const plaintext = await this.readFile();
this.emit('encrypting');
const encrypted = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
tagLength: 128
},
key,
plaintext
);
const keydata = await window.crypto.subtle.exportKey('jwk', key);
return this.uploadFile(encrypted, keydata);
} }
} }

View File

@ -310,6 +310,10 @@ tbody {
opacity: 0.3; opacity: 0.3;
} }
.text-copied {
color: #0a8dff;
}
/* Popup container */ /* Popup container */
.popup { .popup {
position: absolute; position: absolute;

View File

@ -13,236 +13,230 @@ import Storage from './storage';
import * as metrics from './metrics'; import * as metrics from './metrics';
import * as progress from './progress'; import * as progress from './progress';
import * as fileList from './fileList'; import * as fileList from './fileList';
import $ from 'jquery/dist/jquery.slim';
const storage = new Storage(); const storage = new Storage();
$(() => { async function upload(event) {
event.preventDefault();
const pageOne = document.getElementById('page-one');
const link = document.getElementById('link');
const uploadWindow = document.querySelector('.upload-window');
const uploadError = document.getElementById('upload-error');
const uploadProgress = document.getElementById('upload-progress');
const clickOrDrop = event.type === 'drop' ? 'drop' : 'click';
// don't allow upload if not on upload page
if (pageOne.hidden) {
return;
}
storage.totalUploads += 1;
let file = '';
if (clickOrDrop === 'drop') {
if (!event.originalEvent.dataTransfer.files[0]) {
uploadWindow.classList.remove('ondrag');
return;
}
if (
event.originalEvent.dataTransfer.files.length > 1 ||
event.originalEvent.dataTransfer.files[0].size === 0
) {
uploadWindow.classList.remove('ondrag');
document.l10n.formatValue('uploadPageMultipleFilesAlert').then(str => {
alert(str);
});
return;
}
file = event.originalEvent.dataTransfer.files[0];
} else {
file = event.target.files[0];
}
if (file.size > MAXFILESIZE) {
return document.l10n
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
.then(alert);
}
pageOne.hidden = true;
uploadError.hidden = true;
uploadProgress.hidden = false;
document.l10n
.formatValue('uploadingPageProgress', {
size: bytes(file.size),
filename: file.name
})
.then(str => {
document.getElementById('upload-filename').textContent = str;
});
document.l10n.formatValue('importingFile').then(progress.setText);
//don't allow drag and drop when not on page-one
document.body.removeEventListener('drop', upload);
const fileSender = new FileSender(file);
document.getElementById('cancel-upload').addEventListener('click', () => {
fileSender.cancel();
metrics.cancelledUpload({
size: file.size,
type: clickOrDrop
});
location.reload();
});
let uploadStart;
fileSender.on('progress', data => {
uploadStart = uploadStart || Date.now();
progress.setProgress({
complete: data[0],
total: data[1]
});
});
fileSender.on('encrypting', () => {
document.l10n.formatValue('encryptingFile').then(progress.setText);
});
let t;
const startTime = Date.now();
metrics.startedUpload({
size: file.size,
type: clickOrDrop
});
// For large files we need to give the ui a tick to breathe and update
// before we kick off the FileSender
setTimeout(() => {
fileSender
.upload()
.then(info => {
const endTime = Date.now();
const time = endTime - startTime;
const uploadTime = endTime - uploadStart;
const speed = file.size / (uploadTime / 1000);
const expiration = EXPIRE_SECONDS * 1000;
link.setAttribute('value', `${info.url}#${info.secretKey}`);
metrics.completedUpload({
size: file.size,
time,
speed,
type: clickOrDrop
});
const fileData = {
name: file.name,
size: file.size,
fileId: info.fileId,
url: info.url,
secretKey: info.secretKey,
deleteToken: info.deleteToken,
creationDate: new Date(),
expiry: expiration,
totalTime: time,
typeOfUpload: clickOrDrop,
uploadSpeed: speed
};
document.getElementById('delete-file').addEventListener('click', () => {
FileSender.delete(fileData.fileId, fileData.deleteToken).then(() => {
const ttl =
ONE_DAY_IN_MS - (Date.now() - fileData.creationDate.getTime());
metrics
.deletedUpload({
size: fileData.size,
time: fileData.totalTime,
speed: fileData.uploadSpeed,
type: fileData.typeOfUpload,
location: 'success-screen',
ttl
})
.then(() => {
storage.remove(fileData.fileId);
location.reload();
});
});
});
storage.addFile(info.fileId, fileData);
pageOne.hidden = true;
uploadProgress.hidden = true;
uploadError.hidden = true;
document.getElementById('share-link').hidden = false;
fileList.addFile(fileData);
document.l10n.formatValue('notifyUploadDone').then(str => {
notify(str);
});
})
.catch(err => {
// err is 0 when coming from a cancel upload event
if (err === 0) {
return;
}
// only show error page when the error is anything other than user cancelling the upload
Raven.captureException(err);
pageOne.hidden = true;
uploadProgress.hidden = true;
uploadError.hidden = false;
window.clearTimeout(t);
metrics.stoppedUpload({
size: file.size,
type: clickOrDrop,
err
});
});
}, 10);
}
document.addEventListener('DOMContentLoaded', function() {
gcmCompliant() gcmCompliant()
.then(function() { .then(function() {
const $pageOne = $('#page-one'); const pageOne = document.getElementById('page-one');
const $copyBtn = $('#copy-btn'); const copyBtn = document.getElementById('copy-btn');
const $link = $('#link'); const link = document.getElementById('link');
const $uploadWindow = $('.upload-window'); const uploadWindow = document.querySelector('.upload-window');
const $uploadError = $('#upload-error');
const $uploadProgress = $('#upload-progress');
$pageOne.removeAttr('hidden'); pageOne.hidden = false;
$('#file-upload').on('change', onUpload); document.getElementById('file-upload').addEventListener('change', upload);
$(document.body).on('dragover', allowDrop).on('drop', onUpload); document.body.addEventListener('dragover', allowDrop);
document.body.addEventListener('drop', upload);
// reset copy button // reset copy button
$copyBtn.attr({ copyBtn.disabled = !allowedCopy();
disabled: !allowedCopy(), copyBtn.setAttribute('data-l10n-id', 'copyUrlFormButton');
'data-l10n-id': 'copyUrlFormButton'
});
$link.attr('disabled', false); link.disabled = false;
// copy link to clipboard // copy link to clipboard
$copyBtn.on('click', () => { copyBtn.addEventListener('click', () => {
if (allowedCopy() && copyToClipboard($link.attr('value'))) { if (allowedCopy() && copyToClipboard(link.getAttribute('value'))) {
metrics.copiedLink({ location: 'success-screen' }); metrics.copiedLink({ location: 'success-screen' });
//disable button for 3s //disable button for 3s
$copyBtn.attr('disabled', true); copyBtn.disabled = true;
$link.attr('disabled', true); link.disabled = true;
$copyBtn.html( copyBtn.innerHtml =
'<img src="/resources/check-16.svg" class="icon-check"></img>' '<img src="/resources/check-16.svg" class="icon-check"></img>';
);
setTimeout(() => { setTimeout(() => {
$copyBtn.attr({ copyBtn.disabled = !allowedCopy();
disabled: false, copyBtn.setAttribute('data-l10n-id', 'copyUrlFormButton');
'data-l10n-id': 'copyUrlFormButton' link.disabled = false;
});
$link.attr('disabled', false);
}, 3000); }, 3000);
} }
}); });
$uploadWindow uploadWindow.addEventListener('dragover', () =>
.on('dragover', () => { uploadWindow.classList.add('ondrag')
$uploadWindow.addClass('ondrag'); );
}) uploadWindow.addEventListener('dragleave', () =>
.on('dragleave', () => { uploadWindow.classList.remove('ondrag')
$uploadWindow.removeClass('ondrag'); );
});
// on file upload by browse or drag & drop // on file upload by browse or drag & drop
function onUpload(event) {
event.preventDefault();
const clickOrDrop = event.type === 'drop' ? 'drop' : 'click';
// don't allow upload if not on upload page
if ($pageOne.attr('hidden')) {
return;
}
storage.totalUploads += 1;
let file = '';
if (clickOrDrop === 'drop') {
if (!event.originalEvent.dataTransfer.files[0]) {
$uploadWindow.removeClass('ondrag');
return;
}
if (
event.originalEvent.dataTransfer.files.length > 1 ||
event.originalEvent.dataTransfer.files[0].size === 0
) {
$uploadWindow.removeClass('ondrag');
document.l10n
.formatValue('uploadPageMultipleFilesAlert')
.then(str => {
alert(str);
});
return;
}
file = event.originalEvent.dataTransfer.files[0];
} else {
file = event.target.files[0];
}
if (file.size > MAXFILESIZE) {
return document.l10n
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
.then(alert);
}
$pageOne.attr('hidden', true);
$uploadError.attr('hidden', true);
$uploadProgress.removeAttr('hidden');
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);
const fileSender = new FileSender(file);
$('#cancel-upload').on('click', () => {
fileSender.cancel();
metrics.cancelledUpload({
size: file.size,
type: clickOrDrop
});
location.reload();
});
let uploadStart;
fileSender.on('progress', data => {
uploadStart = uploadStart || Date.now();
progress.setProgress({
complete: data[0],
total: data[1]
});
});
fileSender.on('encrypting', () => {
document.l10n.formatValue('encryptingFile').then(progress.setText);
});
let t;
const startTime = Date.now();
metrics.startedUpload({
size: file.size,
type: clickOrDrop
});
// For large files we need to give the ui a tick to breathe and update
// before we kick off the FileSender
setTimeout(() => {
fileSender
.upload()
.then(info => {
const endTime = Date.now();
const time = endTime - startTime;
const uploadTime = endTime - uploadStart;
const speed = file.size / (uploadTime / 1000);
const expiration = EXPIRE_SECONDS * 1000;
metrics.completedUpload({
size: file.size,
time,
speed,
type: clickOrDrop
});
const fileData = {
name: file.name,
size: file.size,
fileId: info.fileId,
url: info.url,
secretKey: info.secretKey,
deleteToken: info.deleteToken,
creationDate: new Date(),
expiry: expiration,
totalTime: time,
typeOfUpload: clickOrDrop,
uploadSpeed: speed
};
$('#delete-file').on('click', () => {
FileSender.delete(
fileData.fileId,
fileData.deleteToken
).then(() => {
const ttl =
ONE_DAY_IN_MS -
(Date.now() - fileData.creationDate.getTime());
metrics
.deletedUpload({
size: fileData.size,
time: fileData.totalTime,
speed: fileData.uploadSpeed,
type: fileData.typeOfUpload,
location: 'success-screen',
ttl
})
.then(() => {
storage.remove(fileData.fileId);
location.reload();
});
});
});
storage.addFile(info.fileId, fileData);
$pageOne.attr('hidden', true);
$uploadProgress.attr('hidden', true);
$uploadError.attr('hidden', true);
$('#share-link').removeAttr('hidden');
fileList.addFile(fileData);
document.l10n.formatValue('notifyUploadDone').then(str => {
notify(str);
});
})
.catch(err => {
// err is 0 when coming from a cancel upload event
if (err === 0) {
return;
}
// only show error page when the error is anything other than user cancelling the upload
Raven.captureException(err);
$pageOne.attr('hidden', true);
$uploadProgress.attr('hidden', true);
$uploadError.removeAttr('hidden');
window.clearTimeout(t);
metrics.stoppedUpload({
size: file.size,
type: clickOrDrop,
err
});
});
}, 10);
}
function allowDrop(ev) { function allowDrop(ev) {
ev.preventDefault(); ev.preventDefault();

118
package-lock.json generated
View File

@ -291,9 +291,9 @@
} }
}, },
"aws-sdk": { "aws-sdk": {
"version": "2.95.0", "version": "2.98.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.95.0.tgz", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.98.0.tgz",
"integrity": "sha1-JuIdsUlEOx8GOUnch5hPDRdwDmo=", "integrity": "sha1-kK0CPXM4ndFex736+TLsq2VEVxE=",
"requires": { "requires": {
"buffer": "4.9.1", "buffer": "4.9.1",
"crypto-browserify": "1.0.9", "crypto-browserify": "1.0.9",
@ -1086,6 +1086,17 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
}, },
"bel": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/bel/-/bel-5.0.3.tgz",
"integrity": "sha512-bMLvUOrKBM2zWp3Ab6UugjCjFmsZtIeKH3oMNWaUr9RA94sNeicajzptZHQWU3K8KNIL8o6JwAmKG1W3mUiwXw==",
"dev": true,
"requires": {
"hyperx": "2.3.0",
"is-electron": "2.1.0",
"pelo": "0.0.3"
}
},
"big.js": { "big.js": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz",
@ -2330,7 +2341,7 @@
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
"dev": true, "dev": true,
"requires": { "requires": {
"es5-ext": "0.10.26" "es5-ext": "0.10.27"
} }
}, },
"dasherize": { "dasherize": {
@ -2640,12 +2651,6 @@
} }
} }
}, },
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=",
"dev": true
},
"domain-browser": { "domain-browser": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
@ -2801,9 +2806,9 @@
} }
}, },
"es5-ext": { "es5-ext": {
"version": "0.10.26", "version": "0.10.27",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.26.tgz", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.27.tgz",
"integrity": "sha1-UbISilMbcMT2dkCTpzy+u4IYY3I=", "integrity": "sha512-3KXJRYzKXTd7xfFy5uZsJCXue55fAYQ035PRjyYk2PicllxIwcW9l3AbM/eGaw3vgVAUW4tl4xg9AXDEI6yw0w==",
"dev": true, "dev": true,
"requires": { "requires": {
"es6-iterator": "2.0.1", "es6-iterator": "2.0.1",
@ -2817,7 +2822,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26", "es5-ext": "0.10.27",
"es6-symbol": "3.1.1" "es6-symbol": "3.1.1"
} }
}, },
@ -2828,7 +2833,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26", "es5-ext": "0.10.27",
"es6-iterator": "2.0.1", "es6-iterator": "2.0.1",
"es6-set": "0.1.5", "es6-set": "0.1.5",
"es6-symbol": "3.1.1", "es6-symbol": "3.1.1",
@ -2848,7 +2853,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26", "es5-ext": "0.10.27",
"es6-iterator": "2.0.1", "es6-iterator": "2.0.1",
"es6-symbol": "3.1.1", "es6-symbol": "3.1.1",
"event-emitter": "0.3.5" "event-emitter": "0.3.5"
@ -2861,7 +2866,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26" "es5-ext": "0.10.27"
} }
}, },
"es6-weak-map": { "es6-weak-map": {
@ -2871,7 +2876,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26", "es5-ext": "0.10.27",
"es6-iterator": "2.0.1", "es6-iterator": "2.0.1",
"es6-symbol": "3.1.1" "es6-symbol": "3.1.1"
} }
@ -3142,7 +3147,7 @@
"dev": true, "dev": true,
"requires": { "requires": {
"d": "1.0.0", "d": "1.0.0",
"es5-ext": "0.10.26" "es5-ext": "0.10.27"
} }
}, },
"event-stream": { "event-stream": {
@ -4520,24 +4525,6 @@
"is-glob": "2.0.1" "is-glob": "2.0.1"
} }
}, },
"global": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz",
"integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
"dev": true,
"requires": {
"min-document": "2.19.0",
"process": "0.5.2"
},
"dependencies": {
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
"integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=",
"dev": true
}
}
},
"globals": { "globals": {
"version": "9.18.0", "version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
@ -4784,6 +4771,21 @@
} }
} }
}, },
"hyperscript-attribute-to-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hyperscript-attribute-to-property/-/hyperscript-attribute-to-property-1.0.0.tgz",
"integrity": "sha1-glMI1Ju44pV5I/cxmBvMgRytev8=",
"dev": true
},
"hyperx": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/hyperx/-/hyperx-2.3.0.tgz",
"integrity": "sha1-cPRz1m1K1VDd0cg+S+JlEna78eI=",
"dev": true,
"requires": {
"hyperscript-attribute-to-property": "1.0.0"
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.15", "version": "0.4.15",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz",
@ -5064,6 +5066,12 @@
"integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
"dev": true "dev": true
}, },
"is-electron": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.1.0.tgz",
"integrity": "sha512-dkg5xT383+M6zIbbXW/z7n2nz4SFUi2OSyhntnFYkRdtV+HVEfdjEK+5AWisfYgkpe3WYjTIuh7toaKmSfFVWw==",
"dev": true
},
"is-equal-shallow": { "is-equal-shallow": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
@ -5260,12 +5268,6 @@
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
}, },
"jquery": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=",
"dev": true
},
"js-base64": { "js-base64": {
"version": "2.1.9", "version": "2.1.9",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz",
@ -6324,15 +6326,6 @@
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
"dev": true "dev": true
}, },
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"dev": true,
"requires": {
"dom-walk": "0.1.1"
}
},
"minimalistic-assert": { "minimalistic-assert": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
@ -6574,7 +6567,7 @@
"stream-browserify": "2.0.1", "stream-browserify": "2.0.1",
"stream-http": "2.7.2", "stream-http": "2.7.2",
"string_decoder": "0.10.31", "string_decoder": "0.10.31",
"timers-browserify": "2.0.3", "timers-browserify": "2.0.4",
"tty-browserify": "0.0.0", "tty-browserify": "0.0.0",
"url": "0.11.0", "url": "0.11.0",
"util": "0.10.3", "util": "0.10.3",
@ -6638,12 +6631,11 @@
} }
}, },
"timers-browserify": { "timers-browserify": {
"version": "2.0.3", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.3.tgz", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz",
"integrity": "sha512-+JAqyNgg+M8+gXIrq2EeUr4kZqRz47Ysco7X5QKRGScRE9HIHckyHD1asozSFGeqx2nmPCgA8T5tIGVO0ML7/w==", "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==",
"dev": true, "dev": true,
"requires": { "requires": {
"global": "4.3.2",
"setimmediate": "1.0.5" "setimmediate": "1.0.5"
} }
}, },
@ -7046,6 +7038,12 @@
"sha.js": "2.4.8" "sha.js": "2.4.8"
} }
}, },
"pelo": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/pelo/-/pelo-0.0.3.tgz",
"integrity": "sha1-+2smsGEgNtsCuRj+qrPowMTLWXw=",
"dev": true
},
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -10560,9 +10558,9 @@
} }
}, },
"webpack": { "webpack": {
"version": "3.5.2", "version": "3.5.4",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-3.5.2.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.5.4.tgz",
"integrity": "sha1-qWAQZuI688gPO/l1j9eUypd48lE=", "integrity": "sha1-VYPrJj7Se3i1vRe/37DrGxzRv4E=",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "5.1.1", "acorn": "5.1.1",

View File

@ -4,7 +4,7 @@
"version": "1.1.0", "version": "1.1.0",
"author": "Mozilla (https://mozilla.org)", "author": "Mozilla (https://mozilla.org)",
"dependencies": { "dependencies": {
"aws-sdk": "^2.95.0", "aws-sdk": "^2.98.0",
"body-parser": "^1.17.2", "body-parser": "^1.17.2",
"connect-busboy": "0.0.2", "connect-busboy": "0.0.2",
"convict": "^3.0.0", "convict": "^3.0.0",
@ -23,6 +23,7 @@
"babel-polyfill": "^6.23.0", "babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
"bel": "^5.0.3",
"browserify": "^14.4.0", "browserify": "^14.4.0",
"cross-env": "^5.0.5", "cross-env": "^5.0.5",
"css-mqpacker": "^6.0.1", "css-mqpacker": "^6.0.1",
@ -33,7 +34,6 @@
"eslint-plugin-security": "^1.4.0", "eslint-plugin-security": "^1.4.0",
"git-rev-sync": "^1.9.1", "git-rev-sync": "^1.9.1",
"husky": "^0.14.3", "husky": "^0.14.3",
"jquery": "^3.2.1",
"l20n": "^5.0.0", "l20n": "^5.0.0",
"lint-staged": "^4.0.3", "lint-staged": "^4.0.3",
"mocha": "^3.4.2", "mocha": "^3.4.2",
@ -50,7 +50,7 @@
"supertest": "^3.0.0", "supertest": "^3.0.0",
"testpilot-ga": "^0.3.0", "testpilot-ga": "^0.3.0",
"webcrypto-liner": "^0.1.25", "webcrypto-liner": "^0.1.25",
"webpack": "^3.5.2", "webpack": "^3.5.4",
"webpack-dev-middleware": "^1.12.0" "webpack-dev-middleware": "^1.12.0"
}, },
"engines": { "engines": {

View File

@ -11,15 +11,15 @@ module.exports = {
publicPath: '/' publicPath: '/'
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
loaders: 'babel-loader', loader: 'babel-loader',
include: [ include: [
path.resolve(__dirname, 'frontend'), path.resolve(__dirname, 'frontend'),
path.resolve(__dirname, 'node_modules/testpilot-ga/src') path.resolve(__dirname, 'node_modules/testpilot-ga/src')
], ],
query: { options: {
babelrc: false, babelrc: false,
presets: [['es2015', { modules: false }], 'stage-2'] presets: [['es2015', { modules: false }], 'stage-2']
} }