From c539ed62820d8128cf4ca2dd70cf372fec5fd1f2 Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Thu, 1 Jun 2017 13:14:14 -0700 Subject: [PATCH 1/6] refactoring code --- package.json | 2 +- public/file.js | 10 +++ public/index.html | 8 +- public/upload.js | 184 ++++++++++++++++++++-------------------- server/portal_server.js | 116 +++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 95 deletions(-) create mode 100644 public/file.js create mode 100644 server/portal_server.js diff --git a/package.json b/package.json index fb53173c..1a2c0418 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "format": "prettier --single-quote --write 'public/*.js' 'app.js'", "test": "echo \"Error: no test specified\" && exit 1", - "start": "node app.js" + "start": "node server/portal_server.js" }, "author": "", "license": "ISC", diff --git a/public/file.js b/public/file.js new file mode 100644 index 00000000..ce680be4 --- /dev/null +++ b/public/file.js @@ -0,0 +1,10 @@ +function ProgressEmitter(name, uuid, type) { + this.name = name; + this.uuid = uuid; + this.type = type; + this.link = null; + + this.emit = () => { + + }; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index c6b31826..aa77f317 100644 --- a/public/index.html +++ b/public/index.html @@ -1,9 +1,9 @@ -Firefox Fileshare - - + Firefox Fileshare + + @@ -13,6 +13,6 @@ - + diff --git a/public/upload.js b/public/upload.js index 2d890a4f..f213490f 100644 --- a/public/upload.js +++ b/public/upload.js @@ -1,101 +1,105 @@ function onChange(event) { + var file = event.target.files[0]; var reader = new FileReader(); + reader.readAsArrayBuffer(file); + + let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); + var hex = ivToStr(random_iv); + reader.onload = function(event) { let self = this; - window.crypto.subtle - .generateKey( - { - name: 'AES-CBC', - length: 128 - }, - true, - ['encrypt', 'decrypt'] - ) - .then(function(key) { - var arrayBuffer = self.result; - var array = new Uint8Array(arrayBuffer); + window.crypto.subtle.generateKey({ + name: "AES-CBC", + length: 128 + }, + true, + ["encrypt", "decrypt"]) + .then((key) => { + let arrayBuffer = self.result; + let array = new Uint8Array(arrayBuffer); - var random_iv = window.crypto.getRandomValues(new Uint8Array(16)); + window.crypto.subtle.encrypt({ + name: "AES-CBC", + iv: random_iv }, + key, + array) + .then(uploadFile.bind(null, file, hex, key)) + .catch((err) => console.error(err)); - window.crypto.subtle - .encrypt( - { - name: 'AES-CBC', - iv: random_iv - }, - key, - array - ) - .then(function(encrypted) { - var dataView = new DataView(encrypted); - var blob = new Blob([dataView], { type: file.type }); - - var fd = new FormData(); - fd.append('fname', file.name); - fd.append('data', blob, file.name); - - var xhr = new XMLHttpRequest(); - var hex = ivToStr(random_iv); - xhr.open('post', '/upload/' + hex, true); - - var li = document.createElement('li'); - var name = document.createElement('p'); - name.innerHTML = file.name; - li.appendChild(name); - - var link = document.createElement('a'); - li.appendChild(link); - - var progress = document.createElement('p'); - li.appendChild(progress); - document.getElementById('uploaded_files').appendChild(li); - - xhr.upload.addEventListener( - 'progress', - returnBindedLI(progress, name, link, li) - ); - - xhr.onreadystatechange = function() { - if (xhr.readyState == XMLHttpRequest.DONE) { - window.crypto.subtle - .exportKey('jwk', key) - .then(function(keydata) { - var curr_name = localStorage.getItem(file.name); - - localStorage.setItem(hex, xhr.responseText); - - link.innerHTML = - 'http://localhost:3000/download/' + - hex + - '/#' + - keydata.k; - link.setAttribute( - 'href', - 'http://localhost:3000/download/' + hex + '/#' + keydata.k - ); - - console.log( - 'Share this link with a friend: http://localhost:3000/download/' + - hex + - '/#' + - keydata.k - ); - }); - } - }; - - xhr.send(fd); - }) - .catch(function(err) { - console.error(err); - }); - }) - .catch(function(err) { - console.error(err); - }); + }).catch((err) => console.error(err)); }; - reader.readAsArrayBuffer(file); +} + +let uploadFile = (file, hex, key, encrypted) => { + var dataView = new DataView(encrypted); + var blob = new Blob([dataView], { type: file.type }); + + var fd = new FormData(); + fd.append("fname", file.name); + fd.append("data", blob, file.name); + + var xhr = new XMLHttpRequest(); + + xhr.open("post", "/upload/" + hex, true); + + let prog = new window.ProgressEmitter(file.name, hex, file.type); + + var li = document.createElement("li"); + var name = document.createElement("p"); + name.innerHTML = file.name; + li.appendChild(name); + + var link = document.createElement("a"); + li.appendChild(link); + + var progress = document.createElement("p"); + li.appendChild(progress); + + + document.getElementById("uploaded_files").appendChild(li); + + + xhr.upload.addEventListener("progress", returnBindedLI(progress, name, link, li)); + + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) { + window.crypto.subtle.exportKey("jwk", key).then(function(keydata) { + var curr_name = localStorage.getItem(file.name); + + localStorage.setItem(hex, xhr.responseText); + + prog.link = "http://localhost:3000/download/" + hex + "/#" + keydata.k; + prog.emit(); + + link.innerHTML = "http://localhost:3000/download/" + hex + "/#" + keydata.k; + link.setAttribute("href", "http://localhost:3000/download/" + hex + "/#" + keydata.k); + + console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); + }) + } + }; + + xhr.send(fd); + // setupLI(file.name); + +} + +function setupLI(emitter) { + var li = document.createElement("li"); + var name = document.createElement("p"); + name.innerHTML = emitter; + li.appendChild(name); + + var link = document.createElement("a"); + link.addEventListener('NotifyProgress', (progress_event) => { + console.log(progress_event); + }); + li.appendChild(link); + + var progress = document.createElement("p"); + li.appendChild(progress); + document.getElementById("uploaded_files").appendChild(li); } function ivToStr(iv) { diff --git a/server/portal_server.js b/server/portal_server.js new file mode 100644 index 00000000..4cc91688 --- /dev/null +++ b/server/portal_server.js @@ -0,0 +1,116 @@ + +const express = require("express") +const busboy = require("connect-busboy"); +const path = require("path"); +const fs = require("fs-extra"); +const bodyParser = require("body-parser"); +const crypto = require("crypto"); + +const app = express() +const redis = require("redis"), + client = redis.createClient(); + +client.on("error", function(err) { + console.log(err); +}) + +app.use(busboy()); +app.use(bodyParser.json()); +app.use(express.static(path.join(__dirname, "../public"))); + +app.get("/download/:id", function(req, res) { + res.sendFile(path.join(__dirname + "/../public/download.html")); +}); + +app.get("/assets/download/:id", function(req, res) { + + let id = req.params.id; + if (!validateID(id)){ + res.send(404); + return; + } + + + client.hget(id, "filename", function(err, reply) { // maybe some expiration logic too + if (!reply) { + res.sendStatus(404); + } else { + res.setHeader("Content-Disposition", "attachment; filename=" + reply); + res.setHeader("Content-Type", "application/octet-stream"); + + res.download(__dirname + "/../static/" + id, reply, function(err) { + if (!err) { + client.del(id); + fs.unlinkSync(__dirname + "/../static/" + id); + } + }); + } + }) + +}); + +app.post("/delete/:id", function(req, res) { + let id = req.params.id; + + if (!validateID(id)){ + res.send(404); + return; + } + + let delete_token = req.body.delete_token; + + if (!delete_token){ + res.sendStatus(404); + } + + client.hget(id, "delete", function(err, reply) { + if (!reply) { + res.sendStatus(404); + } else { + client.del(id); + fs.unlinkSync(__dirname + "/../static/" + id); + res.sendStatus(200); + } + }) +}); + +app.post("/upload/:id", function (req, res, next) { + + if (!validateID(req.params.id)){ + res.send(404); + return; + } + + var fstream; + req.pipe(req.busboy); + req.busboy.on("file", function (fieldname, file, filename) { + console.log("Uploading: " + filename); + + //Path where image will be uploaded + fstream = fs.createWriteStream(__dirname + "/../static/" + req.params.id); + file.pipe(fstream); + fstream.on("close", function () { + let id = req.params.id; + let uuid = crypto.randomBytes(10).toString('hex'); + + client.hmset([id, "filename", filename, "delete", uuid]); + + // delete the file off the server in 24 hours + // setTimeout(function() { + // fs.unlinkSync(__dirname + "/static/" + id); + // }, 86400000); + + client.expire(id, 86400000); + console.log("Upload Finished of " + filename); + res.send(uuid); + }); + }); +}); + +app.listen(3000, function () { + console.log("Portal app listening on port 3000!") +}) + +function validateID(route_id) { + return route_id.match(/^[0-9a-fA-F]{32}$/) !== null; +} \ No newline at end of file From 51910b5fc9a6d951f47577dc51cd152c96d47e32 Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Thu, 1 Jun 2017 15:10:00 -0700 Subject: [PATCH 2/6] added browserify, refactored code to separate UI and network logic --- app.js | 111 ------- frontend/src/download.js | 132 +++++++++ frontend/src/file.js | 27 ++ frontend/src/main.js | 3 + frontend/src/upload.js | 142 +++++++++ package.json | 6 +- public/bundle.js | 614 +++++++++++++++++++++++++++++++++++++++ public/download.html | 2 +- public/download.js | 135 --------- public/file.js | 10 - public/index.html | 6 +- public/upload.js | 164 ----------- server/portal_server.js | 31 +- 13 files changed, 942 insertions(+), 441 deletions(-) delete mode 100644 app.js create mode 100644 frontend/src/download.js create mode 100644 frontend/src/file.js create mode 100644 frontend/src/main.js create mode 100644 frontend/src/upload.js create mode 100644 public/bundle.js delete mode 100644 public/download.js delete mode 100644 public/file.js delete mode 100644 public/upload.js diff --git a/app.js b/app.js deleted file mode 100644 index dcd8a9df..00000000 --- a/app.js +++ /dev/null @@ -1,111 +0,0 @@ -const express = require('express'); -const busboy = require('connect-busboy'); -const path = require('path'); -const fs = require('fs-extra'); -const bodyParser = require('body-parser'); -const crypto = require('crypto'); - -const app = express(); -const redis = require('redis'), client = redis.createClient(); - -client.on('error', function(err) { - console.log(err); -}); - -app.use(busboy()); -app.use(bodyParser.json()); -app.use(express.static(path.join(__dirname, 'public'))); - -app.get('/download/:id', function(req, res) { - res.sendFile(path.join(__dirname + '/public/download.html')); -}); - -app.get('/assets/download/:id', function(req, res) { - let id = req.params.id; - if (!validateID(id)) { - res.send(404); - return; - } - - client.hget(id, 'filename', function(err, reply) { - // maybe some expiration logic too - if (!reply) { - res.sendStatus(404); - } else { - res.setHeader('Content-Disposition', 'attachment; filename=' + reply); - res.setHeader('Content-Type', 'application/octet-stream'); - - res.download(__dirname + '/static/' + id, reply, function(err) { - if (!err) { - client.del(id); - fs.unlinkSync(__dirname + '/static/' + id); - } - }); - } - }); -}); - -app.post('/delete/:id', function(req, res) { - let id = req.params.id; - - if (!validateID(id)) { - res.send(404); - return; - } - - let delete_token = req.body.delete_token; - - if (!delete_token) { - res.sendStatus(404); - } - - client.hget(id, 'delete', function(err, reply) { - if (!reply) { - res.sendStatus(404); - } else { - client.del(id); - fs.unlinkSync(__dirname + '/static/' + id); - res.sendStatus(200); - } - }); -}); - -app.post('/upload/:id', function(req, res, next) { - if (!validateID(req.params.id)) { - res.send(404); - return; - } - - var fstream; - req.pipe(req.busboy); - req.busboy.on('file', function(fieldname, file, filename) { - console.log('Uploading: ' + filename); - - //Path where image will be uploaded - fstream = fs.createWriteStream(__dirname + '/static/' + req.params.id); - file.pipe(fstream); - fstream.on('close', function() { - let id = req.params.id; - let uuid = crypto.randomBytes(10).toString('hex'); - - client.hmset([id, 'filename', filename, 'delete', uuid]); - - // delete the file off the server in 24 hours - // setTimeout(function() { - // fs.unlinkSync(__dirname + "/static/" + id); - // }, 86400000); - - client.expire(id, 86400000); - console.log('Upload Finished of ' + filename); - res.send(uuid); - }); - }); -}); - -app.listen(3000, function() { - console.log('Portal app listening on port 3000!'); -}); - -function validateID(route_id) { - return route_id.match(/^[0-9a-fA-F]{32}$/) !== null; -} diff --git a/frontend/src/download.js b/frontend/src/download.js new file mode 100644 index 00000000..43852d75 --- /dev/null +++ b/frontend/src/download.js @@ -0,0 +1,132 @@ +const UIWrapper = require('./file').UIWrapper; + +let download = () => { + + let xhr = new XMLHttpRequest(); + xhr.open("get", "/assets" + location.pathname.slice(0, -1), true); + xhr.responseType = "blob"; + + let listelem = setupUI(); + xhr.addEventListener("progress", updateProgress.bind(null, listelem)); + + xhr.onload = function(e) { + + // maybe send a separate request before this one to get the filename? + + // maybe render the html itself with the filename, since it's generated server side + // after a get request with the unique id + listelem.emit('name', xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]); + + if (this.status == 200) { + let self = this; + let blob = new Blob([this.response]); + let arrayBuffer; + let fileReader = new FileReader(); + fileReader.onload = function() { + arrayBuffer = this.result; + let array = new Uint8Array(arrayBuffer); + salt = strToIv(location.pathname.slice(10, -1)); + + window.crypto.subtle.importKey( + "jwk", + { + kty: "oct", + k: location.hash.slice(1), + alg: "A128CBC", + ext: true, + }, + { + name: "AES-CBC", + }, + true, + ["encrypt", "decrypt"]) + .then((key) => { + return window.crypto.subtle.decrypt( + { + name: "AES-CBC", + iv: salt, + }, + key, + array) + }) + .then((decrypted) => { + let dataView = new DataView(decrypted); + let blob = new Blob([dataView]); + let downloadUrl = URL.createObjectURL(blob); + let a = document.createElement("a"); + a.href = downloadUrl; + a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]; + document.body.appendChild(a); + a.click(); + }) + .catch((err) => { + alert("This link is either invalid or has expired, or the uploader has deleted the file."); + console.error(err); + }); + }; + + fileReader.readAsArrayBuffer(blob); + } else { + alert("This link is either invalid or has expired, or the uploader has deleted the file.") + } + }; + xhr.send(); +} + +window.download = download; + +let setupUI = () => { + let li = document.createElement("li"); + let name = document.createElement("p"); + li.appendChild(name); + + let progress = document.createElement("p"); + li.appendChild(progress); + + document.getElementById("downloaded_files").appendChild(li); + + return new UIWrapper(li, name, null, progress); +} + +let ivToStr = (iv) => { + let hexStr = ""; + for (let i in iv) { + if (iv[i] < 16) { + hexStr += "0" + iv[i].toString(16); + } else { + hexStr += iv[i].toString(16); + } + } + window.hexStr = hexStr; + return hexStr; +} + +let strToIv = (str) => { + let iv = new Uint8Array(16); + for (let i = 0; i < str.length; i += 2) { + iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + } + + return iv; +} + +let updateProgress = (UIelem, e) => { + if (e.lengthComputable) { + let percentComplete = Math.floor((e.loaded / e.total) * 100); + UIelem.emit('progress', "Progress: " + percentComplete + "%"); + + if (percentComplete === 100) { + let finished = document.createElement("p"); + finished.innerText = "Your download has finished."; + UIelem.li.appendChild(finished); + + let close = document.createElement("button"); + close.innerText = "Ok"; + close.addEventListener("click", () => { + document.getElementById("downloaded_files").removeChild(UIelem.li); + }); + + UIelem.li.appendChild(close); + } + } +} \ No newline at end of file diff --git a/frontend/src/file.js b/frontend/src/file.js new file mode 100644 index 00000000..c581b1ca --- /dev/null +++ b/frontend/src/file.js @@ -0,0 +1,27 @@ +const EventEmitter = require('events'); + +class UIWrapper extends EventEmitter { + constructor(li, name, link, progress) { + super(); + this.li = li; + this.name = name; + this.link = link; + this.progress = progress; + + this.on("name", (filename) => { + this.name.innerText = filename; + }); + + this.on("link", (link) => { + this.link.innerText = link; + this.link.setAttribute('href', link); + }); + + this.on("progress", (progress) => { + this.progress.innerText = progress; + + }); + } +} + +exports.UIWrapper = UIWrapper; \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 00000000..23b00a19 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,3 @@ +require('./upload'); +require('./download'); +require('./file'); \ No newline at end of file diff --git a/frontend/src/upload.js b/frontend/src/upload.js new file mode 100644 index 00000000..df4d2eed --- /dev/null +++ b/frontend/src/upload.js @@ -0,0 +1,142 @@ +const EventEmitter = require('events'); +const UIWrapper = require('./file').UIWrapper; + +let onChange = (event) => { + let file = event.target.files[0]; + let reader = new FileReader(); + reader.readAsArrayBuffer(file); + + let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); + let hex = ivToStr(random_iv); + + reader.onload = function(event) { + let self = this; + window.crypto.subtle.generateKey({ + name: "AES-CBC", + length: 128 + }, + true, + ["encrypt", "decrypt"]) + .then((key) => { + let arrayBuffer = self.result; + let array = new Uint8Array(arrayBuffer); + + window.crypto.subtle.encrypt({ + name: "AES-CBC", + iv: random_iv }, + key, + array) + .then(uploadFile.bind(null, file, hex, key)) + .catch((err) => console.error(err)); + + }).catch((err) => console.error(err)); + }; +} + +window.onChange = onChange; + +let uploadFile = (file, hex, key, encrypted) => { + let dataView = new DataView(encrypted); + let blob = new Blob([dataView], { type: file.type }); + + let fd = new FormData(); + fd.append("fname", file.name); + fd.append("data", blob, file.name); + + let xhr = new XMLHttpRequest(); + xhr.open("post", "/upload/" + hex, true); + + let listelem = setupUI(); + listelem.emit('name', file.name); + xhr.upload.addEventListener("progress", updateProgress.bind(null, listelem)); + + xhr.onreadystatechange = () => { + if (xhr.readyState == XMLHttpRequest.DONE) { + window.crypto.subtle.exportKey("jwk", key).then((keydata) => { + localStorage.setItem(hex, xhr.responseText); + + listelem.emit('link', "http://localhost:3000/download/" + hex + "/#" + keydata.k) + + console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); + }) + } + }; + + xhr.send(fd); +} + +let updateProgress = (UIelem, e) => { + if (e.lengthComputable) { + let percentComplete = Math.floor((e.loaded / e.total) * 100); + UIelem.emit('progress', "Progress: " + percentComplete + "%") + + if (percentComplete === 100) { + let btn = document.createElement("button"); + btn.innerText = "Delete from server"; + btn.addEventListener("click", () => { + let segments = UIelem.link.innerText.split("/"); + let key = segments[segments.length - 2]; + + let xhr = new XMLHttpRequest(); + xhr.open("post", "/delete/" + key, true); + xhr.setRequestHeader("Content-Type", "application/json"); + if (!localStorage.getItem(key)) return; + + xhr.send(JSON.stringify({delete_token: localStorage.getItem(key)})); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + document.getElementById("uploaded_files").removeChild(UIelem.li); + localStorage.removeItem(key); + } + + if (xhr.status === 200) { + console.log("The file was successfully deleted."); + } else { + console.log("The file has expired, or has already been deleted."); + } + } + + }); + UIelem.li.appendChild(btn); + } + } +} + +let setupUI = () => { + let li = document.createElement("li"); + let name = document.createElement("p"); + li.appendChild(name); + + let link = document.createElement("a"); + li.appendChild(link); + + let progress = document.createElement("p"); + li.appendChild(progress); + + document.getElementById("uploaded_files").appendChild(li); + + return new UIWrapper(li, name, link, progress); +} + +let ivToStr = (iv) => { + let hexStr = ""; + for (let i in iv) { + if (iv[i] < 16) { + hexStr += "0" + iv[i].toString(16); + } else { + hexStr += iv[i].toString(16); + } + } + window.hexStr = hexStr; + return hexStr; +} + +let strToIv = (str) => { + let iv = new Uint8Array(16); + for (let i = 0; i < str.length; i += 2) { + iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + } + + return iv; +} \ No newline at end of file diff --git a/package.json b/package.json index 1a2c0418..0f96ffd1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "format": "prettier --single-quote --write 'public/*.js' 'app.js'", "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server/portal_server.js" + "start": "watchify frontend/src/main.js -o public/bundle.js -d | node server/portal_server.js" }, "author": "", "license": "ISC", @@ -17,5 +17,9 @@ "path": "^0.12.7", "prettier": "^1.3.1", "redis": "^2.7.1" + }, + "devDependencies": { + "browserify": "^14.4.0", + "watchify": "^3.9.0" } } diff --git a/public/bundle.js b/public/bundle.js new file mode 100644 index 00000000..9a01e7fd --- /dev/null +++ b/public/bundle.js @@ -0,0 +1,614 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { + + let xhr = new XMLHttpRequest(); + xhr.open("get", "/assets" + location.pathname.slice(0, -1), true); + xhr.responseType = "blob"; + + let listelem = setupUI(); + xhr.addEventListener("progress", updateProgress.bind(null, listelem)); + + xhr.onload = function(e) { + + // maybe send a separate request before this one to get the filename? + + // maybe render the html itself with the filename, since it's generated server side + // after a get request with the unique id + listelem.emit('name', xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]); + + if (this.status == 200) { + let self = this; + let blob = new Blob([this.response]); + let arrayBuffer; + let fileReader = new FileReader(); + fileReader.onload = function() { + arrayBuffer = this.result; + let array = new Uint8Array(arrayBuffer); + salt = strToIv(location.pathname.slice(10, -1)); + + window.crypto.subtle.importKey( + "jwk", + { + kty: "oct", + k: location.hash.slice(1), + alg: "A128CBC", + ext: true, + }, + { + name: "AES-CBC", + }, + true, + ["encrypt", "decrypt"]) + .then((key) => { + return window.crypto.subtle.decrypt( + { + name: "AES-CBC", + iv: salt, + }, + key, + array) + }) + .then((decrypted) => { + let dataView = new DataView(decrypted); + let blob = new Blob([dataView]); + let downloadUrl = URL.createObjectURL(blob); + let a = document.createElement("a"); + a.href = downloadUrl; + a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]; + document.body.appendChild(a); + a.click(); + }) + .catch((err) => { + alert("This link is either invalid or has expired, or the uploader has deleted the file."); + console.error(err); + }); + }; + + fileReader.readAsArrayBuffer(blob); + } else { + alert("This link is either invalid or has expired, or the uploader has deleted the file.") + } + }; + xhr.send(); +} + +window.download = download; + +let setupUI = () => { + let li = document.createElement("li"); + let name = document.createElement("p"); + li.appendChild(name); + + let progress = document.createElement("p"); + li.appendChild(progress); + + document.getElementById("downloaded_files").appendChild(li); + + return new UIWrapper(li, name, null, progress); +} + +let ivToStr = (iv) => { + let hexStr = ""; + for (let i in iv) { + if (iv[i] < 16) { + hexStr += "0" + iv[i].toString(16); + } else { + hexStr += iv[i].toString(16); + } + } + window.hexStr = hexStr; + return hexStr; +} + +let strToIv = (str) => { + let iv = new Uint8Array(16); + for (let i = 0; i < str.length; i += 2) { + iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + } + + return iv; +} + +let updateProgress = (UIelem, e) => { + if (e.lengthComputable) { + let percentComplete = Math.floor((e.loaded / e.total) * 100); + UIelem.emit('progress', "Progress: " + percentComplete + "%"); + + if (percentComplete === 100) { + let finished = document.createElement("p"); + finished.innerText = "Your download has finished."; + UIelem.li.appendChild(finished); + + let close = document.createElement("button"); + close.innerText = "Ok"; + close.addEventListener("click", () => { + document.getElementById("downloaded_files").removeChild(UIelem.li); + }); + + UIelem.li.appendChild(close); + } + } +} +},{"./file":2}],2:[function(require,module,exports){ +const EventEmitter = require('events'); + +class UIWrapper extends EventEmitter { + constructor(li, name, link, progress) { + super(); + this.li = li; + this.name = name; + this.link = link; + this.progress = progress; + + this.on("name", (filename) => { + this.name.innerText = filename; + }); + + this.on("link", (link) => { + this.link.innerText = link; + this.link.setAttribute('href', link); + }); + + this.on("progress", (progress) => { + this.progress.innerText = progress; + + }); + } +} + +exports.UIWrapper = UIWrapper; +},{"events":5}],3:[function(require,module,exports){ +require('./upload'); +require('./download'); +require('./file'); +},{"./download":1,"./file":2,"./upload":4}],4:[function(require,module,exports){ +const EventEmitter = require('events'); +const UIWrapper = require('./file').UIWrapper; + +let onChange = (event) => { + let file = event.target.files[0]; + let reader = new FileReader(); + reader.readAsArrayBuffer(file); + + let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); + let hex = ivToStr(random_iv); + + reader.onload = function(event) { + let self = this; + window.crypto.subtle.generateKey({ + name: "AES-CBC", + length: 128 + }, + true, + ["encrypt", "decrypt"]) + .then((key) => { + let arrayBuffer = self.result; + let array = new Uint8Array(arrayBuffer); + + window.crypto.subtle.encrypt({ + name: "AES-CBC", + iv: random_iv }, + key, + array) + .then(uploadFile.bind(null, file, hex, key)) + .catch((err) => console.error(err)); + + }).catch((err) => console.error(err)); + }; +} + +window.onChange = onChange; + +let uploadFile = (file, hex, key, encrypted) => { + let dataView = new DataView(encrypted); + let blob = new Blob([dataView], { type: file.type }); + + let fd = new FormData(); + fd.append("fname", file.name); + fd.append("data", blob, file.name); + + let xhr = new XMLHttpRequest(); + xhr.open("post", "/upload/" + hex, true); + + let listelem = setupUI(); + listelem.emit('name', file.name); + xhr.upload.addEventListener("progress", updateProgress.bind(null, listelem)); + + xhr.onreadystatechange = () => { + if (xhr.readyState == XMLHttpRequest.DONE) { + window.crypto.subtle.exportKey("jwk", key).then((keydata) => { + localStorage.setItem(hex, xhr.responseText); + + listelem.emit('link', "http://localhost:3000/download/" + hex + "/#" + keydata.k) + + console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); + }) + } + }; + + xhr.send(fd); +} + +let updateProgress = (UIelem, e) => { + if (e.lengthComputable) { + let percentComplete = Math.floor((e.loaded / e.total) * 100); + UIelem.emit('progress', "Progress: " + percentComplete + "%") + + if (percentComplete === 100) { + let btn = document.createElement("button"); + btn.innerText = "Delete from server"; + btn.addEventListener("click", () => { + let segments = UIelem.link.innerText.split("/"); + let key = segments[segments.length - 2]; + + let xhr = new XMLHttpRequest(); + xhr.open("post", "/delete/" + key, true); + xhr.setRequestHeader("Content-Type", "application/json"); + if (!localStorage.getItem(key)) return; + + xhr.send(JSON.stringify({delete_token: localStorage.getItem(key)})); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + document.getElementById("uploaded_files").removeChild(UIelem.li); + localStorage.removeItem(key); + } + + if (xhr.status === 200) { + console.log("The file was successfully deleted."); + } else { + console.log("The file has expired, or has already been deleted."); + } + } + + }); + UIelem.li.appendChild(btn); + } + } +} + +let setupUI = () => { + let li = document.createElement("li"); + let name = document.createElement("p"); + li.appendChild(name); + + let link = document.createElement("a"); + li.appendChild(link); + + let progress = document.createElement("p"); + li.appendChild(progress); + + document.getElementById("uploaded_files").appendChild(li); + + return new UIWrapper(li, name, link, progress); +} + +let ivToStr = (iv) => { + let hexStr = ""; + for (let i in iv) { + if (iv[i] < 16) { + hexStr += "0" + iv[i].toString(16); + } else { + hexStr += iv[i].toString(16); + } + } + window.hexStr = hexStr; + return hexStr; +} + +let strToIv = (str) => { + let iv = new Uint8Array(16); + for (let i = 0; i < str.length; i += 2) { + iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + } + + return iv; +} +},{"./file":2,"events":5}],5:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); + err.context = er; + throw err; + } + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.prototype.listenerCount = function(type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) + return 1; + else if (evlistener) + return evlistener.length; + } + return 0; +}; + +EventEmitter.listenerCount = function(emitter, type) { + return emitter.listenerCount(type); +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}]},{},[3]) +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJmcm9udGVuZC9zcmMvZG93bmxvYWQuanMiLCJmcm9udGVuZC9zcmMvZmlsZS5qcyIsImZyb250ZW5kL3NyYy9tYWluLmpzIiwiZnJvbnRlbmQvc3JjL3VwbG9hZC5qcyIsIm5vZGVfbW9kdWxlcy9ldmVudHMvZXZlbnRzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25JQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTs7QUNGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsImNvbnN0IFVJV3JhcHBlciA9IHJlcXVpcmUoJy4vZmlsZScpLlVJV3JhcHBlcjtcblxubGV0IGRvd25sb2FkID0gKCkgPT4ge1xuXG4gIGxldCB4aHIgPSBuZXcgWE1MSHR0cFJlcXVlc3QoKTtcbiAgeGhyLm9wZW4oXCJnZXRcIiwgXCIvYXNzZXRzXCIgKyBsb2NhdGlvbi5wYXRobmFtZS5zbGljZSgwLCAtMSksIHRydWUpO1xuICB4aHIucmVzcG9uc2VUeXBlID0gXCJibG9iXCI7XG4gIFxuICBsZXQgbGlzdGVsZW0gPSBzZXR1cFVJKCk7XG4gIHhoci5hZGRFdmVudExpc3RlbmVyKFwicHJvZ3Jlc3NcIiwgdXBkYXRlUHJvZ3Jlc3MuYmluZChudWxsLCBsaXN0ZWxlbSkpO1xuICBcbiAgeGhyLm9ubG9hZCA9IGZ1bmN0aW9uKGUpIHtcblxuICAgIC8vIG1heWJlIHNlbmQgYSBzZXBhcmF0ZSByZXF1ZXN0IGJlZm9yZSB0aGlzIG9uZSB0byBnZXQgdGhlIGZpbGVuYW1lP1xuXG4gICAgLy8gbWF5YmUgcmVuZGVyIHRoZSBodG1sIGl0c2VsZiB3aXRoIHRoZSBmaWxlbmFtZSwgc2luY2UgaXQncyBnZW5lcmF0ZWQgc2VydmVyIHNpZGVcbiAgICAvLyBhZnRlciBhIGdldCByZXF1ZXN0IHdpdGggdGhlIHVuaXF1ZSBpZFxuICAgIGxpc3RlbGVtLmVtaXQoJ25hbWUnLCB4aHIuZ2V0UmVzcG9uc2VIZWFkZXIoXCJDb250ZW50LURpc3Bvc2l0aW9uXCIpLm1hdGNoKC9maWxlbmFtZT1cIiguKylcIi8pWzFdKTtcblxuICAgIGlmICh0aGlzLnN0YXR1cyA9PSAyMDApIHtcbiAgICAgIGxldCBzZWxmID0gdGhpcztcbiAgICAgIGxldCBibG9iID0gbmV3IEJsb2IoW3RoaXMucmVzcG9uc2VdKTtcbiAgICAgIGxldCBhcnJheUJ1ZmZlcjtcbiAgICAgIGxldCBmaWxlUmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTtcbiAgICAgIGZpbGVSZWFkZXIub25sb2FkID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIGFycmF5QnVmZmVyID0gdGhpcy5yZXN1bHQ7XG4gICAgICAgIGxldCBhcnJheSA9IG5ldyBVaW50OEFycmF5KGFycmF5QnVmZmVyKTtcbiAgICAgICAgc2FsdCA9IHN0clRvSXYobG9jYXRpb24ucGF0aG5hbWUuc2xpY2UoMTAsIC0xKSk7XG5cbiAgICAgICAgd2luZG93LmNyeXB0by5zdWJ0bGUuaW1wb3J0S2V5KFxuICAgICAgICAgIFwiandrXCIsXG4gICAgICAgICAge1xuICAgICAgICAgICAgICBrdHk6IFwib2N0XCIsXG4gICAgICAgICAgICAgIGs6IGxvY2F0aW9uLmhhc2guc2xpY2UoMSksXG4gICAgICAgICAgICAgIGFsZzogXCJBMTI4Q0JDXCIsXG4gICAgICAgICAgICAgIGV4dDogdHJ1ZSxcbiAgICAgICAgICB9LFxuICAgICAgICAgIHtcbiAgICAgICAgICAgICAgbmFtZTogXCJBRVMtQ0JDXCIsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0cnVlLFxuICAgICAgICAgIFtcImVuY3J5cHRcIiwgXCJkZWNyeXB0XCJdKVxuICAgICAgICAudGhlbigoa2V5KSA9PiB7ICBcbiAgICAgICAgICByZXR1cm4gd2luZG93LmNyeXB0by5zdWJ0bGUuZGVjcnlwdChcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIG5hbWU6IFwiQUVTLUNCQ1wiLFxuICAgICAgICAgICAgICAgIGl2OiBzYWx0LFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICBrZXksXG4gICAgICAgICAgICAgIGFycmF5KVxuICAgICAgICB9KVxuICAgICAgICAudGhlbigoZGVjcnlwdGVkKSA9PiB7XG4gICAgICAgICAgbGV0IGRhdGFWaWV3ID0gbmV3IERhdGFWaWV3KGRlY3J5cHRlZCk7XG4gICAgICAgICAgbGV0IGJsb2IgPSBuZXcgQmxvYihbZGF0YVZpZXddKTtcbiAgICAgICAgICBsZXQgZG93bmxvYWRVcmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuICAgICAgICAgIGxldCBhID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImFcIik7XG4gICAgICAgICAgYS5ocmVmID0gZG93bmxvYWRVcmw7XG4gICAgICAgICAgYS5kb3dubG9hZCA9IHhoci5nZXRSZXNwb25zZUhlYWRlcihcIkNvbnRlbnQtRGlzcG9zaXRpb25cIikubWF0Y2goL2ZpbGVuYW1lPVwiKC4rKVwiLylbMV07XG4gICAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChhKTtcbiAgICAgICAgICBhLmNsaWNrKCk7XG4gICAgICAgIH0pXG4gICAgICAgIC5jYXRjaCgoZXJyKSA9PiB7XG4gICAgICAgICAgYWxlcnQoXCJUaGlzIGxpbmsgaXMgZWl0aGVyIGludmFsaWQgb3IgaGFzIGV4cGlyZWQsIG9yIHRoZSB1cGxvYWRlciBoYXMgZGVsZXRlZCB0aGUgZmlsZS5cIik7XG4gICAgICAgICAgY29uc29sZS5lcnJvcihlcnIpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG5cbiAgICAgIGZpbGVSZWFkZXIucmVhZEFzQXJyYXlCdWZmZXIoYmxvYik7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFsZXJ0KFwiVGhpcyBsaW5rIGlzIGVpdGhlciBpbnZhbGlkIG9yIGhhcyBleHBpcmVkLCBvciB0aGUgdXBsb2FkZXIgaGFzIGRlbGV0ZWQgdGhlIGZpbGUuXCIpXG4gICAgfVxuICB9O1xuICB4aHIuc2VuZCgpO1xufVxuXG53aW5kb3cuZG93bmxvYWQgPSBkb3dubG9hZDtcblxubGV0IHNldHVwVUkgPSAoKSA9PiB7XG4gIGxldCBsaSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJsaVwiKTtcbiAgbGV0IG5hbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwicFwiKTtcbiAgbGkuYXBwZW5kQ2hpbGQobmFtZSk7XG5cbiAgbGV0IHByb2dyZXNzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInBcIik7XG4gIGxpLmFwcGVuZENoaWxkKHByb2dyZXNzKTtcblxuICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcImRvd25sb2FkZWRfZmlsZXNcIikuYXBwZW5kQ2hpbGQobGkpO1xuXG4gIHJldHVybiBuZXcgVUlXcmFwcGVyKGxpLCBuYW1lLCBudWxsLCBwcm9ncmVzcyk7XG59XG5cbmxldCBpdlRvU3RyID0gKGl2KSA9PiB7XG4gIGxldCBoZXhTdHIgPSBcIlwiO1xuICBmb3IgKGxldCBpIGluIGl2KSB7XG4gICAgaWYgKGl2W2ldIDwgMTYpIHtcbiAgICAgIGhleFN0ciArPSBcIjBcIiArIGl2W2ldLnRvU3RyaW5nKDE2KTtcbiAgICB9IGVsc2Uge1xuICAgICAgaGV4U3RyICs9IGl2W2ldLnRvU3RyaW5nKDE2KTtcbiAgICB9XG4gIH1cbiAgd2luZG93LmhleFN0ciA9IGhleFN0cjtcbiAgcmV0dXJuIGhleFN0cjtcbn1cblxubGV0IHN0clRvSXYgPSAoc3RyKSA9PiB7XG4gIGxldCBpdiA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBzdHIubGVuZ3RoOyBpICs9IDIpIHtcbiAgICBpdltpLzJdID0gcGFyc2VJbnQoKHN0ci5jaGFyQXQoaSkgKyBzdHIuY2hhckF0KGkgKyAxKSksIDE2KTtcbiAgfVxuXG4gIHJldHVybiBpdjtcbn1cblxubGV0IHVwZGF0ZVByb2dyZXNzID0gKFVJZWxlbSwgZSkgPT4ge1xuICBpZiAoZS5sZW5ndGhDb21wdXRhYmxlKSB7IFxuICAgIGxldCBwZXJjZW50Q29tcGxldGUgPSBNYXRoLmZsb29yKChlLmxvYWRlZCAvIGUudG90YWwpICogMTAwKTtcbiAgICBVSWVsZW0uZW1pdCgncHJvZ3Jlc3MnLCBcIlByb2dyZXNzOiBcIiArIHBlcmNlbnRDb21wbGV0ZSArIFwiJVwiKTtcblxuICAgIGlmIChwZXJjZW50Q29tcGxldGUgPT09IDEwMCkge1xuICAgICAgbGV0IGZpbmlzaGVkID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInBcIik7XG4gICAgICBmaW5pc2hlZC5pbm5lclRleHQgPSBcIllvdXIgZG93bmxvYWQgaGFzIGZpbmlzaGVkLlwiO1xuICAgICAgVUllbGVtLmxpLmFwcGVuZENoaWxkKGZpbmlzaGVkKTtcblxuICAgICAgbGV0IGNsb3NlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImJ1dHRvblwiKTtcbiAgICAgIGNsb3NlLmlubmVyVGV4dCA9IFwiT2tcIjtcbiAgICAgIGNsb3NlLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCAoKSA9PiB7XG4gICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwiZG93bmxvYWRlZF9maWxlc1wiKS5yZW1vdmVDaGlsZChVSWVsZW0ubGkpO1xuICAgICAgfSk7XG5cbiAgICAgIFVJZWxlbS5saS5hcHBlbmRDaGlsZChjbG9zZSk7XG4gICAgfVxuICB9IFxufSIsImNvbnN0IEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoJ2V2ZW50cycpO1xuXG5jbGFzcyBVSVdyYXBwZXIgZXh0ZW5kcyBFdmVudEVtaXR0ZXIge1xuICBjb25zdHJ1Y3RvcihsaSwgbmFtZSwgbGluaywgcHJvZ3Jlc3MpIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMubGkgPSBsaTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMubGluayA9IGxpbms7XG4gICAgdGhpcy5wcm9ncmVzcyA9IHByb2dyZXNzO1xuXG4gICAgdGhpcy5vbihcIm5hbWVcIiwgKGZpbGVuYW1lKSA9PiB7XG4gICAgICB0aGlzLm5hbWUuaW5uZXJUZXh0ID0gZmlsZW5hbWU7XG4gICAgfSk7XG5cbiAgICB0aGlzLm9uKFwibGlua1wiLCAobGluaykgPT4ge1xuICAgICAgdGhpcy5saW5rLmlubmVyVGV4dCA9IGxpbms7XG4gICAgICB0aGlzLmxpbmsuc2V0QXR0cmlidXRlKCdocmVmJywgbGluayk7XG4gICAgfSk7XG5cbiAgICB0aGlzLm9uKFwicHJvZ3Jlc3NcIiwgKHByb2dyZXNzKSA9PiB7XG4gICAgICB0aGlzLnByb2dyZXNzLmlubmVyVGV4dCA9IHByb2dyZXNzO1xuICAgICAgXG4gICAgfSk7XG4gIH1cbn1cblxuZXhwb3J0cy5VSVdyYXBwZXIgPSBVSVdyYXBwZXI7IiwicmVxdWlyZSgnLi91cGxvYWQnKTtcbnJlcXVpcmUoJy4vZG93bmxvYWQnKTtcbnJlcXVpcmUoJy4vZmlsZScpOyIsImNvbnN0IEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoJ2V2ZW50cycpO1xuY29uc3QgVUlXcmFwcGVyID0gcmVxdWlyZSgnLi9maWxlJykuVUlXcmFwcGVyO1xuXG5sZXQgb25DaGFuZ2UgPSAoZXZlbnQpID0+IHtcbiAgbGV0IGZpbGUgPSBldmVudC50YXJnZXQuZmlsZXNbMF07XG4gIGxldCByZWFkZXIgPSBuZXcgRmlsZVJlYWRlcigpO1xuICByZWFkZXIucmVhZEFzQXJyYXlCdWZmZXIoZmlsZSk7XG5cbiAgbGV0IHJhbmRvbV9pdiA9IHdpbmRvdy5jcnlwdG8uZ2V0UmFuZG9tVmFsdWVzKG5ldyBVaW50OEFycmF5KDE2KSk7XG4gIGxldCBoZXggPSBpdlRvU3RyKHJhbmRvbV9pdik7XG5cbiAgcmVhZGVyLm9ubG9hZCA9IGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgbGV0IHNlbGYgPSB0aGlzO1xuICAgIHdpbmRvdy5jcnlwdG8uc3VidGxlLmdlbmVyYXRlS2V5KHtcbiAgICAgIG5hbWU6IFwiQUVTLUNCQ1wiLFxuICAgICAgbGVuZ3RoOiAxMjggXG4gICAgfSxcbiAgICB0cnVlLFxuICAgIFtcImVuY3J5cHRcIiwgXCJkZWNyeXB0XCJdKVxuICAgIC50aGVuKChrZXkpID0+IHtcbiAgICAgIGxldCBhcnJheUJ1ZmZlciA9IHNlbGYucmVzdWx0O1xuICAgICAgbGV0IGFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlCdWZmZXIpO1xuXG4gICAgICB3aW5kb3cuY3J5cHRvLnN1YnRsZS5lbmNyeXB0KHtcbiAgICAgICAgbmFtZTogXCJBRVMtQ0JDXCIsXG4gICAgICAgIGl2OiByYW5kb21faXYgfSxcbiAgICAgICAga2V5LFxuICAgICAgICBhcnJheSlcbiAgICAgIC50aGVuKHVwbG9hZEZpbGUuYmluZChudWxsLCBmaWxlLCBoZXgsIGtleSkpXG4gICAgICAuY2F0Y2goKGVycikgPT4gY29uc29sZS5lcnJvcihlcnIpKTtcblxuICAgIH0pLmNhdGNoKChlcnIpID0+IGNvbnNvbGUuZXJyb3IoZXJyKSk7ICAgXG4gIH07XG59XG5cbndpbmRvdy5vbkNoYW5nZSA9IG9uQ2hhbmdlO1xuXG5sZXQgdXBsb2FkRmlsZSA9IChmaWxlLCBoZXgsIGtleSwgZW5jcnlwdGVkKSA9PiB7XG4gIGxldCBkYXRhVmlldyA9IG5ldyBEYXRhVmlldyhlbmNyeXB0ZWQpO1xuICBsZXQgYmxvYiA9IG5ldyBCbG9iKFtkYXRhVmlld10sIHsgdHlwZTogZmlsZS50eXBlIH0pO1xuICBcbiAgbGV0IGZkID0gbmV3IEZvcm1EYXRhKCk7XG4gIGZkLmFwcGVuZChcImZuYW1lXCIsIGZpbGUubmFtZSk7XG4gIGZkLmFwcGVuZChcImRhdGFcIiwgYmxvYiwgZmlsZS5uYW1lKTtcblxuICBsZXQgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7XG4gIHhoci5vcGVuKFwicG9zdFwiLCBcIi91cGxvYWQvXCIgKyBoZXgsIHRydWUpO1xuXG4gIGxldCBsaXN0ZWxlbSA9IHNldHVwVUkoKTtcbiAgbGlzdGVsZW0uZW1pdCgnbmFtZScsIGZpbGUubmFtZSk7XG4gIHhoci51cGxvYWQuYWRkRXZlbnRMaXN0ZW5lcihcInByb2dyZXNzXCIsIHVwZGF0ZVByb2dyZXNzLmJpbmQobnVsbCwgbGlzdGVsZW0pKTtcblxuICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gKCkgPT4geyBcbiAgICBpZiAoeGhyLnJlYWR5U3RhdGUgPT0gWE1MSHR0cFJlcXVlc3QuRE9ORSkge1xuICAgICAgd2luZG93LmNyeXB0by5zdWJ0bGUuZXhwb3J0S2V5KFwiandrXCIsIGtleSkudGhlbigoa2V5ZGF0YSkgPT4ge1xuICAgICAgICBsb2NhbFN0b3JhZ2Uuc2V0SXRlbShoZXgsIHhoci5yZXNwb25zZVRleHQpO1xuXG4gICAgICAgIGxpc3RlbGVtLmVtaXQoJ2xpbmsnLCBcImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9kb3dubG9hZC9cIiArIGhleCArIFwiLyNcIiArIGtleWRhdGEuaylcblxuICAgICAgICBjb25zb2xlLmxvZyhcIlNoYXJlIHRoaXMgbGluayB3aXRoIGEgZnJpZW5kOiBodHRwOi8vbG9jYWxob3N0OjMwMDAvZG93bmxvYWQvXCIgKyBoZXggKyBcIi8jXCIgKyBrZXlkYXRhLmspO1xuICAgICAgfSlcbiAgICB9XG4gIH07XG5cbiAgeGhyLnNlbmQoZmQpO1xufVxuXG5sZXQgdXBkYXRlUHJvZ3Jlc3MgPSAoVUllbGVtLCBlKSA9PiB7XG4gIGlmIChlLmxlbmd0aENvbXB1dGFibGUpIHtcbiAgICBsZXQgcGVyY2VudENvbXBsZXRlID0gTWF0aC5mbG9vcigoZS5sb2FkZWQgLyBlLnRvdGFsKSAqIDEwMCk7XG4gICAgVUllbGVtLmVtaXQoJ3Byb2dyZXNzJywgXCJQcm9ncmVzczogXCIgKyBwZXJjZW50Q29tcGxldGUgKyBcIiVcIilcblxuICAgIGlmIChwZXJjZW50Q29tcGxldGUgPT09IDEwMCkge1xuICAgICAgbGV0IGJ0biA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJidXR0b25cIik7XG4gICAgICBidG4uaW5uZXJUZXh0ID0gXCJEZWxldGUgZnJvbSBzZXJ2ZXJcIjtcbiAgICAgIGJ0bi5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgKCkgPT4ge1xuICAgICAgICBsZXQgc2VnbWVudHMgPSBVSWVsZW0ubGluay5pbm5lclRleHQuc3BsaXQoXCIvXCIpO1xuICAgICAgICBsZXQga2V5ID0gc2VnbWVudHNbc2VnbWVudHMubGVuZ3RoIC0gMl07XG5cbiAgICAgICAgbGV0IHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO1xuICAgICAgICB4aHIub3BlbihcInBvc3RcIiwgXCIvZGVsZXRlL1wiICsga2V5LCB0cnVlKTtcbiAgICAgICAgeGhyLnNldFJlcXVlc3RIZWFkZXIoXCJDb250ZW50LVR5cGVcIiwgXCJhcHBsaWNhdGlvbi9qc29uXCIpO1xuICAgICAgICBpZiAoIWxvY2FsU3RvcmFnZS5nZXRJdGVtKGtleSkpIHJldHVybjtcblxuICAgICAgICB4aHIuc2VuZChKU09OLnN0cmluZ2lmeSh7ZGVsZXRlX3Rva2VuOiBsb2NhbFN0b3JhZ2UuZ2V0SXRlbShrZXkpfSkpO1xuXG4gICAgICAgIHhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSAoKSA9PiB7XG4gICAgICAgICAgaWYgKHhoci5yZWFkeVN0YXRlID09PSBYTUxIdHRwUmVxdWVzdC5ET05FKSB7XG4gICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcInVwbG9hZGVkX2ZpbGVzXCIpLnJlbW92ZUNoaWxkKFVJZWxlbS5saSk7XG4gICAgICAgICAgICBsb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShrZXkpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmICh4aHIuc3RhdHVzID09PSAyMDApIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiVGhlIGZpbGUgd2FzIHN1Y2Nlc3NmdWxseSBkZWxldGVkLlwiKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc29sZS5sb2coXCJUaGUgZmlsZSBoYXMgZXhwaXJlZCwgb3IgaGFzIGFscmVhZHkgYmVlbiBkZWxldGVkLlwiKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICB9KTtcbiAgICAgIFVJZWxlbS5saS5hcHBlbmRDaGlsZChidG4pO1xuICAgIH1cbiAgfVxufVxuXG5sZXQgc2V0dXBVSSA9ICgpID0+IHtcbiAgbGV0IGxpID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImxpXCIpO1xuICBsZXQgbmFtZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJwXCIpO1xuICBsaS5hcHBlbmRDaGlsZChuYW1lKTtcbiAgXG4gIGxldCBsaW5rID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImFcIik7XG4gIGxpLmFwcGVuZENoaWxkKGxpbmspO1xuXG4gIGxldCBwcm9ncmVzcyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJwXCIpO1xuICBsaS5hcHBlbmRDaGlsZChwcm9ncmVzcyk7XG5cbiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoXCJ1cGxvYWRlZF9maWxlc1wiKS5hcHBlbmRDaGlsZChsaSk7XG5cbiAgcmV0dXJuIG5ldyBVSVdyYXBwZXIobGksIG5hbWUsIGxpbmssIHByb2dyZXNzKTtcbn1cblxubGV0IGl2VG9TdHIgPSAoaXYpID0+IHtcbiAgbGV0IGhleFN0ciA9IFwiXCI7XG4gIGZvciAobGV0IGkgaW4gaXYpIHtcbiAgICBpZiAoaXZbaV0gPCAxNikge1xuICAgICAgaGV4U3RyICs9IFwiMFwiICsgaXZbaV0udG9TdHJpbmcoMTYpO1xuICAgIH0gZWxzZSB7XG4gICAgICBoZXhTdHIgKz0gaXZbaV0udG9TdHJpbmcoMTYpO1xuICAgIH1cbiAgfVxuICB3aW5kb3cuaGV4U3RyID0gaGV4U3RyO1xuICByZXR1cm4gaGV4U3RyO1xufVxuXG5sZXQgc3RyVG9JdiA9IChzdHIpID0+IHtcbiAgbGV0IGl2ID0gbmV3IFVpbnQ4QXJyYXkoMTYpO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHN0ci5sZW5ndGg7IGkgKz0gMikge1xuICAgIGl2W2kvMl0gPSBwYXJzZUludCgoc3RyLmNoYXJBdChpKSArIHN0ci5jaGFyQXQoaSArIDEpKSwgMTYpO1xuICB9XG5cbiAgcmV0dXJuIGl2O1xufSIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG5mdW5jdGlvbiBFdmVudEVtaXR0ZXIoKSB7XG4gIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gdGhpcy5fbWF4TGlzdGVuZXJzIHx8IHVuZGVmaW5lZDtcbn1cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyO1xuXG4vLyBCYWNrd2FyZHMtY29tcGF0IHdpdGggbm9kZSAwLjEwLnhcbkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX2V2ZW50cyA9IHVuZGVmaW5lZDtcbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDtcblxuLy8gQnkgZGVmYXVsdCBFdmVudEVtaXR0ZXJzIHdpbGwgcHJpbnQgYSB3YXJuaW5nIGlmIG1vcmUgdGhhbiAxMCBsaXN0ZW5lcnMgYXJlXG4vLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLlxuRXZlbnRFbWl0dGVyLmRlZmF1bHRNYXhMaXN0ZW5lcnMgPSAxMDtcblxuLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzXG4vLyB0aGF0IHRvIGJlIGluY3JlYXNlZC4gU2V0IHRvIHplcm8gZm9yIHVubGltaXRlZC5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuc2V0TWF4TGlzdGVuZXJzID0gZnVuY3Rpb24obikge1xuICBpZiAoIWlzTnVtYmVyKG4pIHx8IG4gPCAwIHx8IGlzTmFOKG4pKVxuICAgIHRocm93IFR5cGVFcnJvcignbiBtdXN0IGJlIGEgcG9zaXRpdmUgbnVtYmVyJyk7XG4gIHRoaXMuX21heExpc3RlbmVycyA9IG47XG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzKVxuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuXG4gIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy5cbiAgaWYgKHR5cGUgPT09ICdlcnJvcicpIHtcbiAgICBpZiAoIXRoaXMuX2V2ZW50cy5lcnJvciB8fFxuICAgICAgICAoaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkpIHtcbiAgICAgIGVyID0gYXJndW1lbnRzWzFdO1xuICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXI7IC8vIFVuaGFuZGxlZCAnZXJyb3InIGV2ZW50XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBBdCBsZWFzdCBnaXZlIHNvbWUga2luZCBvZiBjb250ZXh0IHRvIHRoZSB1c2VyXG4gICAgICAgIHZhciBlcnIgPSBuZXcgRXJyb3IoJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQuICgnICsgZXIgKyAnKScpO1xuICAgICAgICBlcnIuY29udGV4dCA9IGVyO1xuICAgICAgICB0aHJvdyBlcnI7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlciA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpXG4gICAgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGhhbmRsZXIpKSB7XG4gICAgc3dpdGNoIChhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgICAvLyBmYXN0IGNhc2VzXG4gICAgICBjYXNlIDE6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDI6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMzpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSwgYXJndW1lbnRzWzJdKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICAvLyBzbG93ZXJcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGFyZ3MgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpO1xuICAgICAgICBoYW5kbGVyLmFwcGx5KHRoaXMsIGFyZ3MpO1xuICAgIH1cbiAgfSBlbHNlIGlmIChpc09iamVjdChoYW5kbGVyKSkge1xuICAgIGFyZ3MgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpO1xuICAgIGxpc3RlbmVycyA9IGhhbmRsZXIuc2xpY2UoKTtcbiAgICBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoO1xuICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKylcbiAgICAgIGxpc3RlbmVyc1tpXS5hcHBseSh0aGlzLCBhcmdzKTtcbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBtO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcblxuICAvLyBUbyBhdm9pZCByZWN1cnNpb24gaW4gdGhlIGNhc2UgdGhhdCB0eXBlID09PSBcIm5ld0xpc3RlbmVyXCIhIEJlZm9yZVxuICAvLyBhZGRpbmcgaXQgdG8gdGhlIGxpc3RlbmVycywgZmlyc3QgZW1pdCBcIm5ld0xpc3RlbmVyXCIuXG4gIGlmICh0aGlzLl9ldmVudHMubmV3TGlzdGVuZXIpXG4gICAgdGhpcy5lbWl0KCduZXdMaXN0ZW5lcicsIHR5cGUsXG4gICAgICAgICAgICAgIGlzRnVuY3Rpb24obGlzdGVuZXIubGlzdGVuZXIpID9cbiAgICAgICAgICAgICAgbGlzdGVuZXIubGlzdGVuZXIgOiBsaXN0ZW5lcik7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgLy8gT3B0aW1pemUgdGhlIGNhc2Ugb2Ygb25lIGxpc3RlbmVyLiBEb24ndCBuZWVkIHRoZSBleHRyYSBhcnJheSBvYmplY3QuXG4gICAgdGhpcy5fZXZlbnRzW3R5cGVdID0gbGlzdGVuZXI7XG4gIGVsc2UgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgLy8gSWYgd2UndmUgYWxyZWFkeSBnb3QgYW4gYXJyYXksIGp1c3QgYXBwZW5kLlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5wdXNoKGxpc3RlbmVyKTtcbiAgZWxzZVxuICAgIC8vIEFkZGluZyB0aGUgc2Vjb25kIGVsZW1lbnQsIG5lZWQgdG8gY2hhbmdlIHRvIGFycmF5LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IFt0aGlzLl9ldmVudHNbdHlwZV0sIGxpc3RlbmVyXTtcblxuICAvLyBDaGVjayBmb3IgbGlzdGVuZXIgbGVha1xuICBpZiAoaXNPYmplY3QodGhpcy5fZXZlbnRzW3R5cGVdKSAmJiAhdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCkge1xuICAgIGlmICghaXNVbmRlZmluZWQodGhpcy5fbWF4TGlzdGVuZXJzKSkge1xuICAgICAgbSA9IHRoaXMuX21heExpc3RlbmVycztcbiAgICB9IGVsc2Uge1xuICAgICAgbSA9IEV2ZW50RW1pdHRlci5kZWZhdWx0TWF4TGlzdGVuZXJzO1xuICAgIH1cblxuICAgIGlmIChtICYmIG0gPiAwICYmIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGggPiBtKSB7XG4gICAgICB0aGlzLl9ldmVudHNbdHlwZV0ud2FybmVkID0gdHJ1ZTtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJyhub2RlKSB3YXJuaW5nOiBwb3NzaWJsZSBFdmVudEVtaXR0ZXIgbWVtb3J5ICcgK1xuICAgICAgICAgICAgICAgICAgICAnbGVhayBkZXRlY3RlZC4gJWQgbGlzdGVuZXJzIGFkZGVkLiAnICtcbiAgICAgICAgICAgICAgICAgICAgJ1VzZSBlbWl0dGVyLnNldE1heExpc3RlbmVycygpIHRvIGluY3JlYXNlIGxpbWl0LicsXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGgpO1xuICAgICAgaWYgKHR5cGVvZiBjb25zb2xlLnRyYWNlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIC8vIG5vdCBzdXBwb3J0ZWQgaW4gSUUgMTBcbiAgICAgICAgY29uc29sZS50cmFjZSgpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5vbiA9IEV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub25jZSA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICB2YXIgZmlyZWQgPSBmYWxzZTtcblxuICBmdW5jdGlvbiBnKCkge1xuICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgZyk7XG5cbiAgICBpZiAoIWZpcmVkKSB7XG4gICAgICBmaXJlZCA9IHRydWU7XG4gICAgICBsaXN0ZW5lci5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH1cbiAgfVxuXG4gIGcubGlzdGVuZXIgPSBsaXN0ZW5lcjtcbiAgdGhpcy5vbih0eXBlLCBnKTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbi8vIGVtaXRzIGEgJ3JlbW92ZUxpc3RlbmVyJyBldmVudCBpZmYgdGhlIGxpc3RlbmVyIHdhcyByZW1vdmVkXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUxpc3RlbmVyID0gZnVuY3Rpb24odHlwZSwgbGlzdGVuZXIpIHtcbiAgdmFyIGxpc3QsIHBvc2l0aW9uLCBsZW5ndGgsIGk7XG5cbiAgaWYgKCFpc0Z1bmN0aW9uKGxpc3RlbmVyKSlcbiAgICB0aHJvdyBUeXBlRXJyb3IoJ2xpc3RlbmVyIG11c3QgYmUgYSBmdW5jdGlvbicpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgbGlzdCA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgbGVuZ3RoID0gbGlzdC5sZW5ndGg7XG4gIHBvc2l0aW9uID0gLTE7XG5cbiAgaWYgKGxpc3QgPT09IGxpc3RlbmVyIHx8XG4gICAgICAoaXNGdW5jdGlvbihsaXN0Lmxpc3RlbmVyKSAmJiBsaXN0Lmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIGlmICh0aGlzLl9ldmVudHMucmVtb3ZlTGlzdGVuZXIpXG4gICAgICB0aGlzLmVtaXQoJ3JlbW92ZUxpc3RlbmVyJywgdHlwZSwgbGlzdGVuZXIpO1xuXG4gIH0gZWxzZSBpZiAoaXNPYmplY3QobGlzdCkpIHtcbiAgICBmb3IgKGkgPSBsZW5ndGg7IGktLSA+IDA7KSB7XG4gICAgICBpZiAobGlzdFtpXSA9PT0gbGlzdGVuZXIgfHxcbiAgICAgICAgICAobGlzdFtpXS5saXN0ZW5lciAmJiBsaXN0W2ldLmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICAgICAgcG9zaXRpb24gPSBpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAocG9zaXRpb24gPCAwKVxuICAgICAgcmV0dXJuIHRoaXM7XG5cbiAgICBpZiAobGlzdC5sZW5ndGggPT09IDEpIHtcbiAgICAgIGxpc3QubGVuZ3RoID0gMDtcbiAgICAgIGRlbGV0ZSB0aGlzLl9ldmVudHNbdHlwZV07XG4gICAgfSBlbHNlIHtcbiAgICAgIGxpc3Quc3BsaWNlKHBvc2l0aW9uLCAxKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVBbGxMaXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciBrZXksIGxpc3RlbmVycztcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICByZXR1cm4gdGhpcztcblxuICAvLyBub3QgbGlzdGVuaW5nIGZvciByZW1vdmVMaXN0ZW5lciwgbm8gbmVlZCB0byBlbWl0XG4gIGlmICghdGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKSB7XG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDApXG4gICAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICBlbHNlIGlmICh0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgLy8gZW1pdCByZW1vdmVMaXN0ZW5lciBmb3IgYWxsIGxpc3RlbmVycyBvbiBhbGwgZXZlbnRzXG4gIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKSB7XG4gICAgZm9yIChrZXkgaW4gdGhpcy5fZXZlbnRzKSB7XG4gICAgICBpZiAoa2V5ID09PSAncmVtb3ZlTGlzdGVuZXInKSBjb250aW51ZTtcbiAgICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKGtleSk7XG4gICAgfVxuICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKCdyZW1vdmVMaXN0ZW5lcicpO1xuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgbGlzdGVuZXJzID0gdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGxpc3RlbmVycykpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGxpc3RlbmVycyk7XG4gIH0gZWxzZSBpZiAobGlzdGVuZXJzKSB7XG4gICAgLy8gTElGTyBvcmRlclxuICAgIHdoaWxlIChsaXN0ZW5lcnMubGVuZ3RoKVxuICAgICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnNbbGlzdGVuZXJzLmxlbmd0aCAtIDFdKTtcbiAgfVxuICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciByZXQ7XG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0ID0gW107XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24odGhpcy5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSBbdGhpcy5fZXZlbnRzW3R5cGVdXTtcbiAgZWxzZVxuICAgIHJldCA9IHRoaXMuX2V2ZW50c1t0eXBlXS5zbGljZSgpO1xuICByZXR1cm4gcmV0O1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24odHlwZSkge1xuICBpZiAodGhpcy5fZXZlbnRzKSB7XG4gICAgdmFyIGV2bGlzdGVuZXIgPSB0aGlzLl9ldmVudHNbdHlwZV07XG5cbiAgICBpZiAoaXNGdW5jdGlvbihldmxpc3RlbmVyKSlcbiAgICAgIHJldHVybiAxO1xuICAgIGVsc2UgaWYgKGV2bGlzdGVuZXIpXG4gICAgICByZXR1cm4gZXZsaXN0ZW5lci5sZW5ndGg7XG4gIH1cbiAgcmV0dXJuIDA7XG59O1xuXG5FdmVudEVtaXR0ZXIubGlzdGVuZXJDb3VudCA9IGZ1bmN0aW9uKGVtaXR0ZXIsIHR5cGUpIHtcbiAgcmV0dXJuIGVtaXR0ZXIubGlzdGVuZXJDb3VudCh0eXBlKTtcbn07XG5cbmZ1bmN0aW9uIGlzRnVuY3Rpb24oYXJnKSB7XG4gIHJldHVybiB0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nO1xufVxuXG5mdW5jdGlvbiBpc051bWJlcihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInO1xufVxuXG5mdW5jdGlvbiBpc09iamVjdChhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdvYmplY3QnICYmIGFyZyAhPT0gbnVsbDtcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQoYXJnKSB7XG4gIHJldHVybiBhcmcgPT09IHZvaWQgMDtcbn1cbiJdfQ== diff --git a/public/download.html b/public/download.html index bf5686b0..3a0e6ac5 100644 --- a/public/download.html +++ b/public/download.html @@ -2,7 +2,7 @@ Download your file - + diff --git a/public/download.js b/public/download.js deleted file mode 100644 index 7ecc9a49..00000000 --- a/public/download.js +++ /dev/null @@ -1,135 +0,0 @@ -function download() { - var xhr = new XMLHttpRequest(); - xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); - xhr.responseType = 'blob'; - - var li = document.createElement('li'); - var progress = document.createElement('p'); - li.appendChild(progress); - document.getElementById('downloaded_files').appendChild(li); - - xhr.addEventListener('progress', returnBindedLI(li, progress)); - - xhr.onload = function(e) { - // maybe send a separate request before this one to get the filename? - - // maybe render the html itself with the filename, since it's generated server side - // after a get request with the unique id - var name = document.createElement('p'); - name.innerHTML = xhr - .getResponseHeader('Content-Disposition') - .match(/filename="(.+)"/)[1]; - li.insertBefore(name, li.firstChild); - - if (this.status == 200) { - let self = this; - var blob = new Blob([this.response]); - var arrayBuffer; - var fileReader = new FileReader(); - fileReader.onload = function() { - arrayBuffer = this.result; - var array = new Uint8Array(arrayBuffer); - salt = strToIv(location.pathname.slice(10, -1)); - - window.crypto.subtle - .importKey( - 'jwk', - { - kty: 'oct', - k: location.hash.slice(1), - alg: 'A128CBC', - ext: true - }, - { - name: 'AES-CBC' - }, - true, - ['encrypt', 'decrypt'] - ) - .then(function(key) { - window.crypto.subtle - .decrypt( - { - name: 'AES-CBC', - iv: salt - }, - key, - array - ) - .then(function(decrypted) { - var dataView = new DataView(decrypted); - var blob = new Blob([dataView]); - var downloadUrl = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = downloadUrl; - a.download = xhr - .getResponseHeader('Content-Disposition') - .match(/filename="(.+)"/)[1]; - document.body.appendChild(a); - a.click(); - }) - .catch(function(err) { - alert( - 'This link is either invalid or has expired, or the uploader has deleted the file.' - ); - console.error(err); - }); - }) - .catch(function(err) { - console.error(err); - }); - }; - fileReader.readAsArrayBuffer(blob); - } else { - alert( - 'This link is either invalid or has expired, or the uploader has deleted the file.' - ); - } - }; - xhr.send(); -} - -function ivToStr(iv) { - let hexStr = ''; - for (var i in iv) { - if (iv[i] < 16) { - hexStr += '0' + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -} - -function strToIv(str) { - var iv = new Uint8Array(16); - for (var i = 0; i < str.length; i += 2) { - iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); - } - - return iv; -} - -function returnBindedLI(li, progress) { - return function updateProgress(e) { - if (e.lengthComputable) { - var percentComplete = Math.floor(e.loaded / e.total * 100); - progress.innerHTML = 'Progress: ' + percentComplete + '%'; - } - - if (percentComplete === 100) { - var finished = document.createElement('p'); - finished.innerHTML = 'Your download has finished.'; - li.appendChild(finished); - - var close = document.createElement('button'); - close.innerHTML = 'Ok'; - close.addEventListener('click', function() { - document.getElementById('downloaded_files').removeChild(li); - }); - - li.appendChild(close); - } - }; -} diff --git a/public/file.js b/public/file.js deleted file mode 100644 index ce680be4..00000000 --- a/public/file.js +++ /dev/null @@ -1,10 +0,0 @@ -function ProgressEmitter(name, uuid, type) { - this.name = name; - this.uuid = uuid; - this.type = type; - this.link = null; - - this.emit = () => { - - }; -} \ No newline at end of file diff --git a/public/index.html b/public/index.html index aa77f317..e258c9f1 100644 --- a/public/index.html +++ b/public/index.html @@ -2,8 +2,8 @@ Firefox Fileshare - - + + @@ -13,6 +13,6 @@
- + diff --git a/public/upload.js b/public/upload.js deleted file mode 100644 index f213490f..00000000 --- a/public/upload.js +++ /dev/null @@ -1,164 +0,0 @@ -function onChange(event) { - - var file = event.target.files[0]; - var reader = new FileReader(); - reader.readAsArrayBuffer(file); - - let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); - var hex = ivToStr(random_iv); - - reader.onload = function(event) { - let self = this; - window.crypto.subtle.generateKey({ - name: "AES-CBC", - length: 128 - }, - true, - ["encrypt", "decrypt"]) - .then((key) => { - let arrayBuffer = self.result; - let array = new Uint8Array(arrayBuffer); - - window.crypto.subtle.encrypt({ - name: "AES-CBC", - iv: random_iv }, - key, - array) - .then(uploadFile.bind(null, file, hex, key)) - .catch((err) => console.error(err)); - - }).catch((err) => console.error(err)); - }; -} - -let uploadFile = (file, hex, key, encrypted) => { - var dataView = new DataView(encrypted); - var blob = new Blob([dataView], { type: file.type }); - - var fd = new FormData(); - fd.append("fname", file.name); - fd.append("data", blob, file.name); - - var xhr = new XMLHttpRequest(); - - xhr.open("post", "/upload/" + hex, true); - - let prog = new window.ProgressEmitter(file.name, hex, file.type); - - var li = document.createElement("li"); - var name = document.createElement("p"); - name.innerHTML = file.name; - li.appendChild(name); - - var link = document.createElement("a"); - li.appendChild(link); - - var progress = document.createElement("p"); - li.appendChild(progress); - - - document.getElementById("uploaded_files").appendChild(li); - - - xhr.upload.addEventListener("progress", returnBindedLI(progress, name, link, li)); - - xhr.onreadystatechange = function() { - if (xhr.readyState == XMLHttpRequest.DONE) { - window.crypto.subtle.exportKey("jwk", key).then(function(keydata) { - var curr_name = localStorage.getItem(file.name); - - localStorage.setItem(hex, xhr.responseText); - - prog.link = "http://localhost:3000/download/" + hex + "/#" + keydata.k; - prog.emit(); - - link.innerHTML = "http://localhost:3000/download/" + hex + "/#" + keydata.k; - link.setAttribute("href", "http://localhost:3000/download/" + hex + "/#" + keydata.k); - - console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); - }) - } - }; - - xhr.send(fd); - // setupLI(file.name); - -} - -function setupLI(emitter) { - var li = document.createElement("li"); - var name = document.createElement("p"); - name.innerHTML = emitter; - li.appendChild(name); - - var link = document.createElement("a"); - link.addEventListener('NotifyProgress', (progress_event) => { - console.log(progress_event); - }); - li.appendChild(link); - - var progress = document.createElement("p"); - li.appendChild(progress); - document.getElementById("uploaded_files").appendChild(li); -} - -function ivToStr(iv) { - let hexStr = ''; - for (var i in iv) { - if (iv[i] < 16) { - hexStr += '0' + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -} - -function strToIv(str) { - var iv = new Uint8Array(16); - for (var i = 0; i < str.length; i += 2) { - iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); - } - - return iv; -} - -function returnBindedLI(a_element, name, link, li) { - return function updateProgress(e) { - if (e.lengthComputable) { - var percentComplete = Math.floor(e.loaded / e.total * 100); - a_element.innerHTML = 'Progress: ' + percentComplete + '%'; - - if (percentComplete === 100) { - var btn = document.createElement('button'); - btn.innerHTML = 'Delete from server'; - btn.addEventListener('click', function() { - var segments = link.innerHTML.split('/'); - var key = segments[segments.length - 2]; - - var xhr = new XMLHttpRequest(); - xhr.open('post', '/delete/' + key, true); - xhr.setRequestHeader('Content-Type', 'application/json'); - if (!localStorage.getItem(key)) return; - - xhr.send(JSON.stringify({ delete_token: localStorage.getItem(key) })); - - xhr.onreadystatechange = function() { - if (xhr.readyState === XMLHttpRequest.DONE) { - document.getElementById('uploaded_files').removeChild(li); - localStorage.removeItem(key); - } - - if (xhr.status === 200) { - console.log('The file was successfully deleted.'); - } else { - console.log('The file has expired, or has already been deleted.'); - } - }; - }); - li.appendChild(btn); - } - } - }; -} diff --git a/server/portal_server.js b/server/portal_server.js index 4cc91688..df726ae4 100644 --- a/server/portal_server.js +++ b/server/portal_server.js @@ -1,4 +1,3 @@ - const express = require("express") const busboy = require("connect-busboy"); const path = require("path"); @@ -10,7 +9,7 @@ const app = express() const redis = require("redis"), client = redis.createClient(); -client.on("error", function(err) { +client.on("error", (err) => { console.log(err); }) @@ -18,12 +17,12 @@ app.use(busboy()); app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, "../public"))); -app.get("/download/:id", function(req, res) { +app.get("/download/:id", (req, res) => { res.sendFile(path.join(__dirname + "/../public/download.html")); }); -app.get("/assets/download/:id", function(req, res) { - +app.get("/assets/download/:id", (req, res) => { + let id = req.params.id; if (!validateID(id)){ res.send(404); @@ -31,14 +30,14 @@ app.get("/assets/download/:id", function(req, res) { } - client.hget(id, "filename", function(err, reply) { // maybe some expiration logic too + client.hget(id, "filename", (err, reply) => { // maybe some expiration logic too if (!reply) { res.sendStatus(404); } else { res.setHeader("Content-Disposition", "attachment; filename=" + reply); res.setHeader("Content-Type", "application/octet-stream"); - res.download(__dirname + "/../static/" + id, reply, function(err) { + res.download(__dirname + "/../static/" + id, reply, (err) => { if (!err) { client.del(id); fs.unlinkSync(__dirname + "/../static/" + id); @@ -49,7 +48,7 @@ app.get("/assets/download/:id", function(req, res) { }); -app.post("/delete/:id", function(req, res) { +app.post("/delete/:id", (req, res) => { let id = req.params.id; if (!validateID(id)){ @@ -63,7 +62,7 @@ app.post("/delete/:id", function(req, res) { res.sendStatus(404); } - client.hget(id, "delete", function(err, reply) { + client.hget(id, "delete", (err, reply) => { if (!reply) { res.sendStatus(404); } else { @@ -74,29 +73,29 @@ app.post("/delete/:id", function(req, res) { }) }); -app.post("/upload/:id", function (req, res, next) { +app.post("/upload/:id", (req, res, next) => { if (!validateID(req.params.id)){ res.send(404); return; } - var fstream; + let fstream; req.pipe(req.busboy); - req.busboy.on("file", function (fieldname, file, filename) { + req.busboy.on("file", (fieldname, file, filename) => { console.log("Uploading: " + filename); //Path where image will be uploaded fstream = fs.createWriteStream(__dirname + "/../static/" + req.params.id); file.pipe(fstream); - fstream.on("close", function () { + fstream.on("close", () => { let id = req.params.id; let uuid = crypto.randomBytes(10).toString('hex'); client.hmset([id, "filename", filename, "delete", uuid]); // delete the file off the server in 24 hours - // setTimeout(function() { + // setTimeout(() => { // fs.unlinkSync(__dirname + "/static/" + id); // }, 86400000); @@ -107,10 +106,10 @@ app.post("/upload/:id", function (req, res, next) { }); }); -app.listen(3000, function () { +app.listen(3000, () => { console.log("Portal app listening on port 3000!") }) -function validateID(route_id) { +let validateID = (route_id) => { return route_id.match(/^[0-9a-fA-F]{32}$/) !== null; } \ No newline at end of file From dd703b228a6d5e2e12dfd637bb178c1421d2dad0 Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Thu, 1 Jun 2017 15:12:30 -0700 Subject: [PATCH 3/6] changed file name, uncommitted bundle --- frontend/src/download.js | 144 +-- frontend/src/main.js | 2 +- frontend/src/{file.js => ui.js} | 9 +- frontend/src/upload.js | 136 ++- package-lock.json | 1840 +++++++++++++++++++++++++++++++ package.json | 2 +- public/bundle.js | 614 ----------- 7 files changed, 1998 insertions(+), 749 deletions(-) rename frontend/src/{file.js => ui.js} (74%) delete mode 100644 public/bundle.js diff --git a/frontend/src/download.js b/frontend/src/download.js index 43852d75..f5c4d74c 100644 --- a/frontend/src/download.js +++ b/frontend/src/download.js @@ -1,21 +1,22 @@ -const UIWrapper = require('./file').UIWrapper; +const UIWrapper = require('./ui').UIWrapper; let download = () => { - let xhr = new XMLHttpRequest(); - xhr.open("get", "/assets" + location.pathname.slice(0, -1), true); - xhr.responseType = "blob"; - - let listelem = setupUI(); - xhr.addEventListener("progress", updateProgress.bind(null, listelem)); - - xhr.onload = function(e) { + xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); + xhr.responseType = 'blob'; + let listelem = setupUI(); + xhr.addEventListener('progress', updateProgress.bind(null, listelem)); + + xhr.onload = function(e) { // maybe send a separate request before this one to get the filename? // maybe render the html itself with the filename, since it's generated server side // after a get request with the unique id - listelem.emit('name', xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]); + listelem.emit( + 'name', + xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] + ); if (this.status == 200) { let self = this; @@ -27,106 +28,115 @@ let download = () => { let array = new Uint8Array(arrayBuffer); salt = strToIv(location.pathname.slice(10, -1)); - window.crypto.subtle.importKey( - "jwk", - { - kty: "oct", + window.crypto.subtle + .importKey( + 'jwk', + { + kty: 'oct', k: location.hash.slice(1), - alg: "A128CBC", - ext: true, - }, - { - name: "AES-CBC", - }, - true, - ["encrypt", "decrypt"]) - .then((key) => { - return window.crypto.subtle.decrypt( + alg: 'A128CBC', + ext: true + }, + { + name: 'AES-CBC' + }, + true, + ['encrypt', 'decrypt'] + ) + .then(key => { + return window.crypto.subtle.decrypt( { - name: "AES-CBC", - iv: salt, + name: 'AES-CBC', + iv: salt }, key, - array) - }) - .then((decrypted) => { - let dataView = new DataView(decrypted); - let blob = new Blob([dataView]); - let downloadUrl = URL.createObjectURL(blob); - let a = document.createElement("a"); - a.href = downloadUrl; - a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]; - document.body.appendChild(a); - a.click(); - }) - .catch((err) => { - alert("This link is either invalid or has expired, or the uploader has deleted the file."); - console.error(err); - }); + array + ); + }) + .then(decrypted => { + let dataView = new DataView(decrypted); + let blob = new Blob([dataView]); + let downloadUrl = URL.createObjectURL(blob); + let a = document.createElement('a'); + a.href = downloadUrl; + a.download = xhr + .getResponseHeader('Content-Disposition') + .match(/filename="(.+)"/)[1]; + document.body.appendChild(a); + a.click(); + }) + .catch(err => { + alert( + 'This link is either invalid or has expired, or the uploader has deleted the file.' + ); + console.error(err); + }); }; fileReader.readAsArrayBuffer(blob); } else { - alert("This link is either invalid or has expired, or the uploader has deleted the file.") + alert( + 'This link is either invalid or has expired, or the uploader has deleted the file.' + ); } }; xhr.send(); -} +}; window.download = download; let setupUI = () => { - let li = document.createElement("li"); - let name = document.createElement("p"); + let li = document.createElement('li'); + let name = document.createElement('p'); li.appendChild(name); - let progress = document.createElement("p"); + let progress = document.createElement('p'); li.appendChild(progress); - document.getElementById("downloaded_files").appendChild(li); + document.getElementById('downloaded_files').appendChild(li); return new UIWrapper(li, name, null, progress); -} +}; -let ivToStr = (iv) => { - let hexStr = ""; +let ivToStr = iv => { + let hexStr = ''; for (let i in iv) { if (iv[i] < 16) { - hexStr += "0" + iv[i].toString(16); + hexStr += '0' + iv[i].toString(16); } else { hexStr += iv[i].toString(16); } } window.hexStr = hexStr; return hexStr; -} +}; -let strToIv = (str) => { +let strToIv = str => { let iv = new Uint8Array(16); for (let i = 0; i < str.length; i += 2) { - iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); } return iv; -} +}; let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor((e.loaded / e.total) * 100); - UIelem.emit('progress', "Progress: " + percentComplete + "%"); + if (e.lengthComputable) { + let percentComplete = Math.floor(e.loaded / e.total * 100); + UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); if (percentComplete === 100) { - let finished = document.createElement("p"); - finished.innerText = "Your download has finished."; + let finished = document.createElement('p'); + finished.innerText = 'Your download has finished.'; UIelem.li.appendChild(finished); - let close = document.createElement("button"); - close.innerText = "Ok"; - close.addEventListener("click", () => { - document.getElementById("downloaded_files").removeChild(UIelem.li); + let close = document.createElement('button'); + close.innerText = 'Ok'; + close.addEventListener('click', () => { + document.getElementById('downloaded_files').removeChild(UIelem.li); }); UIelem.li.appendChild(close); } - } -} \ No newline at end of file + } +}; diff --git a/frontend/src/main.js b/frontend/src/main.js index 23b00a19..5dec39be 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,3 +1,3 @@ require('./upload'); require('./download'); -require('./file'); \ No newline at end of file +require('./ui'); diff --git a/frontend/src/file.js b/frontend/src/ui.js similarity index 74% rename from frontend/src/file.js rename to frontend/src/ui.js index c581b1ca..0b0f0fb8 100644 --- a/frontend/src/file.js +++ b/frontend/src/ui.js @@ -8,20 +8,19 @@ class UIWrapper extends EventEmitter { this.link = link; this.progress = progress; - this.on("name", (filename) => { + this.on('name', filename => { this.name.innerText = filename; }); - this.on("link", (link) => { + this.on('link', link => { this.link.innerText = link; this.link.setAttribute('href', link); }); - this.on("progress", (progress) => { + this.on('progress', progress => { this.progress.innerText = progress; - }); } } -exports.UIWrapper = UIWrapper; \ No newline at end of file +exports.UIWrapper = UIWrapper; diff --git a/frontend/src/upload.js b/frontend/src/upload.js index df4d2eed..2cc529b4 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -1,7 +1,7 @@ const EventEmitter = require('events'); -const UIWrapper = require('./file').UIWrapper; +const UIWrapper = require('./ui').UIWrapper; -let onChange = (event) => { +let onChange = event => { let file = event.target.files[0]; let reader = new FileReader(); reader.readAsArrayBuffer(file); @@ -11,132 +11,146 @@ let onChange = (event) => { reader.onload = function(event) { let self = this; - window.crypto.subtle.generateKey({ - name: "AES-CBC", - length: 128 - }, - true, - ["encrypt", "decrypt"]) - .then((key) => { - let arrayBuffer = self.result; - let array = new Uint8Array(arrayBuffer); + window.crypto.subtle + .generateKey( + { + name: 'AES-CBC', + length: 128 + }, + true, + ['encrypt', 'decrypt'] + ) + .then(key => { + let arrayBuffer = self.result; + let array = new Uint8Array(arrayBuffer); - window.crypto.subtle.encrypt({ - name: "AES-CBC", - iv: random_iv }, - key, - array) - .then(uploadFile.bind(null, file, hex, key)) - .catch((err) => console.error(err)); - - }).catch((err) => console.error(err)); + window.crypto.subtle + .encrypt( + { + name: 'AES-CBC', + iv: random_iv + }, + key, + array + ) + .then(uploadFile.bind(null, file, hex, key)) + .catch(err => console.error(err)); + }) + .catch(err => console.error(err)); }; -} +}; window.onChange = onChange; let uploadFile = (file, hex, key, encrypted) => { let dataView = new DataView(encrypted); let blob = new Blob([dataView], { type: file.type }); - + let fd = new FormData(); - fd.append("fname", file.name); - fd.append("data", blob, file.name); + fd.append('fname', file.name); + fd.append('data', blob, file.name); let xhr = new XMLHttpRequest(); - xhr.open("post", "/upload/" + hex, true); + xhr.open('post', '/upload/' + hex, true); let listelem = setupUI(); listelem.emit('name', file.name); - xhr.upload.addEventListener("progress", updateProgress.bind(null, listelem)); + xhr.upload.addEventListener('progress', updateProgress.bind(null, listelem)); - xhr.onreadystatechange = () => { + xhr.onreadystatechange = () => { if (xhr.readyState == XMLHttpRequest.DONE) { - window.crypto.subtle.exportKey("jwk", key).then((keydata) => { + window.crypto.subtle.exportKey('jwk', key).then(keydata => { localStorage.setItem(hex, xhr.responseText); - listelem.emit('link', "http://localhost:3000/download/" + hex + "/#" + keydata.k) + listelem.emit( + 'link', + 'http://localhost:3000/download/' + hex + '/#' + keydata.k + ); - console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); - }) + console.log( + 'Share this link with a friend: http://localhost:3000/download/' + + hex + + '/#' + + keydata.k + ); + }); } }; xhr.send(fd); -} +}; let updateProgress = (UIelem, e) => { if (e.lengthComputable) { - let percentComplete = Math.floor((e.loaded / e.total) * 100); - UIelem.emit('progress', "Progress: " + percentComplete + "%") + let percentComplete = Math.floor(e.loaded / e.total * 100); + UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); if (percentComplete === 100) { - let btn = document.createElement("button"); - btn.innerText = "Delete from server"; - btn.addEventListener("click", () => { - let segments = UIelem.link.innerText.split("/"); + let btn = document.createElement('button'); + btn.innerText = 'Delete from server'; + btn.addEventListener('click', () => { + let segments = UIelem.link.innerText.split('/'); let key = segments[segments.length - 2]; let xhr = new XMLHttpRequest(); - xhr.open("post", "/delete/" + key, true); - xhr.setRequestHeader("Content-Type", "application/json"); + xhr.open('post', '/delete/' + key, true); + xhr.setRequestHeader('Content-Type', 'application/json'); if (!localStorage.getItem(key)) return; - xhr.send(JSON.stringify({delete_token: localStorage.getItem(key)})); + xhr.send(JSON.stringify({ delete_token: localStorage.getItem(key) })); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { - document.getElementById("uploaded_files").removeChild(UIelem.li); + document.getElementById('uploaded_files').removeChild(UIelem.li); localStorage.removeItem(key); } if (xhr.status === 200) { - console.log("The file was successfully deleted."); + console.log('The file was successfully deleted.'); } else { - console.log("The file has expired, or has already been deleted."); + console.log('The file has expired, or has already been deleted.'); } - } - + }; }); UIelem.li.appendChild(btn); } } -} +}; let setupUI = () => { - let li = document.createElement("li"); - let name = document.createElement("p"); + let li = document.createElement('li'); + let name = document.createElement('p'); li.appendChild(name); - - let link = document.createElement("a"); + + let link = document.createElement('a'); li.appendChild(link); - let progress = document.createElement("p"); + let progress = document.createElement('p'); li.appendChild(progress); - document.getElementById("uploaded_files").appendChild(li); + document.getElementById('uploaded_files').appendChild(li); return new UIWrapper(li, name, link, progress); -} +}; -let ivToStr = (iv) => { - let hexStr = ""; +let ivToStr = iv => { + let hexStr = ''; for (let i in iv) { if (iv[i] < 16) { - hexStr += "0" + iv[i].toString(16); + hexStr += '0' + iv[i].toString(16); } else { hexStr += iv[i].toString(16); } } window.hexStr = hexStr; return hexStr; -} +}; -let strToIv = (str) => { +let strToIv = str => { let iv = new Uint8Array(16); for (let i = 0; i < str.length; i += 2) { - iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); + iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); } return iv; -} \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index 240923c8..51eaa966 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,12 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" }, + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -18,16 +24,88 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "anymatch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true + }, + "arr-flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", + "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", + "dev": true + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1.js": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dev": true + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true + }, "ast-types": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.8.tgz", "integrity": "sha1-bLakC+ujH0nyCSjihDn8FKPasHg=" }, + "astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, "babel-code-frame": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", @@ -43,6 +121,24 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" }, + "base64-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", + "dev": true + }, + "binary-extensions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", + "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", + "dev": true + }, + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + }, "body-parser": { "version": "1.17.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", @@ -53,6 +149,124 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=" }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz", + "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", + "dev": true + }, + "browser-resolve": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "dev": true, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", + "integrity": "sha1-CJo0Y69Y0OSNjNQHCz90ZU1avKk=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", + "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", + "dev": true + }, + "browserify-cipher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true + }, + "browserify-des": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true + }, + "buffer": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.6.tgz", + "integrity": "sha1-LqZp9+7Atu2gWwj4tf9mGyhXNYg=", + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -63,11 +277,29 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" }, + "cached-path-relative": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", + "dev": true + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true + }, + "cipher-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz", + "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=", + "dev": true + }, "color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", @@ -78,16 +310,54 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.2.tgz", "integrity": "sha1-XIq3K2S9IhXWF66VWeuxSEdc+Y0=" }, + "combine-source-map": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true + } + } + }, "connect-busboy": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/connect-busboy/-/connect-busboy-0.0.2.tgz", "integrity": "sha1-rFyclmchcYheV2xmsr/ZXTuxEJc=" }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -98,6 +368,12 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" }, + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -113,36 +389,134 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "create-ecdh": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true + }, + "create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dev": true + }, + "create-hmac": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dev": true + }, + "crypto-browserify": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz", + "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, "debug": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, "depd": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" }, + "deps-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detective": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.5.0.tgz", + "integrity": "sha1-blqMaybmx6JUsca210kNmOyR7dE=", + "dev": true + }, "dicer": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=" }, + "diffie-hellman": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true + }, "encodeurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", @@ -168,11 +542,53 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", + "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true + }, "express": { "version": "4.15.3", "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true + }, "finalhandler": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", @@ -183,6 +599,18 @@ "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.45.0.tgz", "integrity": "sha1-qinUrifwaqAoF3crug/L7+9+YvA=" }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true + }, "forwarded": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", @@ -203,6 +631,707 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", + "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.11.0", + "bundled": true, + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "commander": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "extend": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.10", + "bundled": true, + "dev": true + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.3", + "bundled": true, + "dev": true, + "optional": true + }, + "generate-function": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "generate-object-property": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "getpass": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.1", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "2.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-my-json-valid": { + "version": "2.15.0", + "bundled": true, + "dev": true, + "optional": true + }, + "is-property": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonpointer": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.26.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.14", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "ms": { + "version": "0.7.1", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.33", + "bundled": true, + "dev": true, + "optional": true + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npmlog": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.3.1", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.2", + "bundled": true, + "dev": true, + "optional": true + }, + "request": { + "version": "2.79.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.5.4", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "optional": true + }, + "sshpk": { + "version": "1.10.2", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "tar-pack": { + "version": "3.3.0", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "once": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.1.5", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", + "dev": true + }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", @@ -213,26 +1342,86 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=" }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" }, + "hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dev": true + }, + "hash.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", + "integrity": "sha1-EzL/ABVsCg/92CNgE9B7d6BFFXM=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true + }, + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true + }, "http-errors": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, "iconv-lite": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -243,16 +1432,102 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true + }, + "insert-module-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", + "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", + "dev": true + }, "ipaddr.js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, "jest-matcher-utils": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz", @@ -268,16 +1543,64 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=" }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true + }, "jsonfile": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.0.tgz", "integrity": "sha1-kufHRE5f/V+jLmqa6LhQNN+DR9A=" }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + }, + "labeled-stream-splicer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", + "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=", + "dev": true + }, "leven": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" }, + "lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -293,6 +1616,18 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true + }, + "miller-rabin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "dev": true + }, "mime": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", @@ -308,6 +1643,18 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" }, + "minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -318,16 +1665,61 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "module-deps": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "dev": true, + "optional": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -338,6 +1730,42 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "outpipe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true + }, + "parse-asn1": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true + }, "parseurl": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", @@ -348,16 +1776,46 @@ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=" }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pbkdf2": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", + "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, "prettier": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.3.1.tgz", @@ -380,16 +1838,58 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, "proxy-addr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" }, + "public-encrypt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, "qs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randomatic": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", + "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=", + "dev": true + }, + "randombytes": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", + "integrity": "sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew=", + "dev": true + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -400,11 +1900,63 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, "redis": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/redis/-/redis-2.7.1.tgz", @@ -420,6 +1972,48 @@ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" }, + "regex-cache": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", + "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "resolve": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", + "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", + "dev": true + }, + "ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dev": true + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "dev": true + }, "send": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", @@ -430,16 +2024,150 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, "setprototypeof": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" }, + "sha.js": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", + "dev": true + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + }, "statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, + "stream-http": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.1.tgz", + "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, + "stream-splicer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", @@ -455,16 +2183,90 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, + "syntax-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz", + "integrity": "sha1-HtkmbE1AvnXcVb+bsct3Biu5bKE=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + } + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, "type-is": { "version": "1.6.15", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "umd": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", + "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=", + "dev": true + }, "universalify": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.0.tgz", @@ -475,6 +2277,20 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -487,6 +2303,12 @@ } } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "utils-merge": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", @@ -497,10 +2319,28 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true + }, + "watchify": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.9.0.tgz", + "integrity": "sha1-8HX9LoqGrN6Eztum5cKgvt1SPZ4=", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true } } } diff --git a/package.json b/package.json index 0f96ffd1..18521e0f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "scripts": { - "format": "prettier --single-quote --write 'public/*.js' 'app.js'", + "format": "prettier --single-quote --write 'frontend/src/*.js'", "test": "echo \"Error: no test specified\" && exit 1", "start": "watchify frontend/src/main.js -o public/bundle.js -d | node server/portal_server.js" }, diff --git a/public/bundle.js b/public/bundle.js deleted file mode 100644 index 9a01e7fd..00000000 --- a/public/bundle.js +++ /dev/null @@ -1,614 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { - - let xhr = new XMLHttpRequest(); - xhr.open("get", "/assets" + location.pathname.slice(0, -1), true); - xhr.responseType = "blob"; - - let listelem = setupUI(); - xhr.addEventListener("progress", updateProgress.bind(null, listelem)); - - xhr.onload = function(e) { - - // maybe send a separate request before this one to get the filename? - - // maybe render the html itself with the filename, since it's generated server side - // after a get request with the unique id - listelem.emit('name', xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]); - - if (this.status == 200) { - let self = this; - let blob = new Blob([this.response]); - let arrayBuffer; - let fileReader = new FileReader(); - fileReader.onload = function() { - arrayBuffer = this.result; - let array = new Uint8Array(arrayBuffer); - salt = strToIv(location.pathname.slice(10, -1)); - - window.crypto.subtle.importKey( - "jwk", - { - kty: "oct", - k: location.hash.slice(1), - alg: "A128CBC", - ext: true, - }, - { - name: "AES-CBC", - }, - true, - ["encrypt", "decrypt"]) - .then((key) => { - return window.crypto.subtle.decrypt( - { - name: "AES-CBC", - iv: salt, - }, - key, - array) - }) - .then((decrypted) => { - let dataView = new DataView(decrypted); - let blob = new Blob([dataView]); - let downloadUrl = URL.createObjectURL(blob); - let a = document.createElement("a"); - a.href = downloadUrl; - a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="(.+)"/)[1]; - document.body.appendChild(a); - a.click(); - }) - .catch((err) => { - alert("This link is either invalid or has expired, or the uploader has deleted the file."); - console.error(err); - }); - }; - - fileReader.readAsArrayBuffer(blob); - } else { - alert("This link is either invalid or has expired, or the uploader has deleted the file.") - } - }; - xhr.send(); -} - -window.download = download; - -let setupUI = () => { - let li = document.createElement("li"); - let name = document.createElement("p"); - li.appendChild(name); - - let progress = document.createElement("p"); - li.appendChild(progress); - - document.getElementById("downloaded_files").appendChild(li); - - return new UIWrapper(li, name, null, progress); -} - -let ivToStr = (iv) => { - let hexStr = ""; - for (let i in iv) { - if (iv[i] < 16) { - hexStr += "0" + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -} - -let strToIv = (str) => { - let iv = new Uint8Array(16); - for (let i = 0; i < str.length; i += 2) { - iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); - } - - return iv; -} - -let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor((e.loaded / e.total) * 100); - UIelem.emit('progress', "Progress: " + percentComplete + "%"); - - if (percentComplete === 100) { - let finished = document.createElement("p"); - finished.innerText = "Your download has finished."; - UIelem.li.appendChild(finished); - - let close = document.createElement("button"); - close.innerText = "Ok"; - close.addEventListener("click", () => { - document.getElementById("downloaded_files").removeChild(UIelem.li); - }); - - UIelem.li.appendChild(close); - } - } -} -},{"./file":2}],2:[function(require,module,exports){ -const EventEmitter = require('events'); - -class UIWrapper extends EventEmitter { - constructor(li, name, link, progress) { - super(); - this.li = li; - this.name = name; - this.link = link; - this.progress = progress; - - this.on("name", (filename) => { - this.name.innerText = filename; - }); - - this.on("link", (link) => { - this.link.innerText = link; - this.link.setAttribute('href', link); - }); - - this.on("progress", (progress) => { - this.progress.innerText = progress; - - }); - } -} - -exports.UIWrapper = UIWrapper; -},{"events":5}],3:[function(require,module,exports){ -require('./upload'); -require('./download'); -require('./file'); -},{"./download":1,"./file":2,"./upload":4}],4:[function(require,module,exports){ -const EventEmitter = require('events'); -const UIWrapper = require('./file').UIWrapper; - -let onChange = (event) => { - let file = event.target.files[0]; - let reader = new FileReader(); - reader.readAsArrayBuffer(file); - - let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); - let hex = ivToStr(random_iv); - - reader.onload = function(event) { - let self = this; - window.crypto.subtle.generateKey({ - name: "AES-CBC", - length: 128 - }, - true, - ["encrypt", "decrypt"]) - .then((key) => { - let arrayBuffer = self.result; - let array = new Uint8Array(arrayBuffer); - - window.crypto.subtle.encrypt({ - name: "AES-CBC", - iv: random_iv }, - key, - array) - .then(uploadFile.bind(null, file, hex, key)) - .catch((err) => console.error(err)); - - }).catch((err) => console.error(err)); - }; -} - -window.onChange = onChange; - -let uploadFile = (file, hex, key, encrypted) => { - let dataView = new DataView(encrypted); - let blob = new Blob([dataView], { type: file.type }); - - let fd = new FormData(); - fd.append("fname", file.name); - fd.append("data", blob, file.name); - - let xhr = new XMLHttpRequest(); - xhr.open("post", "/upload/" + hex, true); - - let listelem = setupUI(); - listelem.emit('name', file.name); - xhr.upload.addEventListener("progress", updateProgress.bind(null, listelem)); - - xhr.onreadystatechange = () => { - if (xhr.readyState == XMLHttpRequest.DONE) { - window.crypto.subtle.exportKey("jwk", key).then((keydata) => { - localStorage.setItem(hex, xhr.responseText); - - listelem.emit('link', "http://localhost:3000/download/" + hex + "/#" + keydata.k) - - console.log("Share this link with a friend: http://localhost:3000/download/" + hex + "/#" + keydata.k); - }) - } - }; - - xhr.send(fd); -} - -let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor((e.loaded / e.total) * 100); - UIelem.emit('progress', "Progress: " + percentComplete + "%") - - if (percentComplete === 100) { - let btn = document.createElement("button"); - btn.innerText = "Delete from server"; - btn.addEventListener("click", () => { - let segments = UIelem.link.innerText.split("/"); - let key = segments[segments.length - 2]; - - let xhr = new XMLHttpRequest(); - xhr.open("post", "/delete/" + key, true); - xhr.setRequestHeader("Content-Type", "application/json"); - if (!localStorage.getItem(key)) return; - - xhr.send(JSON.stringify({delete_token: localStorage.getItem(key)})); - - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - document.getElementById("uploaded_files").removeChild(UIelem.li); - localStorage.removeItem(key); - } - - if (xhr.status === 200) { - console.log("The file was successfully deleted."); - } else { - console.log("The file has expired, or has already been deleted."); - } - } - - }); - UIelem.li.appendChild(btn); - } - } -} - -let setupUI = () => { - let li = document.createElement("li"); - let name = document.createElement("p"); - li.appendChild(name); - - let link = document.createElement("a"); - li.appendChild(link); - - let progress = document.createElement("p"); - li.appendChild(progress); - - document.getElementById("uploaded_files").appendChild(li); - - return new UIWrapper(li, name, link, progress); -} - -let ivToStr = (iv) => { - let hexStr = ""; - for (let i in iv) { - if (iv[i] < 16) { - hexStr += "0" + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -} - -let strToIv = (str) => { - let iv = new Uint8Array(16); - for (let i = 0; i < str.length; i += 2) { - iv[i/2] = parseInt((str.charAt(i) + str.charAt(i + 1)), 16); - } - - return iv; -} -},{"./file":2,"events":5}],5:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -function EventEmitter() { - this._events = this._events || {}; - this._maxListeners = this._maxListeners || undefined; -} -module.exports = EventEmitter; - -// Backwards-compat with node 0.10.x -EventEmitter.EventEmitter = EventEmitter; - -EventEmitter.prototype._events = undefined; -EventEmitter.prototype._maxListeners = undefined; - -// By default EventEmitters will print a warning if more than 10 listeners are -// added to it. This is a useful default which helps finding memory leaks. -EventEmitter.defaultMaxListeners = 10; - -// Obviously not all Emitters should be limited to 10. This function allows -// that to be increased. Set to zero for unlimited. -EventEmitter.prototype.setMaxListeners = function(n) { - if (!isNumber(n) || n < 0 || isNaN(n)) - throw TypeError('n must be a positive number'); - this._maxListeners = n; - return this; -}; - -EventEmitter.prototype.emit = function(type) { - var er, handler, len, args, i, listeners; - - if (!this._events) - this._events = {}; - - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || - (isObject(this._events.error) && !this._events.error.length)) { - er = arguments[1]; - if (er instanceof Error) { - throw er; // Unhandled 'error' event - } else { - // At least give some kind of context to the user - var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); - err.context = er; - throw err; - } - } - } - - handler = this._events[type]; - - if (isUndefined(handler)) - return false; - - if (isFunction(handler)) { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - args = Array.prototype.slice.call(arguments, 1); - handler.apply(this, args); - } - } else if (isObject(handler)) { - args = Array.prototype.slice.call(arguments, 1); - listeners = handler.slice(); - len = listeners.length; - for (i = 0; i < len; i++) - listeners[i].apply(this, args); - } - - return true; -}; - -EventEmitter.prototype.addListener = function(type, listener) { - var m; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events) - this._events = {}; - - // To avoid recursion in the case that type === "newListener"! Before - // adding it to the listeners, first emit "newListener". - if (this._events.newListener) - this.emit('newListener', type, - isFunction(listener.listener) ? - listener.listener : listener); - - if (!this._events[type]) - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - else if (isObject(this._events[type])) - // If we've already got an array, just append. - this._events[type].push(listener); - else - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - - // Check for listener leak - if (isObject(this._events[type]) && !this._events[type].warned) { - if (!isUndefined(this._maxListeners)) { - m = this._maxListeners; - } else { - m = EventEmitter.defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - if (typeof console.trace === 'function') { - // not supported in IE 10 - console.trace(); - } - } - } - - return this; -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener) { - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - var fired = false; - - function g() { - this.removeListener(type, g); - - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } - - g.listener = listener; - this.on(type, g); - - return this; -}; - -// emits a 'removeListener' event iff the listener was removed -EventEmitter.prototype.removeListener = function(type, listener) { - var list, position, length, i; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events || !this._events[type]) - return this; - - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || - (isFunction(list.listener) && list.listener === listener)) { - delete this._events[type]; - if (this._events.removeListener) - this.emit('removeListener', type, listener); - - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) { - position = i; - break; - } - } - - if (position < 0) - return this; - - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } - - if (this._events.removeListener) - this.emit('removeListener', type, listener); - } - - return this; -}; - -EventEmitter.prototype.removeAllListeners = function(type) { - var key, listeners; - - if (!this._events) - return this; - - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) - this._events = {}; - else if (this._events[type]) - delete this._events[type]; - return this; - } - - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); - } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; - } - - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else if (listeners) { - // LIFO order - while (listeners.length) - this.removeListener(type, listeners[listeners.length - 1]); - } - delete this._events[type]; - - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - var ret; - if (!this._events || !this._events[type]) - ret = []; - else if (isFunction(this._events[type])) - ret = [this._events[type]]; - else - ret = this._events[type].slice(); - return ret; -}; - -EventEmitter.prototype.listenerCount = function(type) { - if (this._events) { - var evlistener = this._events[type]; - - if (isFunction(evlistener)) - return 1; - else if (evlistener) - return evlistener.length; - } - return 0; -}; - -EventEmitter.listenerCount = function(emitter, type) { - return emitter.listenerCount(type); -}; - -function isFunction(arg) { - return typeof arg === 'function'; -} - -function isNumber(arg) { - return typeof arg === 'number'; -} - -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} - -function isUndefined(arg) { - return arg === void 0; -} - -},{}]},{},[3]) -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJmcm9udGVuZC9zcmMvZG93bmxvYWQuanMiLCJmcm9udGVuZC9zcmMvZmlsZS5qcyIsImZyb250ZW5kL3NyYy9tYWluLmpzIiwiZnJvbnRlbmQvc3JjL3VwbG9hZC5qcyIsIm5vZGVfbW9kdWxlcy9ldmVudHMvZXZlbnRzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25JQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTs7QUNGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsImNvbnN0IFVJV3JhcHBlciA9IHJlcXVpcmUoJy4vZmlsZScpLlVJV3JhcHBlcjtcblxubGV0IGRvd25sb2FkID0gKCkgPT4ge1xuXG4gIGxldCB4aHIgPSBuZXcgWE1MSHR0cFJlcXVlc3QoKTtcbiAgeGhyLm9wZW4oXCJnZXRcIiwgXCIvYXNzZXRzXCIgKyBsb2NhdGlvbi5wYXRobmFtZS5zbGljZSgwLCAtMSksIHRydWUpO1xuICB4aHIucmVzcG9uc2VUeXBlID0gXCJibG9iXCI7XG4gIFxuICBsZXQgbGlzdGVsZW0gPSBzZXR1cFVJKCk7XG4gIHhoci5hZGRFdmVudExpc3RlbmVyKFwicHJvZ3Jlc3NcIiwgdXBkYXRlUHJvZ3Jlc3MuYmluZChudWxsLCBsaXN0ZWxlbSkpO1xuICBcbiAgeGhyLm9ubG9hZCA9IGZ1bmN0aW9uKGUpIHtcblxuICAgIC8vIG1heWJlIHNlbmQgYSBzZXBhcmF0ZSByZXF1ZXN0IGJlZm9yZSB0aGlzIG9uZSB0byBnZXQgdGhlIGZpbGVuYW1lP1xuXG4gICAgLy8gbWF5YmUgcmVuZGVyIHRoZSBodG1sIGl0c2VsZiB3aXRoIHRoZSBmaWxlbmFtZSwgc2luY2UgaXQncyBnZW5lcmF0ZWQgc2VydmVyIHNpZGVcbiAgICAvLyBhZnRlciBhIGdldCByZXF1ZXN0IHdpdGggdGhlIHVuaXF1ZSBpZFxuICAgIGxpc3RlbGVtLmVtaXQoJ25hbWUnLCB4aHIuZ2V0UmVzcG9uc2VIZWFkZXIoXCJDb250ZW50LURpc3Bvc2l0aW9uXCIpLm1hdGNoKC9maWxlbmFtZT1cIiguKylcIi8pWzFdKTtcblxuICAgIGlmICh0aGlzLnN0YXR1cyA9PSAyMDApIHtcbiAgICAgIGxldCBzZWxmID0gdGhpcztcbiAgICAgIGxldCBibG9iID0gbmV3IEJsb2IoW3RoaXMucmVzcG9uc2VdKTtcbiAgICAgIGxldCBhcnJheUJ1ZmZlcjtcbiAgICAgIGxldCBmaWxlUmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTtcbiAgICAgIGZpbGVSZWFkZXIub25sb2FkID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIGFycmF5QnVmZmVyID0gdGhpcy5yZXN1bHQ7XG4gICAgICAgIGxldCBhcnJheSA9IG5ldyBVaW50OEFycmF5KGFycmF5QnVmZmVyKTtcbiAgICAgICAgc2FsdCA9IHN0clRvSXYobG9jYXRpb24ucGF0aG5hbWUuc2xpY2UoMTAsIC0xKSk7XG5cbiAgICAgICAgd2luZG93LmNyeXB0by5zdWJ0bGUuaW1wb3J0S2V5KFxuICAgICAgICAgIFwiandrXCIsXG4gICAgICAgICAge1xuICAgICAgICAgICAgICBrdHk6IFwib2N0XCIsXG4gICAgICAgICAgICAgIGs6IGxvY2F0aW9uLmhhc2guc2xpY2UoMSksXG4gICAgICAgICAgICAgIGFsZzogXCJBMTI4Q0JDXCIsXG4gICAgICAgICAgICAgIGV4dDogdHJ1ZSxcbiAgICAgICAgICB9LFxuICAgICAgICAgIHtcbiAgICAgICAgICAgICAgbmFtZTogXCJBRVMtQ0JDXCIsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0cnVlLFxuICAgICAgICAgIFtcImVuY3J5cHRcIiwgXCJkZWNyeXB0XCJdKVxuICAgICAgICAudGhlbigoa2V5KSA9PiB7ICBcbiAgICAgICAgICByZXR1cm4gd2luZG93LmNyeXB0by5zdWJ0bGUuZGVjcnlwdChcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIG5hbWU6IFwiQUVTLUNCQ1wiLFxuICAgICAgICAgICAgICAgIGl2OiBzYWx0LFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICBrZXksXG4gICAgICAgICAgICAgIGFycmF5KVxuICAgICAgICB9KVxuICAgICAgICAudGhlbigoZGVjcnlwdGVkKSA9PiB7XG4gICAgICAgICAgbGV0IGRhdGFWaWV3ID0gbmV3IERhdGFWaWV3KGRlY3J5cHRlZCk7XG4gICAgICAgICAgbGV0IGJsb2IgPSBuZXcgQmxvYihbZGF0YVZpZXddKTtcbiAgICAgICAgICBsZXQgZG93bmxvYWRVcmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuICAgICAgICAgIGxldCBhID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImFcIik7XG4gICAgICAgICAgYS5ocmVmID0gZG93bmxvYWRVcmw7XG4gICAgICAgICAgYS5kb3dubG9hZCA9IHhoci5nZXRSZXNwb25zZUhlYWRlcihcIkNvbnRlbnQtRGlzcG9zaXRpb25cIikubWF0Y2goL2ZpbGVuYW1lPVwiKC4rKVwiLylbMV07XG4gICAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChhKTtcbiAgICAgICAgICBhLmNsaWNrKCk7XG4gICAgICAgIH0pXG4gICAgICAgIC5jYXRjaCgoZXJyKSA9PiB7XG4gICAgICAgICAgYWxlcnQoXCJUaGlzIGxpbmsgaXMgZWl0aGVyIGludmFsaWQgb3IgaGFzIGV4cGlyZWQsIG9yIHRoZSB1cGxvYWRlciBoYXMgZGVsZXRlZCB0aGUgZmlsZS5cIik7XG4gICAgICAgICAgY29uc29sZS5lcnJvcihlcnIpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG5cbiAgICAgIGZpbGVSZWFkZXIucmVhZEFzQXJyYXlCdWZmZXIoYmxvYik7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFsZXJ0KFwiVGhpcyBsaW5rIGlzIGVpdGhlciBpbnZhbGlkIG9yIGhhcyBleHBpcmVkLCBvciB0aGUgdXBsb2FkZXIgaGFzIGRlbGV0ZWQgdGhlIGZpbGUuXCIpXG4gICAgfVxuICB9O1xuICB4aHIuc2VuZCgpO1xufVxuXG53aW5kb3cuZG93bmxvYWQgPSBkb3dubG9hZDtcblxubGV0IHNldHVwVUkgPSAoKSA9PiB7XG4gIGxldCBsaSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJsaVwiKTtcbiAgbGV0IG5hbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwicFwiKTtcbiAgbGkuYXBwZW5kQ2hpbGQobmFtZSk7XG5cbiAgbGV0IHByb2dyZXNzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInBcIik7XG4gIGxpLmFwcGVuZENoaWxkKHByb2dyZXNzKTtcblxuICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcImRvd25sb2FkZWRfZmlsZXNcIikuYXBwZW5kQ2hpbGQobGkpO1xuXG4gIHJldHVybiBuZXcgVUlXcmFwcGVyKGxpLCBuYW1lLCBudWxsLCBwcm9ncmVzcyk7XG59XG5cbmxldCBpdlRvU3RyID0gKGl2KSA9PiB7XG4gIGxldCBoZXhTdHIgPSBcIlwiO1xuICBmb3IgKGxldCBpIGluIGl2KSB7XG4gICAgaWYgKGl2W2ldIDwgMTYpIHtcbiAgICAgIGhleFN0ciArPSBcIjBcIiArIGl2W2ldLnRvU3RyaW5nKDE2KTtcbiAgICB9IGVsc2Uge1xuICAgICAgaGV4U3RyICs9IGl2W2ldLnRvU3RyaW5nKDE2KTtcbiAgICB9XG4gIH1cbiAgd2luZG93LmhleFN0ciA9IGhleFN0cjtcbiAgcmV0dXJuIGhleFN0cjtcbn1cblxubGV0IHN0clRvSXYgPSAoc3RyKSA9PiB7XG4gIGxldCBpdiA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBzdHIubGVuZ3RoOyBpICs9IDIpIHtcbiAgICBpdltpLzJdID0gcGFyc2VJbnQoKHN0ci5jaGFyQXQoaSkgKyBzdHIuY2hhckF0KGkgKyAxKSksIDE2KTtcbiAgfVxuXG4gIHJldHVybiBpdjtcbn1cblxubGV0IHVwZGF0ZVByb2dyZXNzID0gKFVJZWxlbSwgZSkgPT4ge1xuICBpZiAoZS5sZW5ndGhDb21wdXRhYmxlKSB7IFxuICAgIGxldCBwZXJjZW50Q29tcGxldGUgPSBNYXRoLmZsb29yKChlLmxvYWRlZCAvIGUudG90YWwpICogMTAwKTtcbiAgICBVSWVsZW0uZW1pdCgncHJvZ3Jlc3MnLCBcIlByb2dyZXNzOiBcIiArIHBlcmNlbnRDb21wbGV0ZSArIFwiJVwiKTtcblxuICAgIGlmIChwZXJjZW50Q29tcGxldGUgPT09IDEwMCkge1xuICAgICAgbGV0IGZpbmlzaGVkID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInBcIik7XG4gICAgICBmaW5pc2hlZC5pbm5lclRleHQgPSBcIllvdXIgZG93bmxvYWQgaGFzIGZpbmlzaGVkLlwiO1xuICAgICAgVUllbGVtLmxpLmFwcGVuZENoaWxkKGZpbmlzaGVkKTtcblxuICAgICAgbGV0IGNsb3NlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImJ1dHRvblwiKTtcbiAgICAgIGNsb3NlLmlubmVyVGV4dCA9IFwiT2tcIjtcbiAgICAgIGNsb3NlLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCAoKSA9PiB7XG4gICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwiZG93bmxvYWRlZF9maWxlc1wiKS5yZW1vdmVDaGlsZChVSWVsZW0ubGkpO1xuICAgICAgfSk7XG5cbiAgICAgIFVJZWxlbS5saS5hcHBlbmRDaGlsZChjbG9zZSk7XG4gICAgfVxuICB9IFxufSIsImNvbnN0IEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoJ2V2ZW50cycpO1xuXG5jbGFzcyBVSVdyYXBwZXIgZXh0ZW5kcyBFdmVudEVtaXR0ZXIge1xuICBjb25zdHJ1Y3RvcihsaSwgbmFtZSwgbGluaywgcHJvZ3Jlc3MpIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMubGkgPSBsaTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMubGluayA9IGxpbms7XG4gICAgdGhpcy5wcm9ncmVzcyA9IHByb2dyZXNzO1xuXG4gICAgdGhpcy5vbihcIm5hbWVcIiwgKGZpbGVuYW1lKSA9PiB7XG4gICAgICB0aGlzLm5hbWUuaW5uZXJUZXh0ID0gZmlsZW5hbWU7XG4gICAgfSk7XG5cbiAgICB0aGlzLm9uKFwibGlua1wiLCAobGluaykgPT4ge1xuICAgICAgdGhpcy5saW5rLmlubmVyVGV4dCA9IGxpbms7XG4gICAgICB0aGlzLmxpbmsuc2V0QXR0cmlidXRlKCdocmVmJywgbGluayk7XG4gICAgfSk7XG5cbiAgICB0aGlzLm9uKFwicHJvZ3Jlc3NcIiwgKHByb2dyZXNzKSA9PiB7XG4gICAgICB0aGlzLnByb2dyZXNzLmlubmVyVGV4dCA9IHByb2dyZXNzO1xuICAgICAgXG4gICAgfSk7XG4gIH1cbn1cblxuZXhwb3J0cy5VSVdyYXBwZXIgPSBVSVdyYXBwZXI7IiwicmVxdWlyZSgnLi91cGxvYWQnKTtcbnJlcXVpcmUoJy4vZG93bmxvYWQnKTtcbnJlcXVpcmUoJy4vZmlsZScpOyIsImNvbnN0IEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoJ2V2ZW50cycpO1xuY29uc3QgVUlXcmFwcGVyID0gcmVxdWlyZSgnLi9maWxlJykuVUlXcmFwcGVyO1xuXG5sZXQgb25DaGFuZ2UgPSAoZXZlbnQpID0+IHtcbiAgbGV0IGZpbGUgPSBldmVudC50YXJnZXQuZmlsZXNbMF07XG4gIGxldCByZWFkZXIgPSBuZXcgRmlsZVJlYWRlcigpO1xuICByZWFkZXIucmVhZEFzQXJyYXlCdWZmZXIoZmlsZSk7XG5cbiAgbGV0IHJhbmRvbV9pdiA9IHdpbmRvdy5jcnlwdG8uZ2V0UmFuZG9tVmFsdWVzKG5ldyBVaW50OEFycmF5KDE2KSk7XG4gIGxldCBoZXggPSBpdlRvU3RyKHJhbmRvbV9pdik7XG5cbiAgcmVhZGVyLm9ubG9hZCA9IGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgbGV0IHNlbGYgPSB0aGlzO1xuICAgIHdpbmRvdy5jcnlwdG8uc3VidGxlLmdlbmVyYXRlS2V5KHtcbiAgICAgIG5hbWU6IFwiQUVTLUNCQ1wiLFxuICAgICAgbGVuZ3RoOiAxMjggXG4gICAgfSxcbiAgICB0cnVlLFxuICAgIFtcImVuY3J5cHRcIiwgXCJkZWNyeXB0XCJdKVxuICAgIC50aGVuKChrZXkpID0+IHtcbiAgICAgIGxldCBhcnJheUJ1ZmZlciA9IHNlbGYucmVzdWx0O1xuICAgICAgbGV0IGFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlCdWZmZXIpO1xuXG4gICAgICB3aW5kb3cuY3J5cHRvLnN1YnRsZS5lbmNyeXB0KHtcbiAgICAgICAgbmFtZTogXCJBRVMtQ0JDXCIsXG4gICAgICAgIGl2OiByYW5kb21faXYgfSxcbiAgICAgICAga2V5LFxuICAgICAgICBhcnJheSlcbiAgICAgIC50aGVuKHVwbG9hZEZpbGUuYmluZChudWxsLCBmaWxlLCBoZXgsIGtleSkpXG4gICAgICAuY2F0Y2goKGVycikgPT4gY29uc29sZS5lcnJvcihlcnIpKTtcblxuICAgIH0pLmNhdGNoKChlcnIpID0+IGNvbnNvbGUuZXJyb3IoZXJyKSk7ICAgXG4gIH07XG59XG5cbndpbmRvdy5vbkNoYW5nZSA9IG9uQ2hhbmdlO1xuXG5sZXQgdXBsb2FkRmlsZSA9IChmaWxlLCBoZXgsIGtleSwgZW5jcnlwdGVkKSA9PiB7XG4gIGxldCBkYXRhVmlldyA9IG5ldyBEYXRhVmlldyhlbmNyeXB0ZWQpO1xuICBsZXQgYmxvYiA9IG5ldyBCbG9iKFtkYXRhVmlld10sIHsgdHlwZTogZmlsZS50eXBlIH0pO1xuICBcbiAgbGV0IGZkID0gbmV3IEZvcm1EYXRhKCk7XG4gIGZkLmFwcGVuZChcImZuYW1lXCIsIGZpbGUubmFtZSk7XG4gIGZkLmFwcGVuZChcImRhdGFcIiwgYmxvYiwgZmlsZS5uYW1lKTtcblxuICBsZXQgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7XG4gIHhoci5vcGVuKFwicG9zdFwiLCBcIi91cGxvYWQvXCIgKyBoZXgsIHRydWUpO1xuXG4gIGxldCBsaXN0ZWxlbSA9IHNldHVwVUkoKTtcbiAgbGlzdGVsZW0uZW1pdCgnbmFtZScsIGZpbGUubmFtZSk7XG4gIHhoci51cGxvYWQuYWRkRXZlbnRMaXN0ZW5lcihcInByb2dyZXNzXCIsIHVwZGF0ZVByb2dyZXNzLmJpbmQobnVsbCwgbGlzdGVsZW0pKTtcblxuICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gKCkgPT4geyBcbiAgICBpZiAoeGhyLnJlYWR5U3RhdGUgPT0gWE1MSHR0cFJlcXVlc3QuRE9ORSkge1xuICAgICAgd2luZG93LmNyeXB0by5zdWJ0bGUuZXhwb3J0S2V5KFwiandrXCIsIGtleSkudGhlbigoa2V5ZGF0YSkgPT4ge1xuICAgICAgICBsb2NhbFN0b3JhZ2Uuc2V0SXRlbShoZXgsIHhoci5yZXNwb25zZVRleHQpO1xuXG4gICAgICAgIGxpc3RlbGVtLmVtaXQoJ2xpbmsnLCBcImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9kb3dubG9hZC9cIiArIGhleCArIFwiLyNcIiArIGtleWRhdGEuaylcblxuICAgICAgICBjb25zb2xlLmxvZyhcIlNoYXJlIHRoaXMgbGluayB3aXRoIGEgZnJpZW5kOiBodHRwOi8vbG9jYWxob3N0OjMwMDAvZG93bmxvYWQvXCIgKyBoZXggKyBcIi8jXCIgKyBrZXlkYXRhLmspO1xuICAgICAgfSlcbiAgICB9XG4gIH07XG5cbiAgeGhyLnNlbmQoZmQpO1xufVxuXG5sZXQgdXBkYXRlUHJvZ3Jlc3MgPSAoVUllbGVtLCBlKSA9PiB7XG4gIGlmIChlLmxlbmd0aENvbXB1dGFibGUpIHtcbiAgICBsZXQgcGVyY2VudENvbXBsZXRlID0gTWF0aC5mbG9vcigoZS5sb2FkZWQgLyBlLnRvdGFsKSAqIDEwMCk7XG4gICAgVUllbGVtLmVtaXQoJ3Byb2dyZXNzJywgXCJQcm9ncmVzczogXCIgKyBwZXJjZW50Q29tcGxldGUgKyBcIiVcIilcblxuICAgIGlmIChwZXJjZW50Q29tcGxldGUgPT09IDEwMCkge1xuICAgICAgbGV0IGJ0biA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJidXR0b25cIik7XG4gICAgICBidG4uaW5uZXJUZXh0ID0gXCJEZWxldGUgZnJvbSBzZXJ2ZXJcIjtcbiAgICAgIGJ0bi5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgKCkgPT4ge1xuICAgICAgICBsZXQgc2VnbWVudHMgPSBVSWVsZW0ubGluay5pbm5lclRleHQuc3BsaXQoXCIvXCIpO1xuICAgICAgICBsZXQga2V5ID0gc2VnbWVudHNbc2VnbWVudHMubGVuZ3RoIC0gMl07XG5cbiAgICAgICAgbGV0IHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO1xuICAgICAgICB4aHIub3BlbihcInBvc3RcIiwgXCIvZGVsZXRlL1wiICsga2V5LCB0cnVlKTtcbiAgICAgICAgeGhyLnNldFJlcXVlc3RIZWFkZXIoXCJDb250ZW50LVR5cGVcIiwgXCJhcHBsaWNhdGlvbi9qc29uXCIpO1xuICAgICAgICBpZiAoIWxvY2FsU3RvcmFnZS5nZXRJdGVtKGtleSkpIHJldHVybjtcblxuICAgICAgICB4aHIuc2VuZChKU09OLnN0cmluZ2lmeSh7ZGVsZXRlX3Rva2VuOiBsb2NhbFN0b3JhZ2UuZ2V0SXRlbShrZXkpfSkpO1xuXG4gICAgICAgIHhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSAoKSA9PiB7XG4gICAgICAgICAgaWYgKHhoci5yZWFkeVN0YXRlID09PSBYTUxIdHRwUmVxdWVzdC5ET05FKSB7XG4gICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcInVwbG9hZGVkX2ZpbGVzXCIpLnJlbW92ZUNoaWxkKFVJZWxlbS5saSk7XG4gICAgICAgICAgICBsb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShrZXkpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmICh4aHIuc3RhdHVzID09PSAyMDApIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiVGhlIGZpbGUgd2FzIHN1Y2Nlc3NmdWxseSBkZWxldGVkLlwiKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc29sZS5sb2coXCJUaGUgZmlsZSBoYXMgZXhwaXJlZCwgb3IgaGFzIGFscmVhZHkgYmVlbiBkZWxldGVkLlwiKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICB9KTtcbiAgICAgIFVJZWxlbS5saS5hcHBlbmRDaGlsZChidG4pO1xuICAgIH1cbiAgfVxufVxuXG5sZXQgc2V0dXBVSSA9ICgpID0+IHtcbiAgbGV0IGxpID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImxpXCIpO1xuICBsZXQgbmFtZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJwXCIpO1xuICBsaS5hcHBlbmRDaGlsZChuYW1lKTtcbiAgXG4gIGxldCBsaW5rID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImFcIik7XG4gIGxpLmFwcGVuZENoaWxkKGxpbmspO1xuXG4gIGxldCBwcm9ncmVzcyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJwXCIpO1xuICBsaS5hcHBlbmRDaGlsZChwcm9ncmVzcyk7XG5cbiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoXCJ1cGxvYWRlZF9maWxlc1wiKS5hcHBlbmRDaGlsZChsaSk7XG5cbiAgcmV0dXJuIG5ldyBVSVdyYXBwZXIobGksIG5hbWUsIGxpbmssIHByb2dyZXNzKTtcbn1cblxubGV0IGl2VG9TdHIgPSAoaXYpID0+IHtcbiAgbGV0IGhleFN0ciA9IFwiXCI7XG4gIGZvciAobGV0IGkgaW4gaXYpIHtcbiAgICBpZiAoaXZbaV0gPCAxNikge1xuICAgICAgaGV4U3RyICs9IFwiMFwiICsgaXZbaV0udG9TdHJpbmcoMTYpO1xuICAgIH0gZWxzZSB7XG4gICAgICBoZXhTdHIgKz0gaXZbaV0udG9TdHJpbmcoMTYpO1xuICAgIH1cbiAgfVxuICB3aW5kb3cuaGV4U3RyID0gaGV4U3RyO1xuICByZXR1cm4gaGV4U3RyO1xufVxuXG5sZXQgc3RyVG9JdiA9IChzdHIpID0+IHtcbiAgbGV0IGl2ID0gbmV3IFVpbnQ4QXJyYXkoMTYpO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHN0ci5sZW5ndGg7IGkgKz0gMikge1xuICAgIGl2W2kvMl0gPSBwYXJzZUludCgoc3RyLmNoYXJBdChpKSArIHN0ci5jaGFyQXQoaSArIDEpKSwgMTYpO1xuICB9XG5cbiAgcmV0dXJuIGl2O1xufSIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG5mdW5jdGlvbiBFdmVudEVtaXR0ZXIoKSB7XG4gIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gdGhpcy5fbWF4TGlzdGVuZXJzIHx8IHVuZGVmaW5lZDtcbn1cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyO1xuXG4vLyBCYWNrd2FyZHMtY29tcGF0IHdpdGggbm9kZSAwLjEwLnhcbkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX2V2ZW50cyA9IHVuZGVmaW5lZDtcbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDtcblxuLy8gQnkgZGVmYXVsdCBFdmVudEVtaXR0ZXJzIHdpbGwgcHJpbnQgYSB3YXJuaW5nIGlmIG1vcmUgdGhhbiAxMCBsaXN0ZW5lcnMgYXJlXG4vLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLlxuRXZlbnRFbWl0dGVyLmRlZmF1bHRNYXhMaXN0ZW5lcnMgPSAxMDtcblxuLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzXG4vLyB0aGF0IHRvIGJlIGluY3JlYXNlZC4gU2V0IHRvIHplcm8gZm9yIHVubGltaXRlZC5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuc2V0TWF4TGlzdGVuZXJzID0gZnVuY3Rpb24obikge1xuICBpZiAoIWlzTnVtYmVyKG4pIHx8IG4gPCAwIHx8IGlzTmFOKG4pKVxuICAgIHRocm93IFR5cGVFcnJvcignbiBtdXN0IGJlIGEgcG9zaXRpdmUgbnVtYmVyJyk7XG4gIHRoaXMuX21heExpc3RlbmVycyA9IG47XG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzKVxuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuXG4gIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy5cbiAgaWYgKHR5cGUgPT09ICdlcnJvcicpIHtcbiAgICBpZiAoIXRoaXMuX2V2ZW50cy5lcnJvciB8fFxuICAgICAgICAoaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkpIHtcbiAgICAgIGVyID0gYXJndW1lbnRzWzFdO1xuICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXI7IC8vIFVuaGFuZGxlZCAnZXJyb3InIGV2ZW50XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBBdCBsZWFzdCBnaXZlIHNvbWUga2luZCBvZiBjb250ZXh0IHRvIHRoZSB1c2VyXG4gICAgICAgIHZhciBlcnIgPSBuZXcgRXJyb3IoJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQuICgnICsgZXIgKyAnKScpO1xuICAgICAgICBlcnIuY29udGV4dCA9IGVyO1xuICAgICAgICB0aHJvdyBlcnI7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlciA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpXG4gICAgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGhhbmRsZXIpKSB7XG4gICAgc3dpdGNoIChhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgICAvLyBmYXN0IGNhc2VzXG4gICAgICBjYXNlIDE6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDI6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMzpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSwgYXJndW1lbnRzWzJdKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICAvLyBzbG93ZXJcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGFyZ3MgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpO1xuICAgICAgICBoYW5kbGVyLmFwcGx5KHRoaXMsIGFyZ3MpO1xuICAgIH1cbiAgfSBlbHNlIGlmIChpc09iamVjdChoYW5kbGVyKSkge1xuICAgIGFyZ3MgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpO1xuICAgIGxpc3RlbmVycyA9IGhhbmRsZXIuc2xpY2UoKTtcbiAgICBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoO1xuICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKylcbiAgICAgIGxpc3RlbmVyc1tpXS5hcHBseSh0aGlzLCBhcmdzKTtcbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBtO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcblxuICAvLyBUbyBhdm9pZCByZWN1cnNpb24gaW4gdGhlIGNhc2UgdGhhdCB0eXBlID09PSBcIm5ld0xpc3RlbmVyXCIhIEJlZm9yZVxuICAvLyBhZGRpbmcgaXQgdG8gdGhlIGxpc3RlbmVycywgZmlyc3QgZW1pdCBcIm5ld0xpc3RlbmVyXCIuXG4gIGlmICh0aGlzLl9ldmVudHMubmV3TGlzdGVuZXIpXG4gICAgdGhpcy5lbWl0KCduZXdMaXN0ZW5lcicsIHR5cGUsXG4gICAgICAgICAgICAgIGlzRnVuY3Rpb24obGlzdGVuZXIubGlzdGVuZXIpID9cbiAgICAgICAgICAgICAgbGlzdGVuZXIubGlzdGVuZXIgOiBsaXN0ZW5lcik7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgLy8gT3B0aW1pemUgdGhlIGNhc2Ugb2Ygb25lIGxpc3RlbmVyLiBEb24ndCBuZWVkIHRoZSBleHRyYSBhcnJheSBvYmplY3QuXG4gICAgdGhpcy5fZXZlbnRzW3R5cGVdID0gbGlzdGVuZXI7XG4gIGVsc2UgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgLy8gSWYgd2UndmUgYWxyZWFkeSBnb3QgYW4gYXJyYXksIGp1c3QgYXBwZW5kLlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5wdXNoKGxpc3RlbmVyKTtcbiAgZWxzZVxuICAgIC8vIEFkZGluZyB0aGUgc2Vjb25kIGVsZW1lbnQsIG5lZWQgdG8gY2hhbmdlIHRvIGFycmF5LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IFt0aGlzLl9ldmVudHNbdHlwZV0sIGxpc3RlbmVyXTtcblxuICAvLyBDaGVjayBmb3IgbGlzdGVuZXIgbGVha1xuICBpZiAoaXNPYmplY3QodGhpcy5fZXZlbnRzW3R5cGVdKSAmJiAhdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCkge1xuICAgIGlmICghaXNVbmRlZmluZWQodGhpcy5fbWF4TGlzdGVuZXJzKSkge1xuICAgICAgbSA9IHRoaXMuX21heExpc3RlbmVycztcbiAgICB9IGVsc2Uge1xuICAgICAgbSA9IEV2ZW50RW1pdHRlci5kZWZhdWx0TWF4TGlzdGVuZXJzO1xuICAgIH1cblxuICAgIGlmIChtICYmIG0gPiAwICYmIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGggPiBtKSB7XG4gICAgICB0aGlzLl9ldmVudHNbdHlwZV0ud2FybmVkID0gdHJ1ZTtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJyhub2RlKSB3YXJuaW5nOiBwb3NzaWJsZSBFdmVudEVtaXR0ZXIgbWVtb3J5ICcgK1xuICAgICAgICAgICAgICAgICAgICAnbGVhayBkZXRlY3RlZC4gJWQgbGlzdGVuZXJzIGFkZGVkLiAnICtcbiAgICAgICAgICAgICAgICAgICAgJ1VzZSBlbWl0dGVyLnNldE1heExpc3RlbmVycygpIHRvIGluY3JlYXNlIGxpbWl0LicsXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGgpO1xuICAgICAgaWYgKHR5cGVvZiBjb25zb2xlLnRyYWNlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIC8vIG5vdCBzdXBwb3J0ZWQgaW4gSUUgMTBcbiAgICAgICAgY29uc29sZS50cmFjZSgpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5vbiA9IEV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub25jZSA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICB2YXIgZmlyZWQgPSBmYWxzZTtcblxuICBmdW5jdGlvbiBnKCkge1xuICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgZyk7XG5cbiAgICBpZiAoIWZpcmVkKSB7XG4gICAgICBmaXJlZCA9IHRydWU7XG4gICAgICBsaXN0ZW5lci5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH1cbiAgfVxuXG4gIGcubGlzdGVuZXIgPSBsaXN0ZW5lcjtcbiAgdGhpcy5vbih0eXBlLCBnKTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbi8vIGVtaXRzIGEgJ3JlbW92ZUxpc3RlbmVyJyBldmVudCBpZmYgdGhlIGxpc3RlbmVyIHdhcyByZW1vdmVkXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUxpc3RlbmVyID0gZnVuY3Rpb24odHlwZSwgbGlzdGVuZXIpIHtcbiAgdmFyIGxpc3QsIHBvc2l0aW9uLCBsZW5ndGgsIGk7XG5cbiAgaWYgKCFpc0Z1bmN0aW9uKGxpc3RlbmVyKSlcbiAgICB0aHJvdyBUeXBlRXJyb3IoJ2xpc3RlbmVyIG11c3QgYmUgYSBmdW5jdGlvbicpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgbGlzdCA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgbGVuZ3RoID0gbGlzdC5sZW5ndGg7XG4gIHBvc2l0aW9uID0gLTE7XG5cbiAgaWYgKGxpc3QgPT09IGxpc3RlbmVyIHx8XG4gICAgICAoaXNGdW5jdGlvbihsaXN0Lmxpc3RlbmVyKSAmJiBsaXN0Lmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIGlmICh0aGlzLl9ldmVudHMucmVtb3ZlTGlzdGVuZXIpXG4gICAgICB0aGlzLmVtaXQoJ3JlbW92ZUxpc3RlbmVyJywgdHlwZSwgbGlzdGVuZXIpO1xuXG4gIH0gZWxzZSBpZiAoaXNPYmplY3QobGlzdCkpIHtcbiAgICBmb3IgKGkgPSBsZW5ndGg7IGktLSA+IDA7KSB7XG4gICAgICBpZiAobGlzdFtpXSA9PT0gbGlzdGVuZXIgfHxcbiAgICAgICAgICAobGlzdFtpXS5saXN0ZW5lciAmJiBsaXN0W2ldLmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICAgICAgcG9zaXRpb24gPSBpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAocG9zaXRpb24gPCAwKVxuICAgICAgcmV0dXJuIHRoaXM7XG5cbiAgICBpZiAobGlzdC5sZW5ndGggPT09IDEpIHtcbiAgICAgIGxpc3QubGVuZ3RoID0gMDtcbiAgICAgIGRlbGV0ZSB0aGlzLl9ldmVudHNbdHlwZV07XG4gICAgfSBlbHNlIHtcbiAgICAgIGxpc3Quc3BsaWNlKHBvc2l0aW9uLCAxKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVBbGxMaXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciBrZXksIGxpc3RlbmVycztcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICByZXR1cm4gdGhpcztcblxuICAvLyBub3QgbGlzdGVuaW5nIGZvciByZW1vdmVMaXN0ZW5lciwgbm8gbmVlZCB0byBlbWl0XG4gIGlmICghdGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKSB7XG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDApXG4gICAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICBlbHNlIGlmICh0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgLy8gZW1pdCByZW1vdmVMaXN0ZW5lciBmb3IgYWxsIGxpc3RlbmVycyBvbiBhbGwgZXZlbnRzXG4gIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKSB7XG4gICAgZm9yIChrZXkgaW4gdGhpcy5fZXZlbnRzKSB7XG4gICAgICBpZiAoa2V5ID09PSAncmVtb3ZlTGlzdGVuZXInKSBjb250aW51ZTtcbiAgICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKGtleSk7XG4gICAgfVxuICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKCdyZW1vdmVMaXN0ZW5lcicpO1xuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgbGlzdGVuZXJzID0gdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGxpc3RlbmVycykpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGxpc3RlbmVycyk7XG4gIH0gZWxzZSBpZiAobGlzdGVuZXJzKSB7XG4gICAgLy8gTElGTyBvcmRlclxuICAgIHdoaWxlIChsaXN0ZW5lcnMubGVuZ3RoKVxuICAgICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnNbbGlzdGVuZXJzLmxlbmd0aCAtIDFdKTtcbiAgfVxuICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciByZXQ7XG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0ID0gW107XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24odGhpcy5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSBbdGhpcy5fZXZlbnRzW3R5cGVdXTtcbiAgZWxzZVxuICAgIHJldCA9IHRoaXMuX2V2ZW50c1t0eXBlXS5zbGljZSgpO1xuICByZXR1cm4gcmV0O1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24odHlwZSkge1xuICBpZiAodGhpcy5fZXZlbnRzKSB7XG4gICAgdmFyIGV2bGlzdGVuZXIgPSB0aGlzLl9ldmVudHNbdHlwZV07XG5cbiAgICBpZiAoaXNGdW5jdGlvbihldmxpc3RlbmVyKSlcbiAgICAgIHJldHVybiAxO1xuICAgIGVsc2UgaWYgKGV2bGlzdGVuZXIpXG4gICAgICByZXR1cm4gZXZsaXN0ZW5lci5sZW5ndGg7XG4gIH1cbiAgcmV0dXJuIDA7XG59O1xuXG5FdmVudEVtaXR0ZXIubGlzdGVuZXJDb3VudCA9IGZ1bmN0aW9uKGVtaXR0ZXIsIHR5cGUpIHtcbiAgcmV0dXJuIGVtaXR0ZXIubGlzdGVuZXJDb3VudCh0eXBlKTtcbn07XG5cbmZ1bmN0aW9uIGlzRnVuY3Rpb24oYXJnKSB7XG4gIHJldHVybiB0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nO1xufVxuXG5mdW5jdGlvbiBpc051bWJlcihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInO1xufVxuXG5mdW5jdGlvbiBpc09iamVjdChhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdvYmplY3QnICYmIGFyZyAhPT0gbnVsbDtcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQoYXJnKSB7XG4gIHJldHVybiBhcmcgPT09IHZvaWQgMDtcbn1cbiJdfQ== From 28498af6d544662eeda9c53f98da5f1d68aa5cdc Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Thu, 1 Jun 2017 20:59:27 -0700 Subject: [PATCH 4/6] incremental upload refactor --- .gitignore | 5 ++ frontend/src/fileSender.js | 103 +++++++++++++++++++++++ frontend/src/upload.js | 165 ++++++------------------------------- frontend/src/utils.js | 26 ++++++ 4 files changed, 160 insertions(+), 139 deletions(-) create mode 100644 .gitignore create mode 100644 frontend/src/fileSender.js create mode 100644 frontend/src/utils.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2ff1484e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +public/bundle.js +static/* +!static/info.txt diff --git a/frontend/src/fileSender.js b/frontend/src/fileSender.js new file mode 100644 index 00000000..356c6cf7 --- /dev/null +++ b/frontend/src/fileSender.js @@ -0,0 +1,103 @@ +const EventEmitter = require('events'); +const { ivToStr } = require('./utils'); + +class FileSender extends EventEmitter { + constructor(file) { + super(); + this.file = file; + this.iv = window.crypto.getRandomValues(new Uint8Array(16)); + } + + static delete(fileId, token) { + return new Promise((resolve, reject) => { + if (!fileId || !token) { + return resolve(); + } + let xhr = new XMLHttpRequest(); + xhr.open('post', '/delete/' + fileId, true); + xhr.setRequestHeader('Content-Type', 'application/json'); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + resolve(); + } + + if (xhr.status === 200) { + console.log('The file was successfully deleted.'); + } else { + console.log('The file has expired, or has already been deleted.'); + } + }; + + xhr.send(JSON.stringify({ delete_token: token })); + }); + } + + upload() { + return Promise.all([ + window.crypto.subtle.generateKey( + { + name: 'AES-CBC', + length: 128 + }, + true, + ['encrypt', 'decrypt'] + ), + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsArrayBuffer(this.file); + reader.onload = function(event) { + resolve(new Uint8Array(this.result)); + }; + }) + ]) + .then(([secretKey, plaintext]) => { + return Promise.all([ + window.crypto.subtle.encrypt( + { + name: 'AES-CBC', + iv: this.iv + }, + secretKey, + plaintext + ), + window.crypto.subtle.exportKey('jwk', secretKey) + ]); + }) + .then(([encrypted, keydata]) => { + return new Promise((resolve, reject) => { + let file = this.file; + let fileId = ivToStr(this.iv); + let dataView = new DataView(encrypted); + let blob = new Blob([dataView], { type: file.type }); + let fd = new FormData(); + fd.append('fname', file.name); + fd.append('data', blob, file.name); + + let xhr = new XMLHttpRequest(); + xhr.open('post', '/upload/' + fileId, true); + + xhr.upload.addEventListener('progress', e => { + if (e.lengthComputable) { + let percentComplete = Math.floor(e.loaded / e.total * 100); + this.emit('progress', percentComplete); + } + }); + + xhr.onreadystatechange = () => { + if (xhr.readyState == XMLHttpRequest.DONE) { + resolve({ + fileId: fileId, + secretKey: keydata.k, + deleteToken: xhr.responseText + }); + } + }; + + xhr.send(fd); + }); + }); + } +} + +module.exports = FileSender; diff --git a/frontend/src/upload.js b/frontend/src/upload.js index 2cc529b4..c2d13eb1 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -1,125 +1,11 @@ -const EventEmitter = require('events'); -const UIWrapper = require('./ui').UIWrapper; +const FileSender = require('./fileSender'); let onChange = event => { - let file = event.target.files[0]; - let reader = new FileReader(); - reader.readAsArrayBuffer(file); + const file = event.target.files[0]; - let random_iv = window.crypto.getRandomValues(new Uint8Array(16)); - let hex = ivToStr(random_iv); - - reader.onload = function(event) { - let self = this; - window.crypto.subtle - .generateKey( - { - name: 'AES-CBC', - length: 128 - }, - true, - ['encrypt', 'decrypt'] - ) - .then(key => { - let arrayBuffer = self.result; - let array = new Uint8Array(arrayBuffer); - - window.crypto.subtle - .encrypt( - { - name: 'AES-CBC', - iv: random_iv - }, - key, - array - ) - .then(uploadFile.bind(null, file, hex, key)) - .catch(err => console.error(err)); - }) - .catch(err => console.error(err)); - }; -}; - -window.onChange = onChange; - -let uploadFile = (file, hex, key, encrypted) => { - let dataView = new DataView(encrypted); - let blob = new Blob([dataView], { type: file.type }); - - let fd = new FormData(); - fd.append('fname', file.name); - fd.append('data', blob, file.name); - - let xhr = new XMLHttpRequest(); - xhr.open('post', '/upload/' + hex, true); - - let listelem = setupUI(); - listelem.emit('name', file.name); - xhr.upload.addEventListener('progress', updateProgress.bind(null, listelem)); - - xhr.onreadystatechange = () => { - if (xhr.readyState == XMLHttpRequest.DONE) { - window.crypto.subtle.exportKey('jwk', key).then(keydata => { - localStorage.setItem(hex, xhr.responseText); - - listelem.emit( - 'link', - 'http://localhost:3000/download/' + hex + '/#' + keydata.k - ); - - console.log( - 'Share this link with a friend: http://localhost:3000/download/' + - hex + - '/#' + - keydata.k - ); - }); - } - }; - - xhr.send(fd); -}; - -let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor(e.loaded / e.total * 100); - UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); - - if (percentComplete === 100) { - let btn = document.createElement('button'); - btn.innerText = 'Delete from server'; - btn.addEventListener('click', () => { - let segments = UIelem.link.innerText.split('/'); - let key = segments[segments.length - 2]; - - let xhr = new XMLHttpRequest(); - xhr.open('post', '/delete/' + key, true); - xhr.setRequestHeader('Content-Type', 'application/json'); - if (!localStorage.getItem(key)) return; - - xhr.send(JSON.stringify({ delete_token: localStorage.getItem(key) })); - - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - document.getElementById('uploaded_files').removeChild(UIelem.li); - localStorage.removeItem(key); - } - - if (xhr.status === 200) { - console.log('The file was successfully deleted.'); - } else { - console.log('The file has expired, or has already been deleted.'); - } - }; - }); - UIelem.li.appendChild(btn); - } - } -}; - -let setupUI = () => { let li = document.createElement('li'); let name = document.createElement('p'); + name.innerText = file.name; li.appendChild(name); let link = document.createElement('a'); @@ -130,27 +16,28 @@ let setupUI = () => { document.getElementById('uploaded_files').appendChild(li); - return new UIWrapper(li, name, link, progress); + const fileSender = new FileSender(file); + fileSender.on('progress', percentComplete => { + progress.innerText = `Progress: ${percentComplete}%`; + }); + fileSender.upload().then(info => { + const url = `${window.location.origin}/${info.fileId}/#${info.secretKey}`; + localStorage.setItem(info.fileId, info.deleteToken); + link.innerText = url; + link.setAttribute('href', url); + let btn = document.createElement('button'); + btn.innerText = 'Delete from server'; + btn.addEventListener('click', () => { + FileSender.delete( + info.fileId, + localStorage.getItem(info.fileId) + ).then(() => { + document.getElementById('uploaded_files').removeChild(li); + localStorage.removeItem(info.fileId); + }); + }); + li.appendChild(btn); + }); }; -let ivToStr = iv => { - let hexStr = ''; - for (let i in iv) { - if (iv[i] < 16) { - hexStr += '0' + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -}; - -let strToIv = str => { - let iv = new Uint8Array(16); - for (let i = 0; i < str.length; i += 2) { - iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); - } - - return iv; -}; +window.onChange = onChange; diff --git a/frontend/src/utils.js b/frontend/src/utils.js new file mode 100644 index 00000000..547211fe --- /dev/null +++ b/frontend/src/utils.js @@ -0,0 +1,26 @@ +function ivToStr(iv) { + let hexStr = ''; + for (let i in iv) { + if (iv[i] < 16) { + hexStr += '0' + iv[i].toString(16); + } else { + hexStr += iv[i].toString(16); + } + } + window.hexStr = hexStr; + return hexStr; +} + +function strToIv(str) { + let iv = new Uint8Array(16); + for (let i = 0; i < str.length; i += 2) { + iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); + } + + return iv; +} + +module.exports = { + ivToStr, + strToIv +}; From a45d3dca73f8ed8fdfaa9aaae8e932a970fd1c3e Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Fri, 2 Jun 2017 12:38:05 -0700 Subject: [PATCH 5/6] updated receiving end --- frontend/src/download.js | 284 ++++++++++++++++++++--------------- frontend/src/fileReceiver.js | 75 +++++++++ frontend/src/fileSender.js | 4 +- frontend/src/main.js | 3 +- frontend/src/ui.js | 26 ---- frontend/src/upload.js | 2 +- 6 files changed, 243 insertions(+), 151 deletions(-) create mode 100644 frontend/src/fileReceiver.js delete mode 100644 frontend/src/ui.js diff --git a/frontend/src/download.js b/frontend/src/download.js index f5c4d74c..4fb68810 100644 --- a/frontend/src/download.js +++ b/frontend/src/download.js @@ -1,142 +1,186 @@ -const UIWrapper = require('./ui').UIWrapper; +const FileReceiver = require('./fileReceiver'); let download = () => { - let xhr = new XMLHttpRequest(); - xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); - xhr.responseType = 'blob'; - let listelem = setupUI(); - xhr.addEventListener('progress', updateProgress.bind(null, listelem)); + const fileReceiver = new FileReceiver(); - xhr.onload = function(e) { - // maybe send a separate request before this one to get the filename? - - // maybe render the html itself with the filename, since it's generated server side - // after a get request with the unique id - listelem.emit( - 'name', - xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] - ); - - if (this.status == 200) { - let self = this; - let blob = new Blob([this.response]); - let arrayBuffer; - let fileReader = new FileReader(); - fileReader.onload = function() { - arrayBuffer = this.result; - let array = new Uint8Array(arrayBuffer); - salt = strToIv(location.pathname.slice(10, -1)); - - window.crypto.subtle - .importKey( - 'jwk', - { - kty: 'oct', - k: location.hash.slice(1), - alg: 'A128CBC', - ext: true - }, - { - name: 'AES-CBC' - }, - true, - ['encrypt', 'decrypt'] - ) - .then(key => { - return window.crypto.subtle.decrypt( - { - name: 'AES-CBC', - iv: salt - }, - key, - array - ); - }) - .then(decrypted => { - let dataView = new DataView(decrypted); - let blob = new Blob([dataView]); - let downloadUrl = URL.createObjectURL(blob); - let a = document.createElement('a'); - a.href = downloadUrl; - a.download = xhr - .getResponseHeader('Content-Disposition') - .match(/filename="(.+)"/)[1]; - document.body.appendChild(a); - a.click(); - }) - .catch(err => { - alert( - 'This link is either invalid or has expired, or the uploader has deleted the file.' - ); - console.error(err); - }); - }; - - fileReader.readAsArrayBuffer(blob); - } else { - alert( - 'This link is either invalid or has expired, or the uploader has deleted the file.' - ); - } - }; - xhr.send(); -}; - -window.download = download; - -let setupUI = () => { let li = document.createElement('li'); let name = document.createElement('p'); li.appendChild(name); - let progress = document.createElement('p'); li.appendChild(progress); document.getElementById('downloaded_files').appendChild(li); - return new UIWrapper(li, name, null, progress); -}; - -let ivToStr = iv => { - let hexStr = ''; - for (let i in iv) { - if (iv[i] < 16) { - hexStr += '0' + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -}; - -let strToIv = str => { - let iv = new Uint8Array(16); - for (let i = 0; i < str.length; i += 2) { - iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); - } - - return iv; -}; - -let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor(e.loaded / e.total * 100); - UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); + fileReceiver.on('progress', percentComplete => { + progress.innerText = `Progress: ${percentComplete}%`; if (percentComplete === 100) { let finished = document.createElement('p'); finished.innerText = 'Your download has finished.'; - UIelem.li.appendChild(finished); + li.appendChild(finished); let close = document.createElement('button'); close.innerText = 'Ok'; close.addEventListener('click', () => { - document.getElementById('downloaded_files').removeChild(UIelem.li); + document.getElementById('downloaded_files').removeChild(li); }); - - UIelem.li.appendChild(close); + li.appendChild(close); } - } -}; + }); + + fileReceiver.download().then(([decrypted, fname]) => { + name.innerText = fname; + let dataView = new DataView(decrypted); + let blob = new Blob([dataView]); + let downloadUrl = URL.createObjectURL(blob); + + let a = document.createElement('a'); + a.href = downloadUrl; + a.download = fname; + document.body.appendChild(a); + a.click(); + }) +} +// const FileReceiver = require('./fileReceiver'); + +// let download = () => { +// let xhr = new XMLHttpRequest(); +// xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); +// xhr.responseType = 'blob'; + +// let listelem = setupUI(); +// xhr.addEventListener('progress', updateProgress.bind(null, listelem)); + +// xhr.onload = function(e) { +// // maybe send a separate request before this one to get the filename? + +// // maybe render the html itself with the filename, since it's generated server side +// // after a get request with the unique id +// listelem.emit( +// 'name', +// xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] +// ); + +// if (this.status == 200) { +// let self = this; +// let blob = new Blob([this.response]); +// let arrayBuffer; +// let fileReader = new FileReader(); +// fileReader.onload = function() { +// arrayBuffer = this.result; +// let array = new Uint8Array(arrayBuffer); +// salt = strToIv(location.pathname.slice(10, -1)); + +// window.crypto.subtle +// .importKey( +// 'jwk', +// { +// kty: 'oct', +// k: location.hash.slice(1), +// alg: 'A128CBC', +// ext: true +// }, +// { +// name: 'AES-CBC' +// }, +// true, +// ['encrypt', 'decrypt'] +// ) +// .then(key => { +// return window.crypto.subtle.decrypt( +// { +// name: 'AES-CBC', +// iv: salt +// }, +// key, +// array +// ); +// }) +// .then(decrypted => { +// let dataView = new DataView(decrypted); +// let blob = new Blob([dataView]); +// let downloadUrl = URL.createObjectURL(blob); +// let a = document.createElement('a'); +// a.href = downloadUrl; +// a.download = xhr +// .getResponseHeader('Content-Disposition') +// .match(/filename="(.+)"/)[1]; +// document.body.appendChild(a); +// a.click(); +// }) +// .catch(err => { +// alert( +// 'This link is either invalid or has expired, or the uploader has deleted the file.' +// ); +// console.error(err); +// }); +// }; + +// fileReader.readAsArrayBuffer(blob); +// } else { +// alert( +// 'This link is either invalid or has expired, or the uploader has deleted the file.' +// ); +// } +// }; +// xhr.send(); +// }; + +window.download = download; + +// let setupUI = () => { +// let li = document.createElement('li'); +// let name = document.createElement('p'); +// li.appendChild(name); + +// let progress = document.createElement('p'); +// li.appendChild(progress); + +// document.getElementById('downloaded_files').appendChild(li); + +// return new UIWrapper(li, name, null, progress); +// }; + +// let ivToStr = iv => { +// let hexStr = ''; +// for (let i in iv) { +// if (iv[i] < 16) { +// hexStr += '0' + iv[i].toString(16); +// } else { +// hexStr += iv[i].toString(16); +// } +// } +// window.hexStr = hexStr; +// return hexStr; +// }; + +// let strToIv = str => { +// let iv = new Uint8Array(16); +// for (let i = 0; i < str.length; i += 2) { +// iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); +// } + +// return iv; +// }; + +// let updateProgress = (UIelem, e) => { +// if (e.lengthComputable) { +// let percentComplete = Math.floor(e.loaded / e.total * 100); +// UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); + +// if (percentComplete === 100) { +// let finished = document.createElement('p'); +// finished.innerText = 'Your download has finished.'; +// UIelem.li.appendChild(finished); + +// let close = document.createElement('button'); +// close.innerText = 'Ok'; +// close.addEventListener('click', () => { +// document.getElementById('downloaded_files').removeChild(UIelem.li); +// }); + +// UIelem.li.appendChild(close); +// } +// } +// }; diff --git a/frontend/src/fileReceiver.js b/frontend/src/fileReceiver.js new file mode 100644 index 00000000..2a653359 --- /dev/null +++ b/frontend/src/fileReceiver.js @@ -0,0 +1,75 @@ +const EventEmitter = require('events'); +const { strToIv } = require('./utils'); + +class FileReceiver extends EventEmitter { + constructor() { + super(); + this.salt = strToIv(location.pathname.slice(10, -1)); + } + + + download() { + return Promise.all([ + new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + + xhr.onprogress = e => { + if (e.lengthComputable) { + let percentComplete = Math.floor(e.loaded / e.total * 100); + this.emit('progress', percentComplete); + } + }; + + xhr.onload = function(e) { + let blob = new Blob([this.response]); + let fileReader = new FileReader(); + fileReader.onload = function() { + resolve({ + data: this.result, + fname: xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] + }); + } + + fileReader.readAsArrayBuffer(blob); + } + + xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); + xhr.responseType = 'blob'; + xhr.send(); + }), + window.crypto.subtle + .importKey( + 'jwk', + { + kty: 'oct', + k: location.hash.slice(1), + alg: 'A128CBC', + ext: true + }, + { + name: 'AES-CBC' + }, + true, + ['encrypt', 'decrypt'] + ) + ]) + .then(([fdata, key]) => { + let salt = this.salt; + return Promise.all([ + window.crypto.subtle.decrypt( + { + name: 'AES-CBC', + iv: salt + }, + key, + fdata.data + ), + new Promise((resolve, reject) => { + resolve(fdata.fname); + }) + ]); + }) + } +} + +module.exports = FileReceiver; \ No newline at end of file diff --git a/frontend/src/fileSender.js b/frontend/src/fileSender.js index 356c6cf7..412ca2d9 100644 --- a/frontend/src/fileSender.js +++ b/frontend/src/fileSender.js @@ -75,7 +75,6 @@ class FileSender extends EventEmitter { fd.append('data', blob, file.name); let xhr = new XMLHttpRequest(); - xhr.open('post', '/upload/' + fileId, true); xhr.upload.addEventListener('progress', e => { if (e.lengthComputable) { @@ -93,7 +92,8 @@ class FileSender extends EventEmitter { }); } }; - + + xhr.open('post', '/upload/' + fileId, true); xhr.send(fd); }); }); diff --git a/frontend/src/main.js b/frontend/src/main.js index 5dec39be..191c77bb 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,3 +1,2 @@ require('./upload'); -require('./download'); -require('./ui'); +require('./download'); \ No newline at end of file diff --git a/frontend/src/ui.js b/frontend/src/ui.js deleted file mode 100644 index 0b0f0fb8..00000000 --- a/frontend/src/ui.js +++ /dev/null @@ -1,26 +0,0 @@ -const EventEmitter = require('events'); - -class UIWrapper extends EventEmitter { - constructor(li, name, link, progress) { - super(); - this.li = li; - this.name = name; - this.link = link; - this.progress = progress; - - this.on('name', filename => { - this.name.innerText = filename; - }); - - this.on('link', link => { - this.link.innerText = link; - this.link.setAttribute('href', link); - }); - - this.on('progress', progress => { - this.progress.innerText = progress; - }); - } -} - -exports.UIWrapper = UIWrapper; diff --git a/frontend/src/upload.js b/frontend/src/upload.js index c2d13eb1..65ed38fc 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -21,7 +21,7 @@ let onChange = event => { progress.innerText = `Progress: ${percentComplete}%`; }); fileSender.upload().then(info => { - const url = `${window.location.origin}/${info.fileId}/#${info.secretKey}`; + const url = `${window.location.origin}/download/${info.fileId}/#${info.secretKey}`; localStorage.setItem(info.fileId, info.deleteToken); link.innerText = url; link.setAttribute('href', url); From 028b6400cb72144e6d3f861935b401e852464963 Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Fri, 2 Jun 2017 12:49:56 -0700 Subject: [PATCH 6/6] formatted code and deleted extra code from download.js --- frontend/src/download.js | 145 +---------------------------------- frontend/src/fileReceiver.js | 71 +++++++++-------- frontend/src/fileSender.js | 2 +- frontend/src/main.js | 2 +- frontend/src/upload.js | 3 +- 5 files changed, 41 insertions(+), 182 deletions(-) diff --git a/frontend/src/download.js b/frontend/src/download.js index 4fb68810..9f233f81 100644 --- a/frontend/src/download.js +++ b/frontend/src/download.js @@ -1,7 +1,6 @@ const FileReceiver = require('./fileReceiver'); let download = () => { - const fileReceiver = new FileReceiver(); let li = document.createElement('li'); @@ -40,147 +39,7 @@ let download = () => { a.download = fname; document.body.appendChild(a); a.click(); - }) -} -// const FileReceiver = require('./fileReceiver'); - -// let download = () => { -// let xhr = new XMLHttpRequest(); -// xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); -// xhr.responseType = 'blob'; - -// let listelem = setupUI(); -// xhr.addEventListener('progress', updateProgress.bind(null, listelem)); - -// xhr.onload = function(e) { -// // maybe send a separate request before this one to get the filename? - -// // maybe render the html itself with the filename, since it's generated server side -// // after a get request with the unique id -// listelem.emit( -// 'name', -// xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] -// ); - -// if (this.status == 200) { -// let self = this; -// let blob = new Blob([this.response]); -// let arrayBuffer; -// let fileReader = new FileReader(); -// fileReader.onload = function() { -// arrayBuffer = this.result; -// let array = new Uint8Array(arrayBuffer); -// salt = strToIv(location.pathname.slice(10, -1)); - -// window.crypto.subtle -// .importKey( -// 'jwk', -// { -// kty: 'oct', -// k: location.hash.slice(1), -// alg: 'A128CBC', -// ext: true -// }, -// { -// name: 'AES-CBC' -// }, -// true, -// ['encrypt', 'decrypt'] -// ) -// .then(key => { -// return window.crypto.subtle.decrypt( -// { -// name: 'AES-CBC', -// iv: salt -// }, -// key, -// array -// ); -// }) -// .then(decrypted => { -// let dataView = new DataView(decrypted); -// let blob = new Blob([dataView]); -// let downloadUrl = URL.createObjectURL(blob); -// let a = document.createElement('a'); -// a.href = downloadUrl; -// a.download = xhr -// .getResponseHeader('Content-Disposition') -// .match(/filename="(.+)"/)[1]; -// document.body.appendChild(a); -// a.click(); -// }) -// .catch(err => { -// alert( -// 'This link is either invalid or has expired, or the uploader has deleted the file.' -// ); -// console.error(err); -// }); -// }; - -// fileReader.readAsArrayBuffer(blob); -// } else { -// alert( -// 'This link is either invalid or has expired, or the uploader has deleted the file.' -// ); -// } -// }; -// xhr.send(); -// }; + }); +}; window.download = download; - -// let setupUI = () => { -// let li = document.createElement('li'); -// let name = document.createElement('p'); -// li.appendChild(name); - -// let progress = document.createElement('p'); -// li.appendChild(progress); - -// document.getElementById('downloaded_files').appendChild(li); - -// return new UIWrapper(li, name, null, progress); -// }; - -// let ivToStr = iv => { -// let hexStr = ''; -// for (let i in iv) { -// if (iv[i] < 16) { -// hexStr += '0' + iv[i].toString(16); -// } else { -// hexStr += iv[i].toString(16); -// } -// } -// window.hexStr = hexStr; -// return hexStr; -// }; - -// let strToIv = str => { -// let iv = new Uint8Array(16); -// for (let i = 0; i < str.length; i += 2) { -// iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); -// } - -// return iv; -// }; - -// let updateProgress = (UIelem, e) => { -// if (e.lengthComputable) { -// let percentComplete = Math.floor(e.loaded / e.total * 100); -// UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); - -// if (percentComplete === 100) { -// let finished = document.createElement('p'); -// finished.innerText = 'Your download has finished.'; -// UIelem.li.appendChild(finished); - -// let close = document.createElement('button'); -// close.innerText = 'Ok'; -// close.addEventListener('click', () => { -// document.getElementById('downloaded_files').removeChild(UIelem.li); -// }); - -// UIelem.li.appendChild(close); -// } -// } -// }; diff --git a/frontend/src/fileReceiver.js b/frontend/src/fileReceiver.js index 2a653359..5c5ae918 100644 --- a/frontend/src/fileReceiver.js +++ b/frontend/src/fileReceiver.js @@ -6,13 +6,12 @@ class FileReceiver extends EventEmitter { super(); this.salt = strToIv(location.pathname.slice(10, -1)); } - download() { return Promise.all([ new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); - + xhr.onprogress = e => { if (e.lengthComputable) { let percentComplete = Math.floor(e.loaded / e.total * 100); @@ -26,50 +25,50 @@ class FileReceiver extends EventEmitter { fileReader.onload = function() { resolve({ data: this.result, - fname: xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] + fname: xhr + .getResponseHeader('Content-Disposition') + .match(/filename="(.+)"/)[1] }); - } + }; fileReader.readAsArrayBuffer(blob); - } - + }; + xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); xhr.responseType = 'blob'; xhr.send(); }), - window.crypto.subtle - .importKey( - 'jwk', + window.crypto.subtle.importKey( + 'jwk', + { + kty: 'oct', + k: location.hash.slice(1), + alg: 'A128CBC', + ext: true + }, + { + name: 'AES-CBC' + }, + true, + ['encrypt', 'decrypt'] + ) + ]).then(([fdata, key]) => { + let salt = this.salt; + return Promise.all([ + window.crypto.subtle.decrypt( { - kty: 'oct', - k: location.hash.slice(1), - alg: 'A128CBC', - ext: true - }, - { - name: 'AES-CBC' - }, - true, - ['encrypt', 'decrypt'] - ) - ]) - .then(([fdata, key]) => { - let salt = this.salt; - return Promise.all([ - window.crypto.subtle.decrypt( - { name: 'AES-CBC', iv: salt - }, - key, - fdata.data - ), - new Promise((resolve, reject) => { - resolve(fdata.fname); - }) - ]); - }) + }, + key, + fdata.data + ), + new Promise((resolve, reject) => { + resolve(fdata.fname); + }) + ]); + }); } } -module.exports = FileReceiver; \ No newline at end of file +module.exports = FileReceiver; diff --git a/frontend/src/fileSender.js b/frontend/src/fileSender.js index 412ca2d9..776bddb0 100644 --- a/frontend/src/fileSender.js +++ b/frontend/src/fileSender.js @@ -92,7 +92,7 @@ class FileSender extends EventEmitter { }); } }; - + xhr.open('post', '/upload/' + fileId, true); xhr.send(fd); }); diff --git a/frontend/src/main.js b/frontend/src/main.js index 191c77bb..e7513ea4 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,2 +1,2 @@ require('./upload'); -require('./download'); \ No newline at end of file +require('./download'); diff --git a/frontend/src/upload.js b/frontend/src/upload.js index 65ed38fc..e86ea819 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -21,7 +21,8 @@ let onChange = event => { progress.innerText = `Progress: ${percentComplete}%`; }); fileSender.upload().then(info => { - const url = `${window.location.origin}/download/${info.fileId}/#${info.secretKey}`; + const url = `${window.location + .origin}/download/${info.fileId}/#${info.secretKey}`; localStorage.setItem(info.fileId, info.deleteToken); link.innerText = url; link.setAttribute('href', url);