diff --git a/.eslintignore b/.eslintignore index 1365f08d..a5892099 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,4 @@ public/bundle.js public/webcrypto-shim.js +test/frontend/bundle.js +firefox \ No newline at end of file diff --git a/.eslintrc.yml b/.eslintrc.yml index 63748891..83f4de09 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -26,4 +26,4 @@ rules: no-var: error one-var: [error, never] prefer-const: error - quotes: [error, single] + quotes: [error, single, {avoidEscape: true}] diff --git a/.gitignore b/.gitignore index 6bd525a0..e1f30354 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ public/bundle.js public/version.json static/* !static/info.txt +test/frontend/bundle.js diff --git a/README.md b/README.md index 00a4112d..2a018367 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,20 @@ Pull requests are always welcome! Feel free to check out the list of ["good firs ## License [Mozilla Public License Version 2.0](LICENSE) + + **Entypo** + + Copyright (C) 2012 by Daniel Bruce + + Author: Daniel Bruce + License: SIL (http://scripts.sil.org/OFL) + Homepage: http://www.entypo.com + + + **Font Awesome** + + Copyright (C) 2016 by Dave Gandy + + Author: Dave Gandy + License: SIL () + Homepage: http://fortawesome.github.com/Font-Awesome/ diff --git a/circle.yml b/circle.yml index f4495a1e..b59eee37 100644 --- a/circle.yml +++ b/circle.yml @@ -4,6 +4,13 @@ machine: services: - docker - redis + environment: + PATH: "/home/ubuntu/send/firefox:$PATH" + +dependencies: + pre: + - npm i -g get-firefox geckodriver + - get-firefox --platform linux --extract --target /home/ubuntu/send deployment: latest: diff --git a/frontend/src/download.js b/frontend/src/download.js index f16adf40..ab60e346 100644 --- a/frontend/src/download.js +++ b/frontend/src/download.js @@ -1,37 +1,58 @@ const FileReceiver = require('./fileReceiver'); const { notify } = require('./utils'); const $ = require('jquery'); +require('jquery-circle-progress'); const Raven = window.Raven; - $(document).ready(function() { $('#download-progress').hide(); - $('#send-file').click(() => { + $('.send-new').click(() => { window.location.replace(`${window.location.origin}`); }); + const filename = $('#dl-filename').html(); + + //initiate progress bar + $('#dl-progress').circleProgress({ + value: 0.0, + startAngle: -Math.PI / 2, + fill: '#00C8D7', + size: 158 + }); $('#download-btn').click(download); function download() { const fileReceiver = new FileReceiver(); const name = document.createElement('p'); - const $btn = $('#download-btn'); - fileReceiver.on('progress', percentComplete => { + fileReceiver.on('progress', progress => { $('#download-page-one').hide(); - $('.send-new').hide(); $('#download-progress').show(); + const percent = progress[0] / progress[1]; // update progress bar - document - .querySelector('#progress-bar') - .style.setProperty('--progress', percentComplete + '%'); - $('#progress-text').html(`${percentComplete}%`); + $('#dl-progress').circleProgress('value', percent); + $('.percent-number').html(`${Math.floor(percent * 100)}`); + if (progress[1] < 1000000) { + $('.progress-text').html( + `${filename} (${(progress[0] / 1000).toFixed(1)}KB of ${(progress[1] / + 1000).toFixed(1)}KB)` + ); + } else if (progress[1] < 1000000000) { + $('.progress-text').html( + `${filename} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000).toFixed(1)}MB)` + ); + } else { + $('.progress-text').html( + `${filename} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)` + ); + } //on complete - if (percentComplete === 100) { + if (percent === 1) { fileReceiver.removeAllListeners('progress'); - $('#download-text').html('Download complete!'); - $('.send-new').show(); - $btn.text('Download complete!'); - $btn.attr('disabled', 'true'); notify('Your download has finished.'); + $('.title').html('Download Complete'); } }); diff --git a/frontend/src/fileReceiver.js b/frontend/src/fileReceiver.js index 41fb37d4..155a8dd9 100644 --- a/frontend/src/fileReceiver.js +++ b/frontend/src/fileReceiver.js @@ -13,10 +13,7 @@ class FileReceiver extends EventEmitter { xhr.onprogress = event => { if (event.lengthComputable && event.target.status !== 404) { - const percentComplete = Math.floor( - event.loaded / event.total * 100 - ); - this.emit('progress', percentComplete); + this.emit('progress', [event.loaded, event.total]); } }; @@ -61,60 +58,41 @@ class FileReceiver extends EventEmitter { true, ['encrypt', 'decrypt'] ) - ]) - .then(([fdata, key]) => { - this.emit('decrypting', true); - return Promise.all([ - window.crypto.subtle - .decrypt( - { - name: 'AES-GCM', - iv: hexToArray(fdata.iv), - additionalData: hexToArray(fdata.aad) - }, - key, - fdata.data - ) - .then(decrypted => { - this.emit('decrypting', false); - return new Promise((resolve, reject) => { - resolve(decrypted); - }); - }), - new Promise((resolve, reject) => { - resolve(fdata.filename); - }), - new Promise((resolve, reject) => { - resolve(hexToArray(fdata.aad)); - }) - ]); + ]).then(([fdata, key]) => { + this.emit('decrypting', true); + return Promise.all([ + window.crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv: hexToArray(fdata.iv), + additionalData: hexToArray(fdata.aad) + }, + key, + fdata.data + ).then(decrypted => { + this.emit('decrypting', false); + return Promise.resolve(decrypted) + }), + fdata.filename, + hexToArray(fdata.aad) + ]); + }).then(([decrypted, fname, proposedHash]) => { + this.emit('hashing', true); + return window.crypto.subtle.digest('SHA-256', decrypted).then(calculatedHash => { + this.emit('hashing', false); + const integrity = new Uint8Array(calculatedHash).toString() === proposedHash.toString(); + if (!integrity) { + this.emit('unsafe', true) + return Promise.reject(); + } else { + this.emit('safe', true); + return Promise.all([ + decrypted, + decodeURIComponent(fname) + ]); + } }) - .then(([decrypted, fname, proposedHash]) => { - this.emit('hashing', true); - return window.crypto.subtle - .digest('SHA-256', decrypted) - .then(calculatedHash => { - this.emit('hashing', false); - const integrity = - new Uint8Array(calculatedHash).toString() === - proposedHash.toString(); - if (!integrity) { - return new Promise((resolve, reject) => { - console.log('This file has been tampered with.'); - reject(); - }); - } - - return Promise.all([ - new Promise((resolve, reject) => { - resolve(decrypted); - }), - new Promise((resolve, reject) => { - resolve(decodeURIComponent(fname)); - }) - ]); - }); - }); + }) } } diff --git a/frontend/src/fileSender.js b/frontend/src/fileSender.js index 0e1b32ba..44240625 100644 --- a/frontend/src/fileSender.js +++ b/frontend/src/fileSender.js @@ -107,8 +107,7 @@ class FileSender extends EventEmitter { xhr.upload.addEventListener('progress', e => { if (e.lengthComputable) { - const percentComplete = Math.floor(e.loaded / e.total * 100); - self.emit('progress', percentComplete); + self.emit('progress', [e.loaded, e.total]); } }); diff --git a/frontend/src/upload.js b/frontend/src/upload.js index 69935376..509dd98c 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -1,28 +1,30 @@ const FileSender = require('./fileSender'); const { notify, gcmCompliant } = require('./utils'); const $ = require('jquery'); +require('jquery-circle-progress'); const Raven = window.Raven; $(document).ready(function() { gcmCompliant().catch(err => { $('#page-one').hide(); - $('#compliance-error').show(); + $('#unsupported-browser').show(); }); $('#file-upload').change(onUpload); - $('#page-one').on('dragover', allowDrop).on('drop', onUpload); + $('body').on('dragover', allowDrop).on('drop', onUpload); // reset copy button const $copyBtn = $('#copy-btn'); $copyBtn.attr('disabled', false); - $copyBtn.html('Copy'); + $('#link').attr('disabled', false); + $copyBtn.html('Copy to Clipboard'); - $('#page-one').show(); - $('#file-list').show(); $('#upload-progress').hide(); $('#share-link').hide(); $('#upload-error').hide(); + $('#unsupported-browser').hide(); $('#compliance-error').hide(); + $('#page-one').show(); if (localStorage.length === 0) { toggleHeader(); @@ -44,24 +46,41 @@ $(document).ready(function() { document.body.removeChild(aux); //disable button for 3s $copyBtn.attr('disabled', true); - $copyBtn.html('Copied!'); + $('#link').attr('disabled', true); + $copyBtn.html(''); window.setTimeout(() => { $copyBtn.attr('disabled', false); - $copyBtn.html('Copy'); + $('#link').attr('disabled', false); + $copyBtn.html('Copy to Clipboard'); }, 3000); }); + $('.upload-window').on('dragover', () => { + $('.upload-window').addClass('ondrag'); + }); + $('.upload-window').on('dragleave', () => { + $('.upload-window').removeClass('ondrag'); + }); + //initiate progress bar + $('#ul-progress').circleProgress({ + value: 0.0, + startAngle: -Math.PI / 2, + fill: '#3B9DFF', + size: 158 + }); // link back to home page $('.send-new').click(() => { - $('#page-one').show(); - $('#file-list').show(); $('#upload-progress').hide(); $('#share-link').hide(); $('#upload-error').hide(); $copyBtn.attr('disabled', false); - $copyBtn.html('Copy'); + $('#link').attr('disabled', false); + $copyBtn.html('Copy to Clipboard'); + $('.upload-window').removeClass('ondrag'); + $('#page-one').show(); }); - + //cancel the upload + $('#cancel-upload').click(() => {}); // on file upload by browse or drag & drop function onUpload(event) { event.preventDefault(); @@ -74,17 +93,35 @@ $(document).ready(function() { const expiration = 24 * 60 * 60 * 1000; //will eventually come from a field const fileSender = new FileSender(file); - fileSender.on('progress', percentComplete => { + fileSender.on('progress', progress => { $('#page-one').hide(); - $('#file-list').hide(); - $('#upload-progress').show(); $('#upload-error').hide(); - $('#upload-filename').innerHTML += file.name; + $('#upload-progress').show(); + const percent = progress[0] / progress[1]; // update progress bar - document - .querySelector('#progress-bar') - .style.setProperty('--progress', percentComplete + '%'); - $('#progress-text').html(`${percentComplete}%`); + $('#ul-progress').circleProgress('value', percent); + $('#ul-progress').circleProgress().on('circle-animation-end', function() { + $('.percent-number').html(`${Math.floor(percent * 100)}`); + }); + if (progress[1] < 1000000) { + $('.progress-text').html( + `${file.name} (${(progress[0] / 1000).toFixed( + 1 + )}KB of ${(progress[1] / 1000).toFixed(1)}KB)` + ); + } else if (progress[1] < 1000000000) { + $('.progress-text').html( + `${file.name} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000).toFixed(1)}MB)` + ); + } else { + $('.progress-text').html( + `${file.name} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)` + ); + } }); fileSender.on('loading', isStillLoading => { @@ -113,12 +150,10 @@ $(document).ready(function() { console.log('Finished encrypting'); } }); - + let t = ''; fileSender .upload() .then(info => { - const url = info.url.trim() + `#${info.secretKey}`.trim(); - $('#link').attr('value', url); const fileData = { name: file.name, fileId: info.fileId, @@ -129,12 +164,13 @@ $(document).ready(function() { expiry: expiration }; localStorage.setItem(info.fileId, JSON.stringify(fileData)); - - $('#page-one').hide(); - $('#file-list').hide(); - $('#upload-progress').hide(); - $('#share-link').show(); - $('#upload-error').hide(); + $('#upload-filename').html('Ready to Send'); + t = window.setTimeout(() => { + $('#page-one').hide(); + $('#upload-progress').hide(); + $('#upload-error').hide(); + $('#share-link').show(); + }, 2000); populateFileList(JSON.stringify(fileData)); notify('Your upload has finished.'); @@ -143,7 +179,9 @@ $(document).ready(function() { Raven.captureException(err); console.log(err); $('#page-one').hide(); + $('#upload-progress').hide(); $('#upload-error').show(); + window.clearTimeout(t); }); } @@ -181,19 +219,37 @@ $(document).ready(function() { const link = document.createElement('td'); const expiry = document.createElement('td'); const del = document.createElement('td'); - del.setAttribute('align', 'center'); - const btn = document.createElement('button'); const popupDiv = document.createElement('div'); - const $popupText = $('', { class: 'popuptext' }); + const $popupText = $('
', { class: 'popuptext' }); const cellText = document.createTextNode(file.name); + const url = file.url.trim() + `#${file.secretKey}`.trim(); + $('#link').attr('value', url); + $('#copy-text').html( + 'Copy and share the link to send your file: ' + file.name + ); + $popupText.attr('tabindex', '-1'); + name.appendChild(cellText); // create delete button - btn.innerHTML = 'x'; - btn.classList.add('delete-btn'); + del.innerHTML = ''; - link.innerHTML = file.url.trim() + `#${file.secretKey}`.trim(); + link.innerHTML = ''; + link.style.color = '#0A8DFF'; + //copy link to clipboard when icon clicked + $(link).click(function() { + const aux = document.createElement('input'); + aux.setAttribute('value', url); + document.body.appendChild(aux); + aux.select(); + document.execCommand('copy'); + document.body.removeChild(aux); + link.innerHTML = 'Copied!'; + window.setTimeout(() => { + link.innerHTML = ''; + }, 500); + }); file.creationDate = new Date(file.creationDate); @@ -215,18 +271,13 @@ $(document).ready(function() { seconds = Math.floor(countdown / 1000 % 60); let t; - if (hours > 1) { - expiry.innerHTML = hours + 'h'; - t = window.setTimeout(() => { - poll(); - }, 3600000); - } else if (hours === 1) { - expiry.innerHTML = hours + 'h'; + if (hours >= 1) { + expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm'; t = window.setTimeout(() => { poll(); }, 60000); } else if (hours === 0) { - expiry.innerHTML = minutes + 'm' + seconds + 's'; + expiry.innerHTML = minutes + 'm ' + seconds + 's'; t = window.setTimeout(() => { poll(); }, 1000); @@ -236,6 +287,7 @@ $(document).ready(function() { localStorage.removeItem(file.fileId); $(expiry).parents('tr').remove(); window.clearTimeout(t); + toggleHeader(); } } @@ -253,32 +305,40 @@ $(document).ready(function() { toggleHeader(); }); }); + document.getElementById('delete-file').onclick = () => { + FileSender.delete(file.fileId, file.deleteToken).then(() => { + localStorage.removeItem(file.fileId); + location.reload(); + }); + }; // add data cells to table row row.appendChild(name); row.appendChild(link); row.appendChild(expiry); - popupDiv.appendChild(btn); $(popupDiv).append($popupText); del.appendChild(popupDiv); row.appendChild(del); // show popup - del.addEventListener('click', toggleShow); + del.addEventListener('click', function() { + $popupText.addClass('show'); + $popupText.focus(); + }); // hide popup $popupText.find('.nvm').click(function(e) { e.stopPropagation(); - toggleShow(); + $popupText.removeClass('show'); }); $popupText.click(function(e) { e.stopPropagation(); }); - + //close when popup loses focus + $popupText.blur(() => { + $popupText.removeClass('show'); + }); $('tbody').append(row); //add row to table - function toggleShow() { - $popupText.toggleClass('show'); - } toggleHeader(); } function toggleHeader() { diff --git a/frontend/src/utils.js b/frontend/src/utils.js index e1e7531d..aff37488 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -7,7 +7,6 @@ function arrayToHex(iv) { hexStr += iv[i].toString(16); } } - window.hexStr = hexStr; return hexStr; } diff --git a/package-lock.json b/package-lock.json index fbf213c7..2966e4de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,11 @@ } } }, + "adm-zip": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", + "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=" + }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", @@ -1376,8 +1381,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.1.2", @@ -1388,146 +1392,172 @@ "dependencies": { "abbrev": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", "dev": true, "optional": true }, "ajv": { "version": "4.11.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "optional": true }, "asn1": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", "dev": true, "optional": true }, "assert-plus": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", "dev": true, "optional": true }, "asynckit": { "version": "0.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true, "optional": true }, "aws-sign2": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", "dev": true, "optional": true }, "aws4": { "version": "1.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true, "optional": true }, "balanced-match": { "version": "0.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "dev": true, "optional": true }, "block-stream": { "version": "0.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "dev": true }, "boom": { "version": "2.10.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true }, "brace-expansion": { "version": "1.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", "dev": true }, "buffer-shims": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", "dev": true }, "caseless": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true, "optional": true }, "co": { "version": "4.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "combined-stream": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "cryptiles": { "version": "2.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "optional": true }, "dashdash": { "version": "1.14.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "optional": true, "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -1535,87 +1565,102 @@ }, "debug": { "version": "2.6.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "dev": true, "optional": true }, "deep-extend": { "version": "0.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", "dev": true, "optional": true }, "delayed-stream": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "ecc-jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "dev": true, "optional": true }, "extend": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true, "optional": true }, "extsprintf": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", "dev": true }, "forever-agent": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true, "optional": true }, "form-data": { "version": "2.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "dev": true, "optional": true }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "fstream": { "version": "1.0.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true }, "fstream-ignore": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true }, "getpass": { "version": "0.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "optional": true, "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -1623,132 +1668,155 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true }, "graceful-fs": { "version": "4.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "har-schema": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", "dev": true, "optional": true }, "har-validator": { "version": "4.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "dev": true, "optional": true }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "hawk": { "version": "3.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "optional": true }, "hoek": { "version": "2.16.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, "http-signature": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "dev": true, "optional": true }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true }, "is-typedarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true, "optional": true }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isstream": { "version": "0.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true, "optional": true }, "jodid25519": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", "dev": true, "optional": true }, "jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true, "optional": true }, "json-schema": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true, "optional": true }, "json-stable-stringify": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "optional": true }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true, "optional": true }, "jsonify": { "version": "0.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true, "optional": true }, "jsprim": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", "dev": true, "optional": true, "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -1756,130 +1824,153 @@ }, "mime-db": { "version": "1.27.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", "dev": true }, "mime-types": { "version": "2.1.15", - "bundled": true, + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", "dev": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "node-pre-gyp": { "version": "0.6.36", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", "dev": true, "optional": true }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true }, "npmlog": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", "dev": true, "optional": true }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "oauth-sign": { "version": "0.8.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", "dev": true, "optional": true }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "performance-now": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", "dev": true, "optional": true }, "process-nextick-args": { "version": "1.0.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, "punycode": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true, "optional": true }, "qs": { "version": "6.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", "dev": true, "optional": true }, "rc": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", "dev": true, "optional": true, "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -1887,58 +1978,68 @@ }, "readable-stream": { "version": "2.2.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", "dev": true }, "request": { "version": "2.81.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "dev": true, "optional": true }, "rimraf": { "version": "2.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", "dev": true }, "safe-buffer": { "version": "5.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", "dev": true }, "semver": { "version": "5.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "sntp": { "version": "1.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "optional": true }, "sshpk": { "version": "1.13.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", "dev": true, "optional": true, "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -1946,92 +2047,108 @@ }, "string_decoder": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", "dev": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true }, "stringstream": { "version": "0.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "dev": true, "optional": true }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "2.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true }, "tar-pack": { "version": "3.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", "dev": true, "optional": true }, "tough-cookie": { "version": "2.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "dev": true, "optional": true }, "tunnel-agent": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "optional": true }, "tweetnacl": { "version": "0.14.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true, "optional": true }, "uid-number": { "version": "0.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "dev": true, "optional": true }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "uuid": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", "dev": true, "optional": true }, "verror": { "version": "1.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "optional": true }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true } } @@ -2561,6 +2678,11 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz", "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=" }, + "jquery-circle-progress": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jquery-circle-progress/-/jquery-circle-progress-1.2.2.tgz", + "integrity": "sha1-Jg6RMKyOK1Vy6qepO56Kaye8juo=" + }, "js-base64": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", @@ -3189,8 +3311,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "outpipe": { "version": "1.1.1", @@ -3777,13 +3898,11 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", - "dev": true, "dependencies": { "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==" } } }, @@ -3833,6 +3952,18 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, + "selenium-webdriver": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.4.0.tgz", + "integrity": "sha1-FR90RSlNpqZsScwwB0eioX5TxSo=", + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=" + } + } + }, "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", diff --git a/package.json b/package.json index 44d5e4c1..42f32cd6 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,12 @@ "express-handlebars": "^3.0.0", "helmet": "^3.6.1", "jquery": "^3.2.1", + "jquery-circle-progress": "^1.2.2", "mozlog": "^2.1.1", "raven": "^2.1.0", "raven-js": "^3.16.0", "redis": "^2.7.1", + "selenium-webdriver": "^3.4.0", "supertest": "^3.0.0", "uglify-es": "3.0.19" }, @@ -51,7 +53,8 @@ "lint:css": "stylelint 'public/*.css'", "lint:js": "eslint .", "start": "node server/server", - "test": "mocha test/unit && mocha test/server", + "test": "mocha test/unit && mocha test/server && npm run test-browser && node test/frontend/driver.js", + "test-browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d", "version": "node scripts/version" } } diff --git a/public/main.css b/public/main.css index 2e73aec2..7a6f778b 100644 --- a/public/main.css +++ b/public/main.css @@ -1,44 +1,65 @@ /*** index.html ***/ html { - background: url('resources/background.png'); - font-family: 'Fira Sans'; - font-weight: 300; - font-style: normal; - background-size: contain; + background: url('resources/send_bg.svg'); + font-family: 'SF Pro Text', sans-serif; + font-weight: 200; + background-size: 112%; + background-repeat: no-repeat; + background-position: center top; + margin-top: -25px; height: 100%; - display: flex; - justify-content: center; - align-items: center; - align-content: center; - flex-direction: column; +} + +body { + height: 100%; +} + +.footer { + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 1rem; + font-size: 15px; + color: #858585; +} + +#all { + padding-top: 12%; + overflow-y: scroll; } input, select, textarea, button { font-family: inherit; } -/** page-one **/ -.main-window { - border: 1px solid; - width: 606px; - min-height: 447px; - background-color: white; - border-radius: 5px; +span { + cursor: pointer; } +/** page-one **/ .title { - font-size: 14px; - width: 80%; - margin: 40px auto; + font-size: 33px; + margin: 20px auto; text-align: center; + font-family: 'SF Pro Display', sans-serif; +} + +.description { + font-size: 15px; + line-height: 23px; + width: 630px; + text-align: center; + margin: 0 auto 60px; + color: #0C0C0D; } .upload-window { - border: 1px dashed; + border: 1px dashed rgba(0, 148, 251, 0.5); margin: 0 auto; - width: 470px; - height: 250px; - border-radius: 5px; + width: 640px; + height: 255px; + border-radius: 4px; display: flex; justify-content: center; align-items: center; @@ -46,31 +67,41 @@ input, select, textarea, button { text-align: center; } +.upload-window.ondrag { + border: 3px dashed rgba(0, 148, 251, 0.5); + margin: 0 auto; + width: 672px; + height: 267px; + border-radius: 4.2px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; +} + +.link { + color: #0094FB; + text-decoration: none; +} + +#upload-text { + font-size: 22px; + color: #737373; + margin: 20px 0 30px; + font-family: 'SF Pro Display', sans-serif; +} + #browse { - float: right; - color: #2D7EFF; -} - -#browse-text { - float: left; - width: 128px; -} - -#upload-img { - padding-right: 20px; -} - -.upload-window > div:nth-child(2) { - font-size: 26px; -} - -.upload { - font-size: 12px; - width: auto; - overflow: hidden; -} - -.file-upload { + background: #0297F8; + border-radius: 5px; + font-size: 15px; + color: #FFF; + width: 240px; + height: 44px; + display: flex; + justify-content: center; + align-items: center; cursor: pointer; } @@ -78,26 +109,29 @@ input[type="file"] { display: none; } -form { - width: 45px; - float: right; -} - /** file-list **/ th { - font-size: 10px; - color: #737373; - font-weight: normal; + font-size: 16px; + color: #858585; + font-weight: lighter; text-align: left; + background: rgba(0, 148, 251, 0.05); + height: 40px; + border-top: 1px solid rgba(0, 148, 251, 0.1); + padding: 0 19px; } td { - font-size: 12px; + font-size: 15px; vertical-align: top; + color: #4A4A4A; + padding: 17px 19px 0; + line-height: 23px; } table { - table-layout: fixed; + border-collapse: collapse; + font-family: 'Segoe UI', 'SF Pro Text', sans-serif; } tbody { @@ -105,18 +139,11 @@ tbody { } #uploaded-files { - width: 472px; - margin: 10px auto; + width: 640px; + margin: 45.3px auto; table-layout: fixed; } -.delete-btn { - padding: 0; - border: none; - background: none; - cursor: pointer; -} - /* Popup container */ .popup { position: relative; @@ -135,11 +162,12 @@ tbody { padding: 8px 0; position: absolute; z-index: 1; - bottom: 125%; + bottom: 20px; left: 50%; - margin-left: -80px; + margin-left: -96px; transition: opacity 0.5s; opacity: 0; + outline: 0; } /* Popup arrow */ @@ -160,31 +188,56 @@ tbody { } /** upload-progress **/ -#progress-bar { - width: 300px; - height: 5px; - background: linear-gradient( - 90deg, - #FD9800, - #D73000 var(--progress), - white var(--progress) - ); - border: 0.5px solid; - border-radius: 5px; -} - -/** share-link **/ -.share-window { - margin: 0 auto; - width: 470px; - height: 250px; +.progress-bar { + margin-top: 3px; display: flex; justify-content: center; align-items: center; + text-align: center; } -#share-window-r { - width: 50%; +.percentage { + position: absolute; + letter-spacing: -0.78px; + font-family: 'Segoe UI', 'SF Pro Text', sans-serif; +} + +.percent-number { + font-size: 43.2px; + line-height: 58px; +} + +.percent-sign { + font-size: 28.8px; + color: rgb(104, 104, 104); +} + +.upload { + margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; + font-size: 15px; +} + +.progress-text { + color: rgba(0, 0, 0, 0.5); + letter-spacing: -0.4px; + margin-top: 24px; + margin-bottom: 74px; +} + +#cancel-upload { + color: #D70022; + cursor: pointer; + text-decoration: underline; +} + +/** share-link **/ +#share-window { + width: 645px; margin: 0 auto; display: flex; justify-content: center; @@ -202,46 +255,149 @@ tbody { flex-wrap: nowrap; } +#copy-text { + align-self: flex-start; + margin-top: 60px; + margin-bottom: 10px; + color: #0C0C0D; +} + #link { - width: 216px; - height: 41px; - border: 1px solid #979797; + width: 480px; + height: 56px; + border: 1px solid #0297F8; + border-radius: 6px 0 0 6px; + font-size: 24px; + color: #737373; + font-family: 'SF Pro Display', sans-serif; + letter-spacing: 0; + line-height: 23px; +} + +#link:disabled { + border: 1px solid #05A700; + background: #FFF; } #copy-btn { - width: 60px; - height: 45px; - background: #337FEB; - border: 1px solid #979797; + width: 165px; + height: 60px; + background: #0297F8; + border: 1px solid #0297F8; + border-radius: 0 6px 6px 0; color: white; cursor: pointer; + font-size: 15px; } #copy-btn:disabled { - background: #47B04B; + background: #05A700; + border: 1px solid #05A700; cursor: auto; } +#delete-file { + width: 176px; + height: 44px; + background: #FFF; + border: 1px solid rgba(12, 12, 13, 0.3); + border-radius: 5px; + font-size: 15px; + margin-top: 50px; + margin-bottom: 12px; + cursor: pointer; + color: #313131; +} + .send-new { - font-size: 14px; + font-size: 15px; margin: auto; width: 80%; text-align: center; - color: #2D7EFF; + color: #0094FB; cursor: pointer; + text-decoration: underline; +} + +/* upload-error */ +#upload-error { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; +} + +#upload-error-img { + margin-bottom: 90px; + margin-top: 5px; +} + +/* unsupported-browser */ +#unsupported-browser { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.unsupported-description { + font-size: 13px; + line-height: 23px; + width: 630px; + text-align: center; + color: #7D7D7D; + margin: 0 auto 23px; +} + +#firefox-logo { + width: 70px; +} + +#dl-firefox { + margin-bottom: 181px; + width: 260px; + height: 80px; + background: #12BC00; + border-radius: 3px; + cursor: pointer; + border: 0; + box-shadow: 0 5px 3px rgb(234, 234, 234); + font-family: 'Fira Sans'; + font-weight: 500; + color: #FFF; + font-size: 26px; + display: flex; + justify-content: center; + align-items: center; + line-height: 1; + text-decoration: none; +} + +#dl-firefox-text { + text-align: left; + margin-left: 20.4px; +} + +#dl-firefox-text > span { + font-family: 'Fira Sans'; + font-weight: 300; + font-size: 18px; + letter-spacing: -0.69px; } /** download.html **/ #download-btn { - font-size: 18px; + font-size: 15px; color: white; - width: 214px; - height: 87px; - margin: 50px auto; + width: 180px; + height: 44px; + margin-top: 20px; + margin-bottom: 30px; text-align: center; - background: #337FEB; - border: 1px solid #3EA050; - border-radius: 6px; + background: #0297F8; + border: 1px solid #0297F8; + border-radius: 5px; font-weight: 300; cursor: pointer; } @@ -251,10 +407,8 @@ tbody { cursor: auto; } -#download-page-one { +#download { margin: 0 auto; - width: 470px; - height: 250px; display: flex; justify-content: center; align-items: center; @@ -263,13 +417,20 @@ tbody { } #expired-img { - display: none; + margin: 51px 0 71px; +} + +.expired-description { + font-size: 15px; + line-height: 23px; + width: 630px; + text-align: center; + color: #7D7D7D; + margin: 0 auto 23px; } #download-progress { margin: 0 auto; - width: 470px; - height: 250px; display: flex; justify-content: center; align-items: center; @@ -277,6 +438,7 @@ tbody { text-align: center; } -#download-text { - margin-bottom: 40px; +#download-img { + width: 283px; + height: 196px; } diff --git a/public/resources/background.png b/public/resources/background.png deleted file mode 100644 index 634b72a2..00000000 Binary files a/public/resources/background.png and /dev/null differ diff --git a/public/resources/firefox_logo-only.svg b/public/resources/firefox_logo-only.svg new file mode 100644 index 00000000..6c9d5ce3 --- /dev/null +++ b/public/resources/firefox_logo-only.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/resources/fontello-24c5e6ad/config.json b/public/resources/fontello-24c5e6ad/config.json new file mode 100755 index 00000000..3a91a711 --- /dev/null +++ b/public/resources/fontello-24c5e6ad/config.json @@ -0,0 +1,28 @@ +{ + "name": "", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 850, + "glyphs": [ + { + "uid": "c8585e1e5b0467f28b70bce765d5840c", + "css": "docs", + "code": 61637, + "src": "fontawesome" + }, + { + "uid": "c709da589c923ba3c2ad48d9fc563e93", + "css": "cancel-1", + "code": 59393, + "src": "entypo" + }, + { + "uid": "14017aae737730faeda4a6fd8fb3a5f0", + "css": "check", + "code": 59394, + "src": "entypo" + } + ] +} \ No newline at end of file diff --git a/public/resources/fontello-24c5e6ad/css/fontello.css b/public/resources/fontello-24c5e6ad/css/fontello.css new file mode 100755 index 00000000..ea18ff91 --- /dev/null +++ b/public/resources/fontello-24c5e6ad/css/fontello.css @@ -0,0 +1,60 @@ +@font-face { + font-family: 'fontello'; + src: url('../font/fontello.eot?60405031'); + src: url('../font/fontello.eot?60405031#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?60405031') format('woff2'), + url('../font/fontello.woff?60405031') format('woff'), + url('../font/fontello.ttf?60405031') format('truetype'), + url('../font/fontello.svg?60405031#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'fontello'; + src: url('../font/fontello.svg?60405031#fontello') format('svg'); + } +} +*/ + + [class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +} + +.icon-cancel-1:before { content: '\e801'; font-size: 1.5em; font-weight: lighter;} /* '' */ +.icon-check:before { content: '\e802'; font-size: 1.5em;} /* '' */ +.icon-docs:before { content: '\f0c5'; font-weight: bolder;} /* '' */ diff --git a/public/resources/fontello-24c5e6ad/font/fontello.eot b/public/resources/fontello-24c5e6ad/font/fontello.eot new file mode 100755 index 00000000..30b0ddc1 Binary files /dev/null and b/public/resources/fontello-24c5e6ad/font/fontello.eot differ diff --git a/public/resources/fontello-24c5e6ad/font/fontello.svg b/public/resources/fontello-24c5e6ad/font/fontello.svg new file mode 100755 index 00000000..431b9d7c --- /dev/null +++ b/public/resources/fontello-24c5e6ad/font/fontello.svg @@ -0,0 +1,16 @@ + + + +Copyright (C) 2017 by original authors @ fontello.com + + + + + + + + + + + + \ No newline at end of file diff --git a/public/resources/fontello-24c5e6ad/font/fontello.ttf b/public/resources/fontello-24c5e6ad/font/fontello.ttf new file mode 100755 index 00000000..86b99e44 Binary files /dev/null and b/public/resources/fontello-24c5e6ad/font/fontello.ttf differ diff --git a/public/resources/fontello-24c5e6ad/font/fontello.woff b/public/resources/fontello-24c5e6ad/font/fontello.woff new file mode 100755 index 00000000..25766584 Binary files /dev/null and b/public/resources/fontello-24c5e6ad/font/fontello.woff differ diff --git a/public/resources/fontello-24c5e6ad/font/fontello.woff2 b/public/resources/fontello-24c5e6ad/font/fontello.woff2 new file mode 100755 index 00000000..ca749888 Binary files /dev/null and b/public/resources/fontello-24c5e6ad/font/fontello.woff2 differ diff --git a/public/resources/illustration_download.svg b/public/resources/illustration_download.svg new file mode 100644 index 00000000..85e8c5b9 --- /dev/null +++ b/public/resources/illustration_download.svg @@ -0,0 +1,126 @@ + + + + +illustration_01 +Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/resources/illustration_error.svg b/public/resources/illustration_error.svg new file mode 100644 index 00000000..ca44824d --- /dev/null +++ b/public/resources/illustration_error.svg @@ -0,0 +1,93 @@ + + + + illustration + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/resources/illustration_expired.svg b/public/resources/illustration_expired.svg new file mode 100644 index 00000000..9c551a28 --- /dev/null +++ b/public/resources/illustration_expired.svg @@ -0,0 +1,64 @@ + + + + Group + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/resources/link_expired.png b/public/resources/link_expired.png deleted file mode 100644 index b4872db6..00000000 Binary files a/public/resources/link_expired.png and /dev/null differ diff --git a/public/resources/mozilla-logo.jpg b/public/resources/mozilla-logo.jpg new file mode 100644 index 00000000..9c27a0cd Binary files /dev/null and b/public/resources/mozilla-logo.jpg differ diff --git a/public/resources/send_bg.svg b/public/resources/send_bg.svg new file mode 100644 index 00000000..ae022575 --- /dev/null +++ b/public/resources/send_bg.svg @@ -0,0 +1,90 @@ + + + + File transfer_homepage Copy + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { } + + + + + + + + \ No newline at end of file diff --git a/public/resources/share.png b/public/resources/share.png deleted file mode 100644 index 0628d7ce..00000000 Binary files a/public/resources/share.png and /dev/null differ diff --git a/public/resources/upload.svg b/public/resources/upload.svg index d731adba..e9bb7fc2 100644 --- a/public/resources/upload.svg +++ b/public/resources/upload.svg @@ -1,93 +1,24 @@ - - - - Group 14 + + + + upload Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/server/server.js b/server/server.js index ed4ab692..7fa3b08f 100644 --- a/server/server.js +++ b/server/server.js @@ -32,27 +32,31 @@ app.engine( app.set('view engine', 'handlebars'); app.use(helmet()); +app.use(helmet.hsts({ + maxAge: 31536000, + force: conf.env === 'production' +})); app.use( helmet.contentSecurityPolicy({ directives: { - defaultSrc: ['\'self\''], + defaultSrc: ["'self'"], connectSrc: [ - '\'self\'', + "'self'", 'https://sentry.prod.mozaws.net', 'https://www.google-analytics.com', 'https://ssl.google-analytics.com' ], imgSrc: [ - '\'self\'', + "'self'", 'https://www.google-analytics.com', 'https://ssl.google-analytics.com' ], - scriptSrc: ['\'self\'', 'https://ssl.google-analytics.com'], - styleSrc: ['\'self\'', 'https://code.cdn.mozilla.net'], - fontSrc: ['\'self\'', 'https://code.cdn.mozilla.net'], - formAction: ['\'none\''], - frameAncestors: ['\'none\''], - objectSrc: ['\'none\''] + scriptSrc: ["'self'", 'https://ssl.google-analytics.com'], + styleSrc: ["'self'", 'https://code.cdn.mozilla.net'], + fontSrc: ["'self'", 'https://code.cdn.mozilla.net'], + formAction: ["'none'"], + frameAncestors: ["'none'"], + objectSrc: ["'none'"] } }) ); diff --git a/test/frontend/.eslintrc.yml b/test/frontend/.eslintrc.yml new file mode 100644 index 00000000..07a266de --- /dev/null +++ b/test/frontend/.eslintrc.yml @@ -0,0 +1,2 @@ +env: + browser: true \ No newline at end of file diff --git a/test/frontend/driver.js b/test/frontend/driver.js new file mode 100644 index 00000000..a50d748d --- /dev/null +++ b/test/frontend/driver.js @@ -0,0 +1,22 @@ +const webdriver = require('selenium-webdriver'); +const path = require('path'); +const until = webdriver.until; + +const driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +driver.get(path.join('file:///', __dirname, '/frontend.test.html')); +driver.wait(until.titleIs('Mocha Tests'), 1000); +driver.wait(until.titleMatches(/^[0-1]$/), 10000); + +driver.getTitle().then(title => { + driver.quit().then(() => { + if (title === '0') { + console.log('Frontend tests have passed.'); + } else { + throw new Error('Frontend tests are failing. ' + + 'Please open the frontend.test.html file in a browser.'); + } + }) +}) \ No newline at end of file diff --git a/test/frontend/frontend.bundle.js b/test/frontend/frontend.bundle.js new file mode 100644 index 00000000..e3882cbb --- /dev/null +++ b/test/frontend/frontend.bundle.js @@ -0,0 +1,22 @@ +class FakeFile extends Blob { + constructor(name, data, opt) { + super(data, opt); + this.name = name; + } +} + +window.Raven = { + captureException: function(err) { + console.error(err, err.stack); + } +} + +window.FakeFile = FakeFile; +window.FileSender = require('../../frontend/src/fileSender'); +window.FileReceiver = require('../../frontend/src/fileReceiver'); +window.sinon = require('sinon'); +window.server = window.sinon.fakeServer.create(); +window.assert = require('assert'); +const utils = require('../../frontend/src/utils'); +window.hexToArray = utils.hexToArray; +window.arrayToHex = utils.arrayToHex; diff --git a/test/frontend/frontend.test.html b/test/frontend/frontend.test.html new file mode 100644 index 00000000..757d8eb1 --- /dev/null +++ b/test/frontend/frontend.test.html @@ -0,0 +1,24 @@ + + + + Mocha Tests + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/test/frontend/frontend.test.js b/test/frontend/frontend.test.js new file mode 100644 index 00000000..1e286008 --- /dev/null +++ b/test/frontend/frontend.test.js @@ -0,0 +1,360 @@ +const FileSender = window.FileSender; +const FileReceiver = window.FileReceiver; +const FakeFile = window.FakeFile; +const assert = window.assert; +const server = window.server; +const hexToArray = window.hexToArray; +const arrayToHex = window.arrayToHex; +const sinon = window.sinon; + +let file; +let encryptedIV; +let fileHash; +let secretKey; +let originalBlob; + +describe('File Sender', function() { + before(function() { + server.respondImmediately = true; + server.respondWith( + 'POST', + '/upload', + function(request) { + const reader = new FileReader(); + reader.readAsArrayBuffer(request.requestBody.get('data')); + + reader.onload = function(event) { + file = this.result; + } + + const responseObj = JSON.parse(request.requestHeaders['X-File-Metadata']); + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({url: 'some url', + id: responseObj.id, + delete: responseObj.delete}) + ) + } + ) + }) + + it('Should get a loading event emission', function() { + const file = new FakeFile('hello_world.txt', ['This is some data.']) + const fs = new FileSender(file); + let testLoading = true; + + fs.on('loading', isStillLoading => { + assert(!(!testLoading && isStillLoading)); + testLoading = isStillLoading; + }) + + return fs.upload() + .then(info => { + assert(info); + assert(!testLoading); + }) + .catch(err => { + console.log(err, err.stack); + assert.fail(); + }); + }) + + it('Should get a hashing event emission', function() { + const file = new FakeFile('hello_world.txt', ['This is some data.']) + const fs = new FileSender(file); + let testHashing = true; + + fs.on('hashing', isStillHashing => { + assert(!(!testHashing && isStillHashing)); + testHashing = isStillHashing; + }) + + return fs.upload() + .then(info => { + assert(info); + assert(!testHashing); + }) + .catch(err => { + console.log(err, err.stack); + assert.fail(); + }); + }) + + it('Should get a encrypting event emission', function() { + const file = new FakeFile('hello_world.txt', ['This is some data.']) + const fs = new FileSender(file); + let testEncrypting = true; + + fs.on('encrypting', isStillEncrypting => { + assert(!(!testEncrypting && isStillEncrypting)); + testEncrypting = isStillEncrypting; + }) + + return fs.upload() + .then(info => { + assert(info); + assert(!testEncrypting); + }) + .catch(err => { + console.log(err, err.stack); + assert.fail(); + }); + }) + + it('Should encrypt a file properly', function(done) { + const newFile = new FakeFile('hello_world.txt', ['This is some data.']) + const fs = new FileSender(newFile); + fs.upload().then(info => { + const key = info.secretKey; + secretKey = info.secretKey; + const IV = info.fileId; + encryptedIV = info.fileId; + + const readRaw = new FileReader; + readRaw.onload = function(event) { + const rawArray = new Uint8Array(this.result); + originalBlob = rawArray; + + window.crypto.subtle.digest('SHA-256', rawArray).then(hash => { + fileHash = hash; + window.crypto.subtle.importKey( + 'jwk', + { + kty: 'oct', + k: key, + alg: 'A128GCM', + ext: true, + }, + { + name: 'AES-GCM' + }, + true, + ['encrypt', 'decrypt'] + ) + .then(cryptoKey => { + window.crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv: hexToArray(IV), + additionalData: hash, + tagLength: 128 + }, + cryptoKey, + rawArray + ) + .then(encrypted => { + assert(new Uint8Array(encrypted).toString() === + new Uint8Array(file).toString()); + done(); + }) + }) + }) + + } + + readRaw.readAsArrayBuffer(newFile); + }) + }) + +}); + +describe('File Receiver', function() { + + class FakeXHR { + constructor() { + this.response = file; + this.status = 200; + } + + static setup() { + FakeXHR.prototype.open = sinon.spy(); + FakeXHR.prototype.send = function () { + this.onload(); + } + + FakeXHR.prototype.originalXHR = window.XMLHttpRequest; + + FakeXHR.prototype.getResponseHeader = function () { + return JSON.stringify({ + aad: arrayToHex(new Uint8Array(fileHash)), + filename: 'hello_world.txt', + id: encryptedIV + }) + } + window.XMLHttpRequest = FakeXHR; + } + + static restore() { + // originalXHR is a sinon FakeXMLHttpRequest, since + // fakeServer.create() is called in frontend.bundle.js + window.XMLHttpRequest.prototype.originalXHR.restore(); + } + } + + const cb = function(done) { + if (file === undefined || + encryptedIV === undefined || + fileHash === undefined || + secretKey === undefined) { + assert.fail('Please run file sending tests before trying to receive the files.'); + done(); + } + + FakeXHR.setup(); + done(); + } + + before(cb) + + after(function() { + FakeXHR.restore(); + }) + + it('Should decrypt properly', function() { + const fr = new FileReceiver(); + location.hash = secretKey; + return fr.download().then(([decrypted, name]) => { + assert(name); + assert(new Uint8Array(decrypted).toString() === + new Uint8Array(originalBlob).toString()) + }).catch(err => { + console.log(err, err.stack); + assert.fail(); + }) + }) + + it('Should emit decrypting events', function() { + const fr = new FileReceiver(); + location.hash = secretKey; + + let testDecrypting = true; + + fr.on('decrypting', isStillDecrypting => { + assert(!(!testDecrypting && isStillDecrypting)); + testDecrypting = isStillDecrypting; + }); + + fr.on('safe', isSafe => { + assert(isSafe); + }) + + return fr.download().then(([decrypted, name]) => { + assert(decrypted); + assert(name); + assert(!testDecrypting); + }).catch(err => { + console.log(err, err.stack); + assert.fail(); + }) + }) + + it('Should emit hashing events', function() { + const fr = new FileReceiver(); + location.hash = secretKey; + + let testHashing = true; + + fr.on('hashing', isStillHashing => { + assert(!(!testHashing && isStillHashing)); + testHashing = isStillHashing; + }); + + fr.on('safe', isSafe => { + assert(isSafe); + }) + + return fr.download().then(([decrypted, name]) => { + assert(decrypted); + assert(name); + assert(!testHashing); + }).catch(err => { + assert.fail(); + }) + }) + + it('Should catch fraudulent checksums', function(done) { + // Use the secret key and file hash of the previous file to encrypt, + // which has a different hash than this one (different strings). + const newFile = new FakeFile('hello_world.txt', + ['This is some data, with a changed hash.']) + const readRaw = new FileReader(); + + readRaw.onload = function(event) { + const plaintext = new Uint8Array(this.result); + window.crypto.subtle.importKey( + 'jwk', + { + kty: 'oct', + k: secretKey, + alg: 'A128GCM', + ext: true + }, + { + name: 'AES-GCM' + }, + true, + ['encrypt', 'decrypt'] + ) + .then(key => { + // The file hash used here is the hash of the fake + // file from the previous test; it's a phony checksum. + return window.crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv: hexToArray(encryptedIV), + additionalData: fileHash, + tagLength: 128 + }, + key, + plaintext + ) + }) + .then(encrypted => { + file = encrypted; + const fr = new FileReceiver(); + location.hash = secretKey; + + fr.on('unsafe', isUnsafe => { + assert(isUnsafe) + }) + + fr.on('safe', () => { + // This event should not be emitted. + assert.fail(); + }) + + fr.download().then(() => { + assert.fail(); + done(); + }).catch(err => { + assert(1); + done(); + }) + }) + } + readRaw.readAsArrayBuffer(newFile); + }) + + it('Should not decrypt with an incorrect checksum', function() { + FakeXHR.prototype.getResponseHeader = function () { + return JSON.stringify({ + aad: 'some_bad_hashz', + filename: 'hello_world.txt', + id: encryptedIV + }) + } + + const fr = new FileReceiver(); + location.hash = secretKey; + + return fr.download().then(([decrypted, name]) => { + assert(decrypted); + assert(name); + assert.fail(); + }).catch(err => { + assert(1); + }) + }) + +}) diff --git a/views/download.handlebars b/views/download.handlebars index 50cc365a..dfdb86e3 100644 --- a/views/download.handlebars +++ b/views/download.handlebars @@ -3,49 +3,53 @@ Download your file {{#if dsn}} - {{> sentry dsn=dsn}} + {{> sentry dsn=dsn}} {{/if}} - {{#if trackerId}} - {{> analytics trackerId=trackerId}} + {{> analytics trackerId=trackerId}} {{/if}} - -
+
{{#if filename}} -
- Your friend is sending you a file:
- {{{filename}}} ({{{filesize}}}) -
-
- +
+ Download {{{filename}}} ({{{filesize}}})
-
- +
+ Your friend is sending you a file with Firefox Send, a service that allows you to share files with a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever. +
+ Download +
+
-
-
- Downloading File... +
+ Downloading {{{filename}}} ({{{filesize}}}) +
+
+ Please leave this tab open while we fetch your file and decrypt it. +
+ +
+
+ + % +
- -
-
+
{{{filename}}}
-
- Send your own files +
+ Try Firefox Send
- {{else}}
@@ -53,17 +57,16 @@
-
- Send your own files +
+ Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever. +
+
+ Try Firefox Send
{{/if}}
- - - diff --git a/views/index.handlebars b/views/index.handlebars index b34a9e80..273d9879 100644 --- a/views/index.handlebars +++ b/views/index.handlebars @@ -3,48 +3,46 @@ Firefox Send {{#if dsn}} - {{> sentry dsn=dsn}} + {{> sentry dsn=dsn}} {{/if}} - + + {{#if trackerId}} - {{> analytics trackerId=trackerId}} + {{> analytics trackerId=trackerId}} {{/if}} - -
+
- Share your files quickly, privately and securely. + Private, Encrypted File Sharing
-
+
+ Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
Learn more +
+
Upload
-
- DRAG & DROP -
-
-
- your file/folder here or -
-
-
- - -
-
+
+ Drop your files here to start uploading
+ +
+ + +
+
- - - - + + + + @@ -57,57 +55,73 @@
- Uploading + Uploading Your File
-
-
Upload
-
- -
-
+
+ +
+ +
+
+ 0 + %
+
+
+
Cancel Upload
+
FileCopy URLExpires inDeleteFileCopy URLExpires inDelete