Compare commits

...

49 Commits

Author SHA1 Message Date
Danny Coates
8e6d8eaddd bump version 2017-07-24 16:34:00 -07:00
Daniela Arcese
faaa8afe17 Merge pull request #296 from mozilla/ui
restyle delete popup
2017-07-24 17:42:06 -04:00
Daniela Arcese
84e8abf2c5 fix horizontal scroll issue 2017-07-24 17:38:03 -04:00
Danny Coates
5db8a20b9d Merge pull request #295 from mozilla/envvar
renamed environment variables to remove P2P_ prefix
2017-07-24 14:30:09 -07:00
Danny Coates
117c6ea12d Merge branch 'master' into envvar 2017-07-24 14:06:55 -07:00
Danny Coates
83880d97e6 Merge pull request #294 from mozilla/invalidDragAndDrops
dealing with invalid drag and drops
2017-07-24 13:56:00 -07:00
Abhinav Adduri
c3be5228cb removing dialog 2017-07-24 13:35:40 -07:00
Danny Coates
ade214c69c Merge pull request #297 from mozilla/expire
added environment variable for expire time
2017-07-24 13:27:37 -07:00
Danny Coates
4cb040d70d added environment variable for expire time 2017-07-24 13:20:18 -07:00
Danny Coates
73ccce627c renamed environment variables to remove P2P_ prefix 2017-07-24 13:10:30 -07:00
Daniela Arcese
4043d35c8b restyle delete popup 2017-07-24 16:04:31 -04:00
Abhinav Adduri
a576cc0198 dealing with invalid drag and drops 2017-07-24 12:42:16 -07:00
Abhinav Adduri
fa1f0208a4 added tagLength to fileReceiver 2017-07-24 12:16:01 -07:00
Danny Coates
193d3b1aef Merge pull request #292 from mozilla/fixes289
Fixes289
2017-07-24 12:10:12 -07:00
Abhinav Adduri
3b28ce88bf removing bundle from gitignore 2017-07-24 12:03:38 -07:00
Abhinav Adduri
c77983d902 removing bundle file, adding public/bundle.js to gitignore 2017-07-24 10:54:57 -07:00
Abhinav Adduri
6ef9f8fa43 fixes 289 2017-07-24 10:52:54 -07:00
Danny Coates
9136694d29 Merge pull request #288 from mozilla/uploads
fix: Don`t allow upload when not on the upload page.
2017-07-24 10:30:52 -07:00
Erica Wright
ec7f058afc don`t allow upload when not on the upload page. 2017-07-24 13:04:46 -04:00
Danny Coates
62989ee2c9 Merge pull request #285 from mozilla/phases
added messages for processing phases
2017-07-24 09:53:45 -07:00
Danny Coates
85068e97ae Merge pull request #267 from mozilla/responsive-and-feedback
make site responsive and add feedback link
2017-07-24 09:52:39 -07:00
John Gruen
dde2c4d5a9 Merge branch 'master' of https://github.com/mozilla/send into responsive-and-feedback 2017-07-24 18:31:47 +02:00
Danny Coates
20cf8d0a15 added messages for processing phases 2017-07-24 09:23:56 -07:00
Peter deHaan
b2bd623a49 Merge pull request #286 from pdehaan/issue-205
Update download progress bar color
2017-07-24 08:49:45 -07:00
Peter deHaan
fc3001978c Update download progress bar color 2017-07-23 22:42:40 -07:00
Danny Coates
d6823f492d Merge pull request #281 from pdehaan/deLint
Stop ESLint from linting the /public/ directory
2017-07-23 12:42:53 -07:00
Peter deHaan
c3751c2efc Ignore console statements in test/ directory because yolo 2017-07-23 00:06:24 -07:00
Peter deHaan
70396e2f36 Stop ESLint from linting the /public/ directory 2017-07-22 23:26:42 -07:00
Danny Coates
b557665d04 ignore the firefox installed for selenium in docker 2017-07-21 20:09:59 -07:00
Danny Coates
6393d70a33 rearrange dependencies. fixes #255 2017-07-21 20:00:38 -07:00
Danny Coates
834df8526f Merge pull request #280 from mozilla/unsupported
created /unsupported page and added gcmCompliant to /download page
2017-07-21 17:50:26 -07:00
Danny Coates
f5bd332ff8 created /unsupported page and added gcmCompliant to /download page 2017-07-21 17:42:40 -07:00
Danny Coates
0d5fb1740d npm run format 2017-07-21 17:01:26 -07:00
Danny Coates
5ed4db9689 Merge pull request #279 from mozilla/split
create separate js bundles for upload/download pages
2017-07-21 16:55:50 -07:00
Danny Coates
31b810eb7d create separate js bundles for upload/download pages 2017-07-21 16:23:08 -07:00
Abhinav Adduri
7243a10340 Merge pull request #268 from mozilla/testpilotGA
Testpilot ga
2017-07-21 13:49:45 -07:00
Abhinav Adduri
b18bcd3b6e removing initialization on top in favor of default 2017-07-21 13:41:59 -07:00
Abhinav Adduri
09a6192bf5 merging master 2017-07-21 13:36:26 -07:00
Abhinav Adduri
9585850d6d final fixes 2017-07-21 13:25:08 -07:00
Abhinav Adduri
14f3d837f9 if checkexistence removes the last item in the list, call toggleHeader 2017-07-21 12:52:28 -07:00
Abhinav Adduri
d660eda64c refactored localStorage into storage module for frontend 2017-07-21 12:44:55 -07:00
John Gruen
463393552b make site responsive and add feedback link 2017-07-21 11:59:42 +02:00
Abhinav Adduri
cb0d69c5cd fixed one bug, added some helper functions 2017-07-20 16:06:06 -07:00
Abhinav Adduri
b9eb653f1f linting 2017-07-20 15:20:52 -07:00
Abhinav Adduri
37bb6fd982 updated one metric, cd4 added to upload-deleted 2017-07-20 15:17:11 -07:00
Abhinav Adduri
e5d1e8f028 Merge branch 'master' of github.com:mozilla/send into testpilotGA 2017-07-20 15:16:22 -07:00
Abhinav Adduri
99477774cf finished metrics 2017-07-20 15:16:00 -07:00
Abhinav Adduri
76a6f02eb7 Merge branch 'master' of github.com:mozilla/send into testpilotGA 2017-07-20 08:47:56 -07:00
Abhinav Adduri
744dbb3a6f added analytics for copied, unsupported, and restarted metrics 2017-07-19 16:16:46 -07:00
32 changed files with 1462 additions and 1532 deletions

View File

@@ -5,3 +5,4 @@ static
test test
scripts scripts
docs docs
firefox

View File

@@ -1,4 +1,3 @@
public/bundle.js public
public/webcrypto-shim.js
test/frontend/bundle.js test/frontend/bundle.js
firefox firefox

4
.gitignore vendored
View File

