saves stream to file

This commit is contained in:
Emily 2018-07-06 15:49:50 -07:00
parent 62ed0a411f
commit f98bc0878c
14 changed files with 136 additions and 201 deletions

View File

@ -1,5 +1,5 @@
import { arrayToB64, b64ToArray, delay } from './utils'; import { arrayToB64, b64ToArray, delay } from './utils';
import { ReadableStream as PolyRS} from 'web-streams-polyfill'; import { ReadableStream as PolyRS } from 'web-streams-polyfill';
import { createReadableStreamWrapper } from '@mattiasbuelens/web-streams-adapter'; import { createReadableStreamWrapper } from '@mattiasbuelens/web-streams-adapter';
const RS = createReadableStreamWrapper(PolyRS); const RS = createReadableStreamWrapper(PolyRS);
@ -202,9 +202,10 @@ export function uploadWs(encrypted, info, metadata, verifierB64, onprogress) {
//////////////////////// ////////////////////////
async function downloadS(id, keychain, onprogress, signal) { async function downloadS(id, keychain, signal) {
const auth = await keychain.authHeader(); const auth = await keychain.authHeader();
//this will be already funneled through serviceworker
const response = await fetch(`/api/download/${id}`, { const response = await fetch(`/api/download/${id}`, {
signal: signal, signal: signal,
method: 'GET', method: 'GET',
@ -223,22 +224,20 @@ async function downloadS(id, keychain, onprogress, signal) {
const fileSize = response.headers.get('Content-Length'); const fileSize = response.headers.get('Content-Length');
//right now only chrome allows obtaining a stream from fetch //right now only chrome allows obtaining a stream from fetch
//for other browsers we fetch as a blob and convert to polyfill stream later //for other browsers we fetch as a blob and convert to polyfill stream later
if (response.body) { if (response.body) {
console.log("STREAM")
return RS(response.body); return RS(response.body);
} }
return response.blob(); return response.blob();
} }
async function tryDownloadStream(id, keychain, onprogress, signal, tries = 1) { async function tryDownloadStream(id, keychain, signal, tries = 1) {
try { try {
const result = await downloadS(id, keychain, onprogress, signal); const result = await downloadS(id, keychain, signal);
return result; return result;
} catch (e) { } catch (e) {
if (e.message === '401' && --tries > 0) { if (e.message === '401' && --tries > 0) {
return tryDownloadStream(id, keychain, onprogress, signal, tries); return tryDownloadStream(id, keychain, signal, tries);
} }
if (e.name === 'AbortError') { if (e.name === 'AbortError') {
throw new Error('0'); throw new Error('0');
@ -247,14 +246,14 @@ async function tryDownloadStream(id, keychain, onprogress, signal, tries = 1) {
} }
} }
export function downloadStream(id, keychain, onprogress) { export function downloadStream(id, keychain) {
const controller = new AbortController(); const controller = new AbortController();
function cancel() { function cancel() {
controller.abort(); controller.abort();
} }
return { return {
cancel, cancel,
result: tryDownloadStream(id, keychain, onprogress, controller.signal, 2) result: tryDownloadStream(id, keychain, controller.signal, 2)
}; };
} }

View File

@ -1,8 +1,16 @@
require('buffer'); require('buffer');
import { TransformStream as PolyTS, ReadableStream as PolyRS } from 'web-streams-polyfill'; /*
import { createReadableStreamWrapper, createTransformStreamWrapper } from '@mattiasbuelens/web-streams-adapter'; import {
TransformStream as PolyTS,
ReadableStream as PolyRS
} from 'web-streams-polyfill';
import {
createReadableStreamWrapper,
createTransformStreamWrapper
} from '@mattiasbuelens/web-streams-adapter';
const toTS = createTransformStreamWrapper(PolyTS); const toTS = createTransformStreamWrapper(PolyTS);
const toRS = createReadableStreamWrapper(PolyRS); const toRS = createReadableStreamWrapper(PolyRS);
*/
const NONCE_LENGTH = 12; const NONCE_LENGTH = 12;
const TAG_LENGTH = 16; const TAG_LENGTH = 16;
@ -15,7 +23,7 @@ const encoder = new TextEncoder();
function generateSalt(len) { function generateSalt(len) {
const randSalt = new Uint8Array(len); const randSalt = new Uint8Array(len);
window.crypto.getRandomValues(randSalt); crypto.getRandomValues(randSalt);
return randSalt.buffer; return randSalt.buffer;
} }
@ -31,7 +39,7 @@ class ECETransformer {
} }
async generateKey() { async generateKey() {
const inputKey = await window.crypto.subtle.importKey( const inputKey = await crypto.subtle.importKey(
'raw', 'raw',
this.ikm, this.ikm,
'HKDF', 'HKDF',
@ -39,7 +47,7 @@ class ECETransformer {
['deriveKey'] ['deriveKey']
); );
return window.crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
name: 'HKDF', name: 'HKDF',
salt: this.salt, salt: this.salt,
@ -57,7 +65,7 @@ class ECETransformer {
} }
async generateNonceBase() { async generateNonceBase() {
const inputKey = await window.crypto.subtle.importKey( const inputKey = await crypto.subtle.importKey(
'raw', 'raw',
this.ikm, this.ikm,
'HKDF', 'HKDF',
@ -65,9 +73,9 @@ class ECETransformer {
['deriveKey'] ['deriveKey']
); );
const base = await window.crypto.subtle.exportKey( const base = await crypto.subtle.exportKey(
'raw', 'raw',
await window.crypto.subtle.deriveKey( await crypto.subtle.deriveKey(
{ {
name: 'HKDF', name: 'HKDF',
salt: this.salt, salt: this.salt,
@ -156,7 +164,7 @@ class ECETransformer {
async encryptRecord(buffer, seq, isLast) { async encryptRecord(buffer, seq, isLast) {
const nonce = this.generateNonce(seq); const nonce = this.generateNonce(seq);
const encrypted = await window.crypto.subtle.encrypt( const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: nonce }, { name: 'AES-GCM', iv: nonce },
this.key, this.key,
this.pad(buffer, isLast) this.pad(buffer, isLast)
@ -166,7 +174,7 @@ class ECETransformer {
async decryptRecord(buffer, seq, isLast) { async decryptRecord(buffer, seq, isLast) {
const nonce = this.generateNonce(seq); const nonce = this.generateNonce(seq);
const data = await window.crypto.subtle.decrypt( const data = await crypto.subtle.decrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: nonce, iv: nonce,
@ -266,7 +274,7 @@ class StreamSlicer {
constructor(rs, mode) { constructor(rs, mode) {
this.mode = mode; this.mode = mode;
this.rs = rs; this.rs = rs;
this.chunkSize = (mode === MODE_ENCRYPT) ? (rs - 17) : 21; this.chunkSize = mode === MODE_ENCRYPT ? rs - 17 : 21;
this.partialChunk = new Uint8Array(this.chunkSize); //where partial chunks are saved this.partialChunk = new Uint8Array(this.chunkSize); //where partial chunks are saved
this.offset = 0; this.offset = 0;
} }
@ -285,7 +293,7 @@ class StreamSlicer {
let i = 0; let i = 0;
if (this.offset > 0) { if (this.offset > 0) {
const len = Math.min(chunk.byteLength, (this.chunkSize - this.offset)); const len = Math.min(chunk.byteLength, this.chunkSize - this.offset);
this.partialChunk.set(chunk.slice(0, len), this.offset); this.partialChunk.set(chunk.slice(0, len), this.offset);
this.offset += len; this.offset += len;
i += len; i += len;
@ -297,7 +305,7 @@ class StreamSlicer {
} }
while (i < chunk.byteLength) { while (i < chunk.byteLength) {
if ((chunk.byteLength - i) >= this.chunkSize) { if (chunk.byteLength - i >= this.chunkSize) {
const record = chunk.slice(i, i + this.chunkSize); const record = chunk.slice(i, i + this.chunkSize);
i += this.chunkSize; i += this.chunkSize;
this.send(record, controller); this.send(record, controller);
@ -318,17 +326,6 @@ class StreamSlicer {
} }
} }
async function stream2blob(stream) {
const chunks = [];
const reader = stream.getReader();
let state = await reader.read();
while (!state.done) {
chunks.push(state.value);
state = await reader.read();
}
return new Blob(chunks);
}
/* /*
input: a blob or a ReadableStream containing data to be transformed input: a blob or a ReadableStream containing data to be transformed
key: Uint8Array containing key of size KEY_LENGTH key: Uint8Array containing key of size KEY_LENGTH
@ -354,7 +351,8 @@ export default class ECE {
info() { info() {
return { return {
recordSize: this.rs, recordSize: this.rs,
fileSize: 21 + this.input.size + 16 * Math.floor(this.input.size / (this.rs - 17)) fileSize:
21 + this.input.size + 16 * Math.floor(this.input.size / (this.rs - 17))
}; };
} }
@ -362,13 +360,19 @@ export default class ECE {
let inputStream; let inputStream;
if (this.input instanceof Blob) { if (this.input instanceof Blob) {
inputStream = toRS(new ReadableStream(new BlobSlicer(this.input, this.rs, this.mode))); inputStream = new ReadableStream(
new BlobSlicer(this.input, this.rs, this.mode)
); //inputStream = toRS(new ReadableStream(new BlobSlicer(this.input, this.rs, this.mode)));
} else { } else {
const sliceStream = toTS(new TransformStream(new StreamSlicer(this.rs, this.mode))); const sliceStream = new TransformStream(
new StreamSlicer(this.rs, this.mode)
); //const sliceStream = toTS(new TransformStream(new StreamSlicer(this.rs, this.mode)));
inputStream = this.input.pipeThrough(sliceStream); inputStream = this.input.pipeThrough(sliceStream);
} }
const cryptoStream = toTS(new TransformStream(new ECETransformer(this.mode, this.key, this.rs, this.salt))); const cryptoStream = new TransformStream(
return inputStream.pipeThrough(cryptoStream); new ECETransformer(this.mode, this.key, this.rs, this.salt)
); //const cryptoStream = toTS(new TransformStream(new ECETransformer(this.mode, this.key, this.rs, this.salt)));
return inputStream.pipeThrough(cryptoStream); //return toRS(inputStream.pipeThrough(cryptoStream));
} }
} }

View File

@ -36,12 +36,6 @@ export default function(state, emitter) {
} }
} }
function register() {
navigator.serviceWorker.register('/serviceWorker.js')
.then( reg => console.log("registration successful or already installed"))
.catch( e => console.log(e) );
}
function updateProgress() { function updateProgress() {
if (updateTitle) { if (updateTitle) {
emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio)); emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio));
@ -156,6 +150,7 @@ export default function(state, emitter) {
emitter.on('getMetadata', async () => { emitter.on('getMetadata', async () => {
const file = state.fileInfo; const file = state.fileInfo;
const receiver = new FileReceiver(file); const receiver = new FileReceiver(file);
try { try {
await receiver.getMetadata(); await receiver.getMetadata();
@ -169,12 +164,6 @@ export default function(state, emitter) {
} }
} }
const info = {
key: file.secretKey,
nonce: file.nonce
}
navigator.serviceWorker.controller.postMessage(info);
render(); render();
}); });

View File

@ -1,7 +1,7 @@
import Nanobus from 'nanobus'; import Nanobus from 'nanobus';
import Keychain from './keychain'; import Keychain from './keychain';
import { bytes } from './utils'; import { bytes } from './utils';
import { metadata, downloadFile, downloadStream } from './api'; import { metadata } from './api';
export default class FileReceiver extends Nanobus { export default class FileReceiver extends Nanobus {
constructor(fileInfo) { constructor(fileInfo) {
@ -52,107 +52,57 @@ export default class FileReceiver extends Nanobus {
} }
async streamToArrayBuffer(stream, streamSize, onprogress) { async streamToArrayBuffer(stream, streamSize, onprogress) {
try { const result = new Uint8Array(streamSize);
const result = new Uint8Array(streamSize); let offset = 0;
let offset = 0; const reader = stream.getReader();
const reader = stream.getReader(); let state = await reader.read();
let state = await reader.read(); while (!state.done) {
while (!state.done) { result.set(state.value, offset);
result.set(state.value, offset); offset += state.value.length;
offset += state.value.length; state = await reader.read();
state = await reader.read(); onprogress([offset, streamSize]);
onprogress([offset, streamSize]);
}
onprogress([streamSize, streamSize]);
return result.slice(0, offset).buffer;
} catch (e) {
console.log(e);
throw (e);
} }
onprogress([streamSize, streamSize]);
return result.slice(0, offset).buffer;
} }
async download(noSave = false) { async download(noSave = false) {
const onprogress = p => { const onprogress = p => {
this.progress = p; this.progress = p;
this.emit('progress'); this.emit('progress');
} };
try { try {
this.state = 'downloading'; this.state = 'downloading';
this.downloadRequest = downloadStream(
this.fileInfo.id, const auth = await this.keychain.authHeader();
this.keychain const info = {
); key: this.fileInfo.secretKey,
nonce: this.fileInfo.nonce,
filename: this.fileInfo.name,
auth: auth
};
navigator.serviceWorker.controller.postMessage(info);
onprogress([0, this.fileInfo.size]); onprogress([0, this.fileInfo.size]);
const download = await this.downloadRequest.result;
const plainstream = this.keychain.decryptStream(download);
//temporary
const plaintext = await this.streamToArrayBuffer(
plainstream,
this.fileInfo.size,
onprogress
);
this.downloadRequest = null;
this.msg = 'decryptingFile';
this.state = 'decrypting';
this.emit('decrypting');
if (!noSave) { if (!noSave) {
await saveFile({ const downloadUrl = `${location.protocol}//${
plaintext, location.host
name: decodeURIComponent(this.fileInfo.name), }/api/download/${this.fileInfo.id}`;
type: this.fileInfo.type const a = document.createElement('a');
}); a.href = downloadUrl;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(downloadUrl);
} }
this.msg = 'downloadFinish'; //this.msg = 'downloadFinish';
this.state = 'complete'; //this.state = 'complete';
} catch (e) { } catch (e) {
this.downloadRequest = null; this.downloadRequest = null;
throw e; throw e;
} }
} }
} }
async function saveFile(file) {
return new Promise(function(resolve, reject) {
const dataView = new DataView(file.plaintext);
const blob = new Blob([dataView], { type: file.type });
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, file.name);
return resolve();
} else if (/iPhone|fxios/i.test(navigator.userAgent)) {
// This method is much slower but createObjectURL
// is buggy on iOS
const reader = new FileReader();
reader.addEventListener('loadend', function() {
if (reader.error) {
return reject(reader.error);
}
if (reader.result) {
const a = document.createElement('a');
a.href = reader.result;
a.download = file.name;
document.body.appendChild(a);
a.click();
}
resolve();
});
reader.readAsDataURL(blob);
} else {
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = file.name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(downloadUrl);
setTimeout(resolve, 100);
}
});
}

View File

@ -9,14 +9,14 @@ export default class Keychain {
if (ivB64) { if (ivB64) {
this.iv = b64ToArray(ivB64); this.iv = b64ToArray(ivB64);
} else { } else {
this.iv = window.crypto.getRandomValues(new Uint8Array(12)); this.iv = crypto.getRandomValues(new Uint8Array(12));
} }
if (secretKeyB64) { if (secretKeyB64) {
this.rawSecret = b64ToArray(secretKeyB64); this.rawSecret = b64ToArray(secretKeyB64);
} else { } else {
this.rawSecret = window.crypto.getRandomValues(new Uint8Array(16)); this.rawSecret = crypto.getRandomValues(new Uint8Array(16));
} }
this.secretKeyPromise = window.crypto.subtle.importKey( this.secretKeyPromise = crypto.subtle.importKey(
'raw', 'raw',
this.rawSecret, this.rawSecret,
'HKDF', 'HKDF',
@ -24,7 +24,7 @@ export default class Keychain {
['deriveKey'] ['deriveKey']
); );
this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) { this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
name: 'HKDF', name: 'HKDF',
salt: new Uint8Array(), salt: new Uint8Array(),
@ -41,7 +41,7 @@ export default class Keychain {
); );
}); });
this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) { this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
name: 'HKDF', name: 'HKDF',
salt: new Uint8Array(), salt: new Uint8Array(),
@ -58,7 +58,7 @@ export default class Keychain {
); );
}); });
this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) { this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
name: 'HKDF', name: 'HKDF',
salt: new Uint8Array(), salt: new Uint8Array(),
@ -91,12 +91,12 @@ export default class Keychain {
} }
setPassword(password, shareUrl) { setPassword(password, shareUrl) {
this.authKeyPromise = window.crypto.subtle this.authKeyPromise = crypto.subtle
.importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [ .importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [
'deriveKey' 'deriveKey'
]) ])
.then(passwordKey => .then(passwordKey =>
window.crypto.subtle.deriveKey( crypto.subtle.deriveKey(
{ {
name: 'PBKDF2', name: 'PBKDF2',
salt: encoder.encode(shareUrl), salt: encoder.encode(shareUrl),
@ -115,7 +115,7 @@ export default class Keychain {
} }
setAuthKey(authKeyB64) { setAuthKey(authKeyB64) {
this.authKeyPromise = window.crypto.subtle.importKey( this.authKeyPromise = crypto.subtle.importKey(
'raw', 'raw',
b64ToArray(authKeyB64), b64ToArray(authKeyB64),
{ {
@ -129,13 +129,13 @@ export default class Keychain {
async authKeyB64() { async authKeyB64() {
const authKey = await this.authKeyPromise; const authKey = await this.authKeyPromise;
const rawAuth = await window.crypto.subtle.exportKey('raw', authKey); const rawAuth = await crypto.subtle.exportKey('raw', authKey);
return arrayToB64(new Uint8Array(rawAuth)); return arrayToB64(new Uint8Array(rawAuth));
} }
async authHeader() { async authHeader() {
const authKey = await this.authKeyPromise; const authKey = await this.authKeyPromise;
const sig = await window.crypto.subtle.sign( const sig = await crypto.subtle.sign(
{ {
name: 'HMAC' name: 'HMAC'
}, },
@ -147,7 +147,7 @@ export default class Keychain {
async encryptFile(plaintext) { async encryptFile(plaintext) {
const encryptKey = await this.encryptKeyPromise; const encryptKey = await this.encryptKeyPromise;
const ciphertext = await window.crypto.subtle.encrypt( const ciphertext = await crypto.subtle.encrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: this.iv, iv: this.iv,
@ -161,7 +161,7 @@ export default class Keychain {
async encryptMetadata(metadata) { async encryptMetadata(metadata) {
const metaKey = await this.metaKeyPromise; const metaKey = await this.metaKeyPromise;
const ciphertext = await window.crypto.subtle.encrypt( const ciphertext = await crypto.subtle.encrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: new Uint8Array(12), iv: new Uint8Array(12),
@ -194,7 +194,7 @@ export default class Keychain {
async decryptFile(ciphertext) { async decryptFile(ciphertext) {
const encryptKey = await this.encryptKeyPromise; const encryptKey = await this.encryptKeyPromise;
const plaintext = await window.crypto.subtle.decrypt( const plaintext = await crypto.subtle.decrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: this.iv, iv: this.iv,
@ -208,7 +208,7 @@ export default class Keychain {
async decryptMetadata(ciphertext) { async decryptMetadata(ciphertext) {
const metaKey = await this.metaKeyPromise; const metaKey = await this.metaKeyPromise;
const plaintext = await window.crypto.subtle.decrypt( const plaintext = await crypto.subtle.decrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: new Uint8Array(12), iv: new Uint8Array(12),

View File

@ -9,18 +9,11 @@ import storage from './storage';
import metrics from './metrics'; import metrics from './metrics';
import experiments from './experiments'; import experiments from './experiments';
import Raven from 'raven-js'; import Raven from 'raven-js';
import assets from '../common/assets';
if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) { if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) {
Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install(); Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install();
} }
function register(state, emitter) {
navigator.serviceWorker.register('serviceWorker.js')
.then( reg => console.log("registration successful or already installed"))
.catch( e => console.log(e) );
}
app.use((state, emitter) => { app.use((state, emitter) => {
state.transfer = null; state.transfer = null;
state.fileInfo = null; state.fileInfo = null;
@ -51,7 +44,9 @@ app.use((state, emitter) => {
}); });
}); });
app.use(register); app.use(() => {
navigator.serviceWorker.register('/serviceWorker.js');
});
app.use(metrics); app.use(metrics);
app.use(fileManager); app.use(fileManager);
app.use(dragManager); app.use(dragManager);

View File

@ -1,38 +1,41 @@
import Keychain from './keychain'; import Keychain from './keychain';
self.addEventListener('install', (event) => { self.addEventListener('install', event => {
console.log("install event on sw")
self.skipWaiting(); self.skipWaiting();
}); });
async function decryptStream(request) { async function decryptStream(request) {
console.log("DOWNLOAD FETCH") const response = await fetch(request.url, {
//make actual request to server, get response back, decrypt it, send it method: 'GET',
const response = await fetch(req, headers: { Authorization: self.auth }
{ });
method: 'GET',
headers: { Authorization: auth }
}
);
if (response.status !== 200) { if (response.status !== 200) {
console.log(response.status) return response;
throw new Error(response.status);
} }
const body = response.body; const body = response.body; //stream
console.log(body); const decrypted = self.keychain.decryptStream(body);
return response; const headers = {
headers: {
'Content-Disposition': 'attachment; filename=' + self.filename
}
};
const newRes = new Response(decrypted, headers);
return newRes;
} }
self.onfetch = (event) => { self.onfetch = event => {
const req = event.request.clone(); const req = event.request.clone();
if (req.url.includes('/api/download')) { if (req.url.includes('/api/download')) {
event.respondWith(decryptStream(req)); event.respondWith(decryptStream(req));
} }
}; };
self.onmessage = (event) => { self.onmessage = event => {
self.keychain = new Keychain(event.data.key, event.data.nonce); self.keychain = new Keychain(event.data.key, event.data.nonce);
}; self.filename = event.data.filename;
self.auth = event.data.auth;
};

View File

@ -24,7 +24,7 @@ function loadShim(polyfill) {
async function canHasSend() { async function canHasSend() {
try { try {
const key = await window.crypto.subtle.generateKey( const key = await crypto.subtle.generateKey(
{ {
name: 'AES-GCM', name: 'AES-GCM',
length: 128 length: 128
@ -32,25 +32,25 @@ async function canHasSend() {
true, true,
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
); );
await window.crypto.subtle.encrypt( await crypto.subtle.encrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
iv: window.crypto.getRandomValues(new Uint8Array(12)), iv: crypto.getRandomValues(new Uint8Array(12)),
tagLength: 128 tagLength: 128
}, },
key, key,
new ArrayBuffer(8) new ArrayBuffer(8)
); );
await window.crypto.subtle.importKey( await crypto.subtle.importKey(
'raw', 'raw',
window.crypto.getRandomValues(new Uint8Array(16)), crypto.getRandomValues(new Uint8Array(16)),
'PBKDF2', 'PBKDF2',
false, false,
['deriveKey'] ['deriveKey']
); );
await window.crypto.subtle.importKey( await crypto.subtle.importKey(
'raw', 'raw',
window.crypto.getRandomValues(new Uint8Array(16)), crypto.getRandomValues(new Uint8Array(16)),
'HKDF', 'HKDF',
false, false,
['deriveKey'] ['deriveKey']
@ -75,7 +75,7 @@ function copyToClipboard(str) {
if (navigator.userAgent.match(/iphone|ipad|ipod/i)) { if (navigator.userAgent.match(/iphone|ipad|ipod/i)) {
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(aux); range.selectNodeContents(aux);
const sel = window.getSelection(); const sel = getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(range); sel.addRange(range);
aux.setSelectionRange(0, str.length); aux.setSelectionRange(0, str.length);

5
package-lock.json generated
View File

@ -17701,6 +17701,11 @@
"any-observable": "0.2.0" "any-observable": "0.2.0"
} }
}, },
"streamsaver": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-1.0.1.tgz",
"integrity": "sha1-R11ASXO15pJqVX8OTNhVijUg4Hw="
},
"strftime": { "strftime": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.0.tgz", "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.0.tgz",

View File

@ -122,7 +122,6 @@
"aws-sdk": "^2.206.0", "aws-sdk": "^2.206.0",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-runtime": "^6.26.0",
"choo": "^6.10.0", "choo": "^6.10.0",
"cldr-core": "^32.0.0", "cldr-core": "^32.0.0",
"convict": "^4.0.1", "convict": "^4.0.1",

View File

@ -11,6 +11,7 @@ if (config.sentry_dsn) {
} }
const app = express(); const app = express();
expressWs(app, null, { perMessageDeflate: false }); expressWs(app, null, { perMessageDeflate: false });
app.ws('/api/ws', require('../routes/ws')); app.ws('/api/ws', require('../routes/ws'));
routes(app); routes(app);

View File

@ -22,7 +22,7 @@ describe('API', function() {
const result = await up.result; const result = await up.result;
assert.ok(result.url); assert.ok(result.url);
assert.ok(result.id); assert.ok(result.id);
assert.ok(result.ownerToken); assert.ok(result.ownerToken);
}); });
it('can be cancelled', async function() { it('can be cancelled', async function() {

View File

@ -49,7 +49,7 @@ describe('Streaming', function() {
it('can decrypt', async function() { it('can decrypt', async function() {
const encBlob = new Blob([encrypted]); const encBlob = new Blob([encrypted]);
const ece = new ECE(encBlob, key, 'decrypt', rs); const ece = new ECE(encBlob, key, 'decrypt', rs);
const decStream = await ece.transform() const decStream = await ece.transform();
const reader = decStream.getReader(); const reader = decStream.getReader();
let result = Buffer.from([]); let result = Buffer.from([]);

View File

@ -1,16 +1,9 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack');
const regularJSOptions = { const regularJSOptions = {
babelrc: false, babelrc: false,
presets: [['env', { modules: false }], 'stage-2'], presets: [['env'], 'stage-2'],
// yo-yoify converts html template strings to direct dom api calls plugins: ['transform-runtime']
plugins: [
"transform-runtime", {
//"polyfill": false,
//"regenerator": true
}
]
}; };
const entry = { const entry = {
@ -24,17 +17,14 @@ module.exports = {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
publicPath: '/' publicPath: '/'
}, },
module: { module: {
rules: [ rules: [
{ {
loader: 'babel-loader', loader: 'babel-loader',
// exclude: /node_modules/, exclude: /node_modules/,
include: [
path.resolve(__dirname, 'app'),
path.resolve(__dirname, 'node_modules/buffer')
],
options: regularJSOptions options: regularJSOptions
} }
] ]
} }
}; };