mirror of
https://gitlab.com/timvisee/send.git
synced 2025-12-06 14:10:53 +03:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e6d8eaddd | ||
|
|
faaa8afe17 | ||
|
|
84e8abf2c5 | ||
|
|
5db8a20b9d | ||
|
|
117c6ea12d | ||
|
|
83880d97e6 | ||
|
|
c3be5228cb | ||
|
|
ade214c69c | ||
|
|
4cb040d70d | ||
|
|
73ccce627c | ||
|
|
4043d35c8b | ||
|
|
a576cc0198 | ||
|
|
fa1f0208a4 | ||
|
|
193d3b1aef | ||
|
|
3b28ce88bf | ||
|
|
c77983d902 | ||
|
|
6ef9f8fa43 | ||
|
|
9136694d29 | ||
|
|
ec7f058afc | ||
|
|
62989ee2c9 | ||
|
|
85068e97ae | ||
|
|
dde2c4d5a9 | ||
|
|
20cf8d0a15 | ||
|
|
b2bd623a49 | ||
|
|
fc3001978c | ||
|
|
d6823f492d | ||
|
|
c3751c2efc | ||
|
|
70396e2f36 | ||
|
|
b557665d04 | ||
|
|
6393d70a33 | ||
|
|
834df8526f | ||
|
|
f5bd332ff8 | ||
|
|
0d5fb1740d | ||
|
|
5ed4db9689 | ||
|
|
31b810eb7d | ||
|
|
7243a10340 | ||
|
|
b18bcd3b6e | ||
|
|
09a6192bf5 | ||
|
|
9585850d6d | ||
|
|
14f3d837f9 | ||
|
|
d660eda64c | ||
|
|
463393552b | ||
|
|
cb0d69c5cd | ||
|
|
b9eb653f1f | ||
|
|
37bb6fd982 | ||
|
|
e5d1e8f028 | ||
|
|
99477774cf | ||
|
|
76a6f02eb7 | ||
|
|
744dbb3a6f |
@@ -5,3 +5,4 @@ static
|
||||
test
|
||||
scripts
|
||||
docs
|
||||
firefox
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
public/bundle.js
|
||||
public/webcrypto-shim.js
|
||||
public
|
||||
test/frontend/bundle.js
|
||||
firefox
|
||||
firefox
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,9 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
public/bundle.js
|
||||
public/upload.js
|
||||
public/download.js
|
||||
public/version.json
|
||||
public/l20n.min.js
|
||||
static/*
|
||||
!static/info.txt
|
||||
test/frontend/bundle.js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extends: stylelint-config-standard
|
||||
|
||||
rules:
|
||||
color-hex-case: upper
|
||||
color-hex-case: lower
|
||||
declaration-colon-newline-after: null
|
||||
selector-list-comma-newline-after: null
|
||||
|
||||
@@ -16,7 +16,7 @@ deployment:
|
||||
latest:
|
||||
branch: master
|
||||
commands:
|
||||
- npm run predocker
|
||||
- npm run build
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- docker build -t mozilla/send:latest .
|
||||
- docker push mozilla/send:latest
|
||||
@@ -24,7 +24,7 @@ deployment:
|
||||
tag: /.*/
|
||||
owner: mozilla
|
||||
commands:
|
||||
- npm run predocker
|
||||
- npm run build
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- docker build -t mozilla/send:$CIRCLE_TAG .
|
||||
- docker push mozilla/send:$CIRCLE_TAG
|
||||
|
||||
@@ -7,6 +7,6 @@ services:
|
||||
ports:
|
||||
- "1443:1443"
|
||||
environment:
|
||||
- P2P_REDIS_HOST=redis
|
||||
- REDIS_HOST=redis
|
||||
redis:
|
||||
image: redis:alpine
|
||||
|
||||
@@ -3,12 +3,22 @@
|
||||
| Name | Description
|
||||
|------------------|-------------|
|
||||
| `PORT` | Port the server will listen on (defaults to 1443).
|
||||
| `P2P_S3_BUCKET` | The S3 bucket name.
|
||||
| `P2P_REDIS_HOST` | Host name of the Redis server.
|
||||
| `S3_BUCKET` | The S3 bucket name.
|
||||
| `REDIS_HOST` | Host name of the Redis server.
|
||||
| `GOOGLE_ANALYTICS_ID` | Google Analytics ID
|
||||
| `SENTRY_CLIENT` | Sentry Client ID
|
||||
| `SENTRY_DSN` | Sentry DSN
|
||||
| `MAX_FILE_SIZE` | in bytes (defaults to 2147483648)
|
||||
| `NODE_ENV` | "production"
|
||||
|
||||
## Example:
|
||||
|
||||
```sh
|
||||
$ docker run --net=host -e 'NODE_ENV=production' -e 'P2P_S3_BUCKET=testpilot-p2p-dev' -e 'P2P_REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' mozilla/send:latest
|
||||
$ docker run --net=host -e 'NODE_ENV=production' \
|
||||
-e 'S3_BUCKET=testpilot-p2p-dev' \
|
||||
-e 'REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' \
|
||||
-e 'GOOGLE_ANALYTICS_ID=UA-35433268-78' \
|
||||
-e 'SENTRY_CLIENT=https://51e23d7263e348a7a3b90a5357c61cb2@sentry.prod.mozaws.net/168' \
|
||||
-e 'SENTRY_DSN=https://51e23d7263e348a7a3b90a5357c61cb2:65e23d7263e348a7a3b90a5357c61c44@sentry.prod.mozaws.net/168' \
|
||||
mozilla/send:latest
|
||||
```
|
||||
|
||||
@@ -112,6 +112,7 @@ Fired whenever a user deletes a file they’ve uploaded.
|
||||
- `cm6`
|
||||
- `cm7`
|
||||
- `cd1`
|
||||
- `cd4`
|
||||
|
||||
#### `copied`
|
||||
Fired whenever a user copies the URL of an upload file.
|
||||
|
||||
10
frontend/src/common.js
Normal file
10
frontend/src/common.js
Normal file
@@ -0,0 +1,10 @@
|
||||
window.Raven = require('raven-js');
|
||||
window.Raven.config(window.dsn).install();
|
||||
window.dsn = undefined;
|
||||
|
||||
const testPilotGA = require('testpilot-ga');
|
||||
window.analytics = new testPilotGA({
|
||||
an: 'Firefox Send',
|
||||
ds: 'web',
|
||||
tid: window.trackerId
|
||||
});
|
||||
@@ -1,78 +1,175 @@
|
||||
require('./common');
|
||||
const FileReceiver = require('./fileReceiver');
|
||||
const { notify } = require('./utils');
|
||||
const { notify, findMetric, gcmCompliant, sendEvent } = require('./utils');
|
||||
const bytes = require('bytes');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
|
||||
const Raven = window.Raven;
|
||||
|
||||
$(document).ready(function() {
|
||||
gcmCompliant().catch(err => {
|
||||
$('#download').attr('hidden', true);
|
||||
sendEvent('recipient', 'unsupported', {
|
||||
cd6: err
|
||||
}).then(() => {
|
||||
location.replace('/unsupported');
|
||||
});
|
||||
});
|
||||
//link back to homepage
|
||||
$('.send-new').attr('href', window.location.origin);
|
||||
|
||||
const filename = $('#dl-filename').html();
|
||||
$('.send-new').click(function(target) {
|
||||
target.preventDefault();
|
||||
sendEvent('recipient', 'restarted', {
|
||||
cd2: 'completed'
|
||||
}).then(() => {
|
||||
location.href = target.currentTarget.href;
|
||||
});
|
||||
});
|
||||
|
||||
$('.legal-links a, .social-links a, #dl-firefox').click(function(target) {
|
||||
target.preventDefault();
|
||||
const metric = findMetric(target.currentTarget.href);
|
||||
// record exited event by recipient
|
||||
sendEvent('recipient', 'exited', {
|
||||
cd3: metric
|
||||
}).then(() => {
|
||||
location.href = target.currentTarget.href;
|
||||
});
|
||||
});
|
||||
|
||||
const filename = $('#dl-filename').text();
|
||||
const bytelength = Number($('#dl-bytelength').text());
|
||||
const timeToExpiry = Number($('#dl-ttl').text());
|
||||
|
||||
//initiate progress bar
|
||||
$('#dl-progress').circleProgress({
|
||||
value: 0.0,
|
||||
startAngle: -Math.PI / 2,
|
||||
fill: '#00C8D7',
|
||||
fill: '#3B9DFF',
|
||||
size: 158,
|
||||
animation: { duration: 300 }
|
||||
});
|
||||
$('#download-btn').click(download);
|
||||
function download() {
|
||||
storage.totalDownloads += 1;
|
||||
|
||||
const fileReceiver = new FileReceiver();
|
||||
const unexpiredFiles = storage.numFiles;
|
||||
|
||||
fileReceiver.on('progress', progress => {
|
||||
window.onunload = function() {
|
||||
storage.referrer = 'cancelled-download';
|
||||
// record download-stopped (cancelled by tab close or reload)
|
||||
sendEvent('recipient', 'download-stopped', {
|
||||
cm1: bytelength,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd2: 'cancelled'
|
||||
});
|
||||
};
|
||||
|
||||
$('#download-page-one').attr('hidden', true);
|
||||
$('#download-progress').removeAttr('hidden');
|
||||
const percent = progress[0] / progress[1];
|
||||
// update progress bar
|
||||
$('#dl-progress').circleProgress('value', percent);
|
||||
$('.percent-number').html(`${Math.floor(percent * 100)}`);
|
||||
$('.progress-text').text(`${filename} (${bytes(progress[0], {decimalPlaces: 1, fixedDecimals: true})} of ${bytes(progress[1], {decimalPlaces: 1})})`);
|
||||
//on complete
|
||||
if (percent === 1) {
|
||||
fileReceiver.removeAllListeners('progress');
|
||||
document.l10n.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
$('.title').html(translated[1]);
|
||||
});
|
||||
}
|
||||
$('.percent-number').text(`${Math.floor(percent * 100)}`);
|
||||
$('.progress-text').text(
|
||||
`${filename} (${bytes(progress[0], {
|
||||
decimalPlaces: 1,
|
||||
fixedDecimals: true
|
||||
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
|
||||
);
|
||||
});
|
||||
|
||||
let downloadEnd;
|
||||
fileReceiver.on('decrypting', isStillDecrypting => {
|
||||
// The file is being decrypted
|
||||
if (isStillDecrypting) {
|
||||
console.log('Decrypting');
|
||||
fileReceiver.removeAllListeners('progress');
|
||||
window.onunload = null;
|
||||
document.l10n.formatValue('decryptingFile').then(decryptingFile => {
|
||||
$('.progress-text').text(decryptingFile);
|
||||
});
|
||||
} else {
|
||||
console.log('Done decrypting');
|
||||
downloadEnd = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
fileReceiver.on('hashing', isStillHashing => {
|
||||
// The file is being hashed to make sure a malicious user hasn't tampered with it
|
||||
if (isStillHashing) {
|
||||
console.log('Checking file integrity');
|
||||
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
|
||||
$('.progress-text').text(verifyingFile);
|
||||
});
|
||||
} else {
|
||||
console.log('Integrity check done');
|
||||
$('.progress-text').text(' ');
|
||||
document.l10n
|
||||
.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
$('.title').text(translated[1]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// record download-started by recipient
|
||||
sendEvent('recipient', 'download-started', {
|
||||
cm1: bytelength,
|
||||
cm4: timeToExpiry,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads
|
||||
});
|
||||
|
||||
fileReceiver
|
||||
.download()
|
||||
.catch(() => {
|
||||
document.l10n.formatValue('expiredPageHeader')
|
||||
.then(translated => {
|
||||
$('.title').text(translated);
|
||||
});
|
||||
.catch(err => {
|
||||
// record download-stopped (errored) by recipient
|
||||
sendEvent('recipient', 'download-stopped', {
|
||||
cm1: bytelength,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd2: 'errored',
|
||||
cd6: err
|
||||
});
|
||||
|
||||
document.l10n.formatValue('expiredPageHeader').then(translated => {
|
||||
$('.title').text(translated);
|
||||
});
|
||||
$('#download-btn').attr('hidden', true);
|
||||
$('#expired-img').removeAttr('hidden');
|
||||
console.log('The file has expired, or has already been deleted.');
|
||||
return;
|
||||
})
|
||||
.then(([decrypted, fname]) => {
|
||||
const endTime = Date.now();
|
||||
const totalTime = endTime - startTime;
|
||||
const downloadTime = endTime - downloadEnd;
|
||||
const downloadSpeed = bytelength / (downloadTime / 1000);
|
||||
|
||||
storage.referrer = 'completed-download';
|
||||
// record download-stopped (completed) by recipient
|
||||
sendEvent('recipient', 'download-stopped', {
|
||||
cm1: bytelength,
|
||||
cm2: totalTime,
|
||||
cm3: downloadSpeed,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd2: 'completed'
|
||||
});
|
||||
|
||||
const dataView = new DataView(decrypted);
|
||||
const blob = new Blob([dataView]);
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
|
||||
@@ -58,41 +58,47 @@ 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 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(([fdata, key]) => {
|
||||
this.emit('decrypting', true);
|
||||
return Promise.all([
|
||||
window.crypto.subtle
|
||||
.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: hexToArray(fdata.iv),
|
||||
additionalData: hexToArray(fdata.aad),
|
||||
tagLength: 128
|
||||
},
|
||||
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)]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
window.Raven = require('raven-js');
|
||||
window.Raven.config(window.dsn).install();
|
||||
window.dsn = undefined;
|
||||
require('./upload');
|
||||
require('./download');
|
||||
66
frontend/src/storage.js
Normal file
66
frontend/src/storage.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const { isFile } = require('./utils');
|
||||
|
||||
class Storage {
|
||||
constructor(engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
get totalDownloads() {
|
||||
return Number(this.engine.getItem('totalDownloads'));
|
||||
}
|
||||
set totalDownloads(n) {
|
||||
this.engine.setItem('totalDownloads', n);
|
||||
}
|
||||
get totalUploads() {
|
||||
return Number(this.engine.getItem('totalUploads'));
|
||||
}
|
||||
set totalUploads(n) {
|
||||
this.engine.setItem('totalUploads', n);
|
||||
}
|
||||
get referrer() {
|
||||
return this.engine.getItem('referrer');
|
||||
}
|
||||
set referrer(str) {
|
||||
this.engine.setItem('referrer', str);
|
||||
}
|
||||
|
||||
get files() {
|
||||
const fs = [];
|
||||
for (let i = 0; i < this.engine.length; i++) {
|
||||
const k = this.engine.key(i);
|
||||
if (isFile(k)) {
|
||||
fs.push(JSON.parse(this.engine.getItem(k))); // parse or whatever else
|
||||
}
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
get numFiles() {
|
||||
let length = 0;
|
||||
for (let i = 0; i < this.engine.length; i++) {
|
||||
const k = this.engine.key(i);
|
||||
if (isFile(k)) {
|
||||
length += 1;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
getFileById(id) {
|
||||
return this.engine.getItem(id);
|
||||
}
|
||||
|
||||
has(property) {
|
||||
return this.engine.hasOwnProperty(property);
|
||||
}
|
||||
|
||||
remove(property) {
|
||||
this.engine.removeItem(property);
|
||||
}
|
||||
|
||||
addFile(id, file) {
|
||||
this.engine.setItem(id, JSON.stringify(file));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Storage;
|
||||
@@ -1,19 +1,74 @@
|
||||
/* global MAXFILESIZE */
|
||||
/* global MAXFILESIZE EXPIRE_SECONDS */
|
||||
require('./common');
|
||||
const FileSender = require('./fileSender');
|
||||
const { notify, gcmCompliant } = require('./utils');
|
||||
const {
|
||||
notify,
|
||||
gcmCompliant,
|
||||
findMetric,
|
||||
sendEvent,
|
||||
ONE_DAY_IN_MS
|
||||
} = require('./utils');
|
||||
const bytes = require('bytes');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
|
||||
const Raven = window.Raven;
|
||||
|
||||
if (storage.has('referrer')) {
|
||||
window.referrer = storage.referrer;
|
||||
storage.remove('referrer');
|
||||
} else {
|
||||
window.referrer = 'external';
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
gcmCompliant().catch(err => {
|
||||
$('#page-one').attr('hidden', true);
|
||||
$('#unsupported-browser').removeAttr('hidden');
|
||||
sendEvent('sender', 'unsupported', {
|
||||
cd6: err
|
||||
}).then(() => {
|
||||
location.replace('/unsupported');
|
||||
});
|
||||
});
|
||||
|
||||
$('#file-upload').change(onUpload);
|
||||
|
||||
$('.legal-links a, .social-links a, #dl-firefox').click(function(target) {
|
||||
target.preventDefault();
|
||||
const metric = findMetric(target.currentTarget.href);
|
||||
// record exited event by recipient
|
||||
sendEvent('sender', 'exited', {
|
||||
cd3: metric
|
||||
}).then(() => {
|
||||
location.href = target.currentTarget.href;
|
||||
});
|
||||
});
|
||||
|
||||
$('#send-new-completed').click(function(target) {
|
||||
target.preventDefault();
|
||||
// record restarted event
|
||||
sendEvent('sender', 'restarted', {
|
||||
cd2: 'completed'
|
||||
}).then(() => {
|
||||
storage.referrer = 'completed-upload';
|
||||
location.href = target.currentTarget.href;
|
||||
});
|
||||
});
|
||||
|
||||
$('#send-new-error').click(function(target) {
|
||||
target.preventDefault();
|
||||
// record restarted event
|
||||
sendEvent('sender', 'restarted', {
|
||||
cd2: 'errored'
|
||||
}).then(() => {
|
||||
storage.referrer = 'errored-upload';
|
||||
location.href = target.currentTarget.href;
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('dragover', allowDrop).on('drop', onUpload);
|
||||
// reset copy button
|
||||
const $copyBtn = $('#copy-btn');
|
||||
@@ -21,18 +76,23 @@ $(document).ready(function() {
|
||||
$('#link').attr('disabled', false);
|
||||
$copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
|
||||
|
||||
if (localStorage.length === 0) {
|
||||
const files = storage.files;
|
||||
if (files.length === 0) {
|
||||
toggleHeader();
|
||||
} else {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const id = localStorage.key(i);
|
||||
//check if file exists before adding to list
|
||||
checkExistence(id, true);
|
||||
for (const index in files) {
|
||||
const id = files[index].fileId;
|
||||
//check if file still exists before adding to list
|
||||
checkExistence(id, files[index], true);
|
||||
}
|
||||
}
|
||||
|
||||
// copy link to clipboard
|
||||
$copyBtn.click(() => {
|
||||
// record copied event from success screen
|
||||
sendEvent('sender', 'copied', {
|
||||
cd4: 'success-screen'
|
||||
});
|
||||
const aux = document.createElement('input');
|
||||
aux.setAttribute('value', $('#link').attr('value'));
|
||||
document.body.appendChild(aux);
|
||||
@@ -42,7 +102,9 @@ $(document).ready(function() {
|
||||
//disable button for 3s
|
||||
$copyBtn.attr('disabled', true);
|
||||
$('#link').attr('disabled', true);
|
||||
$copyBtn.html('<img src="/resources/check-16.svg" class="icon-check"></img>');
|
||||
$copyBtn.html(
|
||||
'<img src="/resources/check-16.svg" class="icon-check"></img>'
|
||||
);
|
||||
window.setTimeout(() => {
|
||||
$copyBtn.attr('disabled', false);
|
||||
$('#link').attr('disabled', false);
|
||||
@@ -71,14 +133,28 @@ $(document).ready(function() {
|
||||
// on file upload by browse or drag & drop
|
||||
function onUpload(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// don't allow upload if not on upload page
|
||||
if ($('#page-one').attr('hidden')){
|
||||
return;
|
||||
}
|
||||
|
||||
storage.totalUploads += 1;
|
||||
|
||||
let file = '';
|
||||
if (event.type === 'drop') {
|
||||
if (event.originalEvent.dataTransfer.files.length > 1 || event.originalEvent.dataTransfer.files[0].size === 0){
|
||||
if (!event.originalEvent.dataTransfer.files[0]) {
|
||||
$('.upload-window').removeClass('ondrag');
|
||||
document.l10n.formatValue('uploadPageMultipleFilesAlert')
|
||||
.then(str => {
|
||||
alert(str);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
event.originalEvent.dataTransfer.files.length > 1 ||
|
||||
event.originalEvent.dataTransfer.files[0].size === 0
|
||||
) {
|
||||
$('.upload-window').removeClass('ondrag');
|
||||
document.l10n.formatValue('uploadPageMultipleFilesAlert').then(str => {
|
||||
alert(str);
|
||||
});
|
||||
return;
|
||||
}
|
||||
file = event.originalEvent.dataTransfer.files[0];
|
||||
@@ -87,24 +163,38 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
if (file.size > MAXFILESIZE) {
|
||||
return document.l10n.formatValue('fileTooBig', {size: bytes(MAXFILESIZE)}).then(alert);
|
||||
return document.l10n
|
||||
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
|
||||
.then(alert);
|
||||
}
|
||||
|
||||
$('#page-one').attr('hidden', true);
|
||||
$('#upload-error').attr('hidden', true);
|
||||
$('#upload-progress').removeAttr('hidden');
|
||||
document.l10n.formatValue('importingFile').then(importingFile => {
|
||||
$('.progress-text').text(importingFile);
|
||||
});
|
||||
//don't allow drag and drop when not on page-one
|
||||
$('body').off('drop', onUpload);
|
||||
const expiration = 24 * 60 * 60 * 1000; //will eventually come from a field
|
||||
|
||||
const fileSender = new FileSender(file);
|
||||
$('#cancel-upload').click(() => {
|
||||
fileSender.cancel();
|
||||
location.reload();
|
||||
document.l10n.formatValue('uploadCancelNotification')
|
||||
.then(str => {
|
||||
notify(str);
|
||||
});
|
||||
document.l10n.formatValue('uploadCancelNotification').then(str => {
|
||||
notify(str);
|
||||
});
|
||||
storage.referrer = 'cancelled-upload';
|
||||
|
||||
// record upload-stopped (cancelled) by sender
|
||||
sendEvent('sender', 'upload-stopped', {
|
||||
cm1: file.size,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: event.type === 'drop' ? 'drop' : 'click',
|
||||
cd2: 'cancelled'
|
||||
});
|
||||
});
|
||||
|
||||
fileSender.on('progress', progress => {
|
||||
@@ -112,89 +202,147 @@ $(document).ready(function() {
|
||||
// update progress bar
|
||||
$('#ul-progress').circleProgress('value', percent);
|
||||
$('#ul-progress').circleProgress().on('circle-animation-end', function() {
|
||||
$('.percent-number').html(`${Math.floor(percent * 100)}`);
|
||||
$('.percent-number').text(`${Math.floor(percent * 100)}`);
|
||||
});
|
||||
$('.progress-text').text(`${file.name} (${bytes(progress[0], {decimalPlaces: 1, fixedDecimals: true})} of ${bytes(progress[1], {decimalPlaces: 1})})`);
|
||||
});
|
||||
|
||||
fileSender.on('loading', isStillLoading => {
|
||||
// The file is loading into Firefox at this stage
|
||||
if (isStillLoading) {
|
||||
console.log('Processing');
|
||||
} else {
|
||||
console.log('Finished processing');
|
||||
}
|
||||
$('.progress-text').text(
|
||||
`${file.name} (${bytes(progress[0], {
|
||||
decimalPlaces: 1,
|
||||
fixedDecimals: true
|
||||
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
|
||||
);
|
||||
});
|
||||
|
||||
fileSender.on('hashing', isStillHashing => {
|
||||
// The file is being hashed
|
||||
if (isStillHashing) {
|
||||
console.log('Hashing');
|
||||
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
|
||||
$('.progress-text').text(verifyingFile);
|
||||
});
|
||||
} else {
|
||||
console.log('Finished hashing');
|
||||
}
|
||||
});
|
||||
|
||||
let uploadStart;
|
||||
fileSender.on('encrypting', isStillEncrypting => {
|
||||
// The file is being encrypted
|
||||
if (isStillEncrypting) {
|
||||
console.log('Encrypting');
|
||||
document.l10n.formatValue('encryptingFile').then(encryptingFile => {
|
||||
$('.progress-text').text(encryptingFile);
|
||||
});
|
||||
} else {
|
||||
console.log('Finished encrypting');
|
||||
uploadStart = Date.now();
|
||||
}
|
||||
});
|
||||
let t = '';
|
||||
fileSender
|
||||
.upload()
|
||||
.then(info => {
|
||||
const fileData = {
|
||||
name: file.name,
|
||||
fileId: info.fileId,
|
||||
url: info.url,
|
||||
secretKey: info.secretKey,
|
||||
deleteToken: info.deleteToken,
|
||||
creationDate: new Date(),
|
||||
expiry: expiration
|
||||
};
|
||||
localStorage.setItem(info.fileId, JSON.stringify(fileData));
|
||||
$('#upload-filename').attr('data-l10n-id', 'uploadSuccessConfirmHeader');
|
||||
t = window.setTimeout(() => {
|
||||
|
||||
let t;
|
||||
const startTime = Date.now();
|
||||
const unexpiredFiles = storage.numFiles + 1;
|
||||
|
||||
// record upload-started event by sender
|
||||
sendEvent('sender', 'upload-started', {
|
||||
cm1: file.size,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: event.type === 'drop' ? 'drop' : 'click',
|
||||
cd5: window.referrer
|
||||
});
|
||||
|
||||
// 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 totalTime = endTime - startTime;
|
||||
const uploadTime = endTime - uploadStart;
|
||||
const uploadSpeed = file.size / (uploadTime / 1000);
|
||||
const expiration = EXPIRE_SECONDS * 1000;
|
||||
|
||||
// record upload-stopped (completed) by sender
|
||||
sendEvent('sender', 'upload-stopped', {
|
||||
cm1: file.size,
|
||||
cm2: totalTime,
|
||||
cm3: uploadSpeed,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: event.type === 'drop' ? 'drop' : 'click',
|
||||
cd2: 'completed'
|
||||
});
|
||||
|
||||
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: totalTime,
|
||||
typeOfUpload: event.type === 'drop' ? 'drop' : 'click',
|
||||
uploadSpeed: uploadSpeed
|
||||
};
|
||||
|
||||
storage.addFile(info.fileId, fileData);
|
||||
$('#upload-filename').attr(
|
||||
'data-l10n-id',
|
||||
'uploadSuccessConfirmHeader'
|
||||
);
|
||||
t = window.setTimeout(() => {
|
||||
$('#page-one').attr('hidden', true);
|
||||
$('#upload-progress').attr('hidden', true);
|
||||
$('#upload-error').attr('hidden', true);
|
||||
$('#share-link').removeAttr('hidden');
|
||||
}, 1000);
|
||||
|
||||
populateFileList(fileData);
|
||||
document.l10n.formatValue('notifyUploadDone').then(str => {
|
||||
notify(str);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
Raven.captureException(err);
|
||||
$('#page-one').attr('hidden', true);
|
||||
$('#upload-progress').attr('hidden', true);
|
||||
$('#upload-error').attr('hidden', true);
|
||||
$('#share-link').removeAttr('hidden');
|
||||
}, 1000);
|
||||
$('#upload-error').removeAttr('hidden');
|
||||
window.clearTimeout(t);
|
||||
|
||||
populateFileList(JSON.stringify(fileData));
|
||||
document.l10n.formatValue('notifyUploadDone')
|
||||
.then(str => {
|
||||
notify(str);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
Raven.captureException(err);
|
||||
console.log(err);
|
||||
$('#page-one').attr('hidden', true);
|
||||
$('#upload-progress').attr('hidden', true);
|
||||
$('#upload-error').removeAttr('hidden');
|
||||
window.clearTimeout(t);
|
||||
});
|
||||
// record upload-stopped (errored) by sender
|
||||
sendEvent('sender', 'upload-stopped', {
|
||||
cm1: file.size,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: event.type === 'drop' ? 'drop' : 'click',
|
||||
cd2: 'errored',
|
||||
cd6: err
|
||||
});
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function checkExistence(id, populate) {
|
||||
function checkExistence(id, file, populate) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
if (populate) {
|
||||
populateFileList(localStorage.getItem(id));
|
||||
populateFileList(file);
|
||||
}
|
||||
} else if (xhr.status === 404) {
|
||||
localStorage.removeItem(id);
|
||||
storage.remove(id);
|
||||
if (storage.numFiles === 0) {
|
||||
toggleHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -202,21 +350,23 @@ $(document).ready(function() {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
//update file table with current files in localStorage
|
||||
//update file table with current files in storage
|
||||
function populateFileList(file) {
|
||||
try {
|
||||
file = JSON.parse(file);
|
||||
} catch (e) {
|
||||
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'});
|
||||
const $copyIcon = $('<img>', {
|
||||
src: '/resources/copy-16.svg',
|
||||
class: 'icon-copy',
|
||||
'data-l10n-id': 'copyUrlHover'
|
||||
});
|
||||
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 $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);
|
||||
@@ -224,14 +374,8 @@ $(document).ready(function() {
|
||||
const url = file.url.trim() + `#${file.secretKey}`.trim();
|
||||
|
||||
$('#link').attr('value', url);
|
||||
$('#copy-text').attr(
|
||||
'data-l10n-args',
|
||||
'{"filename": "' + file.name + '"}'
|
||||
);
|
||||
$('#copy-text').attr(
|
||||
'data-l10n-id',
|
||||
'copyUrlFormLabelWithName'
|
||||
);
|
||||
$('#copy-text').attr('data-l10n-args', '{"filename": "' + file.name + '"}');
|
||||
$('#copy-text').attr('data-l10n-id', 'copyUrlFormLabelWithName');
|
||||
$popupText.attr('tabindex', '-1');
|
||||
|
||||
name.appendChild(cellText);
|
||||
@@ -252,16 +396,19 @@ $(document).ready(function() {
|
||||
|
||||
//copy link to clipboard when icon clicked
|
||||
$copyIcon.click(function() {
|
||||
// record copied event from upload list
|
||||
sendEvent('sender', 'copied', {
|
||||
cd4: 'upload-list'
|
||||
});
|
||||
const aux = document.createElement('input');
|
||||
aux.setAttribute('value', url);
|
||||
document.body.appendChild(aux);
|
||||
aux.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(aux);
|
||||
document.l10n.formatValue('copiedUrl')
|
||||
.then(translated => {
|
||||
link.innerHTML = translated;
|
||||
});
|
||||
document.l10n.formatValue('copiedUrl').then(translated => {
|
||||
link.innerHTML = translated;
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
const linkImg = document.createElement('img');
|
||||
$(linkImg).addClass('icon-copy');
|
||||
@@ -277,7 +424,7 @@ $(document).ready(function() {
|
||||
future.setTime(file.creationDate.getTime() + file.expiry);
|
||||
|
||||
let countdown = 0;
|
||||
countdown = future.getTime() - new Date().getTime();
|
||||
countdown = future.getTime() - Date.now();
|
||||
let minutes = Math.floor(countdown / 1000 / 60);
|
||||
let hours = Math.floor(minutes / 60);
|
||||
let seconds = Math.floor(countdown / 1000 % 60);
|
||||
@@ -285,7 +432,7 @@ $(document).ready(function() {
|
||||
poll();
|
||||
|
||||
function poll() {
|
||||
countdown = future.getTime() - new Date().getTime();
|
||||
countdown = future.getTime() - Date.now();
|
||||
minutes = Math.floor(countdown / 1000 / 60);
|
||||
hours = Math.floor(minutes / 60);
|
||||
seconds = Math.floor(countdown / 1000 % 60);
|
||||
@@ -304,7 +451,7 @@ $(document).ready(function() {
|
||||
}
|
||||
//remove from list when expired
|
||||
if (countdown <= 0) {
|
||||
localStorage.removeItem(file.fileId);
|
||||
storage.remove(file.fileId);
|
||||
$(expiry).parents('tr').remove();
|
||||
window.clearTimeout(t);
|
||||
toggleHeader();
|
||||
@@ -313,21 +460,16 @@ $(document).ready(function() {
|
||||
|
||||
// create popup
|
||||
popupDiv.classList.add('popup');
|
||||
const popupDelSpan = document.createElement('span');
|
||||
$(popupDelSpan).addClass('del-file');
|
||||
$(popupDelSpan).attr('data-l10n-id', 'sentFilesTitle4');
|
||||
const $popupMessage = $('<div>', { class: 'popup-message' });
|
||||
$popupMessage.attr('data-l10n-id', 'deletePopupText');
|
||||
|
||||
const popupNvmSpan = document.createElement('span');
|
||||
$(popupNvmSpan).addClass('nvm');
|
||||
$(popupNvmSpan).attr('data-l10n-id', 'nevermindButton');
|
||||
const $popupDelSpan = $('<span>', { class: 'popup-yes' });
|
||||
$popupDelSpan.attr('data-l10n-id', 'deletePopupYes');
|
||||
|
||||
$popupText.html([
|
||||
popupDelSpan,
|
||||
' ',
|
||||
' ',
|
||||
popupNvmSpan
|
||||
]);
|
||||
const $popupNvmSpan = $('<span>', { class: 'popup-no' });
|
||||
$popupNvmSpan.attr('data-l10n-id', 'deletePopupCancel');
|
||||
|
||||
$popupText.html([$popupMessage, $popupDelSpan, $popupNvmSpan]);
|
||||
|
||||
// add data cells to table row
|
||||
row.appendChild(name);
|
||||
@@ -340,18 +482,51 @@ $(document).ready(function() {
|
||||
row.appendChild(del);
|
||||
$('tbody').append(row); //add row to table
|
||||
|
||||
const unexpiredFiles = storage.numFiles;
|
||||
|
||||
// delete file
|
||||
$popupText.find('.del-file').click(e => {
|
||||
$popupText.find('.popup-yes').click(e => {
|
||||
FileSender.delete(file.fileId, file.deleteToken).then(() => {
|
||||
$(e.target).parents('tr').remove();
|
||||
localStorage.removeItem(file.fileId);
|
||||
const timeToExpiry =
|
||||
ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
|
||||
// record upload-deleted from file list
|
||||
sendEvent('sender', 'upload-deleted', {
|
||||
cm1: file.size,
|
||||
cm2: file.totalTime,
|
||||
cm3: file.uploadSpeed,
|
||||
cm4: timeToExpiry,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: file.typeOfUpload,
|
||||
cd4: 'upload-list'
|
||||
}).then(() => {
|
||||
storage.remove(file.fileId);
|
||||
});
|
||||
toggleHeader();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('delete-file').onclick = () => {
|
||||
FileSender.delete(file.fileId, file.deleteToken).then(() => {
|
||||
localStorage.removeItem(file.fileId);
|
||||
location.reload();
|
||||
const timeToExpiry =
|
||||
ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
|
||||
// record upload-deleted from success screen
|
||||
sendEvent('sender', 'upload-deleted', {
|
||||
cm1: file.size,
|
||||
cm2: file.totalTime,
|
||||
cm3: file.uploadSpeed,
|
||||
cm4: timeToExpiry,
|
||||
cm5: storage.totalUploads,
|
||||
cm6: unexpiredFiles,
|
||||
cm7: storage.totalDownloads,
|
||||
cd1: file.typeOfUpload,
|
||||
cd4: 'success-screen'
|
||||
}).then(() => {
|
||||
storage.remove(file.fileId);
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
};
|
||||
// show popup
|
||||
@@ -360,7 +535,7 @@ $(document).ready(function() {
|
||||
$popupText.focus();
|
||||
});
|
||||
// hide popup
|
||||
$popupText.find('.nvm').click(function(e) {
|
||||
$popupText.find('.popup-no').click(function(e) {
|
||||
e.stopPropagation();
|
||||
$popupText.removeClass('show');
|
||||
});
|
||||
@@ -372,7 +547,6 @@ $(document).ready(function() {
|
||||
$popupText.removeClass('show');
|
||||
});
|
||||
|
||||
|
||||
toggleHeader();
|
||||
}
|
||||
function toggleHeader() {
|
||||
|
||||
@@ -69,9 +69,55 @@ function gcmCompliant() {
|
||||
}
|
||||
}
|
||||
|
||||
function findMetric(href) {
|
||||
switch (href) {
|
||||
case 'https://www.mozilla.org/':
|
||||
return 'mozilla';
|
||||
case 'https://www.mozilla.org/about/legal':
|
||||
return 'legal';
|
||||
case 'https://testpilot.firefox.com/about':
|
||||
return 'about';
|
||||
case 'https://testpilot.firefox.com/privacy':
|
||||
return 'privacy';
|
||||
case 'https://testpilot.firefox.com/terms':
|
||||
return 'terms';
|
||||
case 'https://www.mozilla.org/en-US/privacy/websites/#cookies':
|
||||
return 'cookies';
|
||||
case 'https://github.com/mozilla/send':
|
||||
return 'github';
|
||||
case 'https://twitter.com/FxTestPilot':
|
||||
return 'twitter';
|
||||
case 'https://www.mozilla.org/firefox/new/?scene=2':
|
||||
return 'download-firefox';
|
||||
default:
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
function isFile(id) {
|
||||
return ![
|
||||
'referrer',
|
||||
'totalDownloads',
|
||||
'totalUploads',
|
||||
'testpilot_ga__cid'
|
||||
].includes(id);
|
||||
}
|
||||
|
||||
function sendEvent() {
|
||||
return window.analytics.sendEvent
|
||||
.apply(window.analytics, arguments)
|
||||
.catch(() => 0);
|
||||
}
|
||||
|
||||
const ONE_DAY_IN_MS = 86400000;
|
||||
|
||||
module.exports = {
|
||||
arrayToHex,
|
||||
hexToArray,
|
||||
notify,
|
||||
gcmCompliant
|
||||
gcmCompliant,
|
||||
findMetric,
|
||||
isFile,
|
||||
sendEvent,
|
||||
ONE_DAY_IN_MS
|
||||
};
|
||||
|
||||
1665
package-lock.json
generated
1665
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -1,44 +1,43 @@
|
||||
{
|
||||
"name": "firefox-send",
|
||||
"description": "File Sharing Experiment",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"author": "Mozilla (https://mozilla.org)",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.62.0",
|
||||
"aws-sdk": "^2.89.0",
|
||||
"body-parser": "^1.17.2",
|
||||
"bytes": "^2.5.0",
|
||||
"connect-busboy": "0.0.2",
|
||||
"convict": "^3.0.0",
|
||||
"cross-env": "^5.0.1",
|
||||
"express": "^4.15.3",
|
||||
"express-handlebars": "^3.0.0",
|
||||
"helmet": "^3.6.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery-circle-progress": "^1.2.2",
|
||||
"l20n": "^5.0.0",
|
||||
"helmet": "^3.8.0",
|
||||
"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"
|
||||
"redis": "^2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^14.4.0",
|
||||
"eslint": "^4.0.0",
|
||||
"eslint": "^4.3.0",
|
||||
"eslint-plugin-mocha": "^4.11.0",
|
||||
"eslint-plugin-node": "^5.0.0",
|
||||
"eslint-plugin-node": "^5.1.1",
|
||||
"eslint-plugin-security": "^1.4.0",
|
||||
"git-rev-sync": "^1.9.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery-circle-progress": "^1.2.2",
|
||||
"l20n": "^5.0.0",
|
||||
"mocha": "^3.4.2",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"prettier": "^1.4.4",
|
||||
"prettier": "^1.5.3",
|
||||
"proxyquire": "^1.8.0",
|
||||
"sinon": "^2.3.5",
|
||||
"stylelint": "^7.11.0",
|
||||
"raven-js": "^3.17.0",
|
||||
"selenium-webdriver": "^3.5.0",
|
||||
"sinon": "^2.3.8",
|
||||
"stylelint": "^7.13.0",
|
||||
"stylelint-config-standard": "^16.0.0",
|
||||
"watchify": "^3.9.0"
|
||||
"supertest": "^3.0.0",
|
||||
"testpilot-ga": "^0.3.0",
|
||||
"uglifyify": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
@@ -47,15 +46,20 @@
|
||||
"license": "MPL-2.0",
|
||||
"repository": "mozilla/send",
|
||||
"scripts": {
|
||||
"predocker": "browserify frontend/src/main.js | uglifyjs > public/bundle.js && npm run version",
|
||||
"dev": "npm run version && watchify frontend/src/main.js -o public/bundle.js -d | node server/server",
|
||||
"format": "prettier '{frontend/src/,scripts/,server/,test/}*.js' 'public/*.css' --single-quote --write",
|
||||
"build": "npm-run-all build:*",
|
||||
"build:upload": "browserify frontend/src/upload.js -g uglifyify -o public/upload.js",
|
||||
"build:download": "browserify frontend/src/download.js -g uglifyify -o public/download.js",
|
||||
"build:version": "node scripts/version",
|
||||
"build:l10n": "cp node_modules/l20n/dist/web/l20n.min.js public",
|
||||
"dev": "npm run build && npm start",
|
||||
"format": "prettier '{frontend/src/,scripts/,server/,test/**/}*.js' 'public/*.css' --single-quote --write",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:css": "stylelint 'public/*.css'",
|
||||
"lint:js": "eslint .",
|
||||
"start": "node server/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"
|
||||
"test": "npm-run-all test:*",
|
||||
"test:unit": "mocha test/unit",
|
||||
"test:server": "mocha test/server",
|
||||
"test:browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d && node test/frontend/driver.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
title = Firefox Send
|
||||
siteSubtitle = web experiment
|
||||
siteFeedback = Feedback
|
||||
|
||||
uploadPageHeader = Private, Encrypted File Sharing
|
||||
uploadPageExplainer = Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
|
||||
@@ -13,6 +15,10 @@ uploadPageMultipleFilesAlert = Uploading multiple files or a folder is currently
|
||||
uploadPageBrowseButtonTitle = Upload file
|
||||
|
||||
uploadingPageHeader = Uploading Your File
|
||||
importingFile = Importing...
|
||||
verifyingFile = Verifying...
|
||||
encryptingFile = Encrypting...
|
||||
decryptingFile = Decrypting...
|
||||
|
||||
notifyUploadDone = Your upload has finished.
|
||||
|
||||
@@ -77,7 +83,9 @@ sentFilesTitle2 = Copy URL
|
||||
sentFilesTitle3 = Expires In
|
||||
sentFilesTitle4 = Delete
|
||||
|
||||
nevermindButton = Nevermind
|
||||
deletePopupText = Delete this file?
|
||||
deletePopupYes = Yes
|
||||
deletePopupCancel = Cancel
|
||||
deleteButtonHover.title = {sentFilesTitle4}
|
||||
copyUrlHover.title = {sentFilesTitle2}
|
||||
|
||||
|
||||
294
public/main.css
294
public/main.css
@@ -1,7 +1,12 @@
|
||||
/*** index.html ***/
|
||||
html {
|
||||
background: url('resources/send_bg.svg');
|
||||
font-family: 'SF Pro Text', sans-serif;
|
||||
font-family: -apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'SF Pro Text',
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-weight: 200;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
@@ -10,24 +15,95 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 31px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.send-logo {
|
||||
display: flex;
|
||||
position: relative;
|
||||
top: 31px;
|
||||
left: 31px;
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
color: #3e3d40;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.site-subtitle {
|
||||
color: #3e3d40;
|
||||
font-size: 12px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.site-subtitle a {
|
||||
font-weight: bold;
|
||||
color: #3e3d40;
|
||||
transition: color 50ms;
|
||||
}
|
||||
|
||||
.send-logo:hover a {
|
||||
color: #0297f8;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
background-color: #0297f8;
|
||||
background-image: url('resources/feedback.svg');
|
||||
background-position: 4px 6px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 14px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #0297f8;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
opacity: 0.9;
|
||||
padding: 6px 6px 5px 20px;
|
||||
}
|
||||
|
||||
.feedback:hover,
|
||||
.feedback:focus {
|
||||
background-color: #0287e8;
|
||||
}
|
||||
|
||||
.feedback:active {
|
||||
background-color: #0277d8;
|
||||
}
|
||||
|
||||
.all {
|
||||
padding-top: 10%;
|
||||
padding-bottom: 51px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
max-width: 630px;
|
||||
margin: 0 auto;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
input, select, textarea, button {
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
@@ -38,24 +114,26 @@ a {
|
||||
/** page-one **/
|
||||
.title {
|
||||
font-size: 33px;
|
||||
line-height: 40px;
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
max-width: 520px;
|
||||
font-family: 'SF Pro Display', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
line-height: 23px;
|
||||
width: 630px;
|
||||
max-width: 630px;
|
||||
text-align: center;
|
||||
margin: 0 auto 60px;
|
||||
color: #0C0C0D;
|
||||
color: #0c0c0d;
|
||||
width: 92%;
|
||||
}
|
||||
|
||||
.upload-window {
|
||||
border: 1px dashed rgba(0, 148, 251, 0.5);
|
||||
margin: 0 auto;
|
||||
width: 640px;
|
||||
height: 255px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
@@ -63,14 +141,15 @@ a {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
transition: transform 150ms;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.upload-window.ondrag {
|
||||
border: 3px dashed rgba(0, 148, 251, 0.5);
|
||||
margin: 0 auto;
|
||||
width: 636px;
|
||||
height: 251px;
|
||||
transform: scale(1.05);
|
||||
transform: scale(1.04);
|
||||
border-radius: 4.2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -80,7 +159,7 @@ a {
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #0094FB;
|
||||
color: #0094fb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -92,10 +171,10 @@ a {
|
||||
}
|
||||
|
||||
#browse {
|
||||
background: #0297F8;
|
||||
background: #0297f8;
|
||||
border-radius: 5px;
|
||||
font-size: 15px;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
width: 240px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
@@ -104,12 +183,17 @@ a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#browse:hover {
|
||||
background-color: #0287e8;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-size-msg {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #737373;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
@@ -129,9 +213,10 @@ th {
|
||||
td {
|
||||
font-size: 15px;
|
||||
vertical-align: top;
|
||||
color: #4A4A4A;
|
||||
color: #4a4a4a;
|
||||
padding: 17px 19px 0;
|
||||
line-height: 23px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -141,42 +226,44 @@ table {
|
||||
|
||||
tbody {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#uploaded-files {
|
||||
width: 640px;
|
||||
margin: 45.3px auto;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.icon-delete, .icon-copy, .icon-check {
|
||||
.icon-delete,
|
||||
.icon-copy,
|
||||
.icon-check {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Popup container */
|
||||
.popup {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* The actual popup (appears on top) */
|
||||
.popup .popuptext {
|
||||
visibility: hidden;
|
||||
width: 160px;
|
||||
background-color: #555;
|
||||
color: #FFF;
|
||||
min-width: 115px;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #0297f8;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 8px 0;
|
||||
border-radius: 5px;
|
||||
padding: 7px 8px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
margin-left: -88px;
|
||||
bottom: 8px;
|
||||
right: -28px;
|
||||
transition: opacity 0.5s;
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
box-shadow: 3px 3px 7px #888;
|
||||
}
|
||||
|
||||
/* Popup arrow */
|
||||
@@ -184,11 +271,11 @@ tbody {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
right: 30px;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #555 transparent transparent;
|
||||
border-color: #0297f8 transparent transparent;
|
||||
}
|
||||
|
||||
.popup .show {
|
||||
@@ -196,6 +283,29 @@ tbody {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.popup-message {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.popup-yes {
|
||||
color: #fff;
|
||||
background-color: #0297f8;
|
||||
border-radius: 5px;
|
||||
padding: 2px 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popup-yes:hover {
|
||||
background-color: #0287e8;
|
||||
}
|
||||
|
||||
.popup-no {
|
||||
color: #4a4a4a;
|
||||
border-radius: 6px;
|
||||
padding: 3px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/** upload-progress **/
|
||||
.progress-bar {
|
||||
margin-top: 3px;
|
||||
@@ -239,14 +349,13 @@ tbody {
|
||||
}
|
||||
|
||||
#cancel-upload {
|
||||
color: #D70022;
|
||||
color: #d70022;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/** share-link **/
|
||||
#share-window {
|
||||
width: 645px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -262,53 +371,59 @@ tbody {
|
||||
#copy {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
#copy-text {
|
||||
align-self: flex-start;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 10px;
|
||||
color: #0C0C0D;
|
||||
color: #0c0c0d;
|
||||
}
|
||||
|
||||
#link {
|
||||
width: 480px;
|
||||
flex: 1;
|
||||
height: 56px;
|
||||
border: 1px solid #0297F8;
|
||||
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;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#link:disabled {
|
||||
border: 1px solid #05A700;
|
||||
background: #FFF;
|
||||
border: 1px solid #05a700;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#copy-btn {
|
||||
width: 165px;
|
||||
height: 60px;
|
||||
background: #0297F8;
|
||||
border: 1px solid #0297F8;
|
||||
flex: 0 1 165px;
|
||||
background: #0297f8;
|
||||
border-radius: 0 6px 6px 0;
|
||||
border: 1px solid #0297f8;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
height: 60px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#copy-btn:disabled {
|
||||
background: #05A700;
|
||||
border: 1px solid #05A700;
|
||||
background: #05a700;
|
||||
border: 1px solid #05a700;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
#delete-file {
|
||||
width: 176px;
|
||||
height: 44px;
|
||||
background: #FFF;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(12, 12, 13, 0.3);
|
||||
border-radius: 5px;
|
||||
font-size: 15px;
|
||||
@@ -322,7 +437,7 @@ tbody {
|
||||
font-size: 15px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
color: #0094FB;
|
||||
color: #0094fb;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -336,7 +451,8 @@ tbody {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#upload-error[hidden], #unsupported-browser[hidden] {
|
||||
#upload-error[hidden],
|
||||
#unsupported-browser[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -356,9 +472,8 @@ tbody {
|
||||
.unsupported-description {
|
||||
font-size: 13px;
|
||||
line-height: 23px;
|
||||
width: 630px;
|
||||
text-align: center;
|
||||
color: #7D7D7D;
|
||||
color: #7d7d7d;
|
||||
margin: 0 auto 23px;
|
||||
}
|
||||
|
||||
@@ -370,14 +485,14 @@ tbody {
|
||||
margin-bottom: 181px;
|
||||
width: 260px;
|
||||
height: 80px;
|
||||
background: #12BC00;
|
||||
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;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -406,15 +521,15 @@ tbody {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
background: #0297F8;
|
||||
border: 1px solid #0297F8;
|
||||
background: #0297f8;
|
||||
border: 1px solid #0297f8;
|
||||
border-radius: 5px;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#download-btn:disabled {
|
||||
background: #47B04B;
|
||||
background: #47b04b;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
@@ -434,9 +549,8 @@ tbody {
|
||||
.expired-description {
|
||||
font-size: 15px;
|
||||
line-height: 23px;
|
||||
width: 630px;
|
||||
text-align: center;
|
||||
color: #7D7D7D;
|
||||
color: #7d7d7d;
|
||||
margin: 0 auto 23px;
|
||||
}
|
||||
|
||||
@@ -460,14 +574,13 @@ tbody {
|
||||
|
||||
/* footer */
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 10px;
|
||||
padding: 50px 10px 10px;
|
||||
}
|
||||
|
||||
.mozilla-logo {
|
||||
@@ -495,8 +608,69 @@ tbody {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.github, .twitter {
|
||||
.github,
|
||||
.twitter {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
@media (max-device-width: 768px) {
|
||||
.description {
|
||||
margin: 0 auto 25px;
|
||||
}
|
||||
|
||||
#copy {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#link {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.mozilla-logo {
|
||||
margin-left: -7px;
|
||||
}
|
||||
|
||||
.legal-links > * {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#copy {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#link {
|
||||
font-size: 22px;
|
||||
padding: 15px 10px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
#copy-btn {
|
||||
border-radius: 0 0 6px 6px;
|
||||
flex: 0 1 65px;
|
||||
}
|
||||
|
||||
th {
|
||||
font-size: 14px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
padding: 17px 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
1
public/resources/feedback.svg
Normal file
1
public/resources/feedback.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="15" height="13" viewBox="0 0 15 13" xmlns="http://www.w3.org/2000/svg"><title>Combined Shape</title><path d="M10.274 9.193a5.957 5.957 0 0 1-2.98.778C4.37 9.97 2 7.963 2 5.485 2 3.008 4.37 1 7.294 1c2.924 0 5.294 2.008 5.294 4.485 0 .843-.274 1.632-.751 2.305l.577 2.21-2.14-.807zm-5.983-2.96a.756.756 0 0 0 .763-.748.756.756 0 0 0-.763-.747.756.756 0 0 0-.764.747c0 .413.342.748.764.748zm3.054 0a.756.756 0 0 0 .764-.748.756.756 0 0 0-.764-.747.756.756 0 0 0-.764.747c0 .413.342.748.764.748zm3.054 0a.756.756 0 0 0 .764-.748.756.756 0 0 0-.764-.747.756.756 0 0 0-.763.747c0 .413.342.748.763.748z" fill="#FFF" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 649 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 14 KiB |
@@ -4,12 +4,12 @@ const conf = convict({
|
||||
s3_bucket: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'P2P_S3_BUCKET'
|
||||
env: 'S3_BUCKET'
|
||||
},
|
||||
redis_host: {
|
||||
format: String,
|
||||
default: 'localhost',
|
||||
env: 'P2P_REDIS_HOST'
|
||||
env: 'REDIS_HOST'
|
||||
},
|
||||
listen_port: {
|
||||
format: 'port',
|
||||
@@ -25,12 +25,12 @@ const conf = convict({
|
||||
sentry_id: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'P2P_SENTRY_CLIENT'
|
||||
env: 'SENTRY_CLIENT'
|
||||
},
|
||||
sentry_dsn: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'P2P_SENTRY_DSN'
|
||||
env: 'SENTRY_DSN'
|
||||
},
|
||||
env: {
|
||||
format: ['production', 'development', 'test'],
|
||||
@@ -39,8 +39,13 @@ const conf = convict({
|
||||
},
|
||||
max_file_size: {
|
||||
format: Number,
|
||||
default: (1024 * 1024 * 1024) * 2,
|
||||
env: 'P2P_MAX_FILE_SIZE'
|
||||
default: 1024 * 1024 * 1024 * 2,
|
||||
env: 'MAX_FILE_SIZE'
|
||||
},
|
||||
expire_seconds: {
|
||||
format: Number,
|
||||
default: 86400,
|
||||
env: 'EXPIRE_SECONDS'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ const mozlog = require('./log.js');
|
||||
const log = mozlog('send.server');
|
||||
|
||||
const STATIC_PATH = path.join(__dirname, '../public');
|
||||
const L20N = path.join(__dirname, '../node_modules/l20n');
|
||||
const LOCALES = path.join(__dirname, '../public/locales');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -34,10 +32,12 @@ app.engine(
|
||||
app.set('view engine', 'handlebars');
|
||||
|
||||
app.use(helmet());
|
||||
app.use(helmet.hsts({
|
||||
maxAge: 31536000,
|
||||
force: conf.env === 'production'
|
||||
}));
|
||||
app.use(
|
||||
helmet.hsts({
|
||||
maxAge: 31536000,
|
||||
force: conf.env === 'production'
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
@@ -62,26 +62,31 @@ app.use(
|
||||
}
|
||||
})
|
||||
);
|
||||
app.use(busboy({
|
||||
limits: {
|
||||
fileSize: conf.max_file_size
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
busboy({
|
||||
limits: {
|
||||
fileSize: conf.max_file_size
|
||||
}
|
||||
})
|
||||
);
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.static(STATIC_PATH));
|
||||
app.use('/l20n', express.static(L20N));
|
||||
app.use('/locales', express.static(LOCALES));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
app.get('/unsupported', (req, res) => {
|
||||
res.render('unsupported');
|
||||
});
|
||||
|
||||
app.get('/jsconfig.js', (req, res) => {
|
||||
res.set('Content-Type', 'application/javascript');
|
||||
res.render('jsconfig', {
|
||||
trackerId: conf.analytics_id,
|
||||
dsn: conf.sentry_id,
|
||||
maxFileSize: conf.max_file_size,
|
||||
expireSeconds: conf.expire_seconds,
|
||||
layout: false
|
||||
});
|
||||
});
|
||||
@@ -112,15 +117,17 @@ app.get('/download/:id', (req, res) => {
|
||||
storage
|
||||
.length(id)
|
||||
.then(contentLength => {
|
||||
res.render('download', {
|
||||
filename: decodeURIComponent(filename),
|
||||
filesize: bytes(contentLength),
|
||||
trackerId: conf.analytics_id,
|
||||
dsn: conf.sentry_id
|
||||
storage.ttl(id).then(timeToExpiry => {
|
||||
res.render('download', {
|
||||
filename: decodeURIComponent(filename),
|
||||
filesize: bytes(contentLength),
|
||||
sizeInBytes: contentLength,
|
||||
timeToExpiry: timeToExpiry
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
res.render('download');
|
||||
res.status(404).render('notfound');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -224,21 +231,23 @@ app.post('/upload', (req, res, next) => {
|
||||
req.busboy.on('file', (fieldname, file, filename) => {
|
||||
log.info('Uploading:', newId);
|
||||
|
||||
storage.set(newId, file, filename, meta).then(() => {
|
||||
const protocol = conf.env === 'production' ? 'https' : req.protocol;
|
||||
const url = `${protocol}://${req.get('host')}/download/${newId}/`;
|
||||
res.json({
|
||||
url,
|
||||
delete: meta.delete,
|
||||
id: newId
|
||||
});
|
||||
},
|
||||
err => {
|
||||
if (err.message === 'limit') {
|
||||
return res.sendStatus(413);
|
||||
storage.set(newId, file, filename, meta).then(
|
||||
() => {
|
||||
const protocol = conf.env === 'production' ? 'https' : req.protocol;
|
||||
const url = `${protocol}://${req.get('host')}/download/${newId}/`;
|
||||
res.json({
|
||||
url,
|
||||
delete: meta.delete,
|
||||
id: newId
|
||||
});
|
||||
},
|
||||
err => {
|
||||
if (err.message === 'limit') {
|
||||
return res.sendStatus(413);
|
||||
}
|
||||
res.sendStatus(500);
|
||||
}
|
||||
res.sendStatus(500);
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
req.on('close', err => {
|
||||
@@ -252,7 +261,7 @@ app.post('/upload', (req, res, next) => {
|
||||
.catch(err => {
|
||||
log.info('DeleteError:', newId);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/__lbheartbeat__', (req, res) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ if (conf.s3_bucket) {
|
||||
module.exports = {
|
||||
filename: filename,
|
||||
exists: exists,
|
||||
ttl: ttl,
|
||||
length: awsLength,
|
||||
get: awsGet,
|
||||
set: awsSet,
|
||||
@@ -39,6 +40,7 @@ if (conf.s3_bucket) {
|
||||
module.exports = {
|
||||
filename: filename,
|
||||
exists: exists,
|
||||
ttl: ttl,
|
||||
length: localLength,
|
||||
get: localGet,
|
||||
set: localSet,
|
||||
@@ -73,6 +75,18 @@ function metadata(id) {
|
||||
});
|
||||
}
|
||||
|
||||
function ttl(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
redis_client.ttl(id, (err, reply) => {
|
||||
if (!err) {
|
||||
resolve(reply * 1000);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function filename(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
redis_client.hget(id, 'filename', (err, reply) => {
|
||||
@@ -138,7 +152,7 @@ function localSet(newId, file, filename, meta) {
|
||||
});
|
||||
fstream.on('finish', () => {
|
||||
redis_client.hmset(newId, meta);
|
||||
redis_client.expire(newId, 86400000);
|
||||
redis_client.expire(newId, conf.expire_seconds);
|
||||
log.info('localSet:', 'Upload Finished of ' + newId);
|
||||
resolve(meta.delete);
|
||||
});
|
||||
@@ -221,10 +235,10 @@ function awsSet(newId, file, filename, meta) {
|
||||
hitLimit = true;
|
||||
upload.abort();
|
||||
});
|
||||
return upload.promise()
|
||||
.then(() => {
|
||||
return upload.promise().then(
|
||||
() => {
|
||||
redis_client.hmset(newId, meta);
|
||||
redis_client.expire(newId, 86400000);
|
||||
redis_client.expire(newId, conf.expire_seconds);
|
||||
log.info('awsUploadFinish', 'Upload Finished of ' + filename);
|
||||
},
|
||||
err => {
|
||||
@@ -233,7 +247,8 @@ function awsSet(newId, file, filename, meta) {
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function awsDelete(id, delete_token) {
|
||||
|
||||
@@ -19,3 +19,5 @@ rules:
|
||||
mocha/no-pending-tests: error
|
||||
mocha/no-return-and-callback: warn
|
||||
mocha/no-skipped-tests: error
|
||||
|
||||
no-console: off # ¯\_(ツ)_/¯
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('Testing Set using aws', function() {
|
||||
.set('123', {on: sinon.stub()}, 'Filename.moz', {})
|
||||
.then(() => {
|
||||
assert(expire.calledOnce);
|
||||
assert(expire.calledWith('123', 86400000));
|
||||
assert(expire.calledWith('123', 86400));
|
||||
})
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div id="download">
|
||||
{{#if filename}}
|
||||
<script src="/download.js"></script>
|
||||
<div id="download-page-one">
|
||||
<div class="title">
|
||||
<span id="dl-filename"
|
||||
@@ -7,6 +7,8 @@
|
||||
data-l10n-args='{"filename": "{{filename}}"}'></span>
|
||||
<span data-l10n-id="downloadFileSize"
|
||||
data-l10n-args='{"size": "{{filesize}}"}'></span>
|
||||
<span id="dl-bytelength" hidden="true">{{sizeInBytes}}</span>
|
||||
<span id="dl-ttl" hidden="true">{{timeToExpiry}}</span>
|
||||
</div>
|
||||
<div class="description" data-l10n-id="downloadMessage"></div>
|
||||
<img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
|
||||
@@ -34,13 +36,4 @@
|
||||
</div>
|
||||
|
||||
<a class="send-new" data-l10n-id="sendYourFilesLink"></a>
|
||||
{{else}}
|
||||
<div class="title" data-l10n-id="expiredPageHeader"></div>
|
||||
|
||||
<div class="share-window">
|
||||
<img src="/resources/illustration_expired.svg" id="expired-img" data-l10n-id="linkExpiredAlt"/>
|
||||
</div>
|
||||
<div class="expired-description" data-l10n-id="uploadPageExplainer"></div>
|
||||
<a class="send-new" data-l10n-id="sendYourFilesLink"></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="page-one">
|
||||
<script src="/upload.js"></script>
|
||||
<div class="title" data-l10n-id="uploadPageHeader"></div>
|
||||
<div class="description">
|
||||
<div data-l10n-id="uploadPageExplainer"></div>
|
||||
@@ -60,7 +61,7 @@
|
||||
<button id="copy-btn" data-l10n-id="copyUrlFormButton"></button>
|
||||
</div>
|
||||
<button id="delete-file" data-l10n-id="deleteFileButton"></button>
|
||||
<a class="send-new" data-l10n-id="sendAnotherFileLink"></a>
|
||||
<a class="send-new" id="send-new-completed" data-l10n-id="sendAnotherFileLink"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,17 +69,5 @@
|
||||
<div class="title" data-l10n-id="errorPageHeader"></div>
|
||||
<div class="expired-description" data-l10n-id="errorPageMessage"></div>
|
||||
<img id="upload-error-img" data-l10n-id="errorAltText" src="/resources/illustration_error.svg"/>
|
||||
<a class="send-new" data-l10n-id="sendAnotherFileLink"></a>
|
||||
</div>
|
||||
|
||||
<div id="unsupported-browser" hidden="true">
|
||||
<div class="title" data-l10n-id="notSupportedHeader"></div>
|
||||
<div class="description" data-l10n-id="notSupportedDetail"></div>
|
||||
<a id="dl-firefox" href="https://www.mozilla.org/firefox/new/?scene=2" target="_blank">
|
||||
<img src="/resources/firefox_logo-only.svg" id="firefox-logo" alt="Firefox"/>
|
||||
<div id="dl-firefox-text">Firefox<br>
|
||||
<span data-l10n-id="downloadFirefoxButtonSub"></span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="unsupported-description" data-l10n-id="uploadPageExplainer"></div>
|
||||
<a class="send-new" id="send-new-error" data-l10n-id="sendAnotherFileLink"></a>
|
||||
</div>
|
||||
|
||||
@@ -5,3 +5,4 @@ window.dsn = '{{{dsn}}}';
|
||||
window.trackerId = '{{{trackerId}}}';
|
||||
{{/if}}
|
||||
const MAXFILESIZE = {{{maxFileSize}}};
|
||||
const EXPIRE_SECONDS = {{{expireSeconds}}};
|
||||
|
||||
@@ -3,27 +3,34 @@
|
||||
<head>
|
||||
<title>Firefox Send</title>
|
||||
<script src="/jsconfig.js"></script>
|
||||
<script src="/bundle.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/main.css" />
|
||||
<link rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="/locales/send.{locale}.ftl">
|
||||
<script defer src="/l20n/dist/web/l20n.js"></script>
|
||||
<script defer src="/l20n.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="send-logo">
|
||||
<img src="/resources/send_logo.svg"/>
|
||||
<img src="/resources/send_logo_type.svg"/>
|
||||
</div>
|
||||
<header class="header">
|
||||
<div class="send-logo">
|
||||
<img src="/resources/send_logo.svg" alt="Send"/>
|
||||
<h1 class="site-title">Send</h1>
|
||||
<div class="site-subtitle">
|
||||
<a href="https://testpilot.firefox.com" target="_blank">Firefox Test Pilot</a>
|
||||
<div data-l10n-id="siteSubtitle">web experiment</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send" rel="noreferrer noopener" class="feedback" target="_blank" data-l10n-id="siteFeedback">Feedback</a>
|
||||
</header>
|
||||
<div class="all">
|
||||
{{{body}}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="legal-links">
|
||||
<a href="https://www.mozilla.org"><img class="mozilla-logo" src="/resources/mozilla-logo.svg"/></a>
|
||||
<a href="https://www.mozilla.org/about/legal/" data-l10n-id="footerLinkLegal"></a>
|
||||
<a href="https://www.mozilla.org"><img class="mozilla-logo" src="/resources/mozilla-logo.svg"/></a>
|
||||
<a href="https://www.mozilla.org/about/legal" data-l10n-id="footerLinkLegal"></a>
|
||||
<a href="https://testpilot.firefox.com/about" data-l10n-id="footerLinkAbout"></a>
|
||||
<a href="https://testpilot.firefox.com/privacy" data-l10n-id="footerLinkPrivacy"></a>
|
||||
<a href="https://testpilot.firefox.com/terms" data-l10n-id="footerLinkTerms"></a>
|
||||
|
||||
8
views/notfound.handlebars
Normal file
8
views/notfound.handlebars
Normal file
@@ -0,0 +1,8 @@
|
||||
<div id="download">
|
||||
<div class="title" data-l10n-id="expiredPageHeader"></div>
|
||||
<div class="share-window">
|
||||
<img src="/resources/illustration_expired.svg" id="expired-img" data-l10n-id="linkExpiredAlt"/>
|
||||
</div>
|
||||
<div class="expired-description" data-l10n-id="uploadPageExplainer"></div>
|
||||
<a class="send-new" href="/" id="expired-send-new" data-l10n-id="sendYourFilesLink"></a>
|
||||
</div>
|
||||
11
views/unsupported.handlebars
Normal file
11
views/unsupported.handlebars
Normal file
@@ -0,0 +1,11 @@
|
||||
<div id="unsupported-browser">
|
||||
<div class="title" data-l10n-id="notSupportedHeader"></div>
|
||||
<div class="description" data-l10n-id="notSupportedDetail"></div>
|
||||
<a id="dl-firefox" href="https://www.mozilla.org/firefox/new/?scene=2" target="_blank">
|
||||
<img src="/resources/firefox_logo-only.svg" id="firefox-logo" alt="Firefox"/>
|
||||
<div id="dl-firefox-text">Firefox<br>
|
||||
<span data-l10n-id="downloadFirefoxButtonSub"></span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="unsupported-description" data-l10n-id="uploadPageExplainer"></div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user