diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index 40ed5ede89..245312f16d 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -106,14 +106,14 @@ define('pictureCropper', ['cropper'], function (Cropper) { return; } - cropperModal.find('#upload-progress-bar').css('width', '100%'); + cropperModal.find('#upload-progress-bar').css('width', '0%'); cropperModal.find('#upload-progress-box').show().removeClass('hide'); - var socketData = {}; - socketData[data.paramName] = data.paramValue; - socketData.imageData = imageData; - - socket.emit(data.socketMethod, socketData, function (err, imageData) { + socketUpload({ + data: data, + imageData: imageData, + progressBarEl: cropperModal.find('#upload-progress-bar'), + }, function (err, result) { if (err) { cropperModal.find('#upload-progress-box').hide(); cropperModal.find('.upload-btn').removeClass('disabled'); @@ -121,7 +121,7 @@ define('pictureCropper', ['cropper'], function (Cropper) { return app.alertError(err.message); } - callback(imageData.url); + callback(result.url); cropperModal.modal('hide'); }); }); @@ -143,6 +143,36 @@ define('pictureCropper', ['cropper'], function (Cropper) { }); }; + function socketUpload(params, callback) { + var socketData = {}; + socketData[params.data.paramName] = params.data.paramValue; + socketData.method = params.data.socketMethod; + socketData.size = params.imageData.length; + socketData.progress = 0; + + var chunkSize = 100000; + function doUpload() { + var chunk = params.imageData.slice(socketData.progress, socketData.progress + chunkSize); + socket.emit('uploads.upload', { + chunk: chunk, + params: socketData, + }, function (err, result) { + if (err) { + return app.alertError(err); + } + + if (socketData.progress + chunkSize < socketData.size) { + socketData.progress += chunk.length; + params.progressBarEl.css('width', (socketData.progress / socketData.size * 100).toFixed(2) + '%'); + return setTimeout(doUpload, 100); + } + params.progressBarEl.css('width', '100%'); + callback(null, result); + }); + } + doUpload(); + } + function checkCORS(cropperTool, data) { var imageData; try { @@ -177,7 +207,7 @@ define('pictureCropper', ['cropper'], function (Cropper) { var file = fileInput[0].files[0]; var fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false; if (fileSize && file.size > fileSize * 1024) { - return app.alertError('[[error:file-too-big, ' + fileSize + ']]'); + return showAlert('error', '[[error:file-too-big, ' + fileSize + ']]'); } if (file.name.endsWith('.gif')) { diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 6e6be257ae..4168aa3a1e 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -69,6 +69,14 @@ function onConnection(socket) { socket.on('*', function (payload) { onMessage(socket, payload); }); + + socket.on('disconnect', function () { + onDisconnect(socket); + }); +} + +function onDisconnect(socket) { + require('./uploads').clear(socket.id); } function onConnect(socket) { @@ -148,7 +156,8 @@ async function onMessage(socket, payload) { function requireModules() { var modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', 'flags', + 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', + 'flags', 'uploads', ]; modules.forEach(function (module) { diff --git a/src/socket.io/uploads.js b/src/socket.io/uploads.js new file mode 100644 index 0000000000..9355a8ac71 --- /dev/null +++ b/src/socket.io/uploads.js @@ -0,0 +1,52 @@ +'use strict'; + +const socketUser = require('./user'); +const socketGroup = require('./groups'); +const image = require('../image'); +const meta = require('../meta'); + +const inProgress = {}; + +const uploads = module.exports; + +uploads.upload = async function (socket, data) { + const methodToFunc = { + 'user.uploadCroppedPicture': socketUser.uploadCroppedPicture, + 'user.updateCover': socketUser.updateCover, + 'groups.cover.update': socketGroup.cover.update, + }; + if (!data || !data.chunk || !data.params || !data.params.method || !methodToFunc[data.params.method]) { + throw new Error('[[error:invalid-data]]'); + } + + inProgress[socket.id] = inProgress[socket.id] || {}; + const socketUploads = inProgress[socket.id]; + const method = data.params.method; + + socketUploads[method] = socketUploads[method] || { imageData: '' }; + socketUploads[method].imageData += data.chunk; + + try { + const maxSize = data.params.method === 'user.uploadCroppedPicture' ? + meta.config.maximumProfileImageSize : meta.config.maximumCoverImageSize; + const size = image.sizeFromBase64(socketUploads[method].imageData); + + if (size > maxSize * 1024) { + throw new Error('[[error:file-too-big, ' + maxSize + ']]'); + } + if (socketUploads[method].imageData.length < data.params.size) { + return; + } + data.params.imageData = socketUploads[method].imageData; + const result = await methodToFunc[data.params.method](socket, data.params); + delete socketUploads[method]; + return result; + } catch (err) { + delete inProgress[socket.id]; + throw err; + } +}; + +uploads.clear = function (sid) { + delete inProgress[sid]; +};