From f98bc0878ca6a3e6dac6842770d604799811f308 Mon Sep 17 00:00:00 2001 From: Emily Date: Fri, 6 Jul 2018 15:49:50 -0700 Subject: [PATCH] saves stream to file --- app/api.js | 19 ++-- app/ece.js | 62 ++++++------- app/fileManager.js | 13 +-- app/fileReceiver.js | 116 +++++++------------------ app/keychain.js | 30 +++---- app/main.js | 11 +-- app/serviceWorker.js | 39 +++++---- app/utils.js | 16 ++-- package-lock.json | 5 ++ package.json | 1 - server/bin/prod.js | 1 + test/frontend/tests/api-tests.js | 2 +- test/frontend/tests/streaming-tests.js | 2 +- webpackSw.config.js | 20 ++--- 14 files changed, 136 insertions(+), 201 deletions(-) diff --git a/app/api.js b/app/api.js index 168b3abc..2047c2c2 100644 --- a/app/api.js +++ b/app/api.js @@ -1,5 +1,5 @@ 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'; 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(); + //this will be already funneled through serviceworker const response = await fetch(`/api/download/${id}`, { signal: signal, method: 'GET', @@ -223,22 +224,20 @@ async function downloadS(id, keychain, onprogress, signal) { const fileSize = response.headers.get('Content-Length'); //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) { - console.log("STREAM") return RS(response.body); } return response.blob(); - } -async function tryDownloadStream(id, keychain, onprogress, signal, tries = 1) { +async function tryDownloadStream(id, keychain, signal, tries = 1) { try { - const result = await downloadS(id, keychain, onprogress, signal); + const result = await downloadS(id, keychain, signal); return result; } catch (e) { if (e.message === '401' && --tries > 0) { - return tryDownloadStream(id, keychain, onprogress, signal, tries); + return tryDownloadStream(id, keychain, signal, tries); } if (e.name === 'AbortError') { 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(); function cancel() { controller.abort(); } return { cancel, - result: tryDownloadStream(id, keychain, onprogress, controller.signal, 2) + result: tryDownloadStream(id, keychain, controller.signal, 2) }; } diff --git a/app/ece.js b/app/ece.js index e80b2e65..07b06a5a 100644 --- a/app/ece.js +++ b/app/ece.js @@ -1,8 +1,16 @@ 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 toRS = createReadableStreamWrapper(PolyRS); +*/ const NONCE_LENGTH = 12; const TAG_LENGTH = 16; @@ -15,7 +23,7 @@ const encoder = new TextEncoder(); function generateSalt(len) { const randSalt = new Uint8Array(len); - window.crypto.getRandomValues(randSalt); + crypto.getRandomValues(randSalt); return randSalt.buffer; } @@ -31,7 +39,7 @@ class ECETransformer { } async generateKey() { - const inputKey = await window.crypto.subtle.importKey( + const inputKey = await crypto.subtle.importKey( 'raw', this.ikm, 'HKDF', @@ -39,7 +47,7 @@ class ECETransformer { ['deriveKey'] ); - return window.crypto.subtle.deriveKey( + return crypto.subtle.deriveKey( { name: 'HKDF', salt: this.salt, @@ -57,7 +65,7 @@ class ECETransformer { } async generateNonceBase() { - const inputKey = await window.crypto.subtle.importKey( + const inputKey = await crypto.subtle.importKey( 'raw', this.ikm, 'HKDF', @@ -65,9 +73,9 @@ class ECETransformer { ['deriveKey'] ); - const base = await window.crypto.subtle.exportKey( + const base = await crypto.subtle.exportKey( 'raw', - await window.crypto.subtle.deriveKey( + await crypto.subtle.deriveKey( { name: 'HKDF', salt: this.salt, @@ -156,7 +164,7 @@ class ECETransformer { async encryptRecord(buffer, seq, isLast) { const nonce = this.generateNonce(seq); - const encrypted = await window.crypto.subtle.encrypt( + const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: nonce }, this.key, this.pad(buffer, isLast) @@ -166,7 +174,7 @@ class ECETransformer { async decryptRecord(buffer, seq, isLast) { const nonce = this.generateNonce(seq); - const data = await window.crypto.subtle.decrypt( + const data = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: nonce, @@ -266,7 +274,7 @@ class StreamSlicer { constructor(rs, mode) { this.mode = mode; 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.offset = 0; } @@ -285,7 +293,7 @@ class StreamSlicer { let i = 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.offset += len; i += len; @@ -297,7 +305,7 @@ class StreamSlicer { } while (i < chunk.byteLength) { - if ((chunk.byteLength - i) >= this.chunkSize) { + if (chunk.byteLength - i >= this.chunkSize) { const record = chunk.slice(i, i + this.chunkSize); i += this.chunkSize; 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 key: Uint8Array containing key of size KEY_LENGTH @@ -354,7 +351,8 @@ export default class ECE { info() { return { 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; 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 { - 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); } - const cryptoStream = toTS(new TransformStream(new ECETransformer(this.mode, this.key, this.rs, this.salt))); - return inputStream.pipeThrough(cryptoStream); + const cryptoStream = new TransformStream( + 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)); } } diff --git a/app/fileManager.js b/app/fileManager.js index 88f024f9..704d3578 100644 --- a/app/fileManager.js +++ b/app/fileManager.js @@ -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() { if (updateTitle) { emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio)); @@ -156,6 +150,7 @@ export default function(state, emitter) { emitter.on('getMetadata', async () => { const file = state.fileInfo; + const receiver = new FileReceiver(file); try { 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(); }); diff --git a/app/fileReceiver.js b/app/fileReceiver.js index cf23dcb2..c610b76a 100644 --- a/app/fileReceiver.js +++ b/app/fileReceiver.js @@ -1,7 +1,7 @@ import Nanobus from 'nanobus'; import Keychain from './keychain'; import { bytes } from './utils'; -import { metadata, downloadFile, downloadStream } from './api'; +import { metadata } from './api'; export default class FileReceiver extends Nanobus { constructor(fileInfo) { @@ -52,107 +52,57 @@ export default class FileReceiver extends Nanobus { } async streamToArrayBuffer(stream, streamSize, onprogress) { - try { - const result = new Uint8Array(streamSize); - let offset = 0; - const reader = stream.getReader(); - let state = await reader.read(); - while (!state.done) { - result.set(state.value, offset); - offset += state.value.length; - state = await reader.read(); - onprogress([offset, streamSize]); - } - - onprogress([streamSize, streamSize]); - return result.slice(0, offset).buffer; - } catch (e) { - console.log(e); - throw (e); + const result = new Uint8Array(streamSize); + let offset = 0; + const reader = stream.getReader(); + let state = await reader.read(); + while (!state.done) { + result.set(state.value, offset); + offset += state.value.length; + state = await reader.read(); + onprogress([offset, streamSize]); } + + onprogress([streamSize, streamSize]); + return result.slice(0, offset).buffer; } async download(noSave = false) { const onprogress = p => { this.progress = p; this.emit('progress'); - } + }; try { this.state = 'downloading'; - this.downloadRequest = downloadStream( - this.fileInfo.id, - this.keychain - ); + + const auth = await this.keychain.authHeader(); + 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]); - 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) { - await saveFile({ - plaintext, - name: decodeURIComponent(this.fileInfo.name), - type: this.fileInfo.type - }); + const downloadUrl = `${location.protocol}//${ + location.host + }/api/download/${this.fileInfo.id}`; + const a = document.createElement('a'); + a.href = downloadUrl; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(downloadUrl); } - this.msg = 'downloadFinish'; - this.state = 'complete'; + //this.msg = 'downloadFinish'; + //this.state = 'complete'; } catch (e) { this.downloadRequest = null; 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); - } - }); -} diff --git a/app/keychain.js b/app/keychain.js index d02ff83b..ab57b852 100644 --- a/app/keychain.js +++ b/app/keychain.js @@ -9,14 +9,14 @@ export default class Keychain { if (ivB64) { this.iv = b64ToArray(ivB64); } else { - this.iv = window.crypto.getRandomValues(new Uint8Array(12)); + this.iv = crypto.getRandomValues(new Uint8Array(12)); } if (secretKeyB64) { this.rawSecret = b64ToArray(secretKeyB64); } 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', this.rawSecret, 'HKDF', @@ -24,7 +24,7 @@ export default class Keychain { ['deriveKey'] ); this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) { - return window.crypto.subtle.deriveKey( + return crypto.subtle.deriveKey( { name: 'HKDF', salt: new Uint8Array(), @@ -41,7 +41,7 @@ export default class Keychain { ); }); this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) { - return window.crypto.subtle.deriveKey( + return crypto.subtle.deriveKey( { name: 'HKDF', salt: new Uint8Array(), @@ -58,7 +58,7 @@ export default class Keychain { ); }); this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) { - return window.crypto.subtle.deriveKey( + return crypto.subtle.deriveKey( { name: 'HKDF', salt: new Uint8Array(), @@ -91,12 +91,12 @@ export default class Keychain { } setPassword(password, shareUrl) { - this.authKeyPromise = window.crypto.subtle + this.authKeyPromise = crypto.subtle .importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [ 'deriveKey' ]) .then(passwordKey => - window.crypto.subtle.deriveKey( + crypto.subtle.deriveKey( { name: 'PBKDF2', salt: encoder.encode(shareUrl), @@ -115,7 +115,7 @@ export default class Keychain { } setAuthKey(authKeyB64) { - this.authKeyPromise = window.crypto.subtle.importKey( + this.authKeyPromise = crypto.subtle.importKey( 'raw', b64ToArray(authKeyB64), { @@ -129,13 +129,13 @@ export default class Keychain { async authKeyB64() { 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)); } async authHeader() { const authKey = await this.authKeyPromise; - const sig = await window.crypto.subtle.sign( + const sig = await crypto.subtle.sign( { name: 'HMAC' }, @@ -147,7 +147,7 @@ export default class Keychain { async encryptFile(plaintext) { const encryptKey = await this.encryptKeyPromise; - const ciphertext = await window.crypto.subtle.encrypt( + const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: this.iv, @@ -161,7 +161,7 @@ export default class Keychain { async encryptMetadata(metadata) { const metaKey = await this.metaKeyPromise; - const ciphertext = await window.crypto.subtle.encrypt( + const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: new Uint8Array(12), @@ -194,7 +194,7 @@ export default class Keychain { async decryptFile(ciphertext) { const encryptKey = await this.encryptKeyPromise; - const plaintext = await window.crypto.subtle.decrypt( + const plaintext = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: this.iv, @@ -208,7 +208,7 @@ export default class Keychain { async decryptMetadata(ciphertext) { const metaKey = await this.metaKeyPromise; - const plaintext = await window.crypto.subtle.decrypt( + const plaintext = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: new Uint8Array(12), diff --git a/app/main.js b/app/main.js index fb13587a..41934485 100644 --- a/app/main.js +++ b/app/main.js @@ -9,18 +9,11 @@ import storage from './storage'; import metrics from './metrics'; import experiments from './experiments'; import Raven from 'raven-js'; -import assets from '../common/assets'; if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) { 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) => { state.transfer = 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(fileManager); app.use(dragManager); diff --git a/app/serviceWorker.js b/app/serviceWorker.js index 624a6028..b16e8520 100644 --- a/app/serviceWorker.js +++ b/app/serviceWorker.js @@ -1,38 +1,41 @@ import Keychain from './keychain'; -self.addEventListener('install', (event) => { - console.log("install event on sw") +self.addEventListener('install', event => { self.skipWaiting(); }); async function decryptStream(request) { - console.log("DOWNLOAD FETCH") - //make actual request to server, get response back, decrypt it, send it - const response = await fetch(req, - { - method: 'GET', - headers: { Authorization: auth } - } - ); + const response = await fetch(request.url, { + method: 'GET', + headers: { Authorization: self.auth } + }); if (response.status !== 200) { - console.log(response.status) - throw new Error(response.status); + return response; } - const body = response.body; - console.log(body); + const body = response.body; //stream + 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(); if (req.url.includes('/api/download')) { event.respondWith(decryptStream(req)); } }; -self.onmessage = (event) => { +self.onmessage = event => { self.keychain = new Keychain(event.data.key, event.data.nonce); -}; \ No newline at end of file + self.filename = event.data.filename; + self.auth = event.data.auth; +}; diff --git a/app/utils.js b/app/utils.js index f675476e..e3c900a6 100644 --- a/app/utils.js +++ b/app/utils.js @@ -24,7 +24,7 @@ function loadShim(polyfill) { async function canHasSend() { try { - const key = await window.crypto.subtle.generateKey( + const key = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 128 @@ -32,25 +32,25 @@ async function canHasSend() { true, ['encrypt', 'decrypt'] ); - await window.crypto.subtle.encrypt( + await crypto.subtle.encrypt( { name: 'AES-GCM', - iv: window.crypto.getRandomValues(new Uint8Array(12)), + iv: crypto.getRandomValues(new Uint8Array(12)), tagLength: 128 }, key, new ArrayBuffer(8) ); - await window.crypto.subtle.importKey( + await crypto.subtle.importKey( 'raw', - window.crypto.getRandomValues(new Uint8Array(16)), + crypto.getRandomValues(new Uint8Array(16)), 'PBKDF2', false, ['deriveKey'] ); - await window.crypto.subtle.importKey( + await crypto.subtle.importKey( 'raw', - window.crypto.getRandomValues(new Uint8Array(16)), + crypto.getRandomValues(new Uint8Array(16)), 'HKDF', false, ['deriveKey'] @@ -75,7 +75,7 @@ function copyToClipboard(str) { if (navigator.userAgent.match(/iphone|ipad|ipod/i)) { const range = document.createRange(); range.selectNodeContents(aux); - const sel = window.getSelection(); + const sel = getSelection(); sel.removeAllRanges(); sel.addRange(range); aux.setSelectionRange(0, str.length); diff --git a/package-lock.json b/package-lock.json index b8c6358e..7ca004fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17701,6 +17701,11 @@ "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": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.0.tgz", diff --git a/package.json b/package.json index a0685123..dce00a5a 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,6 @@ "aws-sdk": "^2.206.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.26.0", - "babel-runtime": "^6.26.0", "choo": "^6.10.0", "cldr-core": "^32.0.0", "convict": "^4.0.1", diff --git a/server/bin/prod.js b/server/bin/prod.js index d52393ed..a193d84c 100644 --- a/server/bin/prod.js +++ b/server/bin/prod.js @@ -11,6 +11,7 @@ if (config.sentry_dsn) { } const app = express(); + expressWs(app, null, { perMessageDeflate: false }); app.ws('/api/ws', require('../routes/ws')); routes(app); diff --git a/test/frontend/tests/api-tests.js b/test/frontend/tests/api-tests.js index 301a09de..eaf4b04a 100644 --- a/test/frontend/tests/api-tests.js +++ b/test/frontend/tests/api-tests.js @@ -22,7 +22,7 @@ describe('API', function() { const result = await up.result; assert.ok(result.url); assert.ok(result.id); - assert.ok(result.ownerToken); + assert.ok(result.ownerToken); }); it('can be cancelled', async function() { diff --git a/test/frontend/tests/streaming-tests.js b/test/frontend/tests/streaming-tests.js index 77fdec5a..44f898d8 100644 --- a/test/frontend/tests/streaming-tests.js +++ b/test/frontend/tests/streaming-tests.js @@ -49,7 +49,7 @@ describe('Streaming', function() { it('can decrypt', async function() { const encBlob = new Blob([encrypted]); const ece = new ECE(encBlob, key, 'decrypt', rs); - const decStream = await ece.transform() + const decStream = await ece.transform(); const reader = decStream.getReader(); let result = Buffer.from([]); diff --git a/webpackSw.config.js b/webpackSw.config.js index ded7bc29..d56f9b71 100644 --- a/webpackSw.config.js +++ b/webpackSw.config.js @@ -1,16 +1,9 @@ const path = require('path'); -const webpack = require('webpack'); const regularJSOptions = { babelrc: false, - presets: [['env', { modules: false }], 'stage-2'], - // yo-yoify converts html template strings to direct dom api calls - plugins: [ - "transform-runtime", { - //"polyfill": false, - //"regenerator": true - } - ] + presets: [['env'], 'stage-2'], + plugins: ['transform-runtime'] }; const entry = { @@ -24,17 +17,14 @@ module.exports = { path: path.resolve(__dirname, 'dist'), publicPath: '/' }, + module: { rules: [ { loader: 'babel-loader', - // exclude: /node_modules/, - include: [ - path.resolve(__dirname, 'app'), - path.resolve(__dirname, 'node_modules/buffer') - ], + exclude: /node_modules/, options: regularJSOptions } ] } -}; \ No newline at end of file +};