socketio-file-upload
Advanced tools
Comparing version 0.4.6 to 0.4.7
@@ -40,3 +40,3 @@ /* | ||
else if (typeof module === 'object' && module.exports) { | ||
module.exports = factory(); | ||
module.exports = factory(); | ||
} | ||
@@ -58,4 +58,5 @@ else { | ||
// Private and Public Variables | ||
var callbacks = {}, uploadedFiles = [], readyCallbacks = []; | ||
var callbacks = {}, uploadedFiles = [], readyCallbacks = [], communicators = {}; | ||
self.fileInputElementId = "siofu_input"; | ||
self.resetFileInputs = true; | ||
self.useText = false; | ||
@@ -65,2 +66,3 @@ self.serializedOctets = false; | ||
self.chunkSize = 1024 * 100; // 100kb default chunk size | ||
self.chunkDelay = 0; | ||
@@ -138,2 +140,5 @@ /** | ||
// An object for the outside to use to communicate with us | ||
var communicator = { id: id }; | ||
// Calculate chunk size | ||
@@ -198,2 +203,5 @@ var chunkSize = self.chunkSize; | ||
var processChunk = function () { | ||
// Abort if we are told to do so. | ||
if (communicator.abort) return; | ||
var chunk = file.slice(offset, Math.min(offset+chunkSize, file.size)); | ||
@@ -210,2 +218,5 @@ if (useText) { | ||
var loadCb = function (event) { | ||
// Abort if we are told to do so. | ||
if (communicator.abort) return; | ||
// Transmit the newly loaded data to the server and emit a client event | ||
@@ -224,3 +235,3 @@ var bytesLoaded = Math.min(offset+chunkSize, file.size); | ||
// Read in the next chunk | ||
processChunk(); | ||
setTimeout(processChunk, self.chunkDelay); | ||
} | ||
@@ -274,2 +285,4 @@ else { | ||
readyCallbacks.push(readyCallback); | ||
return communicator; | ||
}; | ||
@@ -289,3 +302,4 @@ | ||
// instance of FileReader for each file. | ||
_loadOne(files[i]); | ||
var communicator = _loadOne(files[i]); | ||
communicators[communicator.id] = communicator; | ||
} | ||
@@ -352,2 +366,15 @@ }; | ||
_baseFileSelectCallback(files); | ||
if (self.resetFileInputs) { | ||
try { | ||
event.target.value = ""; //for IE11, latest Chrome/Firefox/Opera... | ||
} catch(err) {} | ||
if (event.target.value) { //for IE5 ~ IE10 | ||
var form = document.createElement("form"), | ||
parentNode = event.target.parentNode, ref = event.target.nextSibling; | ||
form.appendChild(event.target); | ||
form.reset(); | ||
parentNode.insertBefore(event.target, ref); | ||
} | ||
} | ||
}; | ||
@@ -462,3 +489,8 @@ | ||
_removeInputElement(); | ||
callbacks = {}, uploadedFiles = [], readyCallbacks = []; | ||
for (var id in communicators) { | ||
if (communicators.hasOwnProperty(id)) { | ||
communicators[id].abort = true; | ||
} | ||
} | ||
callbacks = null, uploadedFiles = null, readyCallbacks = null, communicators = null; | ||
}; | ||
@@ -564,4 +596,5 @@ | ||
}); | ||
communicators[data.id].abort = true; | ||
}); | ||
} | ||
})); |
@@ -7,9 +7,10 @@ /* Socket IO File Upload Client-Side Library | ||
(function(h,d,f){"function"===typeof define&&define.amd?define(d,f):h[d]=f()})(this,"SocketIOFileUpload",function(){return function(h){var d=this;if(!window.File||!window.FileReader)throw Error("Socket.IO File Upload: Browser Not Supported");var f={},p=[],u=[];d.fileInputElementId="siofu_input";d.useText=!1;d.serializedOctets=!1;d.useBuffer=!0;d.chunkSize=102400;var q=function(a,b){var c=document.createEvent("Event");c.initEvent(a,!1,!1);for(var x in b)b.hasOwnProperty(x)&&(c[x]=b[x]);return d.dispatchEvent(c)}, | ||
n=[],e=function(a,b,c,d){a.addEventListener(b,c,d);n.push(arguments)},r=function(a,b,c,d){a.removeEventListener&&a.removeEventListener(b,c,d)},y=function(){for(var a=n.length-1;0<=a;a--)r.apply(this,n[a]);n=[]},z=function(a){if(null!==d.maxFileSize&&a.size>d.maxFileSize)q("error",{file:a,message:"Attempt by client to upload file exceeding the maximum file size",code:1});else if(q("start",{file:a})){var b=new FileReader,c=p.length,f=d.useText,t=0,n;p.push(a);var v=d.chunkSize;if(v>=a.size||0>=v)v= | ||
a.size;var w=function(){var c=a.slice(t,Math.min(t+v,a.size));f?b.readAsText(c):b.readAsArrayBuffer(c)},l=function(e){var l=Math.min(t+v,a.size);a:{var p=t;e=e.target.result;var u=!1;if(!f)try{var m=new Uint8Array(e);if(d.serializedOctets)e=m;else if(d.useBuffer)e=m.buffer;else{var u=!0,k,r=m.buffer.byteLength,g="";for(k=0;k<r;k+=3)g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m[k]>>2],g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m[k]&3)<<4|m[k+1]>> | ||
4],g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m[k+1]&15)<<2|m[k+2]>>6],g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m[k+2]&63];2===r%3?g=g.substring(0,g.length-1)+"=":1===r%3&&(g=g.substring(0,g.length-2)+"==");e=g}}catch(y){h.emit("siofu_done",{id:c,interrupt:!0});break a}h.emit("siofu_progress",{id:c,size:a.size,start:p,end:l,content:e,base64:u})}q("progress",{file:a,bytesLoaded:l,name:n});t+=v;t<a.size?w():(h.emit("siofu_done",{id:c}),q("load", | ||
{file:a,reader:b,name:n}))};e(b,"load",l);e(b,"error",function(){h.emit("siofu_done",{id:c,interrupt:!0});r(b,"load",l)});e(b,"abort",function(){h.emit("siofu_done",{id:c,interrupt:!0});r(b,"load",l)});h.emit("siofu_start",{name:a.name,mtime:a.lastModifiedDate,meta:a.meta,size:a.size,encoding:f?"text":"octet",id:c});u.push(function(a){n=a;w()})}},w=function(a){if(0!==a.length){for(var b=0;b<a.length;b++)a[b].meta||(a[b].meta={});if(q("choose",{files:a}))for(b=0;b<a.length;b++)z(a[b])}},l=function(a){var b= | ||
a.target.files||a.dataTransfer.files;a.preventDefault();w(b)};this.submitFiles=function(a){a&&w(a)};this.listenOnSubmit=function(a,b){b.files&&e(a,"click",function(){w(b.files)},!1)};this.listenOnArraySubmit=function(a,b){for(var c in b)this.listenOnSubmit(a,b[c])};this.listenOnInput=function(a){a.files&&e(a,"change",l,!1)};this.listenOnDrop=function(a){e(a,"dragover",function(a){a.preventDefault()},!1);e(a,"drop",l)};this.prompt=function(){var a;a=document.getElementById(d.fileInputElementId);a|| | ||
(a=document.createElement("input"),a.setAttribute("type","file"),a.setAttribute("id",d.fileInputElementId),a.style.display="none",document.body.appendChild(a));e(a,"change",l,!1);var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null);a.dispatchEvent(b)};this.destroy=function(){y();var a=document.getElementById(d.fileInputElementId);a&&a.parentNode.removeChild(a);f={};p=[];u=[]};this.addEventListener=function(a,b){f[a]||(f[a]=[]);f[a].push(b)}; | ||
this.removeEventListener=function(a,b){if(!f[a])return!1;for(var c=0;c<f[a].length;c++)if(f[a][c]===b)return f[a].splice(c,1),!0;return!1};this.dispatchEvent=function(a){var b=f[a.type];if(!b)return!0;for(var c=!0,d=0;d<b.length;d++)!1===b[d](a)&&(c=!1);return c};e(h,"siofu_ready",function(a){u[a.id](a.name)});e(h,"siofu_complete",function(a){q("complete",{file:p[a.id],detail:a.detail,success:a.success})});e(h,"siofu_error",function(a){q("error",{file:p[a.id],message:a.message,code:0})})}}); | ||
(function(g,d,f){"function"===typeof define&&define.amd?define(d,f):"object"===typeof module&&module.exports?module.exports=f():g[d]=f()})(this,"SocketIOFileUpload",function(){return function(g){var d=this;if(!window.File||!window.FileReader)throw Error("Socket.IO File Upload: Browser Not Supported");var f={},q=[],u=[],k={};d.fileInputElementId="siofu_input";d.resetFileInputs=!0;d.useText=!1;d.serializedOctets=!1;d.useBuffer=!0;d.chunkSize=102400;d.chunkDelay=0;var r=function(a,b){var c=document.createEvent("Event"); | ||
c.initEvent(a,!1,!1);for(var y in b)b.hasOwnProperty(y)&&(c[y]=b[y]);return d.dispatchEvent(c)},p=[],e=function(a,b,c,d){a.addEventListener(b,c,d);p.push(arguments)},t=function(a,b,c,d){a.removeEventListener&&a.removeEventListener(b,c,d)},z=function(){for(var a=p.length-1;0<=a;a--)t.apply(this,p[a]);p=[]},A=function(a){if(null!==d.maxFileSize&&a.size>d.maxFileSize)r("error",{file:a,message:"Attempt by client to upload file exceeding the maximum file size",code:1});else if(r("start",{file:a})){var b= | ||
new FileReader,c=q.length,f=d.useText,v=0,k;b._realReader&&(b=b._realReader);q.push(a);var p={id:c},w=d.chunkSize;if(w>=a.size||0>=w)w=a.size;var x=function(){if(!p.abort){var c=a.slice(v,Math.min(v+w,a.size));f?b.readAsText(c):b.readAsArrayBuffer(c)}},l=function(e){if(!p.abort){var l=Math.min(v+w,a.size);a:{var q=v;e=e.target.result;var u=!1;if(!f)try{var m=new Uint8Array(e);if(d.serializedOctets)e=m;else if(d.useBuffer)e=m.buffer;else{var u=!0,n,t=m.buffer.byteLength,h="";for(n=0;n<t;n+=3)h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m[n]>> | ||
2],h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m[n]&3)<<4|m[n+1]>>4],h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m[n+1]&15)<<2|m[n+2]>>6],h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m[n+2]&63];2===t%3?h=h.substring(0,h.length-1)+"=":1===t%3&&(h=h.substring(0,h.length-2)+"==");e=h}}catch(z){g.emit("siofu_done",{id:c,interrupt:!0});break a}g.emit("siofu_progress",{id:c,size:a.size,start:q,end:l,content:e,base64:u})}r("progress", | ||
{file:a,bytesLoaded:l,name:k});v+=w;v<a.size?setTimeout(x,d.chunkDelay):(g.emit("siofu_done",{id:c}),r("load",{file:a,reader:b,name:k}))}};e(b,"load",l);e(b,"error",function(){g.emit("siofu_done",{id:c,interrupt:!0});t(b,"load",l)});e(b,"abort",function(){g.emit("siofu_done",{id:c,interrupt:!0});t(b,"load",l)});g.emit("siofu_start",{name:a.name,mtime:a.lastModifiedDate,meta:a.meta,size:a.size,encoding:f?"text":"octet",id:c});u.push(function(a){k=a;x()});return p}},x=function(a){if(0!==a.length){for(var b= | ||
0;b<a.length;b++)a[b].meta||(a[b].meta={});if(r("choose",{files:a}))for(b=0;b<a.length;b++){var c=A(a[b]);k[c.id]=c}}},l=function(a){var b=a.target.files||a.dataTransfer.files;a.preventDefault();x(b);if(d.resetFileInputs){try{a.target.value=""}catch(e){}if(a.target.value){var b=document.createElement("form"),c=a.target.parentNode,f=a.target.nextSibling;b.appendChild(a.target);b.reset();c.insertBefore(a.target,f)}}};this.submitFiles=function(a){a&&x(a)};this.listenOnSubmit=function(a,b){b.files&&e(a, | ||
"click",function(){x(b.files)},!1)};this.listenOnArraySubmit=function(a,b){for(var c in b)this.listenOnSubmit(a,b[c])};this.listenOnInput=function(a){a.files&&e(a,"change",l,!1)};this.listenOnDrop=function(a){e(a,"dragover",function(a){a.preventDefault()},!1);e(a,"drop",l)};this.prompt=function(){var a;a=document.getElementById(d.fileInputElementId);a||(a=document.createElement("input"),a.setAttribute("type","file"),a.setAttribute("id",d.fileInputElementId),a.style.display="none",document.body.appendChild(a)); | ||
e(a,"change",l,!1);var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null);a.dispatchEvent(b)};this.destroy=function(){z();var a=document.getElementById(d.fileInputElementId);a&&a.parentNode.removeChild(a);for(var b in k)k.hasOwnProperty(b)&&(k[b].abort=!0);k=u=q=f=null};this.addEventListener=function(a,b){f[a]||(f[a]=[]);f[a].push(b)};this.removeEventListener=function(a,b){if(!f[a])return!1;for(var c=0;c<f[a].length;c++)if(f[a][c]===b)return f[a].splice(c, | ||
1),!0;return!1};this.dispatchEvent=function(a){var b=f[a.type];if(!b)return!0;for(var c=!0,d=0;d<b.length;d++)!1===b[d](a)&&(c=!1);return c};e(g,"siofu_ready",function(a){u[a.id](a.name)});e(g,"siofu_complete",function(a){r("complete",{file:q[a.id],detail:a.detail,success:a.success})});e(g,"siofu_error",function(a){r("error",{file:q[a.id],message:a.message,code:0});k[a.id].abort=!0})}}); |
{ | ||
"name": "socketio-file-upload", | ||
"version": "0.4.6", | ||
"version": "0.4.7", | ||
"description": "Uploads files to a Node.JS server using Socket.IO", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -77,4 +77,6 @@ Socket.IO File Upload | ||
- [instance.destroy()](#instancedestroy) | ||
- [instance.resetFileInputs = true](#instanceresetFileInputs--true) | ||
- [instance.maxFileSize = null](#instancemaxfilesize--null) | ||
- [instance.chunkSize = 100 KiB](#instancechunksize--100-kib) | ||
- [instance.chunkDelay = 0 ms](#instancechunkdelay--0-ms) | ||
- [instance.useText = false](#instanceusetext--false) | ||
@@ -94,2 +96,3 @@ - [instance.useBuffer = true](#instanceusebuffer--true) | ||
- [instance.listen(socket)](#instancelistensocket) | ||
- [instance.abort(id, socket)](#instanceabortid-socket) | ||
- [instance.dir = "/path/to/upload/directory"](#instancedir--pathtouploaddirectory) | ||
@@ -251,2 +254,6 @@ - [instance.mode = "0666"](#instancemode--0666) | ||
#### instance.resetFileInputs = true | ||
Defaults to `true`, which resets file input elements to their empty state after the user selects a file. If you do not reset the file input elements, if the user selects a file with the same name as the previous file, then the second file may not be uploaded. | ||
#### instance.maxFileSize = null | ||
@@ -278,2 +285,6 @@ | ||
#### instance.chunkDelay = 0 ms | ||
The delay between reading chunks from the file. Defaults to 0 ms, or no delay. Set this to a larger number in order to put time between reading chunks, which might be useful for rate-limiting, for example. | ||
#### instance.useText = false | ||
@@ -424,2 +435,14 @@ | ||
#### instance.abort(id, socket) | ||
Aborts an upload that is in progress. Example use case: | ||
```javascript | ||
uploader.on("start", function(event){ | ||
if (/\.exe$/.test(event.file.name)) { | ||
uploader.abort(event.file.id, socket); | ||
} | ||
}); | ||
``` | ||
#### instance.dir = "/path/to/upload/directory" | ||
@@ -426,0 +449,0 @@ |
112
server.js
@@ -79,2 +79,8 @@ /* | ||
var fileInfo = files[id]; | ||
// Check if the upload was aborted | ||
if (!fileInfo) { | ||
return; | ||
} | ||
socket.emit("siofu_complete", { | ||
@@ -154,9 +160,18 @@ id: id, | ||
var fileInfo = files[data.id]; | ||
// Check if the upload was aborted | ||
if (!fileInfo) { | ||
return; | ||
} | ||
try { | ||
if (fileInfo.writeStream) { | ||
fileInfo.writeStream.end(); | ||
// Update the file modified time. This doesn't seem to work; I'm not | ||
// sure if it's my error or a bug in Node. | ||
fs.utimes(fileInfo.pathName, new Date(), fileInfo.mtime, function (err) { | ||
// Check if the upload was aborted | ||
if (!files[data.id]) { | ||
return; | ||
} | ||
// I'm not sure what arguments the futimes callback is passed. | ||
@@ -171,2 +186,4 @@ // Based on node_file.cc, it looks like it is passed zero | ||
console.log(err); | ||
_cleanupFile(data.id); | ||
return; | ||
} | ||
@@ -179,2 +196,3 @@ | ||
_emitComplete(socket, data.id, fileInfo.success); | ||
_cleanupFile(data.id); | ||
}); | ||
@@ -184,2 +202,3 @@ } | ||
_emitComplete(socket, data.id, fileInfo.success); | ||
_cleanupFile(data.id); | ||
} | ||
@@ -204,2 +223,8 @@ } | ||
var fileInfo = files[data.id], buffer; | ||
// Check if the upload was aborted | ||
if (!fileInfo) { | ||
return; | ||
} | ||
try { | ||
@@ -227,2 +252,3 @@ if (data.base64) { | ||
}); | ||
_cleanupFile(data.id); | ||
} | ||
@@ -279,2 +305,7 @@ else { | ||
// Abort right now if the "start" event aborted the file upload. | ||
if (!files[data.id]) { | ||
return; | ||
} | ||
// If we're not saving the file, we are ready to start receiving data now. | ||
@@ -291,2 +322,7 @@ if (!self.dir) { | ||
_findFileName(fileInfo, function (err, newBase, pathName) { | ||
// Check if the upload was aborted | ||
if (!files[data.id]) { | ||
return; | ||
} | ||
if (err) { | ||
@@ -299,2 +335,3 @@ _emitComplete(socket, data.id, false); | ||
}); | ||
_cleanupFile(data.id); | ||
return; | ||
@@ -312,2 +349,7 @@ } | ||
writeStream.on("open", function () { | ||
// Check if the upload was aborted | ||
if (!files[data.id]) { | ||
return; | ||
} | ||
socket.emit("siofu_ready", { | ||
@@ -319,2 +361,7 @@ id: data.id, | ||
writeStream.on("error", function (err) { | ||
// Check if the upload was aborted | ||
if (!files[data.id]) { | ||
return; | ||
} | ||
_emitComplete(socket, data.id, false); | ||
@@ -326,2 +373,3 @@ self.emit("error", { | ||
}); | ||
_cleanupFile(data.id); | ||
}); | ||
@@ -337,2 +385,3 @@ files[data.id].writeStream = writeStream; | ||
}); | ||
_cleanupFile(data.id); | ||
return; | ||
@@ -345,3 +394,33 @@ } | ||
var _cleanupFile = function (id) { | ||
var fileInfo = files[id]; | ||
if (fileInfo.writeStream) { | ||
fileInfo.writeStream.end(); | ||
} | ||
delete files[id]; | ||
} | ||
/** | ||
* Private function to handle a client disconnect event. | ||
* @param {Socket} socket The socket on which the listener is bound | ||
* @return {Function} A function compatible with a Socket.IO callback | ||
*/ | ||
var _onDisconnect = function (socket) { | ||
return function () { | ||
for (var id in files) { | ||
if (files.hasOwnProperty(id)) { | ||
var fileInfo = files[id]; | ||
self.emit("error", { | ||
file: fileInfo, | ||
error: new Error("Client disconnected in the middle of an upload"), | ||
memo: "disconnect during upload" | ||
}); | ||
_cleanupFile(id); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* Public method. Listen to a Socket.IO socket for a file upload event | ||
@@ -357,3 +436,30 @@ * emitted from the client-side library. | ||
socket.on("siofu_done", _uploadDone(socket)); | ||
socket.on("disconnect", _onDisconnect(socket)); | ||
}; | ||
/** | ||
* Public method. Abort an upload that may be in progress. Throws an | ||
* exception if the specified file upload is not in progress. | ||
* | ||
* @param {String} id The ID of the file upload to abort. | ||
* @param {Socket} socket The socket that this instance is connected to. | ||
* @return {void} | ||
*/ | ||
this.abort = function (id, socket) { | ||
if (!socket) { | ||
throw new Error("Please pass the socket instance as the second argument to abort()"); | ||
} | ||
var fileInfo = files[id]; | ||
if (!fileInfo) { | ||
throw new Error("File with specified ID does not exist: " + id); | ||
} | ||
fileInfo.success = false; | ||
socket.emit("siofu_error", { | ||
id: id, | ||
message: "File upload aborted by server" | ||
}); | ||
_cleanupFile(id); | ||
} | ||
} | ||
@@ -376,3 +482,3 @@ util.inherits(SocketIOFileUploadServer, EventEmitter); | ||
fs.readFile(__dirname + "/client.min.js", function (err, data) { | ||
fs.readFile(__dirname + "/client.js", function (err, data) { | ||
if (err) throw err; | ||
@@ -379,0 +485,0 @@ res.writeHead(200, { |
60438
1020
661