diff --git a/Dockerfile b/Dockerfile index fd92b4de..66cb24e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,13 +16,12 @@ RUN set -x \ --home /app \ --uid 10001 \ app -RUN npm i -g npm COPY --chown=app:app . /app USER app WORKDIR /app RUN set -x \ # Build - && npm ci \ + && PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm ci \ && npm run build diff --git a/app/api.js b/app/api.js index fd29db77..a36d2228 100644 --- a/app/api.js +++ b/app/api.js @@ -61,7 +61,10 @@ async function fetchWithAuth(url, params, keychain) { const result = {}; params = params || {}; const h = await keychain.authHeader(); - params.headers = new Headers({ Authorization: h }); + params.headers = new Headers({ + Authorization: h, + 'Content-Type': 'application/json' + }); const response = await fetch(url, params); result.response = response; result.ok = response.ok; diff --git a/app/capabilities.js b/app/capabilities.js index d37e9e05..d43a6b10 100644 --- a/app/capabilities.js +++ b/app/capabilities.js @@ -77,6 +77,7 @@ async function polyfillStreams() { export default async function getCapabilities() { const browser = browserName(); + const isMobile = /mobi|android/i.test(navigator.userAgent); const serviceWorker = 'serviceWorker' in navigator && browser !== 'edge'; let crypto = await checkCrypto(); const nativeStreams = checkStreams(); @@ -91,14 +92,15 @@ export default async function getCapabilities() { account = false; } const share = - typeof navigator.share === 'function' && locale().startsWith('en'); // en until strings merge + isMobile && + typeof navigator.share === 'function' && + locale().startsWith('en'); // en until strings merge const standalone = window.matchMedia('(display-mode: standalone)').matches || navigator.standalone; - const mobileFirefox = - browser === 'firefox' && /mobile/i.test(navigator.userAgent); + const mobileFirefox = browser === 'firefox' && isMobile; return { account, diff --git a/app/controller.js b/app/controller.js index 3943721d..6648f85b 100644 --- a/app/controller.js +++ b/app/controller.js @@ -49,8 +49,8 @@ export default function(state, emitter) { state.user.login(email); }); - emitter.on('logout', () => { - state.user.logout(); + emitter.on('logout', async () => { + await state.user.logout(); metrics.loggedOut({ trigger: 'button' }); emitter.emit('pushState', '/'); }); @@ -178,6 +178,12 @@ export default function(state, emitter) { //cancelled. do nothing metrics.cancelledUpload(archive, err.duration); render(); + } 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); @@ -229,6 +235,9 @@ export default function(state, emitter) { if (!file.requiresPassword) { return emitter.emit('pushState', '/404'); } + } else { + console.error(e); + return emitter.emit('pushState', '/error'); } } diff --git a/app/fileReceiver.js b/app/fileReceiver.js index 262b19e1..7c2e368b 100644 --- a/app/fileReceiver.js +++ b/app/fileReceiver.js @@ -1,7 +1,7 @@ import Nanobus from 'nanobus'; import Keychain from './keychain'; import { delay, bytes, streamToArrayBuffer } from './utils'; -import { downloadFile, metadata, getApiUrl } from './api'; +import { downloadFile, metadata, getApiUrl, reportLink } from './api'; import { blobStream } from './streams'; import Zip from './zip'; @@ -53,6 +53,10 @@ export default class FileReceiver extends Nanobus { this.state = 'ready'; } + async reportLink(reason) { + await reportLink(this.fileInfo.id, this.keychain, reason); + } + sendMessageToSw(msg) { return new Promise((resolve, reject) => { const channel = new MessageChannel(); diff --git a/app/main.css b/app/main.css index 9b1a9e13..9b804330 100644 --- a/app/main.css +++ b/app/main.css @@ -283,7 +283,7 @@ select { @apply m-auto; @apply py-8; - min-height: 36rem; + min-height: 42rem; max-height: 42rem; width: calc(100% - 3rem); } diff --git a/app/routes.js b/app/routes.js index 1ba8d412..175ac0c2 100644 --- a/app/routes.js +++ b/app/routes.js @@ -13,7 +13,10 @@ module.exports = function(app = choo({ hash: true })) { app.route('/oauth', function(state, emit) { emit('authenticate', state.query.code, state.query.state); }); - app.route('/login', body(require('./ui/home'))); + app.route('/login', function(state, emit) { + emit('replaceState', '/'); + setTimeout(() => emit('render')); + }); app.route('*', body(require('./ui/notFound'))); return app; }; diff --git a/app/serviceWorker.js b/app/serviceWorker.js index bc824e2d..34ae25b2 100644 --- a/app/serviceWorker.js +++ b/app/serviceWorker.js @@ -9,7 +9,7 @@ import contentDisposition from 'content-disposition'; let noSave = false; const map = new Map(); const IMAGES = /.*\.(png|svg|jpg)$/; -const VERSIONED_ASSET = /\.[A-Fa-f0-9]{8}\.(js|css|png|svg|jpg)$/; +const VERSIONED_ASSET = /\.[A-Fa-f0-9]{8}\.(js|css|png|svg|jpg)(#\w+)?$/; const DOWNLOAD_URL = /\/api\/download\/([A-Fa-f0-9]{4,})/; const FONT = /\.woff2?$/; diff --git a/app/ui/account.js b/app/ui/account.js index a81117e7..7f6430ec 100644 --- a/app/ui/account.js +++ b/app/ui/account.js @@ -54,12 +54,17 @@ class Account extends Component { createElement() { if (!this.enabled) { return html` -
++ +
${state.translate('trySendDescription')}
${state.translate('sendYourFilesLink')}${state.translate(btnText)}
+
${state.translate('trySendDescription')}
${state.translate('sendYourFilesLink')}${state.translate(btnText)}
diff --git a/app/ui/header.js b/app/ui/header.js index 2b71b295..4ab38033 100644 --- a/app/ui/header.js +++ b/app/ui/header.js @@ -33,7 +33,7 @@ class Header extends Component { alt="${this.state.translate('title')}" src="${assets.get('icon.svg')}" /> -