├── .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 | [](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 |
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 |
--------------------------------------------------------------------------------