@@ -1,7 +1,9 @@
.DS_Store .DS_Store
node_modules node_modules
public/bundle.js public/upload.js
public/download.js
public/version.json public/version.json
public/l20n.min.js
static/* static/*
!static/info.txt !static/info.txt
test/frontend/bundle.js test/frontend/bundle.js

View File

@@ -1,6 +1,6 @@
extends: stylelint-config-standard extends: stylelint-config-standard
rules: rules:
color-hex-case: upper color-hex-case: lower
declaration-colon-newline-after: null declaration-colon-newline-after: null
selector-list-comma-newline-after: null selector-list-comma-newline-after: null

View File

@@ -16,7 +16,7 @@ deployment:
latest: latest:
branch: master branch: master
commands: commands:
- npm run predocker - npm run build
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- docker build -t mozilla/send:latest . - docker build -t mozilla/send:latest .
- docker push mozilla/send:latest - docker push mozilla/send:latest
@@ -24,7 +24,7 @@ deployment:
tag: /.*/ tag: /.*/
owner: mozilla owner: mozilla
commands: commands:
- npm run predocker - npm run build
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- docker build -t mozilla/send:$CIRCLE_TAG . - docker build -t mozilla/send:$CIRCLE_TAG .
- docker push mozilla/send:$CIRCLE_TAG - docker push mozilla/send:$CIRCLE_TAG

View File

@@ -7,6 +7,6 @@ services:
ports: ports:
- "1443:1443" - "1443:1443"
environment: environment:
- P2P_REDIS_HOST=redis - REDIS_HOST=redis
redis: redis:
image: redis:alpine image: redis:alpine

View File

