├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── demo ├── demo.css ├── demo.js ├── index.html ├── jquery.js ├── package.json └── server.js ├── js └── jquery.tus.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - npm install 3 | language: node_js 4 | node_js: 5 | - '0.10' 6 | script: 7 | - npm test 8 | 9 | cache: 10 | directories: 11 | - node_modules 12 | sudo: false 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Transloadit Ltd and Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: Deprecated Project 2 | 3 | tus-jquery-client is not maintained anymore and no support is available. 4 | Please use [tus-js-client](https://github.com/tus/tus-js-client) for a modern 5 | tus client for browsers. More implementations for different environments can 6 | be found on [tus.io](https://tus.io/implementations.html). 7 | 8 | ## tus-jquery-client 9 | [![Build Status](https://travis-ci.org/tus/tus-jquery-client.svg?branch=master)](https://travis-ci.org/tus/tus-jquery-client) 10 | 11 | A jQuery client implementing the [tus resumable upload 12 | protocol](https://github.com/tus/tus-resumable-upload-protocol). 13 | If you looking for a browser client without the need of jQuery, you 14 | may enjoy [tus-js-client](https://github.com/tus/tus-js-client). 15 | 16 | ## Example 17 | 18 | The code below outlines how the API could work. 19 | 20 | ```js 21 | $('input[type=file]').change(function() { 22 | var options = { endpoint: 'http://localhost:1080/files' }; 23 | var input = $(this); 24 | 25 | tus 26 | .upload(this.files[0], options) 27 | .fail(function(error) { 28 | console.log('upload failed', error); 29 | }) 30 | .always(function() { 31 | input.val(''); 32 | }) 33 | .progress(function(e, bytesUploaded, bytesTotal) { 34 | console.log(bytesUploaded, bytesTotal); 35 | }) 36 | .done(function(url, file) { 37 | console.log(url); 38 | console.log(file.name); 39 | }); 40 | }); 41 | ``` 42 | 43 | ## Try the demo 44 | 45 | Without installing anything, you can testdrive over at the 46 | [tus.io](http://www.tus.io/demo.html) website. 47 | 48 | But for local development, here's how to run the repo-included demo: 49 | 50 | - Install a tusd server to accept the upload on http://127.0.0.1:1080 51 | as instructed [here](https://github.com/tus/tusd/blob/master/README.md). 52 | - Install node.js to serve the demo from http://127.0.0.1:8080 53 | (osx: `brew install nodejs`) 54 | - Install & run the demo 55 | 56 | ```bash 57 | cd demo 58 | npm install 59 | node server.js 60 | ``` 61 | 62 | - Point your browser to http://localhost:8080 63 | 64 | ## License 65 | 66 | This project is licensed under the MIT license, see `LICENSE.txt`. 67 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 40px; 3 | } 4 | 5 | .progress { 6 | height: 32px; 7 | } 8 | 9 | a.btn { 10 | margin-bottom: 2px; 11 | } 12 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var upload = null; 3 | 4 | $('.js-stop').click(function(e) { 5 | e.preventDefault(); 6 | 7 | if (upload) { 8 | upload.stop(); 9 | } 10 | }); 11 | 12 | $('input[type=file]').change(function() { 13 | var $input = $(this); 14 | var $parent = $input.parent(); 15 | var file = this.files[0]; 16 | console.log('selected file', file); 17 | 18 | $('.js-stop').removeClass('disabled'); 19 | 20 | var options = { 21 | endpoint: 'http://localhost:1080/files/', 22 | resetBefore: $('#reset_before').prop('checked'), 23 | resetAfter: false, 24 | metadata: { 25 | name: file.name, 26 | type: file.type 27 | } 28 | }; 29 | 30 | $('.progress').addClass('active'); 31 | 32 | upload = tus.upload(file, options) 33 | .fail(function(error) { 34 | alert('Failed because: ' + error); 35 | }) 36 | .always(function() { 37 | $input.val(''); 38 | $('.js-stop').addClass('disabled'); 39 | $('.progress').removeClass('active'); 40 | }) 41 | .progress(function(e, bytesUploaded, bytesTotal) { 42 | var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2); 43 | $('.progress .bar').css('width', percentage + '%'); 44 | console.log(bytesUploaded, bytesTotal, percentage + '%'); 45 | }) 46 | .done(function(url, file) { 47 | var $download = $('Download ' + file.name + ' (' + file.size + ' bytes)
').appendTo($parent); 48 | $download.attr('href', url); 49 | $download.addClass('btn').addClass('btn-success'); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tus-jquery-client demo 6 | 7 | 8 | 9 | 10 |
11 |

tus-jquery-client demo

12 | 13 |

14 | For a prettier demo please go to the 15 | tus.io website. 16 | This demo is just here to aid developers. 17 |

18 | 19 |
20 | 21 |
22 | 26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |

Uploads

42 |

43 | Succesful uploads will be listed here. Try one! 44 |

45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tus-jquery-client-demo", 3 | "version": "0.0.0", 4 | "description": "A demo for the tus jquery client.", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "repository": "git://github.com/tus/tus-jquery-client.git", 11 | "author": "Felix Geisendörfer ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "send": "~0.1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var send = require('send'); 3 | var port = process.env.PORT || 8080; 4 | 5 | http.createServer(function(req, res) { 6 | if (req.url === '/') { 7 | res.writeHead(301, {'Location': '/demo/index.html'}); 8 | res.end(); 9 | return; 10 | } 11 | 12 | send(req, req.url) 13 | .root(__dirname+'/../') 14 | .pipe(res); 15 | }).listen(port); 16 | 17 | console.log('Demo running at http://localhost:' + port + '/'); 18 | console.log('Make sure to run a tusd server at http://localhost:1080/ for this to work'); 19 | -------------------------------------------------------------------------------- /js/jquery.tus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tus-jquery-client 3 | * https://github.com/tus/tus-jquery-client 4 | * 5 | * Copyright (c) 2013-2015 Transloadit Ltd and Contributors 6 | * http://tus.io/ 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | (function (factory) { 13 | if(typeof module === "object" && typeof module.exports === "object") { 14 | module.exports = factory(require("jquery"), window, document); 15 | } else { 16 | window.tus = factory(jQuery, window, document); 17 | } 18 | }(function($, window, document, undefined) { 19 | 'use strict'; 20 | 21 | // taken from https://github.com/23/resumable.js/blob/master/resumable.js 22 | var support = ((typeof(File) !== 'undefined') && (typeof(Blob) !== 'undefined') && 23 | (typeof(FileList)!=='undefined') && 24 | (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false) 25 | ); 26 | if(!support){ 27 | // not supported in browser 28 | return; 29 | } 30 | 31 | // The Public API 32 | var tus = { 33 | upload: function(file, options) { 34 | var upload = new ResumableUpload(file, options); 35 | if (file) { 36 | upload._start(); 37 | } 38 | return upload; 39 | }, 40 | fingerprint: function(file) { 41 | return 'tus-' + file.name + '-' + file.type + '-' + file.size; 42 | } 43 | }; 44 | 45 | function ResumableUpload(file, options) { 46 | // The file to upload 47 | this.file = file; 48 | // Options for resumable file uploads 49 | this.options = { 50 | // The tus upload endpoint url 51 | endpoint: options.endpoint, 52 | 53 | // The fingerprint for the file. 54 | // Uses our own fingerprinting if undefined. 55 | fingerprint: options.fingerprint, 56 | 57 | // @TODO: second option: resumable: true/false 58 | // false -> removes resume functionality 59 | resumable: options.resumable !== undefined ? options.resetBefore : true, 60 | resetBefore: options.resetBefore, 61 | resetAfter: options.resetAfter, 62 | headers: options.headers !== undefined ? options.headers : {}, 63 | chunkSize: options.chunkSize, 64 | 65 | // Optional metadata about the uploading file 66 | metadata: options.metadata || {} 67 | }; 68 | 69 | // Add tus version to headers 70 | this.options.headers["Tus-Resumable"] = "1.0.0"; 71 | 72 | // The url of the uploaded file, assigned by the tus upload endpoint 73 | this.fileUrl = null; 74 | 75 | // Bytes sent to the server so far 76 | this.bytesWritten = null; 77 | 78 | // @TODO Add this.bytesTotal again 79 | 80 | // the jqXHR object 81 | this._jqXHR = null; 82 | 83 | // Create a deferred and make our upload a promise object 84 | this._deferred = $.Deferred(); 85 | this._deferred.promise(this); 86 | } 87 | 88 | // Creates a file resource at the configured tus endpoint and gets the url for it. 89 | ResumableUpload.prototype._start = function() { 90 | var self = this; 91 | 92 | // Optionally resetBefore 93 | if (!self.options.resumable || self.options.resetBefore === true) { 94 | self._urlCache(false); 95 | } 96 | 97 | if (!(self.fileUrl = self._urlCache())) { 98 | self._post(); 99 | } else { 100 | self._head(); 101 | } 102 | }; 103 | 104 | ResumableUpload.prototype._post = function() { 105 | var self = this; 106 | var headers = $.extend({ 107 | 'Upload-Length': self.file.size 108 | }, self.options.headers); 109 | 110 | var metadataHeader = this._generateMetadata(); 111 | if (metadataHeader.length > 0) { 112 | headers['Upload-Metadata'] = metadataHeader; 113 | } 114 | 115 | var options = { 116 | type: 'POST', 117 | url: self.options.endpoint, 118 | headers: headers 119 | }; 120 | 121 | $.ajax(options) 122 | .fail(function(jqXHR, textStatus, errorThrown) { 123 | // @todo: Implement retry support 124 | self._emitFail('Could not post to file resource ' + 125 | self.options.endpoint + '. ' + textStatus); 126 | }) 127 | .done(function(data, textStatus, jqXHR) { 128 | var location = jqXHR.getResponseHeader('Location'); 129 | if (!location) { 130 | return self._emitFail('Could not get url for file resource. ' + textStatus); 131 | } 132 | 133 | self.fileUrl = location; 134 | self._uploadFile(0); 135 | }); 136 | }; 137 | 138 | ResumableUpload.prototype._head = function() { 139 | var self = this; 140 | var options = { 141 | type: 'HEAD', 142 | url: this.fileUrl, 143 | cache: false, 144 | headers: self.options.headers 145 | }; 146 | 147 | console.log('Resuming known url ' + this.fileUrl); 148 | $.ajax(options) 149 | .fail(function(jqXHR, textStatus, errorThrown) { 150 | // @TODO: Implement retry support 151 | if(jqXHR.status == 404){ 152 | // not valid, not on server, start with post request and restart 153 | // upload 154 | self._post(); 155 | }else{ 156 | self._emitFail('Could not head at file resource: ' + textStatus); 157 | } 158 | }) 159 | .done(function(data, textStatus, jqXHR) { 160 | var offset = jqXHR.getResponseHeader('Upload-Offset'); 161 | var bytesWritten = offset ? parseInt(offset, 10) : 0; 162 | self._uploadFile(bytesWritten); 163 | }); 164 | }; 165 | 166 | // Uploads the file data to tus resource url created by _start() 167 | ResumableUpload.prototype._uploadFile = function(range_from) { 168 | var self = this; 169 | this.bytesWritten = range_from; 170 | 171 | if (this.bytesWritten === this.file.size) { 172 | // Cool, we already completely uploaded this. 173 | // Update progress to 100%. 174 | this._emitProgress(); 175 | return this._emitDone(); 176 | } 177 | 178 | this._urlCache(self.fileUrl); 179 | this._emitProgress(); 180 | 181 | var bytesWrittenAtStart = this.bytesWritten; 182 | 183 | var range_to = self.file.size; 184 | if(self.options.chunkSize){ 185 | range_to = Math.min(range_to, range_from + self.options.chunkSize); 186 | } 187 | 188 | var slice = self.file.slice || self.file.webkitSlice || self.file.mozSlice; 189 | var blob = slice.call(self.file, range_from, range_to, self.file.type); 190 | var xhr = $.ajaxSettings.xhr(); 191 | 192 | var headers = $.extend({ 193 | 'Upload-Offset': range_from, 194 | 'Content-Type': 'application/offset+octet-stream' 195 | }, self.options.headers); 196 | 197 | var options = { 198 | type: 'PATCH', 199 | url: self.fileUrl, 200 | data: blob, 201 | processData: false, 202 | contentType: self.file.type, 203 | cache: false, 204 | xhr: function() { 205 | return xhr; 206 | }, 207 | headers: headers 208 | }; 209 | 210 | $(xhr.upload).bind('progress', function(e) { 211 | self.bytesWritten = bytesWrittenAtStart + e.originalEvent.loaded; 212 | self._emitProgress(e); 213 | }); 214 | 215 | this._jqXHR = $.ajax(options) 216 | .fail(function(jqXHR, textStatus, errorThrown) { 217 | // @TODO: Compile somewhat meaningful error 218 | // Needs to be cleaned up 219 | // Needs to have retry 220 | var msg = jqXHR.responseText || textStatus || errorThrown; 221 | self._emitFail(msg); 222 | }) 223 | .done(function() { 224 | if(range_to === self.file.size){ 225 | console.log('done', arguments, self, self.fileUrl); 226 | 227 | if (self.options.resetAfter === true) { 228 | self._urlCache(false); 229 | } 230 | 231 | self._emitDone(); 232 | }else{ 233 | // still have more to upload 234 | self._uploadFile(range_to); 235 | } 236 | }); 237 | }; 238 | 239 | ResumableUpload.prototype.stop = function() { 240 | if (this._jqXHR) { 241 | this._jqXHR.abort(); 242 | } 243 | }; 244 | 245 | ResumableUpload.prototype._emitProgress = function(e) { 246 | this._deferred.notifyWith(this, [e, this.bytesWritten, this.file.size]); 247 | }; 248 | 249 | ResumableUpload.prototype._emitDone = function() { 250 | this._deferred.resolveWith(this, [this.fileUrl, this.file]); 251 | }; 252 | 253 | ResumableUpload.prototype._emitFail = function(err) { 254 | this._deferred.rejectWith(this, [err]); 255 | }; 256 | 257 | ResumableUpload.prototype._urlCache = function(url) { 258 | var fingerPrint = this.options.fingerprint; 259 | if (fingerPrint === undefined) { 260 | fingerPrint = tus.fingerprint(this.file); 261 | } 262 | 263 | if (url === false) { 264 | console.log('Resetting any known cached url for ' + this.file.name); 265 | return localStorage.removeItem(fingerPrint); 266 | } 267 | 268 | if (url) { 269 | var result = false; 270 | try { 271 | result = localStorage.setItem(fingerPrint, url); 272 | } catch (e) { 273 | // most likely quota exceeded error 274 | } 275 | 276 | return result; 277 | } 278 | 279 | return localStorage.getItem(fingerPrint); 280 | }; 281 | 282 | ResumableUpload.prototype._generateMetadata = function() { 283 | var elements = []; 284 | var metadata = this.options.metadata; 285 | 286 | for (var key in metadata) { 287 | if (metadata.hasOwnProperty(key)) { 288 | elements.push(key + " " + btoa(metadata[key])); 289 | } 290 | } 291 | 292 | return elements.join(","); 293 | }; 294 | 295 | return tus; 296 | })); 297 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tus-jquery", 3 | "version": "1.0.0", 4 | "description": "A jquery plugin implementing the tus resumable upload protocol.", 5 | "main": "js/jquery.tus.js", 6 | "scripts": { 7 | "test": "jshint js/jquery.tus.js --show-non-errors" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/tus/tus-jquery-client.git" 12 | }, 13 | "keywords": [ 14 | "tus", 15 | "resumable", 16 | "upload", 17 | "browser", 18 | "browserify", 19 | "jquery", 20 | "jquery-plugin" 21 | ], 22 | "author": "", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/tus/tus-jquery-client/issues" 26 | }, 27 | "homepage": "https://github.com/tus/tus-jquery-client", 28 | "dependencies": { 29 | "jquery": "2.1.4" 30 | }, 31 | "devDependencies": { 32 | "jshint": "2.8.0" 33 | } 34 | } 35 | --------------------------------------------------------------------------------