send/app/controller.js

348 lines
9.2 KiB
JavaScript
Raw Normal View History

import FileSender from './fileSender';
import FileReceiver from './fileReceiver';
2018-07-31 18:09:18 +00:00
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
import * as metrics from './metrics';
2019-04-26 20:30:33 +00:00
import { bytes, locale } from './utils';
2018-10-25 02:32:53 +00:00
import okDialog from './ui/okDialog';
2018-10-26 01:55:11 +00:00
import copyDialog from './ui/copyDialog';
2019-03-10 01:59:06 +00:00
import shareDialog from './ui/shareDialog';
import signupDialog from './ui/signupDialog';
2019-04-26 20:30:33 +00:00
import surveyDialog from './ui/surveyDialog';
export default function(state, emitter) {
let lastRender = 0;
2017-09-13 19:01:55 +00:00
let updateTitle = false;
function render() {
emitter.emit('render');
}
async function checkFiles() {
2018-08-07 22:40:17 +00:00
const changes = await state.user.syncFileList();
const rerender = changes.incoming || changes.downloadCount;
if (rerender) {
render();
}
}
2017-09-13 19:01:55 +00:00
function updateProgress() {
if (updateTitle) {
emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio));
}
render();
}
emitter.on('DOMContentLoaded', () => {
document.addEventListener('blur', () => (updateTitle = true));
document.addEventListener('focus', () => {
updateTitle = false;
emitter.emit('DOMTitleChange', 'Firefox Send');
});
checkFiles();
});
emitter.on('render', () => {
lastRender = Date.now();
});
emitter.on('login', email => {
state.user.login(email);
2018-08-07 22:40:17 +00:00
});
2020-07-25 01:11:50 +00:00
emitter.on('logout', async () => {
await state.user.logout();
2019-02-12 19:50:06 +00:00
metrics.loggedOut({ trigger: 'button' });
2018-08-31 21:20:15 +00:00
emitter.emit('pushState', '/');
2018-08-07 22:40:17 +00:00
});
2018-10-25 02:07:10 +00:00
emitter.on('removeUpload', file => {
state.archive.remove(file);
if (state.archive.numFiles === 0) {
state.archive.clear();
}
2018-08-03 19:24:41 +00:00
render();
2018-07-31 18:09:18 +00:00
});
2019-02-12 19:50:06 +00:00
emitter.on('delete', async ownedFile => {
try {
metrics.deletedUpload({
2019-02-12 19:50:06 +00:00
size: ownedFile.size,
time: ownedFile.time,
speed: ownedFile.speed,
type: ownedFile.type,
ttl: ownedFile.expiresAt - Date.now(),
location
});
2019-02-12 19:50:06 +00:00
state.storage.remove(ownedFile.id);
await ownedFile.del();
} catch (e) {
state.sentry.captureException(e);
}
2018-10-25 02:07:10 +00:00
render();
});
emitter.on('cancel', () => {
state.transfer.cancel();
});
2018-07-31 18:09:18 +00:00
emitter.on('addFiles', async ({ files }) => {
2018-11-19 18:48:52 +00:00
if (files.length < 1) {
return;
}
2018-08-07 22:40:17 +00:00
const maxSize = state.user.maxSize;
try {
state.archive.addFiles(
files,
maxSize,
state.LIMITS.MAX_FILES_PER_ARCHIVE
);
2018-08-07 22:40:17 +00:00
} catch (e) {
if (e.message === 'fileTooBig' && maxSize < state.LIMITS.MAX_FILE_SIZE) {
2019-02-12 19:50:06 +00:00
return emitter.emit('signup-cta', 'size');
}
2019-02-12 19:50:06 +00:00
state.modal = okDialog(
state.translate(e.message, {
size: bytes(maxSize),
count: state.LIMITS.MAX_FILES_PER_ARCHIVE
2019-02-12 19:50:06 +00:00
})
);
2018-07-31 18:09:18 +00:00
}
render();
});
2019-02-12 19:50:06 +00:00
emitter.on('signup-cta', source => {
2019-03-05 20:58:40 +00:00
const query = state.query;
state.user.startAuthFlow(source, {
campaign: query.utm_campaign,
content: query.utm_content,
medium: query.utm_medium,
source: query.utm_source,
term: query.utm_term
});
2019-02-12 19:50:06 +00:00
state.modal = signupDialog(source);
render();
});
emitter.on('authenticate', async (code, oauthState) => {
try {
await state.user.finishLogin(code, oauthState);
await state.user.syncFileList();
emitter.emit('replaceState', '/');
} catch (e) {
emitter.emit('replaceState', '/error');
setTimeout(render);
}
});
emitter.on('upload', async () => {
if (state.storage.files.length >= state.LIMITS.MAX_ARCHIVES_PER_USER) {
2018-09-07 17:53:40 +00:00
state.modal = okDialog(
2018-08-07 22:40:17 +00:00
state.translate('tooManyArchives', {
count: state.LIMITS.MAX_ARCHIVES_PER_USER
2018-08-07 22:40:17 +00:00
})
);
2018-09-07 17:53:40 +00:00
return render();
2018-08-07 22:40:17 +00:00
}
2019-02-12 19:50:06 +00:00
const archive = state.archive;
2018-08-31 21:20:15 +00:00
const sender = new FileSender();
2018-08-08 18:07:09 +00:00
2017-09-13 19:01:55 +00:00
sender.on('progress', updateProgress);
sender.on('encrypting', render);
2018-07-31 18:09:18 +00:00
sender.on('complete', render);
state.transfer = sender;
state.uploading = true;
render();
2018-01-24 18:23:13 +00:00
const links = openLinksInNewTab();
await delay(200);
2019-02-12 19:50:06 +00:00
const start = Date.now();
try {
2019-02-12 19:50:06 +00:00
const ownedFile = await sender.upload(archive, state.user.bearerToken);
2018-01-24 18:23:13 +00:00
state.storage.totalUploads += 1;
2019-02-12 19:50:06 +00:00
const duration = Date.now() - start;
metrics.completedUpload(archive, duration);
2018-01-24 18:23:13 +00:00
state.storage.addFile(ownedFile);
2018-08-31 21:20:15 +00:00
// TODO integrate password into /upload request
2019-02-12 19:50:06 +00:00
if (archive.password) {
emitter.emit('password', {
password: archive.password,
file: ownedFile
});
2018-07-31 18:09:18 +00:00
}
2019-03-10 01:59:06 +00:00
state.modal = state.capabilities.share
? shareDialog(ownedFile.name, ownedFile.url)
: copyDialog(ownedFile.name, ownedFile.url);
} catch (err) {
if (err.message === '0') {
//cancelled. do nothing
metrics.cancelledUpload(archive, err.duration);
render();
2020-07-25 01:11:50 +00:00
} else if (err.message === '401') {
const refreshed = await state.user.refresh();
if (refreshed) {
return emitter.emit('upload');
}
emitter.emit('pushState', '/error');
} else {
// eslint-disable-next-line no-console
console.error(err);
state.sentry.withScope(scope => {
scope.setExtra('duration', err.duration);
scope.setExtra('size', err.size);
state.sentry.captureException(err);
});
metrics.stoppedUpload(archive, err.duration);
emitter.emit('pushState', '/error');
}
} finally {
openLinksInNewTab(links, false);
2019-02-12 19:50:06 +00:00
archive.clear();
state.uploading = false;
state.transfer = null;
await state.user.syncFileList();
2018-10-25 02:07:10 +00:00
render();
}
});
2018-01-24 18:23:13 +00:00
emitter.on('password', async ({ password, file }) => {
try {
2018-02-16 20:56:53 +00:00
state.settingPassword = true;
render();
2018-01-24 18:23:13 +00:00
await file.setPassword(password);
state.storage.writeFile(file);
2018-02-16 20:56:53 +00:00
await delay(1000);
2018-01-24 18:23:13 +00:00
} catch (err) {
2018-03-02 05:36:45 +00:00
// eslint-disable-next-line no-console
2018-01-24 18:23:13 +00:00
console.error(err);
2018-02-21 20:35:52 +00:00
state.passwordSetError = err;
} finally {
state.settingPassword = false;
}
render();
});
2018-01-24 18:23:13 +00:00
emitter.on('getMetadata', async () => {
const file = state.fileInfo;
2018-07-06 22:49:50 +00:00
2018-01-24 18:23:13 +00:00
const receiver = new FileReceiver(file);
try {
2018-01-24 18:23:13 +00:00
await receiver.getMetadata();
state.transfer = receiver;
} catch (e) {
2018-08-07 22:40:17 +00:00
if (e.message === '401' || e.message === '404') {
file.password = null;
2018-01-24 18:23:13 +00:00
if (!file.requiresPassword) {
return emitter.emit('pushState', '/404');
}
}
}
2018-07-05 19:40:49 +00:00
render();
});
emitter.on('download', async file => {
state.transfer.on('progress', updateProgress);
state.transfer.on('decrypting', render);
2018-07-31 18:09:18 +00:00
state.transfer.on('complete', render);
const links = openLinksInNewTab();
const size = file.size;
2019-02-12 19:50:06 +00:00
const start = Date.now();
try {
2018-07-31 18:29:26 +00:00
const dl = state.transfer.download({
stream: state.capabilities.streamDownload
});
render();
await dl;
state.storage.totalDownloads += 1;
2019-02-12 19:50:06 +00:00
const duration = Date.now() - start;
metrics.completedDownload({
size,
duration,
password_protected: file.requiresPassword
});
} catch (err) {
2018-01-24 18:23:13 +00:00
if (err.message === '0') {
// download cancelled
state.transfer.reset();
render();
} else {
// eslint-disable-next-line no-console
state.transfer = null;
const location = err.message === '404' ? '/404' : '/error';
if (location === '/error') {
state.sentry.withScope(scope => {
scope.setExtra('duration', err.duration);
scope.setExtra('size', err.size);
scope.setExtra('progress', err.progress);
state.sentry.captureException(err);
});
2019-02-12 19:50:06 +00:00
const duration = Date.now() - start;
metrics.stoppedDownload({
size,
duration,
password_protected: file.requiresPassword
});
}
emitter.emit('pushState', location);
}
} finally {
openLinksInNewTab(links, false);
}
});
2019-02-12 19:50:06 +00:00
emitter.on('copy', ({ url }) => {
copyToClipboard(url);
2019-02-12 19:50:06 +00:00
// metrics.copiedLink({ location });
});
2019-04-26 20:30:33 +00:00
emitter.on('closeModal', () => {
if (
state.PREFS.surveyUrl &&
['copy', 'share'].includes(state.modal.type) &&
locale().startsWith('en') &&
(state.storage.totalUploads > 1 || state.storage.totalDownloads > 0) &&
!state.user.surveyed
) {
state.user.surveyed = true;
state.modal = surveyDialog();
} else {
state.modal = null;
}
render();
});
emitter.on('report', async ({ reason }) => {
try {
2020-07-25 22:36:09 +00:00
await state.transfer.reportLink(reason);
render();
} catch (err) {
console.error(err);
if (err.message === '404') {
state.fileInfo = { reported: true };
return render();
}
emitter.emit('pushState', '/error');
}
});
2018-01-24 18:23:13 +00:00
setInterval(() => {
// poll for updates of the upload list
2019-03-01 22:12:23 +00:00
if (!state.modal && state.route === '/') {
2018-01-24 18:23:13 +00:00
checkFiles();
}
}, 2 * 60 * 1000);
setInterval(() => {
// poll for rerendering the file list countdown timers
if (
2019-03-01 22:12:23 +00:00
!state.modal &&
state.route === '/' &&
state.storage.files.length > 0 &&
Date.now() - lastRender > 30000
) {
render();
}
}, 60000);
}