Add file uploading support

Co-Authored-By: Max Leiter <hello@maxleiter.com>
Co-Authored-By: Jérémie Astori <astorije@users.noreply.github.com>
This commit is contained in:
Pavel Djundik 2018-09-03 10:30:05 +03:00
parent 7c12883dc1
commit ce212e001c
13 changed files with 367 additions and 41 deletions

View file

@ -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 {

View file

@ -84,9 +84,14 @@
<div id="chat-container" class="window">
<div id="chat"></div>
<div id="connection-error"></div>
<span id="upload-progressbar"></span>
<form id="form" method="post" action="">
<span id="nick"></span>
<textarea id="input" class="mousetrap"></textarea>
<span id="upload-tooltip" class="tooltipped tooltipped-w tooltipped-no-touch" aria-label="Upload File">
<input id="upload-input" type="file" multiple></button>
<button id="upload" type="button" aria-label="Upload file"></button>
</span>
<span id="submit-tooltip" class="tooltipped tooltipped-w tooltipped-no-touch" aria-label="Send message">
<button id="submit" type="submit" aria-label="Send message"></button>
</span>
@ -102,6 +107,7 @@
<div id="context-menu-container"></div>
<div id="image-viewer"></div>
<div id="upload-overlay"></div>
<script src="js/bundle.vendor.js"></script>
<script src="js/bundle.js"></script>

View file

@ -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();

View file

@ -15,6 +15,7 @@ require("./part");
require("./quit");
require("./sync_sort");
require("./topic");
require("./uploads");
require("./users");
require("./sign_out");
require("./sessions_list");

View file

@ -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, " ");
});

View file

@ -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

54
client/js/upload.js Normal file
View file

@ -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};

View file

@ -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.

View file

@ -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",

View file

@ -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;
}

152
src/plugins/uploader.js Normal file
View file

@ -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;

View file

@ -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;
}

View file

@ -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"