@@ -3,12 +3,22 @@
| Name | Description | Name | Description
|------------------|-------------| |------------------|-------------|
| `PORT` | Port the server will listen on (defaults to 1443). | `PORT` | Port the server will listen on (defaults to 1443).
| `P2P_S3_BUCKET` | The S3 bucket name. | `S3_BUCKET` | The S3 bucket name.
| `P2P_REDIS_HOST` | Host name of the Redis server. | `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" | `NODE_ENV` | "production"
## Example: ## Example:
```sh ```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
``` ```

View File

@@ -112,6 +112,7 @@ Fired whenever a user deletes a file theyve uploaded.
- `cm6` - `cm6`
- `cm7` - `cm7`
- `cd1` - `cd1`
- `cd4`
#### `copied` #### `copied`
Fired whenever a user copies the URL of an upload file. Fired whenever a user copies the URL of an upload file.

10
frontend/src/common.js Normal file
View 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
});

View File

@@ -1,78 +1,175 @@
require('./common');
const FileReceiver = require('./fileReceiver'); const FileReceiver = require('./fileReceiver');
const { notify } = require('./utils'); const { notify, findMetric, gcmCompliant, sendEvent } = require('./utils');
const bytes = require('bytes'); const bytes = require('bytes');
const Storage = require('./storage');
const storage = new Storage(localStorage);
const $ = require('jquery'); const $ = require('jquery');
require('jquery-circle-progress'); require('jquery-circle-progress');
const Raven = window.Raven; const Raven = window.Raven;
$(document).ready(function() { $(document).ready(function() {
gcmCompliant().catch(err => {
$('#download').attr('hidden', true);
sendEvent('recipient', 'unsupported', {
cd6: err
}).then(() => {
location.replace('/unsupported');
});
});
//link back to homepage //link back to homepage
$('.send-new').attr('href', window.location.origin); $('.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 //initiate progress bar
$('#dl-progress').circleProgress({ $('#dl-progress').circleProgress({
value: 0.0, value: 0.0,
startAngle: -Math.PI / 2, startAngle: -Math.PI / 2,
fill: '#00C8D7', fill: '#3B9DFF',
size: 158, size: 158,
animation: { duration: 300 } animation: { duration: 300 }
}); });
$('#download-btn').click(download); $('#download-btn').click(download);
function download() { function download() {
storage.totalDownloads += 1;
const fileReceiver = new FileReceiver(); const fileReceiver = new FileReceiver();
const unexpiredFiles = storage.numFiles;
fileReceiver.on('progress', progress => { 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-page-one').attr('hidden', true);
$('#download-progress').removeAttr('hidden'); $('#download-progress').removeAttr('hidden');
const percent = progress[0] / progress[1]; const percent = progress[0] / progress[1];
// update progress bar // update progress bar
$('#dl-progress').circleProgress('value', percent); $('#dl-progress').circleProgress('value', percent);
$('.percent-number').html(`${Math.floor(percent * 100)}`); $('.percent-number').text(`${Math.floor(percent * 100)}`);
$('.progress-text').text(`${filename} (${bytes(progress[0], {decimalPlaces: 1, fixedDecimals: true})} of ${bytes(progress[1], {decimalPlaces: 1})})`); $('.progress-text').text(
//on complete `${filename} (${bytes(progress[0], {
if (percent === 1) { decimalPlaces: 1,
fileReceiver.removeAllListeners('progress'); fixedDecimals: true
document.l10n.formatValues('downloadNotification', 'downloadFinish') })} of ${bytes(progress[1], { decimalPlaces: 1 })})`
.then(translated => { );
notify(translated[0]);
$('.title').html(translated[1]);
});
}
}); });
let downloadEnd;
fileReceiver.on('decrypting', isStillDecrypting => { fileReceiver.on('decrypting', isStillDecrypting => {
// The file is being decrypted // The file is being decrypted
if (isStillDecrypting) { if (isStillDecrypting) {
console.log('Decrypting'); fileReceiver.removeAllListeners('progress');
window.onunload = null;
document.l10n.formatValue('decryptingFile').then(decryptingFile => {
$('.progress-text').text(decryptingFile);
});
} else { } else {
console.log('Done decrypting'); console.log('Done decrypting');
downloadEnd = Date.now();
} }
}); });
fileReceiver.on('hashing', isStillHashing => { fileReceiver.on('hashing', isStillHashing => {
// The file is being hashed to make sure a malicious user hasn't tampered with it // The file is being hashed to make sure a malicious user hasn't tampered with it
if (isStillHashing) { if (isStillHashing) {
console.log('Checking file integrity'); document.l10n.formatValue('verifyingFile').then(verifyingFile => {
$('.progress-text').text(verifyingFile);
});
} else { } 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 fileReceiver
.download() .download()
.catch(() => { .catch(err => {
document.l10n.formatValue('expiredPageHeader') // record download-stopped (errored) by recipient
.then(translated => { sendEvent('recipient', 'download-stopped', {
$('.title').text(translated); 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); $('#download-btn').attr('hidden', true);
$('#expired-img').removeAttr('hidden'); $('#expired-img').removeAttr('hidden');
console.log('The file has expired, or has already been deleted.'); console.log('The file has expired, or has already been deleted.');
return; return;
}) })
.then(([decrypted, fname]) => { .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 dataView = new DataView(decrypted);
const blob = new Blob([dataView]); const blob = new Blob([dataView]);
const downloadUrl = URL.createObjectURL(blob); const downloadUrl = URL.createObjectURL(blob);

View File

@@ -58,41 +58,47 @@ class FileReceiver extends EventEmitter {
true, true,
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
) )
]).then(([fdata, key]) => { ])
this.emit('decrypting', true); .then(([fdata, key]) => {
return Promise.all([ this.emit('decrypting', true);
window.crypto.subtle.decrypt( return Promise.all([
{ window.crypto.subtle
name: 'AES-GCM', .decrypt(
iv: hexToArray(fdata.iv), {
additionalData: hexToArray(fdata.aad) name: 'AES-GCM',
}, iv: hexToArray(fdata.iv),
key, additionalData: hexToArray(fdata.aad),
fdata.data tagLength: 128
).then(decrypted => { },
this.emit('decrypting', false); key,
return Promise.resolve(decrypted) fdata.data
}), )
fdata.filename, .then(decrypted => {
hexToArray(fdata.aad) this.emit('decrypting', false);
]); return Promise.resolve(decrypted);
}).then(([decrypted, fname, proposedHash]) => { }),
this.emit('hashing', true); fdata.filename,
return window.crypto.subtle.digest('SHA-256', decrypted).then(calculatedHash => { hexToArray(fdata.aad)
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) {
this.emit('unsafe', true);
return Promise.reject();
} else {
this.emit('safe', true);
return Promise.all([decrypted, decodeURIComponent(fname)]);
}
});
});
} }
} }

View File

@@ -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
View 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;

View File

@@ -1,19 +1,74 @@
/* global MAXFILESIZE */ /* global MAXFILESIZE EXPIRE_SECONDS */
require('./common');
const FileSender = require('./fileSender'); 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 bytes = require('bytes');
const Storage = require('./storage');
const storage = new Storage(localStorage);
const $ = require('jquery'); const $ = require('jquery');
require('jquery-circle-progress'); require('jquery-circle-progress');
const Raven = window.Raven; const Raven = window.Raven;
if (storage.has('referrer')) {
window.referrer = storage.referrer;
storage.remove('referrer');
} else {
window.referrer = 'external';
}
$(document).ready(function() { $(document).ready(function() {
gcmCompliant().catch(err => { gcmCompliant().catch(err => {
$('#page-one').attr('hidden', true); $('#page-one').attr('hidden', true);
$('#unsupported-browser').removeAttr('hidden'); sendEvent('sender', 'unsupported', {
cd6: err
}).then(() => {
location.replace('/unsupported');
});
}); });
$('#file-upload').change(onUpload); $('#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); $('body').on('dragover', allowDrop).on('drop', onUpload);
// reset copy button // reset copy button
const $copyBtn = $('#copy-btn'); const $copyBtn = $('#copy-btn');
@@ -21,18 +76,23 @@ $(document).ready(function() {
$('#link').attr('disabled', false); $('#link').attr('disabled', false);
$copyBtn.attr('data-l10n-id', 'copyUrlFormButton'); $copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
if (localStorage.length === 0) { const files = storage.files;
if (files.length === 0) {
toggleHeader(); toggleHeader();
} else { } else {
for (let i = 0; i < localStorage.length; i++) { for (const index in files) {
const id = localStorage.key(i); const id = files[index].fileId;
//check if file exists before adding to list //check if file still exists before adding to list
checkExistence(id, true); checkExistence(id, files[index], true);
} }
} }
// copy link to clipboard // copy link to clipboard
$copyBtn.click(() => { $copyBtn.click(() => {
// record copied event from success screen
sendEvent('sender', 'copied', {
cd4: 'success-screen'
});
const aux = document.createElement('input'); const aux = document.createElement('input');
aux.setAttribute('value', $('#link').attr('value')); aux.setAttribute('value', $('#link').attr('value'));
document.body.appendChild(aux); document.body.appendChild(aux);
@@ -42,7 +102,9 @@ $(document).ready(function() {
//disable button for 3s //disable button for 3s
$copyBtn.attr('disabled', true); $copyBtn.attr('disabled', true);
$('#link').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(() => { window.setTimeout(() => {
$copyBtn.attr('disabled', false); $copyBtn.attr('disabled', false);
$('#link').attr('disabled', false); $('#link').attr('disabled', false);
@@ -71,14 +133,28 @@ $(document).ready(function() {
// on file upload by browse or drag & drop // on file upload by browse or drag & drop
function onUpload(event) { function onUpload(event) {
event.preventDefault(); event.preventDefault();
// don't allow upload if not on upload page
if ($('#page-one').attr('hidden')){
return;
}
storage.totalUploads += 1;
let file = ''; let file = '';
if (event.type === 'drop') { 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'); $('.upload-window').removeClass('ondrag');
document.l10n.formatValue('uploadPageMultipleFilesAlert') return;
.then(str => { }
alert(str); 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; return;
} }
file = event.originalEvent.dataTransfer.files[0]; file = event.originalEvent.dataTransfer.files[0];
@@ -87,24 +163,38 @@ $(document).ready(function() {
} }
if (file.size > MAXFILESIZE) { 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); $('#page-one').attr('hidden', true);
$('#upload-error').attr('hidden', true); $('#upload-error').attr('hidden', true);
$('#upload-progress').removeAttr('hidden'); $('#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 //don't allow drag and drop when not on page-one
$('body').off('drop', onUpload); $('body').off('drop', onUpload);
const expiration = 24 * 60 * 60 * 1000; //will eventually come from a field
const fileSender = new FileSender(file); const fileSender = new FileSender(file);
$('#cancel-upload').click(() => { $('#cancel-upload').click(() => {
fileSender.cancel(); fileSender.cancel();
location.reload(); location.reload();
document.l10n.formatValue('uploadCancelNotification') document.l10n.formatValue('uploadCancelNotification').then(str => {
.then(str => { notify(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 => { fileSender.on('progress', progress => {
@@ -112,89 +202,147 @@ $(document).ready(function() {
// update progress bar // update progress bar
$('#ul-progress').circleProgress('value', percent); $('#ul-progress').circleProgress('value', percent);
$('#ul-progress').circleProgress().on('circle-animation-end', function() { $('#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})})`); $('.progress-text').text(
}); `${file.name} (${bytes(progress[0], {
decimalPlaces: 1,
fileSender.on('loading', isStillLoading => { fixedDecimals: true
// The file is loading into Firefox at this stage })} of ${bytes(progress[1], { decimalPlaces: 1 })})`
if (isStillLoading) { );
console.log('Processing');
} else {
console.log('Finished processing');
}
}); });
fileSender.on('hashing', isStillHashing => { fileSender.on('hashing', isStillHashing => {
// The file is being hashed // The file is being hashed
if (isStillHashing) { if (isStillHashing) {
console.log('Hashing'); document.l10n.formatValue('verifyingFile').then(verifyingFile => {
$('.progress-text').text(verifyingFile);
});
} else { } else {
console.log('Finished hashing'); console.log('Finished hashing');
} }
}); });
let uploadStart;
fileSender.on('encrypting', isStillEncrypting => { fileSender.on('encrypting', isStillEncrypting => {
// The file is being encrypted // The file is being encrypted
if (isStillEncrypting) { if (isStillEncrypting) {
console.log('Encrypting'); document.l10n.formatValue('encryptingFile').then(encryptingFile => {
$('.progress-text').text(encryptingFile);
});
} else { } else {
console.log('Finished encrypting'); console.log('Finished encrypting');
uploadStart = Date.now();
} }
}); });
let t = '';
fileSender let t;
.upload() const startTime = Date.now();
.then(info => { const unexpiredFiles = storage.numFiles + 1;
const fileData = {
name: file.name, // record upload-started event by sender
fileId: info.fileId, sendEvent('sender', 'upload-started', {
url: info.url, cm1: file.size,
secretKey: info.secretKey, cm5: storage.totalUploads,
deleteToken: info.deleteToken, cm6: unexpiredFiles,
creationDate: new Date(), cm7: storage.totalDownloads,
expiry: expiration cd1: event.type === 'drop' ? 'drop' : 'click',
}; cd5: window.referrer
localStorage.setItem(info.fileId, JSON.stringify(fileData)); });
$('#upload-filename').attr('data-l10n-id', 'uploadSuccessConfirmHeader');
t = window.setTimeout(() => { // 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); $('#page-one').attr('hidden', true);
$('#upload-progress').attr('hidden', true); $('#upload-progress').attr('hidden', true);
$('#upload-error').attr('hidden', true); $('#upload-error').removeAttr('hidden');
$('#share-link').removeAttr('hidden'); window.clearTimeout(t);
}, 1000);
populateFileList(JSON.stringify(fileData)); // record upload-stopped (errored) by sender
document.l10n.formatValue('notifyUploadDone') sendEvent('sender', 'upload-stopped', {
.then(str => { cm1: file.size,
notify(str); cm5: storage.totalUploads,
}); cm6: unexpiredFiles,
}) cm7: storage.totalDownloads,
.catch(err => { cd1: event.type === 'drop' ? 'drop' : 'click',
Raven.captureException(err); cd2: 'errored',
console.log(err); cd6: err
$('#page-one').attr('hidden', true); });
$('#upload-progress').attr('hidden', true); });
$('#upload-error').removeAttr('hidden'); }, 10);
window.clearTimeout(t);
});
} }
function allowDrop(ev) { function allowDrop(ev) {
ev.preventDefault(); ev.preventDefault();
} }
function checkExistence(id, populate) { function checkExistence(id, file, populate) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) { if (xhr.status === 200) {
if (populate) { if (populate) {
populateFileList(localStorage.getItem(id)); populateFileList(file);
} }
} else if (xhr.status === 404) { } 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(); xhr.send();
} }
//update file table with current files in localStorage //update file table with current files in storage
function populateFileList(file) { function populateFileList(file) {
try {
file = JSON.parse(file);
} catch (e) {
return;
}
const row = document.createElement('tr'); const row = document.createElement('tr');
const name = document.createElement('td'); const name = document.createElement('td');
const link = 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 expiry = document.createElement('td');
const del = 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 popupDiv = document.createElement('div');
const $popupText = $('<div>', { class: 'popuptext' }); const $popupText = $('<div>', { class: 'popuptext' });
const cellText = document.createTextNode(file.name); const cellText = document.createTextNode(file.name);
@@ -224,14 +374,8 @@ $(document).ready(function() {
const url = file.url.trim() + `#${file.secretKey}`.trim(); const url = file.url.trim() + `#${file.secretKey}`.trim();
$('#link').attr('value', url); $('#link').attr('value', url);
$('#copy-text').attr( $('#copy-text').attr('data-l10n-args', '{"filename": "' + file.name + '"}');
'data-l10n-args', $('#copy-text').attr('data-l10n-id', 'copyUrlFormLabelWithName');
'{"filename": "' + file.name + '"}'
);
$('#copy-text').attr(
'data-l10n-id',
'copyUrlFormLabelWithName'
);
$popupText.attr('tabindex', '-1'); $popupText.attr('tabindex', '-1');
name.appendChild(cellText); name.appendChild(cellText);
@@ -252,16 +396,19 @@ $(document).ready(function() {
//copy link to clipboard when icon clicked //copy link to clipboard when icon clicked
$copyIcon.click(function() { $copyIcon.click(function() {
// record copied event from upload list
sendEvent('sender', 'copied', {
cd4: 'upload-list'
});
const aux = document.createElement('input'); const aux = document.createElement('input');
aux.setAttribute('value', url); aux.setAttribute('value', url);
document.body.appendChild(aux); document.body.appendChild(aux);
aux.select(); aux.select();
document.execCommand('copy'); document.execCommand('copy');
document.body.removeChild(aux); document.body.removeChild(aux);
document.l10n.formatValue('copiedUrl') document.l10n.formatValue('copiedUrl').then(translated => {
.then(translated => { link.innerHTML = translated;
link.innerHTML = translated; });
});
window.setTimeout(() => { window.setTimeout(() => {
const linkImg = document.createElement('img'); const linkImg = document.createElement('img');
$(linkImg).addClass('icon-copy'); $(linkImg).addClass('icon-copy');
@@ -277,7 +424,7 @@ $(document).ready(function() {
future.setTime(file.creationDate.getTime() + file.expiry); future.setTime(file.creationDate.getTime() + file.expiry);
let countdown = 0; let countdown = 0;
countdown = future.getTime() - new Date().getTime(); countdown = future.getTime() - Date.now();
let minutes = Math.floor(countdown / 1000 / 60); let minutes = Math.floor(countdown / 1000 / 60);
let hours = Math.floor(minutes / 60); let hours = Math.floor(minutes / 60);
let seconds = Math.floor(countdown / 1000 % 60); let seconds = Math.floor(countdown / 1000 % 60);
@@ -285,7 +432,7 @@ $(document).ready(function() {
poll(); poll();
function poll() { function poll() {
countdown = future.getTime() - new Date().getTime(); countdown = future.getTime() - Date.now();
minutes = Math.floor(countdown / 1000 / 60); minutes = Math.floor(countdown / 1000 / 60);
hours = Math.floor(minutes / 60); hours = Math.floor(minutes / 60);
seconds = Math.floor(countdown / 1000 % 60); seconds = Math.floor(countdown / 1000 % 60);
@@ -304,7 +451,7 @@ $(document).ready(function() {
} }
//remove from list when expired //remove from list when expired
if (countdown <= 0) { if (countdown <= 0) {
localStorage.removeItem(file.fileId); storage.remove(file.fileId);
$(expiry).parents('tr').remove(); $(expiry).parents('tr').remove();
window.clearTimeout(t); window.clearTimeout(t);
toggleHeader(); toggleHeader();
@@ -313,21 +460,16 @@ $(document).ready(function() {
// create popup // create popup
popupDiv.classList.add('popup'); popupDiv.classList.add('popup');
const popupDelSpan = document.createElement('span'); const $popupMessage = $('<div>', { class: 'popup-message' });
$(popupDelSpan).addClass('del-file'); $popupMessage.attr('data-l10n-id', 'deletePopupText');
$(popupDelSpan).attr('data-l10n-id', 'sentFilesTitle4');
const popupNvmSpan = document.createElement('span'); const $popupDelSpan = $('<span>', { class: 'popup-yes' });
$(popupNvmSpan).addClass('nvm'); $popupDelSpan.attr('data-l10n-id', 'deletePopupYes');
$(popupNvmSpan).attr('data-l10n-id', 'nevermindButton');
$popupText.html([ const $popupNvmSpan = $('<span>', { class: 'popup-no' });
popupDelSpan, $popupNvmSpan.attr('data-l10n-id', 'deletePopupCancel');
'&nbsp;',
'&nbsp;',
popupNvmSpan
]);
$popupText.html([$popupMessage, $popupDelSpan, $popupNvmSpan]);
// add data cells to table row // add data cells to table row
row.appendChild(name); row.appendChild(name);
@@ -340,18 +482,51 @@ $(document).ready(function() {
row.appendChild(del); row.appendChild(del);
$('tbody').append(row); //add row to table $('tbody').append(row); //add row to table
const unexpiredFiles = storage.numFiles;
// delete file // delete file
$popupText.find('.del-file').click(e => { $popupText.find('.popup-yes').click(e => {
FileSender.delete(file.fileId, file.deleteToken).then(() => { FileSender.delete(file.fileId, file.deleteToken).then(() => {
$(e.target).parents('tr').remove(); $(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(); toggleHeader();
}); });
}); });
document.getElementById('delete-file').onclick = () => { document.getElementById('delete-file').onclick = () => {
FileSender.delete(file.fileId, file.deleteToken).then(() => { FileSender.delete(file.fileId, file.deleteToken).then(() => {
localStorage.removeItem(file.fileId); const timeToExpiry =
location.reload(); 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 // show popup
@@ -360,7 +535,7 @@ $(document).ready(function() {
$popupText.focus(); $popupText.focus();
}); });
// hide popup // hide popup
$popupText.find('.nvm').click(function(e) { $popupText.find('.popup-no').click(function(e) {
e.stopPropagation(); e.stopPropagation();
$popupText.removeClass('show'); $popupText.removeClass('show');
}); });
@@ -372,7 +547,6 @@ $(document).ready(function() {
$popupText.removeClass('show'); $popupText.removeClass('show');
}); });
toggleHeader(); toggleHeader();
} }
function toggleHeader() { function toggleHeader() {

View File

@@ -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 = { module.exports = {
arrayToHex, arrayToHex,
hexToArray, hexToArray,
notify, notify,
gcmCompliant gcmCompliant,
findMetric,
isFile,
sendEvent,
ONE_DAY_IN_MS
}; };

1665
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +1,43 @@
{ {
"name": "firefox-send", "name": "firefox-send",
"description": "File Sharing Experiment", "description": "File Sharing Experiment",
"version": "0.2.0", "version": "0.2.1",
"author": "Mozilla (https://mozilla.org)", "author": "Mozilla (https://mozilla.org)",
"dependencies": { "dependencies": {
"aws-sdk": "^2.62.0", "aws-sdk": "^2.89.0",
"body-parser": "^1.17.2", "body-parser": "^1.17.2",
"bytes": "^2.5.0", "bytes": "^2.5.0",
"connect-busboy": "0.0.2", "connect-busboy": "0.0.2",
"convict": "^3.0.0", "convict": "^3.0.0",
"cross-env": "^5.0.1",
"express": "^4.15.3", "express": "^4.15.3",
"express-handlebars": "^3.0.0", "express-handlebars": "^3.0.0",
"helmet": "^3.6.1", "helmet": "^3.8.0",
"jquery": "^3.2.1",
"jquery-circle-progress": "^1.2.2",
"l20n": "^5.0.0",
"mozlog": "^2.1.1", "mozlog": "^2.1.1",
"raven": "^2.1.0", "raven": "^2.1.0",
"raven-js": "^3.16.0", "redis": "^2.7.1"
"redis": "^2.7.1",
"selenium-webdriver": "^3.4.0",
"supertest": "^3.0.0",
"uglify-es": "3.0.19"
}, },
"devDependencies": { "devDependencies": {
"browserify": "^14.4.0", "browserify": "^14.4.0",
"eslint": "^4.0.0", "eslint": "^4.3.0",
"eslint-plugin-mocha": "^4.11.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", "eslint-plugin-security": "^1.4.0",
"git-rev-sync": "^1.9.1", "git-rev-sync": "^1.9.1",
"jquery": "^3.2.1",
"jquery-circle-progress": "^1.2.2",
"l20n": "^5.0.0",
"mocha": "^3.4.2", "mocha": "^3.4.2",
"npm-run-all": "^4.0.2", "npm-run-all": "^4.0.2",
"prettier": "^1.4.4", "prettier": "^1.5.3",
"proxyquire": "^1.8.0", "proxyquire": "^1.8.0",
"sinon": "^2.3.5", "raven-js": "^3.17.0",
"stylelint": "^7.11.0", "selenium-webdriver": "^3.5.0",
"sinon": "^2.3.8",
"stylelint": "^7.13.0",
"stylelint-config-standard": "^16.0.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": { "engines": {
"node": ">=8.0.0" "node": ">=8.0.0"
@@ -47,15 +46,20 @@
"license": "MPL-2.0", "license": "MPL-2.0",
"repository": "mozilla/send", "repository": "mozilla/send",
"scripts": { "scripts": {
"predocker": "browserify frontend/src/main.js | uglifyjs > public/bundle.js && npm run version", "build": "npm-run-all build:*",
"dev": "npm run version && watchify frontend/src/main.js -o public/bundle.js -d | node server/server", "build:upload": "browserify frontend/src/upload.js -g uglifyify -o public/upload.js",
"format": "prettier '{frontend/src/,scripts/,server/,test/}*.js' 'public/*.css' --single-quote --write", "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": "npm-run-all lint:*",
"lint:css": "stylelint 'public/*.css'", "lint:css": "stylelint 'public/*.css'",
"lint:js": "eslint .", "lint:js": "eslint .",
"start": "node server/server", "start": "node server/server",
"test": "mocha test/unit && mocha test/server && npm run test-browser && node test/frontend/driver.js", "test": "npm-run-all test:*",
"test-browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d", "test:unit": "mocha test/unit",
"version": "node scripts/version" "test:server": "mocha test/server",
"test:browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d && node test/frontend/driver.js"
} }
} }

View File

@@ -1,4 +1,6 @@
title = Firefox Send title = Firefox Send
siteSubtitle = web experiment
siteFeedback = Feedback
uploadPageHeader = Private, Encrypted File Sharing 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. 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 uploadPageBrowseButtonTitle = Upload file
uploadingPageHeader = Uploading Your File uploadingPageHeader = Uploading Your File
importingFile = Importing...
verifyingFile = Verifying...
encryptingFile = Encrypting...
decryptingFile = Decrypting...
notifyUploadDone = Your upload has finished. notifyUploadDone = Your upload has finished.
@@ -77,7 +83,9 @@ sentFilesTitle2 = Copy URL
sentFilesTitle3 = Expires In sentFilesTitle3 = Expires In
sentFilesTitle4 = Delete sentFilesTitle4 = Delete
nevermindButton = Nevermind deletePopupText = Delete this file?
deletePopupYes = Yes
deletePopupCancel = Cancel
deleteButtonHover.title = {sentFilesTitle4} deleteButtonHover.title = {sentFilesTitle4}
copyUrlHover.title = {sentFilesTitle2} copyUrlHover.title = {sentFilesTitle2}

View File

@@ -1,7 +1,12 @@
/*** index.html ***/ /*** index.html ***/
html { html {
background: url('resources/send_bg.svg'); 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; font-weight: 200;
background-size: 100%; background-size: 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -10,24 +15,95 @@ html {
} }
body { body {
min-height: 100%; display: flex;
position: relative; flex-direction: column;
margin: 0; 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 { .send-logo {
display: flex;
position: relative; position: relative;
top: 31px; align-items: center;
left: 31px; }
display: inline-block;
.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 { .all {
padding-top: 10%; flex: 1;
padding-bottom: 51px; 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; font-family: inherit;
} }
@@ -38,24 +114,26 @@ a {
/** page-one **/ /** page-one **/
.title { .title {
font-size: 33px; font-size: 33px;
line-height: 40px;
margin: 20px auto; margin: 20px auto;
text-align: center; text-align: center;
max-width: 520px;
font-family: 'SF Pro Display', sans-serif; font-family: 'SF Pro Display', sans-serif;
} }
.description { .description {
font-size: 15px; font-size: 15px;
line-height: 23px; line-height: 23px;
width: 630px; max-width: 630px;
text-align: center; text-align: center;
margin: 0 auto 60px; margin: 0 auto 60px;
color: #0C0C0D; color: #0c0c0d;
width: 92%;
} }
.upload-window { .upload-window {
border: 1px dashed rgba(0, 148, 251, 0.5); border: 1px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto; margin: 0 auto;
width: 640px;
height: 255px; height: 255px;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@@ -63,14 +141,15 @@ a {
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
transition: transform 150ms;
padding: 15px;
} }
.upload-window.ondrag { .upload-window.ondrag {
border: 3px dashed rgba(0, 148, 251, 0.5); border: 3px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto; margin: 0 auto;
width: 636px;
height: 251px; height: 251px;
transform: scale(1.05); transform: scale(1.04);
border-radius: 4.2px; border-radius: 4.2px;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -80,7 +159,7 @@ a {
} }
.link { .link {
color: #0094FB; color: #0094fb;
text-decoration: none; text-decoration: none;
} }
@@ -92,10 +171,10 @@ a {
} }
#browse { #browse {
background: #0297F8; background: #0297f8;
border-radius: 5px; border-radius: 5px;
font-size: 15px; font-size: 15px;
color: #FFF; color: #fff;
width: 240px; width: 240px;
height: 44px; height: 44px;
display: flex; display: flex;
@@ -104,12 +183,17 @@ a {
cursor: pointer; cursor: pointer;
} }
#browse:hover {
background-color: #0287e8;
}
input[type="file"] { input[type="file"] {
display: none; display: none;
} }
#file-size-msg { #file-size-msg {
font-size: 12px; font-size: 12px;
line-height: 16px;
color: #737373; color: #737373;
margin-bottom: 22px; margin-bottom: 22px;
} }
@@ -129,9 +213,10 @@ th {
td { td {
font-size: 15px; font-size: 15px;
vertical-align: top; vertical-align: top;
color: #4A4A4A; color: #4a4a4a;
padding: 17px 19px 0; padding: 17px 19px 0;
line-height: 23px; line-height: 23px;
position: relative;
} }
table { table {
@@ -141,42 +226,44 @@ table {
tbody { tbody {
word-wrap: break-word; word-wrap: break-word;
word-break: break-all;
} }
#uploaded-files { #uploaded-files {
width: 640px;
margin: 45.3px auto; margin: 45.3px auto;
table-layout: fixed; table-layout: fixed;
} }
.icon-delete, .icon-copy, .icon-check { .icon-delete,
.icon-copy,
.icon-check {
cursor: pointer; cursor: pointer;
} }
/* Popup container */ /* Popup container */
.popup { .popup {
position: relative; position: absolute;
display: inline-block; display: inline-block;
cursor: pointer;
} }
/* The actual popup (appears on top) */ /* The actual popup (appears on top) */
.popup .popuptext { .popup .popuptext {
visibility: hidden; visibility: hidden;
width: 160px; min-width: 115px;
background-color: #555; background-color: #fff;
color: #FFF; color: #000;
border: 1px solid #0297f8;
text-align: center; text-align: center;
border-radius: 6px; border-radius: 5px;
padding: 8px 0; padding: 7px 8px;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
bottom: 20px; bottom: 8px;
left: 50%; right: -28px;
margin-left: -88px;
transition: opacity 0.5s; transition: opacity 0.5s;
opacity: 0; opacity: 0;
outline: 0; outline: 0;
box-shadow: 3px 3px 7px #888;
} }
/* Popup arrow */ /* Popup arrow */
@@ -184,11 +271,11 @@ tbody {
content: ""; content: "";
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 50%; right: 30px;
margin-left: -5px; margin-left: -5px;
border-width: 5px; border-width: 5px;
border-style: solid; border-style: solid;
border-color: #555 transparent transparent; border-color: #0297f8 transparent transparent;
} }
.popup .show { .popup .show {
@@ -196,6 +283,29 @@ tbody {
opacity: 1; 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 **/ /** upload-progress **/
.progress-bar { .progress-bar {
margin-top: 3px; margin-top: 3px;
@@ -239,14 +349,13 @@ tbody {
} }
#cancel-upload { #cancel-upload {
color: #D70022; color: #d70022;
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
/** share-link **/ /** share-link **/
#share-window { #share-window {
width: 645px;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -262,53 +371,59 @@ tbody {
#copy { #copy {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
width: 640px;
} }
#copy-text { #copy-text {
align-self: flex-start; align-self: flex-start;
margin-top: 60px; margin-top: 60px;
margin-bottom: 10px; margin-bottom: 10px;
color: #0C0C0D; color: #0c0c0d;
} }
#link { #link {
width: 480px; flex: 1;
height: 56px; height: 56px;
border: 1px solid #0297F8; border: 1px solid #0297f8;
border-radius: 6px 0 0 6px; border-radius: 6px 0 0 6px;
font-size: 24px; font-size: 24px;
color: #737373; color: #737373;
font-family: 'SF Pro Display', sans-serif; font-family: 'SF Pro Display', sans-serif;
letter-spacing: 0; letter-spacing: 0;
line-height: 23px; line-height: 23px;
padding-left: 5px;
padding-right: 5px;
} }
#link:disabled { #link:disabled {
border: 1px solid #05A700; border: 1px solid #05a700;
background: #FFF; background: #fff;
} }
#copy-btn { #copy-btn {
width: 165px; flex: 0 1 165px;
height: 60px; background: #0297f8;
background: #0297F8;
border: 1px solid #0297F8;
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
border: 1px solid #0297f8;
color: white; color: white;
cursor: pointer; cursor: pointer;
font-size: 15px; font-size: 15px;
height: 60px;
padding-left: 10px;
padding-right: 10px;
white-space: nowrap;
} }
#copy-btn:disabled { #copy-btn:disabled {
background: #05A700; background: #05a700;
border: 1px solid #05A700; border: 1px solid #05a700;
cursor: auto; cursor: auto;
} }
#delete-file { #delete-file {
width: 176px; width: 176px;
height: 44px; height: 44px;
background: #FFF; background: #fff;
border: 1px solid rgba(12, 12, 13, 0.3); border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 5px; border-radius: 5px;
font-size: 15px; font-size: 15px;
@@ -322,7 +437,7 @@ tbody {
font-size: 15px; font-size: 15px;
margin: auto; margin: auto;
text-align: center; text-align: center;
color: #0094FB; color: #0094fb;
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
} }
@@ -336,7 +451,8 @@ tbody {
text-align: center; text-align: center;
} }
#upload-error[hidden], #unsupported-browser[hidden] { #upload-error[hidden],
#unsupported-browser[hidden] {
display: none; display: none;
} }
@@ -356,9 +472,8 @@ tbody {
.unsupported-description { .unsupported-description {
font-size: 13px; font-size: 13px;
line-height: 23px; line-height: 23px;
width: 630px;
text-align: center; text-align: center;
color: #7D7D7D; color: #7d7d7d;
margin: 0 auto 23px; margin: 0 auto 23px;
} }
@@ -370,14 +485,14 @@ tbody {
margin-bottom: 181px; margin-bottom: 181px;
width: 260px; width: 260px;
height: 80px; height: 80px;
background: #12BC00; background: #12bc00;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
border: 0; border: 0;
box-shadow: 0 5px 3px rgb(234, 234, 234); box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans'; font-family: 'Fira Sans';
font-weight: 500; font-weight: 500;
color: #FFF; color: #fff;
font-size: 26px; font-size: 26px;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -406,15 +521,15 @@ tbody {
margin-top: 20px; margin-top: 20px;
margin-bottom: 30px; margin-bottom: 30px;
text-align: center; text-align: center;
background: #0297F8; background: #0297f8;
border: 1px solid #0297F8; border: 1px solid #0297f8;
border-radius: 5px; border-radius: 5px;
font-weight: 300; font-weight: 300;
cursor: pointer; cursor: pointer;
} }
#download-btn:disabled { #download-btn:disabled {
background: #47B04B; background: #47b04b;
cursor: auto; cursor: auto;
} }
@@ -434,9 +549,8 @@ tbody {
.expired-description { .expired-description {
font-size: 15px; font-size: 15px;
line-height: 23px; line-height: 23px;
width: 630px;
text-align: center; text-align: center;
color: #7D7D7D; color: #7d7d7d;
margin: 0 auto 23px; margin: 0 auto 23px;
} }
@@ -460,14 +574,13 @@ tbody {
/* footer */ /* footer */
.footer { .footer {
position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
font-size: 15px; font-size: 15px;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
padding: 10px; padding: 50px 10px 10px;
} }
.mozilla-logo { .mozilla-logo {
@@ -495,8 +608,69 @@ tbody {
margin-left: 30px; margin-left: 30px;
} }
.github, .twitter { .github,
.twitter {
width: 32px; width: 32px;
height: 32px; height: 32px;
margin-bottom: -5px; 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;
}
}

View 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

View File

@@ -4,12 +4,12 @@ const conf = convict({
s3_bucket: { s3_bucket: {
format: String, format: String,
default: '', default: '',
env: 'P2P_S3_BUCKET' env: 'S3_BUCKET'
}, },
redis_host: { redis_host: {
format: String, format: String,
default: 'localhost', default: 'localhost',
env: 'P2P_REDIS_HOST' env: 'REDIS_HOST'
}, },
listen_port: { listen_port: {
format: 'port', format: 'port',
@@ -25,12 +25,12 @@ const conf = convict({
sentry_id: { sentry_id: {
format: String, format: String,
default: '', default: '',
env: 'P2P_SENTRY_CLIENT' env: 'SENTRY_CLIENT'
}, },
sentry_dsn: { sentry_dsn: {
format: String, format: String,
default: '', default: '',
env: 'P2P_SENTRY_DSN' env: 'SENTRY_DSN'
}, },
env: { env: {
format: ['production', 'development', 'test'], format: ['production', 'development', 'test'],
@@ -39,8 +39,13 @@ const conf = convict({
}, },
max_file_size: { max_file_size: {
format: Number, format: Number,
default: (1024 * 1024 * 1024) * 2, default: 1024 * 1024 * 1024 * 2,
env: 'P2P_MAX_FILE_SIZE' env: 'MAX_FILE_SIZE'
},
expire_seconds: {
format: Number,
default: 86400,
env: 'EXPIRE_SECONDS'
} }
}); });

View File

@@ -19,8 +19,6 @@ const mozlog = require('./log.js');
const log = mozlog('send.server'); const log = mozlog('send.server');
const STATIC_PATH = path.join(__dirname, '../public'); 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(); const app = express();
@@ -34,10 +32,12 @@ app.engine(
app.set('view engine', 'handlebars'); app.set('view engine', 'handlebars');
app.use(helmet()); app.use(helmet());
app.use(helmet.hsts({ app.use(
maxAge: 31536000, helmet.hsts({
force: conf.env === 'production' maxAge: 31536000,
})); force: conf.env === 'production'
})
);
app.use( app.use(
helmet.contentSecurityPolicy({ helmet.contentSecurityPolicy({
directives: { directives: {
@@ -62,26 +62,31 @@ app.use(
} }
}) })
); );
app.use(busboy({ app.use(
limits: { busboy({
fileSize: conf.max_file_size limits: {
} fileSize: conf.max_file_size
})); }
})
);
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(express.static(STATIC_PATH)); app.use(express.static(STATIC_PATH));
app.use('/l20n', express.static(L20N));
app.use('/locales', express.static(LOCALES));
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.render('index'); res.render('index');
}); });
app.get('/unsupported', (req, res) => {
res.render('unsupported');
});
app.get('/jsconfig.js', (req, res) => { app.get('/jsconfig.js', (req, res) => {
res.set('Content-Type', 'application/javascript'); res.set('Content-Type', 'application/javascript');
res.render('jsconfig', { res.render('jsconfig', {
trackerId: conf.analytics_id, trackerId: conf.analytics_id,
dsn: conf.sentry_id, dsn: conf.sentry_id,
maxFileSize: conf.max_file_size, maxFileSize: conf.max_file_size,
expireSeconds: conf.expire_seconds,
layout: false layout: false
}); });
}); });
@@ -112,15 +117,17 @@ app.get('/download/:id', (req, res) => {
storage storage
.length(id) .length(id)
.then(contentLength => { .then(contentLength => {
res.render('download', { storage.ttl(id).then(timeToExpiry => {
filename: decodeURIComponent(filename), res.render('download', {
filesize: bytes(contentLength), filename: decodeURIComponent(filename),
trackerId: conf.analytics_id, filesize: bytes(contentLength),
dsn: conf.sentry_id sizeInBytes: contentLength,
timeToExpiry: timeToExpiry
});
}); });
}) })
.catch(() => { .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) => { req.busboy.on('file', (fieldname, file, filename) => {
log.info('Uploading:', newId); log.info('Uploading:', newId);
storage.set(newId, file, filename, meta).then(() => { storage.set(newId, file, filename, meta).then(
const protocol = conf.env === 'production' ? 'https' : req.protocol; () => {
const url = `${protocol}://${req.get('host')}/download/${newId}/`; const protocol = conf.env === 'production' ? 'https' : req.protocol;
res.json({ const url = `${protocol}://${req.get('host')}/download/${newId}/`;
url, res.json({
delete: meta.delete, url,
id: newId delete: meta.delete,
}); id: newId
}, });
err => { },
if (err.message === 'limit') { err => {
return res.sendStatus(413); if (err.message === 'limit') {
return res.sendStatus(413);
}
res.sendStatus(500);
} }
res.sendStatus(500); );
});
}); });
req.on('close', err => { req.on('close', err => {
@@ -252,7 +261,7 @@ app.post('/upload', (req, res, next) => {
.catch(err => { .catch(err => {
log.info('DeleteError:', newId); log.info('DeleteError:', newId);
}); });
}) });
}); });
app.get('/__lbheartbeat__', (req, res) => { app.get('/__lbheartbeat__', (req, res) => {

View File

@@ -23,6 +23,7 @@ if (conf.s3_bucket) {
module.exports = { module.exports = {
filename: filename, filename: filename,
exists: exists, exists: exists,
ttl: ttl,
length: awsLength, length: awsLength,
get: awsGet, get: awsGet,
set: awsSet, set: awsSet,
@@ -39,6 +40,7 @@ if (conf.s3_bucket) {
module.exports = { module.exports = {
filename: filename, filename: filename,
exists: exists, exists: exists,
ttl: ttl,
length: localLength, length: localLength,
get: localGet, get: localGet,
set: localSet, 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) { function filename(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
redis_client.hget(id, 'filename', (err, reply) => { redis_client.hget(id, 'filename', (err, reply) => {
@@ -138,7 +152,7 @@ function localSet(newId, file, filename, meta) {
}); });
fstream.on('finish', () => { fstream.on('finish', () => {
redis_client.hmset(newId, meta); redis_client.hmset(newId, meta);
redis_client.expire(newId, 86400000); redis_client.expire(newId, conf.expire_seconds);
log.info('localSet:', 'Upload Finished of ' + newId); log.info('localSet:', 'Upload Finished of ' + newId);
resolve(meta.delete); resolve(meta.delete);
}); });
@@ -221,10 +235,10 @@ function awsSet(newId, file, filename, meta) {
hitLimit = true; hitLimit = true;
upload.abort(); upload.abort();
}); });
return upload.promise() return upload.promise().then(
.then(() => { () => {
redis_client.hmset(newId, meta); redis_client.hmset(newId, meta);
redis_client.expire(newId, 86400000); redis_client.expire(newId, conf.expire_seconds);
log.info('awsUploadFinish', 'Upload Finished of ' + filename); log.info('awsUploadFinish', 'Upload Finished of ' + filename);
}, },
err => { err => {
@@ -233,7 +247,8 @@ function awsSet(newId, file, filename, meta) {
} else { } else {
throw err; throw err;
} }
}); }
);
} }
function awsDelete(id, delete_token) { function awsDelete(id, delete_token) {

View File

@@ -19,3 +19,5 @@ rules:
mocha/no-pending-tests: error mocha/no-pending-tests: error
mocha/no-return-and-callback: warn mocha/no-return-and-callback: warn
mocha/no-skipped-tests: error mocha/no-skipped-tests: error
no-console: off # ¯\_(ツ)_/¯

View File

@@ -115,7 +115,7 @@ describe('Testing Set using aws', function() {
.set('123', {on: sinon.stub()}, 'Filename.moz', {}) .set('123', {on: sinon.stub()}, 'Filename.moz', {})
.then(() => { .then(() => {
assert(expire.calledOnce); assert(expire.calledOnce);
assert(expire.calledWith('123', 86400000)); assert(expire.calledWith('123', 86400));
}) })
.catch(err => assert.fail()); .catch(err => assert.fail());
}); });

View File

@@ -1,5 +1,5 @@
<div id="download"> <div id="download">
{{#if filename}} <script src="/download.js"></script>
<div id="download-page-one"> <div id="download-page-one">
<div class="title"> <div class="title">
<span id="dl-filename" <span id="dl-filename"
@@ -7,6 +7,8 @@
data-l10n-args='{"filename": "{{filename}}"}'></span> data-l10n-args='{"filename": "{{filename}}"}'></span>
<span data-l10n-id="downloadFileSize" <span data-l10n-id="downloadFileSize"
data-l10n-args='{"size": "{{filesize}}"}'></span> data-l10n-args='{"size": "{{filesize}}"}'></span>
<span id="dl-bytelength" hidden="true">{{sizeInBytes}}</span>
<span id="dl-ttl" hidden="true">{{timeToExpiry}}</span>
</div> </div>
<div class="description" data-l10n-id="downloadMessage"></div> <div class="description" data-l10n-id="downloadMessage"></div>
<img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/> <img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
@@ -34,13 +36,4 @@
</div> </div>
<a class="send-new" data-l10n-id="sendYourFilesLink"></a> <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> </div>

View File

@@ -1,4 +1,5 @@
<div id="page-one"> <div id="page-one">
<script src="/upload.js"></script>
<div class="title" data-l10n-id="uploadPageHeader"></div> <div class="title" data-l10n-id="uploadPageHeader"></div>
<div class="description"> <div class="description">
<div data-l10n-id="uploadPageExplainer"></div> <div data-l10n-id="uploadPageExplainer"></div>
@@ -60,7 +61,7 @@
<button id="copy-btn" data-l10n-id="copyUrlFormButton"></button> <button id="copy-btn" data-l10n-id="copyUrlFormButton"></button>
</div> </div>
<button id="delete-file" data-l10n-id="deleteFileButton"></button> <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>
</div> </div>
@@ -68,17 +69,5 @@
<div class="title" data-l10n-id="errorPageHeader"></div> <div class="title" data-l10n-id="errorPageHeader"></div>
<div class="expired-description" data-l10n-id="errorPageMessage"></div> <div class="expired-description" data-l10n-id="errorPageMessage"></div>
<img id="upload-error-img" data-l10n-id="errorAltText" src="/resources/illustration_error.svg"/> <img id="upload-error-img" data-l10n-id="errorAltText" src="/resources/illustration_error.svg"/>
<a class="send-new" data-l10n-id="sendAnotherFileLink"></a> <a class="send-new" id="send-new-error" 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>
</div> </div>

View File

@@ -5,3 +5,4 @@ window.dsn = '{{{dsn}}}';
window.trackerId = '{{{trackerId}}}'; window.trackerId = '{{{trackerId}}}';
{{/if}} {{/if}}
const MAXFILESIZE = {{{maxFileSize}}}; const MAXFILESIZE = {{{maxFileSize}}};
const EXPIRE_SECONDS = {{{expireSeconds}}};

View File

@@ -3,27 +3,34 @@
<head> <head>
<title>Firefox Send</title> <title>Firefox Send</title>
<script src="/jsconfig.js"></script> <script src="/jsconfig.js"></script>
<script src="/bundle.js"></script>
<link rel="stylesheet" type="text/css" href="/main.css" /> <link rel="stylesheet" type="text/css" href="/main.css" />
<link rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.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="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US"> <meta name="availableLanguages" content="en-US">
<link rel="localization" href="/locales/send.{locale}.ftl"> <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> </head>
<body> <body>
<div class="send-logo"> <header class="header">
<img src="/resources/send_logo.svg"/> <div class="send-logo">
<img src="/resources/send_logo_type.svg"/> <img src="/resources/send_logo.svg" alt="Send"/>
</div> <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"> <div class="all">
{{{body}}} {{{body}}}
</div> </div>
<div class="footer"> <div class="footer">
<div class="legal-links"> <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"><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/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/about" data-l10n-id="footerLinkAbout"></a>
<a href="https://testpilot.firefox.com/privacy" data-l10n-id="footerLinkPrivacy"></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> <a href="https://testpilot.firefox.com/terms" data-l10n-id="footerLinkTerms"></a>

View 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>

View 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>