From ce212e001c9cb2884a2951c8a3a77168cae779c8 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 3 Sep 2018 10:30:05 +0300 Subject: [PATCH] Add file uploading support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Max Leiter Co-Authored-By: Jérémie Astori --- client/css/style.css | 23 +++- client/index.html.tpl | 6 + client/js/socket-events/configuration.js | 12 ++ client/js/socket-events/index.js | 1 + client/js/socket-events/uploads.js | 10 ++ client/js/socket.js | 5 + client/js/upload.js | 54 ++++++++ defaults/config.js | 21 ++++ package.json | 4 + src/helper.js | 7 ++ src/plugins/uploader.js | 152 +++++++++++++++++++++++ src/server.js | 16 ++- yarn.lock | 97 +++++++++------ 13 files changed, 367 insertions(+), 41 deletions(-) create mode 100644 client/js/socket-events/uploads.js create mode 100644 client/js/upload.js create mode 100644 src/plugins/uploader.js diff --git a/client/css/style.css b/client/css/style.css index e68590b4..fde9641c 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -242,6 +242,7 @@ kbd { #settings .extra-experimental, #settings .extra-help, #settings #play::before, +#form #upload::before, #form #submit::before, #chat .away .from::before, #chat .back .from::before, @@ -315,6 +316,7 @@ kbd { #footer .settings::before { content: "\f013"; /* http://fontawesome.io/icon/cog/ */ } #footer .help::before { content: "\f059"; /* http://fontawesome.io/icon/question/ */ } +#form #upload::before { content: "\f0c6"; /* https://fontawesome.com/icons/paperclip?style=solid */ } #form #submit::before { content: "\f1d8"; /* http://fontawesome.io/icon/paper-plane/ */ } #chat .away .from::before, @@ -1930,6 +1932,12 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ content: "\f00c"; /* http://fontawesome.io/icon/check/ */ } +#upload-progressbar { + background: blue; + width: 0%; + height: 2px; +} + #form { flex: 0 0 auto; border: 0; @@ -1957,6 +1965,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ #connection-error.shown { display: block; + cursor: pointer; } #form #nick { @@ -1991,6 +2000,11 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ touch-action: pan-y; } +#form #upload-input { + display: none; +} + +#form #upload, #form #submit { color: #607992; font-size: 14px; @@ -2519,8 +2533,9 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ background: rgba(0, 0, 0, 0.6); } -/* Image viewer */ +/* Image viewer and drag-and-drop overlay */ +#upload-overlay, #image-viewer, #image-viewer .close-btn { /* Vertically and horizontally center stuff */ @@ -2530,6 +2545,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ justify-content: center; } +#upload-overlay, #image-viewer { position: fixed; top: 0; @@ -2548,6 +2564,11 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */ opacity: 1; } +#upload-overlay.is-dragover { + visibility: visible; + opacity: 0.3; +} + #image-viewer .close-btn, #image-viewer .previous-image-btn, #image-viewer .next-image-btn { diff --git a/client/index.html.tpl b/client/index.html.tpl index ebfa1ecb..97ff3142 100644 --- a/client/index.html.tpl +++ b/client/index.html.tpl @@ -84,9 +84,14 @@
+
+ + + + @@ -102,6 +107,7 @@
+
diff --git a/client/js/socket-events/configuration.js b/client/js/socket-events/configuration.js index f00d5b20..2cb14ae2 100644 --- a/client/js/socket-events/configuration.js +++ b/client/js/socket-events/configuration.js @@ -7,6 +7,7 @@ const options = require("../options"); const webpush = require("../webpush"); const connect = $("#connect"); const utils = require("../utils"); +const upload = require("../upload"); window.addEventListener("beforeinstallprompt", (installPromptEvent) => { $("#webapp-install-button") @@ -23,6 +24,12 @@ window.addEventListener("beforeinstallprompt", (installPromptEvent) => { }); socket.on("configuration", function(data) { + if (data.fileUpload) { + $("#upload").show(); + } else { + $("#upload").hide(); + } + if (options.initialized) { // Likely a reconnect, request sync for possibly missed settings. socket.emit("setting:get"); @@ -44,6 +51,11 @@ socket.on("configuration", function(data) { pop.play(); }); + if (data.fileUpload) { + upload.initialize(); + upload.setMaxFileSize(data.fileUploadMaxFileSize); + } + utils.togglePasswordField("#change-password .reveal-password"); options.initialize(); diff --git a/client/js/socket-events/index.js b/client/js/socket-events/index.js index 951eb0dc..508c4f5a 100644 --- a/client/js/socket-events/index.js +++ b/client/js/socket-events/index.js @@ -15,6 +15,7 @@ require("./part"); require("./quit"); require("./sync_sort"); require("./topic"); +require("./uploads"); require("./users"); require("./sign_out"); require("./sessions_list"); diff --git a/client/js/socket-events/uploads.js b/client/js/socket-events/uploads.js new file mode 100644 index 00000000..308f1701 --- /dev/null +++ b/client/js/socket-events/uploads.js @@ -0,0 +1,10 @@ +"use strict"; + +const socket = require("../socket"); +const wrapCursor = require("undate").wrapCursor; + +socket.on("upload:success", (url) => { + const fullURL = new URL(url, location); + const textbox = document.getElementById("input"); + wrapCursor(textbox, fullURL, " "); +}); diff --git a/client/js/socket.js b/client/js/socket.js index 8e77ff70..2956c6d5 100644 --- a/client/js/socket.js +++ b/client/js/socket.js @@ -11,6 +11,10 @@ const socket = io({ reconnection: !$(document.body).hasClass("public"), }); +$("#connection-error").on("click", function() { + $(this).removeClass("shown"); +}); + socket.on("disconnect", handleDisconnect); socket.on("connect_error", handleDisconnect); socket.on("error", handleDisconnect); @@ -42,6 +46,7 @@ function handleDisconnect(data) { $("#loading-page-message, #connection-error").text(`Waiting to reconnect… (${message})`).addClass("shown"); $(".show-more button, #input").prop("disabled", true); $("#submit").hide(); + $("#upload").hide(); // If the server shuts down, socket.io skips reconnection // and we have to manually call connect to start the process diff --git a/client/js/upload.js b/client/js/upload.js new file mode 100644 index 00000000..4abb0a16 --- /dev/null +++ b/client/js/upload.js @@ -0,0 +1,54 @@ +"use strict"; + +const $ = require("jquery"); +const socket = require("./socket"); +const SocketIOFileUpload = require("socketio-file-upload/client"); +const instance = new SocketIOFileUpload(socket); + +function initialize() { + instance.listenOnInput(document.getElementById("upload-input")); + instance.listenOnDrop(document); + + $("#upload").on("click", () => { + $("#upload-input").trigger("click"); + }); + + instance.addEventListener("complete", () => { + // Reset progressbar + $("#upload-progressbar").width(0); + }); + + instance.addEventListener("progress", (event) => { + const percent = `${((event.bytesLoaded / event.file.size) * 100)}%`; + $("#upload-progressbar").width(percent); + }); + + instance.addEventListener("error", (event) => { + // Reset progressbar + $("#upload-progressbar").width(0); + $("#connection-error").addClass("shown").text(event.message); + }); + + const $form = $(document); + const $overlay = $("#upload-overlay"); + + $form.on("dragover", () => { + $overlay.addClass("is-dragover"); + return false; + }); + + $form.on("dragend dragleave drop", () => { + $overlay.removeClass("is-dragover"); + return false; + }); +} + +/** +* Called in the `configuration` socket event. +* Makes it so the user can be notified if a file is too large without waiting for the upload to finish server-side. +**/ +function setMaxFileSize(kb) { + instance.maxFileSize = kb; +} + +module.exports = {initialize, setMaxFileSize}; diff --git a/defaults/config.js b/defaults/config.js index 0fda53ef..39afe2e2 100644 --- a/defaults/config.js +++ b/defaults/config.js @@ -139,6 +139,27 @@ module.exports = { // This value is set to `2048` kilobytes by default. prefetchMaxImageSize: 2048, + // ### `fileUpload` + // + // Allow uploading files to the server hosting The Lounge. + // + // Files are stored in the `${THELOUNGE_HOME}/uploads` folder, do not expire, + // and are not removed by The Lounge. This may cause issues depending on your + // hardware, for example in terms of disk usage. + // + // The available keys for the `fileUpload` object are: + // + // - `enable`: When set to `true`, files can be uploaded on the client with a + // drag-and-drop or using the upload dialog. + // - `maxFileSize`: When file upload is enabled, users sending files above + // this limit will be prompted an error message in their browser. A value of + // `-1` disables the file size limit and allows files of any size. **Use at + // your own risk.** This value is set to `10240` kilobytes by default. + fileUpload: { + enable: true, + maxFileSize: 10240, + }, + // ### `transports` // // Set `socket.io` transports. diff --git a/package.json b/package.json index fb15101a..4ed684b1 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,11 @@ "cheerio": "0.22.0", "commander": "2.17.1", "express": "4.16.3", + "file-type": "9.0.0", "filenamify": "2.1.0", "fs-extra": "7.0.0", "irc-framework": "3.1.0", + "is-utf8": "0.2.1", "linkify-it": "2.0.3", "lodash": "4.17.10", "mime-types": "2.1.20", @@ -54,9 +56,11 @@ "package-json": "5.0.0", "primer-tooltips": "1.5.7", "read": "1.0.7", + "read-chunk": "3.0.0", "request": "2.88.0", "semver": "5.5.1", "socket.io": "2.1.1", + "socketio-file-upload": "0.6.2", "thelounge-ldapjs-non-maintained-fork": "1.0.2", "tlds": "1.203.1", "ua-parser-js": "0.7.18", diff --git a/src/helper.js b/src/helper.js index 7fb2a9d7..a549b333 100644 --- a/src/helper.js +++ b/src/helper.js @@ -15,6 +15,7 @@ let configPath; let usersPath; let storagePath; let packagesPath; +let fileUploadPath; let userLogsPath; const Helper = { @@ -25,6 +26,7 @@ const Helper = { getPackageModulePath, getStoragePath, getConfigPath, + getFileUploadPath, getUsersPath, getUserConfigPath, getUserLogsPath, @@ -92,6 +94,7 @@ function setHome(newPath) { configPath = path.join(homePath, "config.js"); usersPath = path.join(homePath, "users"); storagePath = path.join(homePath, "storage"); + fileUploadPath = path.join(homePath, "uploads"); packagesPath = path.join(homePath, "packages"); userLogsPath = path.join(homePath, "logs"); @@ -142,6 +145,10 @@ function getConfigPath() { return configPath; } +function getFileUploadPath() { + return fileUploadPath; +} + function getUsersPath() { return usersPath; } diff --git a/src/plugins/uploader.js b/src/plugins/uploader.js new file mode 100644 index 00000000..7ef76708 --- /dev/null +++ b/src/plugins/uploader.js @@ -0,0 +1,152 @@ +"use strict"; + +const SocketIOFileUploadServer = require("socketio-file-upload/server"); +const Helper = require("../helper"); +const path = require("path"); +const fsextra = require("fs-extra"); +const fs = require("fs"); +const fileType = require("file-type"); +const readChunk = require("read-chunk"); +const crypto = require("crypto"); +const isUtf8 = require("is-utf8"); +const log = require("../log"); + +const whitelist = [ + "application/ogg", + "audio/midi", + "audio/mpeg", + "audio/ogg", + "audio/x-wav", + "image/bmp", + "image/gif", + "image/jpeg", + "image/png", + "image/webp", + "text/plain", + "video/mp4", + "video/ogg", + "video/webm", +]; + +class Uploader { + constructor(client, socket) { + const uploader = new SocketIOFileUploadServer(); + const folder = path.join(Helper.getFileUploadPath(), ".tmp"); + + fsextra.ensureDir(folder, (err) => { + if (err) { + log.err(`Error ensuring ${folder} exists for uploads.`); + } else { + uploader.dir = folder; + } + }); + + uploader.on("complete", (data) => { + handleSaving(data).then((randomName) => { + const randomFileName = randomName; + const slug = data.file.base; + const url = `uploads/${randomFileName}/${slug}`; + client.emit("upload:success", url); + }); + }); + + uploader.on("error", (data) => { + log.error(`Error uploading ${data.error.name}`); + log.error(data.error); + }); + + // maxFileSize is in bytes, but config option is passed in as KB + uploader.maxFileSize = Uploader.getMaxFileSize(); + uploader.listen(socket); + + // Returns Promise + function handleSaving(data) { + const tempPath = path.join(Helper.getFileUploadPath(), ".tmp", data.file.name); + let randomName, destPath; + + // If file conflicts + do { + randomName = crypto.randomBytes(8).toString("hex"); + destPath = path.join(Helper.getFileUploadPath(), randomName.substring(0, 2), randomName); + } while (fs.stat(destPath, (err) => (err ? true : false))); + + return fsextra.move(tempPath, destPath) + .then(() => randomName).catch(() => { + log.warn(`Unable to move file ${tempPath} to ${destPath}`); + }); + } + } + + static isValidType(mimeType) { + return whitelist.includes(mimeType); + } + + static router(express) { + express.get("/uploads/:name/:slug*?", (req, res) => { + const name = req.params.name; + + const nameRegex = /^[0-9a-f]{16}$/; + + if (!nameRegex.test(name)) { + return res.status(404).send("Not found"); + } + + const folder = name.substring(0, 2); + const uploadPath = Helper.getFileUploadPath(); + const filePath = path.join(uploadPath, folder, name); + const type = Uploader.getFileType(filePath); + const mimeType = type || "application/octet-stream"; + const contentDisposition = Uploader.isValidType(type) ? "inline" : "attachment"; + + // doesn't exist + if (type === undefined) { + return res.status(404).send("Not found"); + } + + res.setHeader("Content-Disposition", contentDisposition); + res.setHeader("Cache-Control", "max-age=86400"); + res.contentType(mimeType); + + return res.sendFile(filePath); + }); + } + + static getMaxFileSize() { + const configOption = Helper.config.fileUpload.maxFileSize; + + if (configOption === -1) { // no file size limit + return null; + } + + return configOption * 1024; + } + + static getFileType(filePath) { + let buffer; + let type; + + try { + buffer = readChunk.sync(filePath, 0, 4100); + } catch (e) { + if (e.code === "ENOENT") { // doesn't exist + return; + } + + log.warn(`Failed to read ${filePath}`); + return; + } + + // returns {ext, mime} if found, null if not. + const file = fileType(buffer); + + if (file) { + type = file.mime; + } else if (isUtf8(buffer)) { + type = "text/plain"; + } + + return type; + } +} + +module.exports = Uploader; diff --git a/src/server.js b/src/server.js index 5b502e03..97bdb051 100644 --- a/src/server.js +++ b/src/server.js @@ -10,6 +10,7 @@ const fs = require("fs"); const path = require("path"); const io = require("socket.io"); const dns = require("dns"); +const Uploader = require("./plugins/uploader"); const Helper = require("./helper"); const colors = require("chalk"); const net = require("net"); @@ -51,9 +52,13 @@ module.exports = function() { .use(express.static(path.join(__dirname, "..", "public"), staticOptions)) .use("/storage/", express.static(Helper.getStoragePath(), staticOptions)); + if (Helper.config.fileUpload.enable) { + Uploader.router(app); + } + // This route serves *installed themes only*. Local themes are served directly // from the `public/themes/` folder as static assets, without entering this - // handler. Remember this is you make changes to this function, serving of + // handler. Remember this if you make changes to this function, serving of // local themes will not get those changes. app.get("/themes/:theme.css", (req, res) => { const themeName = req.params.theme; @@ -284,6 +289,10 @@ function initializeClient(socket, client, token, lastMessage) { client.clientAttach(socket.id, token); + if (Helper.config.fileUpload.enable) { + new Uploader(client, socket); + } + socket.on("disconnect", function() { client.clientDetach(socket.id); }); @@ -583,6 +592,7 @@ function getClientConfiguration(network) { "prefetch", ]); + config.fileUpload = Helper.config.fileUpload.enable; config.ldapEnabled = Helper.config.ldap.enable; if (config.displayNetwork) { @@ -607,6 +617,10 @@ function getClientConfiguration(network) { config.defaults.nick = Helper.getDefaultNick(); } + if (Uploader) { + config.fileUploadMaxFileSize = Uploader.getMaxFileSize(); + } + return config; } diff --git a/yarn.lock b/yarn.lock index c63e0146..03077093 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1528,12 +1528,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000882" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000882.tgz#d9d50e5189be253ffb31d347cd7f3c615b602f7f" + version "1.0.30000883" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000883.tgz#976f22d6a9be119b342d5ce6c7ee98fc6e0bc94a" caniuse-lite@^1.0.30000878: - version "1.0.30000882" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000882.tgz#0d5066847a11a5af0e50ffce6c062ef0665f68ea" + version "1.0.30000883" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000883.tgz#597c1eabfb379bd9fbeaa778632762eb574706ac" caseless@~0.12.0: version "0.12.0" @@ -1571,7 +1571,7 @@ chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4. chalk@^1.1.3: version "1.1.3" - resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -1595,9 +1595,9 @@ character-reference-invalid@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" -chardet@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" check-error@^1.0.1: version "1.0.2" @@ -2309,8 +2309,8 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" detect-node@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" diff@3.5.0, diff@^3.5.0: version "3.5.0" @@ -2434,8 +2434,8 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.61: - version "1.3.61" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz#a8ac295b28d0f03d85e37326fd16b6b6b17a1795" + version "1.3.62" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.62.tgz#2e8e2dc070c800ec8ce23ff9dfcceb585d6f9ed8" elliptic@^6.0.0: version "6.4.1" @@ -2667,7 +2667,7 @@ etag@~1.8.1: event-stream@~3.3.0: version "3.3.4" - resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" dependencies: duplexer "~0.1.1" from "~0" @@ -2809,11 +2809,11 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" external-editor@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.1.tgz#fc9638c4d7cde4f0bb82b12307a1a23912c492e3" + version "3.0.3" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" dependencies: - chardet "^0.5.0" - iconv-lite "^0.4.22" + chardet "^0.7.0" + iconv-lite "^0.4.24" tmp "^0.0.33" extglob@^0.3.1: @@ -2903,6 +2903,10 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +file-type@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -3249,7 +3253,7 @@ globjoin@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" -gonzales-pe@4.2.3: +gonzales-pe@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2" dependencies: @@ -3552,7 +3556,7 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@^0.4.11, iconv-lite@^0.4.22, iconv-lite@^0.4.4: +iconv-lite@^0.4.11, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" dependencies: @@ -3980,6 +3984,10 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-utf8@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + is-whitespace-character@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" @@ -4759,7 +4767,7 @@ mixin-deep@^1.2.0: mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" @@ -5744,11 +5752,11 @@ postcss-safe-parser@^4.0.0: postcss "^7.0.0" postcss-sass@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.3.2.tgz#17f3074cecb28128b156f1a4407c6ad075d7e00c" + version "0.3.3" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.3.3.tgz#bec188ac285d21ac8feba194c2f327fdda31e671" dependencies: - gonzales-pe "4.2.3" - postcss "6.0.22" + gonzales-pe "^4.2.3" + postcss "^7.0.1" postcss-scss@^2.0.0: version "2.0.0" @@ -5809,14 +5817,6 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@6.0.22: - version "6.0.22" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: version "5.2.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" @@ -5834,7 +5834,7 @@ postcss@^6.0.1, postcss@^6.0.23, postcss@^6.0.8: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.2: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.2.tgz#7b5a109de356804e27f95a960bef0e4d5bc9bb18" dependencies: @@ -6044,6 +6044,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-chunk@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.0.0.tgz#086cd198406104355626afacd2d21084afc367ec" + dependencies: + pify "^4.0.0" + with-open-file "^0.1.3" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -6407,8 +6414,8 @@ runes@^0.4.3: resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355" rxjs@^6.1.0: - version "6.2.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" + version "6.3.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.1.tgz#878a1a8c64b8a5da11dcf74b5033fe944cdafb84" dependencies: tslib "^1.9.0" @@ -6683,6 +6690,10 @@ socket.io@2.1.1: socket.io-client "2.1.1" socket.io-parser "~3.2.0" +socketio-file-upload@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/socketio-file-upload/-/socketio-file-upload-0.6.2.tgz#0e13c5e2b2a6798f0754d2bdcf2b404f8707e192" + sockjs-client@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83" @@ -7342,8 +7353,8 @@ uglify-es@^3.3.4: source-map "~0.6.1" uglify-js@3.4.x: - version "3.4.8" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.8.tgz#d590777b208258b54131b1ae45bc9d3f68033a3e" + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" dependencies: commander "~2.17.1" source-map "~0.6.1" @@ -7742,8 +7753,8 @@ webpack-log@^2.0.0: uuid "^3.3.2" webpack-sources@^1.0.1, webpack-sources@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" + version "1.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.2.0.tgz#18181e0d013fce096faf6f8e6d41eeffffdceac2" dependencies: source-list-map "^2.0.0" source-map "~0.6.1" @@ -7817,6 +7828,14 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +with-open-file@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/with-open-file/-/with-open-file-0.1.3.tgz#9d8ed7a993cd15c55b3f7b815930a94c430885a1" + dependencies: + p-finally "^1.0.0" + p-try "^2.0.0" + pify "^3.0.0" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"