diff --git a/.gitignore b/.gitignore index fb2ac3c1..e0027aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules demos/reactnative/.expo lib.es5 dist +.DS_Store diff --git a/lib/browser/storage.js b/lib/browser/storage.js index ec5c3bbb..4c66fe3f 100644 --- a/lib/browser/storage.js +++ b/lib/browser/storage.js @@ -22,17 +22,23 @@ try { export const canStoreURLs = hasStorage; -export function setItem(key, value) { - if (!hasStorage) return; - return localStorage.setItem(key, value); -} +class LocalStorage { + setItem(key, value, cb) { + if (!hasStorage) return cb(); + cb(null, localStorage.setItem(key, value)); + } -export function getItem(key) { - if (!hasStorage) return; - return localStorage.getItem(key); + getItem(key, cb) { + if (!hasStorage) return cb(); + cb(null, localStorage.getItem(key)); + } + + removeItem(key, cb) { + if (!hasStorage) return cb(); + cb(null, localStorage.removeItem(key)); + } } -export function removeItem(key) { - if (!hasStorage) return; - return localStorage.removeItem(key); +export function getStorage() { + return new LocalStorage(); } diff --git a/lib/fingerprint.js b/lib/fingerprint.js index b35b616e..f578a35d 100644 --- a/lib/fingerprint.js +++ b/lib/fingerprint.js @@ -1,3 +1,5 @@ +import isReactNative from "./node/isReactNative"; + /** * Generate a fingerprint for a file which will be used the store the endpoint * @@ -5,6 +7,10 @@ * @return {String} */ export default function fingerprint(file, options) { + if (isReactNative) { + return reactNativeFingerprint(file, options); + } + return [ "tus", file.name, @@ -14,3 +20,28 @@ export default function fingerprint(file, options) { options.endpoint ].join("-"); } + +function reactNativeFingerprint(file, options) { + let exifHash = file.exif ? hashCode(JSON.stringify(file.exif)) : "noexif"; + return [ + "tus", + file.name || "noname", + file.size || "nosize", + exifHash, + options.endpoint + ].join("/"); +} + +function hashCode(str) { + // from https://stackoverflow.com/a/8831937/151666 + var hash = 0; + if (str.length === 0) { + return hash; + } + for (var i = 0; i < str.length; i++) { + var char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} diff --git a/lib/index.js b/lib/index.js index d0667870..9b75bf36 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,30 +1,32 @@ /* global window */ import Upload from "./upload"; -import {canStoreURLs} from "./node/storage"; +import * as storage from "./node/storage"; const {defaultOptions} = Upload; -let isSupported; + +const moduleExport = { + Upload, + canStoreURLs: storage.canStoreURLs, + defaultOptions +}; if (typeof window !== "undefined") { // Browser environment using XMLHttpRequest const {XMLHttpRequest, Blob} = window; - isSupported = ( + moduleExport.isSupported = ( XMLHttpRequest && Blob && typeof Blob.prototype.slice === "function" ); } else { // Node.js environment using http module - isSupported = true; + moduleExport.isSupported = true; + // make FileStorage module available as it will not be set by default. + moduleExport.FileStorage = storage.FileStorage; } // The usage of the commonjs exporting syntax instead of the new ECMAScript // one is actually inteded and prevents weird behaviour if we are trying to // import this module in another module using Babel. -module.exports = { - Upload, - isSupported, - canStoreURLs, - defaultOptions -}; +module.exports = moduleExport; diff --git a/lib/node/isReactNative.js b/lib/node/isReactNative.js new file mode 100644 index 00000000..18755f43 --- /dev/null +++ b/lib/node/isReactNative.js @@ -0,0 +1,3 @@ +const isReactNative = false; + +export default isReactNative; diff --git a/lib/node/storage.js b/lib/node/storage.js index aa085b37..6f17a81f 100644 --- a/lib/node/storage.js +++ b/lib/node/storage.js @@ -1,15 +1,111 @@ /* eslint no-unused-vars: 0 */ +import { readFile, writeFile } from "fs"; +import * as lockfile from "proper-lockfile"; -export const canStoreURLs = false; - -export function setItem(key, value) { +export const canStoreURLs = true; +export function getStorage() { + // don't support storage by default. + return null; } -export function getItem(key) { -} +export class FileStorage { + constructor(filePath) { + this.path = filePath; + } + + setItem(key, value, cb) { + lockfile.lock(this.path, this._lockfileOptions(), (err, release) => { + if (err) { + return cb(err); + } + + cb = this._releaseAndCb(release, cb); + this._getData((err, data) => { + if (err) { + return cb(err); + } + + data[key] = value; + this._writeData(data, (err) => cb(err)); + }); + }); + } + + getItem(key, cb) { + this._getData((err, data) => { + if (err) { + return cb(err); + } + cb(null, data[key]); + }); + } + + removeItem(key, cb) { + lockfile.lock(this.path, this._lockfileOptions(), (err, release) => { + if (err) { + return cb(err); + } + + cb = this._releaseAndCb(release, cb); + this._getData((err, data) => { + if (err) { + return cb(err); + } + + delete data[key]; + this._writeData(data, (err) => cb(err)); + }); + }); + } + + _lockfileOptions() { + return { + realpath: false, + retries: { + retries: 5, + minTimeout: 20 + } + }; + } + + _releaseAndCb(release, cb) { + return (err) => { + if (err) { + // @TODO consider combining error from release callback + release(() => cb(err)); + return; + } + + release(cb); + }; + } -export function removeItem(key) { + _writeData(data, cb) { + const opts = { + encoding: "utf8", + mode: 0o660, + flag: "w" + }; + writeFile(this.path, JSON.stringify(data), opts, (err) => cb(err)); + } + _getData(cb) { + readFile(this.path, "utf8", (err, data) => { + if (err) { + // return empty data if file does not exist + err.code === "ENOENT" ? cb(null, {}) : cb(err); + return; + } else { + try { + data = !data.trim().length ? {} : JSON.parse(data); + } catch (error) { + cb(error); + return; + } + cb(null, data); + } + }); + } } diff --git a/lib/upload.js b/lib/upload.js index eaeb2ba0..6dd439d2 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -6,9 +6,9 @@ import { Base64 } from "js-base64"; // We import the files used inside the Node environment which are rewritten // for browsers using the rules defined in the package.json -import {newRequest, resolveUrl} from "./node/request"; -import {getSource} from "./node/source"; -import * as Storage from "./node/storage"; +import { newRequest, resolveUrl } from "./node/request"; +import { getSource } from "./node/source"; +import { getStorage } from "./node/storage"; const defaultOptions = { endpoint: null, @@ -26,13 +26,18 @@ const defaultOptions = { overridePatchMethod: false, retryDelays: null, removeFingerprintOnSuccess: false, - uploadLengthDeferred: false + uploadLengthDeferred: false, + urlStorage: null, + fileReader: null }; class Upload { constructor(file, options) { this.options = extend(true, {}, defaultOptions, options); + // The storage module used to store URLs + this._storage = this.options.urlStorage; + // The underlying File/Blob object this.file = file; @@ -82,10 +87,15 @@ class Upload { return; } + if (this.options.resume && this._storage == null) { + this._storage = getStorage(); + } + if (this._source) { this._start(this._source); } else { - getSource(file, this.options.chunkSize, (err, source) => { + const fileReader = this.options.fileReader || getSource; + fileReader(file, this.options.chunkSize, (err, source) => { if (err) { this._emitError(err); return; @@ -193,27 +203,33 @@ class Upload { } // Try to find the endpoint for the file in the storage - if (this.options.resume) { + if (this._hasStorage()) { this._fingerprint = this.options.fingerprint(file, this.options); - let resumedUrl = Storage.getItem(this._fingerprint); + this._storage.getItem(this._fingerprint, (err, resumedUrl) => { + if (err) { + this._emitError(err); + return; + } - if (resumedUrl != null) { - this.url = resumedUrl; - this._resumeUpload(); - return; - } + if (resumedUrl != null) { + this.url = resumedUrl; + this._resumeUpload(); + } else { + this._createUpload(); + } + }); + } else { + // An upload has not started for the file yet, so we start a new one + this._createUpload(); } - - // An upload has not started for the file yet, so we start a new one - this._createUpload(); } abort() { if (this._xhr !== null) { this._xhr.abort(); this._source.close(); - this._aborted = true; } + this._aborted = true; if (this._retryTimeout != null) { clearTimeout(this._retryTimeout); @@ -221,6 +237,10 @@ class Upload { } } + _hasStorage() { + return this.options.resume && this._storage; + } + _emitXhrError(xhr, err, causingErr) { this._emitError(new DetailedError(err, causingErr, xhr)); } @@ -322,8 +342,12 @@ class Upload { return; } - if (this.options.resume) { - Storage.setItem(this._fingerprint, this.url); + if (this._hasStorage()) { + this._storage.setItem(this._fingerprint, this.url, (err) => { + if (err) { + this._emitError(err); + } + }); } this._offset = 0; @@ -363,10 +387,14 @@ class Upload { xhr.onload = () => { if (!inStatusCategory(xhr.status, 200)) { - if (this.options.resume && inStatusCategory(xhr.status, 400)) { + if (this.options.resume && this._storage && inStatusCategory(xhr.status, 400)) { // Remove stored fingerprint and corresponding endpoint, // on client errors since the file can not be found - Storage.removeItem(this._fingerprint); + this._storage.removeItem(this._fingerprint, (err) => { + if (err) { + this._emitError(err); + } + }); } // If the upload is locked (indicated by the 423 Locked status code), we diff --git a/package-lock.json b/package-lock.json index 0b70f365..37c72527 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,9 +56,9 @@ }, "dependencies": { "acorn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", - "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -119,6 +119,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, + "optional": true, "requires": { "micromatch": "^2.1.5", "normalize-path": "^2.0.0" @@ -138,6 +139,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -201,7 +203,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "arrify": { "version": "1.0.1", @@ -1046,6 +1049,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -2912,6 +2916,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, + "optional": true, "requires": { "anymatch": "^1.3.0", "async-each": "^1.0.0", @@ -3233,6 +3238,12 @@ "es5-ext": "^0.10.9" } }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3520,12 +3531,12 @@ } }, "detective": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", - "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", "dev": true, "requires": { - "acorn-node": "^1.3.0", + "acorn-node": "^1.6.1", "defined": "^1.0.0", "minimist": "^1.1.1" }, @@ -4094,6 +4105,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -4103,6 +4115,7 @@ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, + "optional": true, "requires": { "fill-range": "^2.1.0" } @@ -4149,6 +4162,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -4238,13 +4252,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "dev": true, + "optional": true }, "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, + "optional": true, "requires": { "is-number": "^2.1.0", "isobject": "^2.0.0", @@ -4311,6 +4327,7 @@ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, + "optional": true, "requires": { "for-in": "^1.0.1" } @@ -4371,9 +4388,9 @@ "dev": true }, "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", "dev": true, "optional": true, "requires": { @@ -4400,7 +4417,7 @@ "optional": true }, "are-we-there-yet": { - "version": "1.1.4", + "version": "1.1.5", "bundled": true, "dev": true, "optional": true, @@ -4426,7 +4443,7 @@ } }, "chownr": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true @@ -4465,7 +4482,7 @@ } }, "deep-extend": { - "version": "0.5.1", + "version": "0.6.0", "bundled": true, "dev": true, "optional": true @@ -4514,7 +4531,7 @@ } }, "glob": { - "version": "7.1.2", + "version": "7.1.3", "bundled": true, "dev": true, "optional": true, @@ -4534,12 +4551,12 @@ "optional": true }, "iconv-lite": { - "version": "0.4.21", + "version": "0.4.24", "bundled": true, "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { @@ -4604,17 +4621,17 @@ "optional": true }, "minipass": { - "version": "2.2.4", + "version": "2.3.5", "bundled": true, "dev": true, "optional": true, "requires": { - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.1.0", + "version": "1.2.1", "bundled": true, "dev": true, "optional": true, @@ -4638,7 +4655,7 @@ "optional": true }, "needle": { - "version": "2.2.0", + "version": "2.2.4", "bundled": true, "dev": true, "optional": true, @@ -4649,18 +4666,18 @@ } }, "node-pre-gyp": { - "version": "0.10.0", + "version": "0.10.3", "bundled": true, "dev": true, "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", - "needle": "^2.2.0", + "needle": "^2.2.1", "nopt": "^4.0.1", "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", - "rc": "^1.1.7", + "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", "tar": "^4" @@ -4677,13 +4694,13 @@ } }, "npm-bundled": { - "version": "1.0.3", + "version": "1.0.5", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.1.10", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true, @@ -4760,12 +4777,12 @@ "optional": true }, "rc": { - "version": "1.2.7", + "version": "1.2.8", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -4795,16 +4812,16 @@ } }, "rimraf": { - "version": "2.6.2", + "version": "2.6.3", "bundled": true, "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.1", + "version": "5.1.2", "bundled": true, "dev": true, "optional": true @@ -4822,7 +4839,7 @@ "optional": true }, "semver": { - "version": "5.5.0", + "version": "5.6.0", "bundled": true, "dev": true, "optional": true @@ -4875,17 +4892,17 @@ "optional": true }, "tar": { - "version": "4.4.1", + "version": "4.4.8", "bundled": true, "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.2" } }, @@ -4896,12 +4913,12 @@ "optional": true }, "wide-align": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "^1.0.2 || 2" } }, "wrappy": { @@ -4911,7 +4928,7 @@ "optional": true }, "yallist": { - "version": "3.0.2", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true @@ -4982,6 +4999,7 @@ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, + "optional": true, "requires": { "glob-parent": "^2.0.0", "is-glob": "^2.0.0" @@ -4992,6 +5010,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, + "optional": true, "requires": { "is-glob": "^2.0.0" } @@ -5044,8 +5063,7 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { "version": "2.0.0", @@ -5291,9 +5309,9 @@ "dev": true }, "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, "ignore": { @@ -5549,13 +5567,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true + "dev": true, + "optional": true }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, + "optional": true, "requires": { "is-primitive": "^2.0.0" } @@ -5570,7 +5590,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-finite": { "version": "1.0.2", @@ -5592,6 +5613,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -5601,6 +5623,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" } @@ -5650,13 +5673,15 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true + "dev": true, + "optional": true }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true + "dev": true, + "optional": true }, "is-promise": { "version": "2.1.0", @@ -5729,6 +5754,7 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, + "optional": true, "requires": { "isarray": "1.0.0" } @@ -6121,7 +6147,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.2.tgz", "integrity": "sha512-Bp2Bx2wFaUymE7pWi0bbldiheIXMvyzC3hRkT5YAv2qiqqJO5VB8KafgYgZmGCxkTmloLuAx3Jv2OmJ66990mg==", - "dev": true + "dev": true, + "optional": true }, "md5.js": { "version": "1.3.5", @@ -6156,6 +6183,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -6290,9 +6318,9 @@ }, "dependencies": { "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -6462,6 +6490,7 @@ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, + "optional": true, "requires": { "for-own": "^0.1.4", "is-extendable": "^0.1.1" @@ -6624,9 +6653,9 @@ "dev": true }, "pako": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", - "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "dev": true }, "parents": { @@ -6639,16 +6668,17 @@ } }, "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-glob": { @@ -6656,6 +6686,7 @@ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, + "optional": true, "requires": { "glob-base": "^0.3.0", "is-dotfile": "^1.0.0", @@ -6684,6 +6715,12 @@ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", "dev": true }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -6816,7 +6853,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true + "dev": true, + "optional": true }, "private": { "version": "0.1.8", @@ -6852,6 +6890,15 @@ "object-assign": "^4.1.1" } }, + "proper-lockfile": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz", + "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=", + "requires": { + "graceful-fs": "^4.1.2", + "retry": "^0.10.0" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -6914,6 +6961,7 @@ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", "dev": true, + "optional": true, "requires": { "is-number": "^4.0.0", "kind-of": "^6.0.0", @@ -6924,20 +6972,22 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true } } }, "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { "safe-buffer": "^5.1.0" @@ -7348,6 +7398,7 @@ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, + "optional": true, "requires": { "is-equal-shallow": "^0.1.3" } @@ -7624,6 +7675,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + }, "rimraf": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", @@ -8056,9 +8112,9 @@ "dev": true }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -8689,12 +8745,13 @@ "dev": true }, "undeclared-identifiers": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz", - "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", "dev": true, "requires": { "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", "get-assigned-identifiers": "^1.2.0", "simple-concat": "^1.0.0", "xtend": "^4.0.1" @@ -8781,6 +8838,12 @@ } } }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -8903,20 +8966,71 @@ "dev": true }, "watchify": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.0.tgz", - "integrity": "sha512-7jWG0c3cKKm2hKScnSAMUEUjRJKXUShwMPk0ASVhICycQhwND3IMAdhJYmc1mxxKzBUJTSF5HZizfrKrS6BzkA==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.1.tgz", + "integrity": "sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog==", "dev": true, "requires": { - "anymatch": "^1.3.0", + "anymatch": "^2.0.0", "browserify": "^16.1.0", - "chokidar": "^1.0.0", + "chokidar": "^2.1.1", "defined": "^1.0.0", "outpipe": "^1.1.0", "through2": "^2.0.0", "xtend": "^4.0.0" }, "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "browserify": { "version": "16.2.3", "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.3.tgz", @@ -8973,6 +9087,857 @@ "xtend": "^4.0.0" } }, + "chokidar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "string_decoder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", diff --git a/package.json b/package.json index d7d55dfc..41d14f82 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "./lib/node/base64.js": "./lib/browser/base64.js", "./lib/node/storage.js": "./lib/browser/storage.js", "./lib/node/source.js": "./lib/browser/source.js", + "./lib/node/isReactNative.js": "./lib/browser/isReactNative.js", "./lib.es5/node/request.js": "./lib.es5/browser/request.js", "./lib.es5/node/storage.js": "./lib.es5/browser/storage.js", - "./lib.es5/node/source.js": "./lib.es5/browser/source.js" + "./lib.es5/node/source.js": "./lib.es5/browser/source.js", + "./lib.es5/node/isReactNative.js": "./lib.es5/browser/isReactNative.js" }, "repository": { "type": "git", @@ -58,13 +60,14 @@ "synchronous-promise": "^2.0.5", "temp": "^0.8.3", "uglify-js": "^2.6.0", - "watchify": "^3.11.0" + "watchify": "^3.11.1" }, "dependencies": { "buffer-from": "^0.1.1", "extend": "^3.0.0", "js-base64": "^2.4.9", "lodash.throttle": "^4.1.1", + "proper-lockfile": "^2.0.1", "url-parse": "^1.4.3" }, "scripts": { diff --git a/test/spec/upload.js b/test/spec/upload.js index db819fab..72280942 100644 --- a/test/spec/upload.js +++ b/test/spec/upload.js @@ -14,6 +14,18 @@ if (isNode) { var Blob = Buffer; } +var getStorage = function () { + // this storage is only needed for the node environment. It defaults to localStorage + // for browser. + if (isNode) { + var temp = require("temp"); + var storagePath = temp.path(); + return new tus.FileStorage(storagePath); + } + + return null; +}; + // Set Jasmine's timeout for a single test to 10s jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 1000; @@ -54,8 +66,9 @@ describe("tus", function () { it("should upload a file", function (done) { var file = new Blob("hello world".split("")); + var host = "http://files.tus.io"; var options = { - endpoint: "http://tus.io/uploads", + endpoint: host + "/uploads", headers: { Custom: "blargh" }, @@ -66,6 +79,7 @@ describe("tus", function () { number: 100 }, withCredentials: true, + urlStorage: getStorage(), onProgress: function () {}, fingerprint: function () {} }; @@ -77,43 +91,44 @@ describe("tus", function () { expect(options.fingerprint).toHaveBeenCalledWith(file, upload.options); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads"); - expect(req.method).toBe("POST"); - expect(req.requestHeaders.Custom).toBe("blargh"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Length"]).toBe(11); - if (isBrowser) expect(req.withCredentials).toBe(true); - expect(req.requestHeaders["Upload-Metadata"]).toBe("foo aGVsbG8=,bar d29ybGQ=,nonlatin c8WCb8WEY2U=,number MTAw"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/uploads"); + expect(req.method).toBe("POST"); + expect(req.requestHeaders.Custom).toBe("blargh"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Length"]).toBe(11); + if (isBrowser) expect(req.withCredentials).toBe(true); + expect(req.requestHeaders["Upload-Metadata"]).toBe("foo aGVsbG8=,bar d29ybGQ=,nonlatin c8WCb8WEY2U=,number MTAw"); - req.respondWith({ - status: 201, - responseHeaders: { - Location: "http://tus.io/uploads/blargh" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + Location: host + "/uploads/blargh" + } + }); - expect(upload.url).toBe("http://tus.io/uploads/blargh"); + expect(upload.url).toBe(host + "/uploads/blargh"); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads/blargh"); - expect(req.method).toBe("PATCH"); - expect(req.requestHeaders.Custom).toBe("blargh"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Offset"]).toBe(0); - expect(req.contentType()).toBe("application/offset+octet-stream"); - expect(req.params.size).toBe(11); - if (isBrowser) expect(req.withCredentials).toBe(true); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/uploads/blargh"); + expect(req.method).toBe("PATCH"); + expect(req.requestHeaders.Custom).toBe("blargh"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Offset"]).toBe(0); + expect(req.contentType()).toBe("application/offset+octet-stream"); + expect(req.params.size).toBe(11); + if (isBrowser) expect(req.withCredentials).toBe(true); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 11 - } - }); + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); - expect(options.onProgress).toHaveBeenCalledWith(11, 11); - done(); + expect(options.onProgress).toHaveBeenCalledWith(11, 11); + done(); + }); }); it("should create an upload if resuming fails", function (done) { @@ -169,50 +184,55 @@ describe("tus", function () { done(); }); - it("should resolve relative URLs", function () { + it("should resolve relative URLs", function (done) { var file = new Blob("hello world".split("")); + var host = "http://relative.tus.io"; var options = { - endpoint: "http://master.tus.io:1080/files/" + endpoint: host + "/files/" }; var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://master.tus.io:1080/files/"); - expect(req.method).toBe("POST"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); - req.respondWith({ - status: 201, - responseHeaders: { - "Location": "//localhost/uploads/foo" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + "Location": "//relative.tus.io/uploads/foo" + } + }); - expect(upload.url).toBe("http://localhost/uploads/foo"); + expect(upload.url).toBe(host + "/uploads/foo"); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://localhost/uploads/foo"); - expect(req.method).toBe("PATCH"); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/uploads/foo"); + expect(req.method).toBe("PATCH"); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 11 - } + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); + done(); }); }); it("should upload a file in chunks", function (done) { + var host = "http://chunks.tus.io"; var file = new Blob("hello world".split("")); var options = { - endpoint: "http://tus.io/uploads", + endpoint: host + "/uploads", chunkSize: 7, + urlStorage: getStorage(), onProgress: function () {}, onChunkComplete: function () {}, fingerprint: function () {} }; - spyOn(options, "fingerprint").and.returnValue("fingerprinted"); + spyOn(options, "fingerprint").and.returnValue("fingerprinted.chunk"); spyOn(options, "onProgress"); spyOn(options, "onChunkComplete"); @@ -221,61 +241,63 @@ describe("tus", function () { expect(options.fingerprint).toHaveBeenCalledWith(file, upload.options); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads"); - expect(req.method).toBe("POST"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Length"]).toBe(11); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/uploads"); + expect(req.method).toBe("POST"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Length"]).toBe(11); - req.respondWith({ - status: 201, - responseHeaders: { - Location: "/uploads/blargh" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + Location: "/uploads/blargh" + } + }); - expect(upload.url).toBe("http://tus.io/uploads/blargh"); + expect(upload.url).toBe(host + "/uploads/blargh"); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads/blargh"); - expect(req.method).toBe("PATCH"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Offset"]).toBe(0); - expect(req.contentType()).toBe("application/offset+octet-stream"); - expect(req.params.size).toBe(7); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/uploads/blargh"); + expect(req.method).toBe("PATCH"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Offset"]).toBe(0); + expect(req.contentType()).toBe("application/offset+octet-stream"); + expect(req.params.size).toBe(7); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 7 - } - }); + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 7 + } + }); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads/blargh"); - expect(req.method).toBe("PATCH"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Offset"]).toBe(7); - expect(req.contentType()).toBe("application/offset+octet-stream"); - expect(req.params.size).toBe(4); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/uploads/blargh"); + expect(req.method).toBe("PATCH"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Offset"]).toBe(7); + expect(req.contentType()).toBe("application/offset+octet-stream"); + expect(req.params.size).toBe(4); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 11 - } + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); + expect(options.onProgress).toHaveBeenCalledWith(11, 11); + expect(options.onChunkComplete).toHaveBeenCalledWith(7, 7, 11); + expect(options.onChunkComplete).toHaveBeenCalledWith(4, 11, 11); + done(); }); - expect(options.onProgress).toHaveBeenCalledWith(11, 11); - expect(options.onChunkComplete).toHaveBeenCalledWith(7, 7, 11); - expect(options.onChunkComplete).toHaveBeenCalledWith(4, 11, 11); - done(); }); - it("should add the original request to errors", function () { + it("should add the original request to errors", function (done) { var file = new Blob("hello world".split("")); + var host = "http://original.tus.io"; var err; var options = { - endpoint: "http://tus.io/uploads", + endpoint: host + "/uploads", onError: function (e) { err = e; } @@ -284,28 +306,31 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads"); - expect(req.method).toBe("POST"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/uploads"); + expect(req.method).toBe("POST"); - req.respondWith({ - status: 500, - responseHeaders: { - Custom: "blargh" - } - }); + req.respondWith({ + status: 500, + responseHeaders: { + Custom: "blargh" + } + }); - expect(upload.url).toBe(null); + expect(upload.url).toBe(null); - expect(err.message).toBe("tus: unexpected response while creating upload, originated from request (response code: 500, response text: )"); - expect(err.originalRequest).toBeDefined(); - expect(err.originalRequest.getResponseHeader("Custom")).toBe("blargh"); + expect(err.message).toBe("tus: unexpected response while creating upload, originated from request (response code: 500, response text: )"); + expect(err.originalRequest).toBeDefined(); + expect(err.originalRequest.getResponseHeader("Custom")).toBe("blargh"); + done(); + }); }); it("should only create an upload for empty files", function (done) { var file = new Blob([]); + var host = "http://empty.tus.io"; var options = { - endpoint: "http://tus.io/uploads", + endpoint: host + "/uploads", onSuccess: function () {} }; spyOn(options, "onSuccess"); @@ -313,21 +338,22 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads"); - expect(req.method).toBe("POST"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Length"]).toBe(0); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/uploads"); + expect(req.method).toBe("POST"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Length"]).toBe(0); - req.respondWith({ - status: 201, - responseHeaders: { - "Location": "http://tus.io/uploads/empty" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + "Location": host + "/uploads/empty" + } + }); - expect(options.onSuccess).toHaveBeenCalled(); - done(); + expect(options.onSuccess).toHaveBeenCalled(); + done(); + }); }); it("should not resume a finished upload", function (done) { @@ -478,50 +504,53 @@ describe("tus", function () { it("should override the PATCH method", function (done) { var file = new Blob("hello world".split("")); + var host = "http://override.tus.io"; var options = { - endpoint: "http://tus.io/uploads", - uploadUrl: "http://tus.io/files/upload", + endpoint: host + "/uploads", + uploadUrl: host + "/files/upload", overridePatchMethod: true }; var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/upload"); - expect(req.method).toBe("HEAD"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/upload"); + expect(req.method).toBe("HEAD"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Length": 11, - "Upload-Offset": 3 - } - }); + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Length": 11, + "Upload-Offset": 3 + } + }); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/upload"); - expect(req.method).toBe("POST"); - expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); - expect(req.requestHeaders["Upload-Offset"]).toBe(3); - expect(req.requestHeaders["X-HTTP-Method-Override"]).toBe("PATCH"); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/upload"); + expect(req.method).toBe("POST"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Offset"]).toBe(3); + expect(req.requestHeaders["X-HTTP-Method-Override"]).toBe("PATCH"); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 11 - } - }); + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); - done(); + done(); + }); }); it("should emit an error if an upload is locked", function (done) { var file = new Blob("hello world".split("")); + var host = "http://locked.tus.io"; var options = { - endpoint: "http://tus.io/uploads", - uploadUrl: "http://tus.io/files/upload", + endpoint: host + "/uploads", + uploadUrl: host + "/files/upload", onError: function () {} }; @@ -530,22 +559,24 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/upload"); - expect(req.method).toBe("HEAD"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/upload"); + expect(req.method).toBe("HEAD"); - req.respondWith({ - status: 423 // Locked - }); + req.respondWith({ + status: 423 // Locked + }); - expect(options.onError).toHaveBeenCalledWith(new Error("tus: upload is currently locked; retry later, originated from request (response code: 423, response text: )")); - done(); + expect(options.onError).toHaveBeenCalledWith(new Error("tus: upload is currently locked; retry later, originated from request (response code: 423, response text: )")); + done(); + }); }); it("should emit an error if no Location header is presented", function (done) { var file = new Blob("hello world".split("")); + var host = "http://emit.tus.io"; var options = { - endpoint: "http://tus.io/uploads", + endpoint: host + "/uploads", onError: function () {} }; @@ -554,17 +585,18 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/uploads"); - expect(req.method).toBe("POST"); - // The Location header is omitted on purpose here + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/uploads"); + expect(req.method).toBe("POST"); + // The Location header is omitted on purpose here - req.respondWith({ - status: 201 - }); + req.respondWith({ + status: 201 + }); - expect(options.onError).toHaveBeenCalledWith(new Error("tus: invalid or missing Location header, originated from request (response code: 201, response text: )")); - done(); + expect(options.onError).toHaveBeenCalledWith(new Error("tus: invalid or missing Location header, originated from request (response code: 201, response text: )")); + done(); + }); }); it("should throw if retryDelays is not an array", function () { @@ -580,8 +612,9 @@ describe("tus", function () { // response has the code 500 Internal Error, 423 Locked or 409 Conflict. it("should retry the upload", function (done) { var file = new Blob("hello world".split("")); + var host = "http://retry.tus.io"; var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", retryDelays: [10, 10, 10], onSuccess: function () {} }; @@ -591,58 +624,37 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); - - req.respondWith({ - status: 500 - }); - - setTimeout(function () { - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); expect(req.method).toBe("POST"); req.respondWith({ - status: 201, - responseHeaders: { - Location: "/files/foo" - } - }); - - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("PATCH"); - - req.respondWith({ - status: 423 + status: 500 }); setTimeout(function () { req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("HEAD"); + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); req.respondWith({ status: 201, responseHeaders: { - "Upload-Offset": 0, - "Upload-Length": 11 + Location: "/files/foo" } }); req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("PATCH"); req.respondWith({ - status: 409 + status: 423 }); setTimeout(function () { req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("HEAD"); req.respondWith({ @@ -654,24 +666,46 @@ describe("tus", function () { }); req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("PATCH"); req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 11 - } + status: 409 }); - expect(options.onSuccess).toHaveBeenCalled(); - done(); + setTimeout(function () { + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("HEAD"); + + req.respondWith({ + status: 201, + responseHeaders: { + "Upload-Offset": 0, + "Upload-Length": 11 + } + }); + + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("PATCH"); + + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); + + expect(options.onSuccess).toHaveBeenCalled(); + done(); + }, 20); }, 20); }, 20); - }, 20); + }); }); - it("should not retry if the error has not been caused by a request", function () { + it("should not retry if the error has not been caused by a request", function (done) { var file = new Blob("hello world".split("")); var options = { endpoint: "http://tus.io/files/", @@ -687,19 +721,22 @@ describe("tus", function () { spyOn(upload, "_createUpload"); upload.start(); + setTimeout(function () { + var error = new Error("custom error"); + upload._emitError(error); - var error = new Error("custom error"); - upload._emitError(error); - - expect(upload._createUpload).toHaveBeenCalledTimes(1); - expect(options.onError).toHaveBeenCalledWith(error); - expect(options.onSuccess).not.toHaveBeenCalled(); + expect(upload._createUpload).toHaveBeenCalledTimes(1); + expect(options.onError).toHaveBeenCalledWith(error); + expect(options.onSuccess).not.toHaveBeenCalled(); + done(); + }, 200); }); it("should stop retrying after all delays have been used", function (done) { var file = new Blob("hello world".split("")); + var host = "http://delays.tus.io"; var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", retryDelays: [10], onSuccess: function () {}, onError: function () {} @@ -711,35 +748,37 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); - - req.respondWith({ - status: 500 - }); - - setTimeout(function () { - expect(options.onError).not.toHaveBeenCalled(); - - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); expect(req.method).toBe("POST"); req.respondWith({ status: 500 }); - expect(options.onSuccess).not.toHaveBeenCalled(); - expect(options.onError).toHaveBeenCalledTimes(1); - done(); - }, 200); + setTimeout(function () { + expect(options.onError).not.toHaveBeenCalled(); + + var req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); + + req.respondWith({ + status: 500 + }); + + expect(options.onSuccess).not.toHaveBeenCalled(); + expect(options.onError).toHaveBeenCalledTimes(1); + done(); + }, 200); + }); }); it("should stop retrying when the abort function is called", function (done) { var file = new Blob("hello world".split("")); + var host = "http://noretry.tus.io"; var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", retryDelays: [100], onError: function () {} }; @@ -749,29 +788,31 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); - spyOn(upload, "start"); + spyOn(upload, "start"); - req.respondWith({ - status: 500 - }); + req.respondWith({ + status: 500 + }); - upload.abort(); + upload.abort(); - setTimeout(function () { - expect(upload.start).not.toHaveBeenCalled(); - done(); - }, 200); + setTimeout(function () { + expect(upload.start).not.toHaveBeenCalled(); + done(); + }, 200); + }); }); it("should stop upload when the abort function is called during a callback", function (done) { var upload; + var host = "http://abort2.tus.io"; var file = new Blob("hello world".split("")); var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", chunkSize: 5, onChunkComplete: function () { upload.abort(); @@ -783,39 +824,41 @@ describe("tus", function () { upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); - req.respondWith({ - status: 201, - responseHeaders: { - Location: "/files/foo" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + Location: "/files/foo" + } + }); - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("PATCH"); + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("PATCH"); - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 5 - } - }); + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 5 + } + }); - setTimeout(function () { - expect(options.onChunkComplete).toHaveBeenCalled(); - expect(jasmine.Ajax.requests.mostRecent()).toBe(req); - done(); - }, 200); + setTimeout(function () { + expect(options.onChunkComplete).toHaveBeenCalled(); + expect(jasmine.Ajax.requests.mostRecent()).toBe(req); + done(); + }, 200); + }); }); it("should stop upload when the abort function is called during the POST request", function (done) { var file = new Blob("hello world".split("")); + var host = "http://abort.tus.io"; var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", onError: function () {} }; @@ -826,28 +869,30 @@ describe("tus", function () { upload.abort(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); - req.respondWith({ - status: 201, - responseHeaders: { - Location: "/files/foo" - } - }); + req.respondWith({ + status: 201, + responseHeaders: { + Location: "/files/foo" + } + }); - setTimeout(function () { - expect(options.onError).not.toHaveBeenCalled(); - expect(jasmine.Ajax.requests.mostRecent()).toBe(req); - done(); - }, 200); + setTimeout(function () { + expect(options.onError).not.toHaveBeenCalled(); + expect(jasmine.Ajax.requests.mostRecent()).toBe(req); + done(); + }, 200); + }); }); it("should reset the attempt counter if an upload proceeds", function (done) { var file = new Blob("hello world".split("")); + var host = "http://reset.tus.io"; var options = { - endpoint: "http://tus.io/files/", + endpoint: host + "/files/", retryDelays: [10], onError: function () {}, onSuccess: function () {} @@ -859,51 +904,19 @@ describe("tus", function () { var upload = new tus.Upload(file, options); upload.start(); - var req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/"); - expect(req.method).toBe("POST"); - - req.respondWith({ - status: 201, - responseHeaders: { - Location: "/files/foo" - } - }); - - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("PATCH"); - - req.respondWith({ - status: 500 - }); - - setTimeout(function () { - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("HEAD"); - - req.respondWith({ - status: 204, - responseHeaders: { - "Upload-Offset": 0, - "Upload-Length": 11 - } - }); - - req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); - expect(req.method).toBe("PATCH"); + waitTillNextReq(host, null, function (req) { + expect(req.url).toBe(host + "/files/"); + expect(req.method).toBe("POST"); req.respondWith({ - status: 204, + status: 201, responseHeaders: { - "Upload-Offset": 5 + Location: "/files/foo" } }); req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("PATCH"); req.respondWith({ @@ -912,33 +925,67 @@ describe("tus", function () { setTimeout(function () { req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("HEAD"); req.respondWith({ status: 204, responseHeaders: { - "Upload-Offset": 5, + "Upload-Offset": 0, "Upload-Length": 11 } }); req = jasmine.Ajax.requests.mostRecent(); - expect(req.url).toBe("http://tus.io/files/foo"); + expect(req.url).toBe(host + "/files/foo"); expect(req.method).toBe("PATCH"); req.respondWith({ status: 204, responseHeaders: { - "Upload-Offset": 11 + "Upload-Offset": 5 } }); - expect(options.onError).not.toHaveBeenCalled(); - expect(options.onSuccess).toHaveBeenCalled(); - done(); + waitTillNextReq(host, req, function (req) { + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("PATCH"); + + req.respondWith({ + status: 500 + }); + + setTimeout(function () { + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("HEAD"); + + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 5, + "Upload-Length": 11 + } + }); + + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe(host + "/files/foo"); + expect(req.method).toBe("PATCH"); + + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); + + expect(options.onError).not.toHaveBeenCalled(); + expect(options.onSuccess).toHaveBeenCalled(); + done(); + }, 20); + }); }, 20); - }, 20); + }); }); }); @@ -1011,3 +1058,22 @@ function validateUploadMetadata(upload, done) { }) .catch(done.fail); } + +function waitTillNextReq(id, req, cb, level) { + var allowedLevels = 5; + level = level || 0; + if (level >= allowedLevels) { + fail(new Error("call level exceeded")); + return; + } + setTimeout(function () { + var newReq = jasmine.Ajax.requests.mostRecent(); + if (!req || (req && newReq != req)) { + if (newReq.url.indexOf(id) === 0) { + return cb(newReq); + } + } + + waitTillNextReq(id, req, cb, level + 1); + }, 20); +} \ No newline at end of file diff --git a/test/spec/upload.node.js b/test/spec/upload.node.js index 46dcb3bc..d4ffd522 100644 --- a/test/spec/upload.node.js +++ b/test/spec/upload.node.js @@ -5,8 +5,8 @@ const fs = require("fs"); describe("tus", function () { describe("#canStoreURLs", function () { - it("should be false", function () { - expect(tus.canStoreURLs).toBe(false); + it("should be true", function () { + expect(tus.canStoreURLs).toBe(true); }); }); @@ -22,6 +22,7 @@ describe("tus", function () { it("should accept Buffers", function (done) { var buffer = new Buffer("hello world"); var options = { + resume: false, endpoint: "/uploads", chunkSize: 7 }; @@ -32,6 +33,7 @@ describe("tus", function () { it("should reject streams without specifing the size", function () { var input = new stream.PassThrough(); var options = { + resume: false, endpoint: "/uploads", chunkSize: 100 }; @@ -43,6 +45,7 @@ describe("tus", function () { it("should reject streams without specifing the chunkSize", function () { var input = new stream.PassThrough(); var options = { + resume: false, endpoint: "/uploads" }; @@ -53,6 +56,7 @@ describe("tus", function () { it("should accept Readable streams", function (done) { var input = new stream.PassThrough(); var options = { + resume: false, endpoint: "/uploads", chunkSize: 7, uploadSize: 11 @@ -65,6 +69,7 @@ describe("tus", function () { it("should accept Readable streams with deferred size", function (done) { var input = new stream.PassThrough(); var options = { + resume: false, endpoint: "/uploads", chunkSize: 7, uploadLengthDeferred: true @@ -81,6 +86,7 @@ describe("tus", function () { var file = fs.createReadStream(path); var options = { + resume: false, endpoint: "/uploads", chunkSize: 7, uploadSize: 11 @@ -93,6 +99,7 @@ describe("tus", function () { var resErr = new Error("something bad, really!"); var buffer = new Buffer("hello world"); var option = { + resume: false, endpoint: "/uploads", onError: function (err) { expect(err.causingError).toBe(resErr); @@ -112,6 +119,57 @@ describe("tus", function () { expect(option.onError).toHaveBeenCalled(); }); + + it("should resume an upload from a stored url", function (done) { + var storagePath = temp.path(); + fs.writeFileSync(storagePath, "{\"fingerprinted.resume\":\"/uploads/resuming\"}"); + var storage = new tus.FileStorage(storagePath); + var input = new Buffer("hello world"); + var options = { + endpoint: "/uploads", + fingerprint: () => "fingerprinted.resume", + urlStorage: storage + }; + + var upload = new tus.Upload(input, options); + upload.start(); + var req = jasmine.Ajax.requests.mostRecent(); + + setTimeout(() => { + tickTillNewReq(req, (req) => { + expect(req.url).toBe("/uploads/resuming"); + expect(req.method).toBe("HEAD"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Length": 11, + "Upload-Offset": 3 + } + }); + + expect(upload.url).toBe("/uploads/resuming"); + + req = jasmine.Ajax.requests.mostRecent(); + expect(req.url).toBe("/uploads/resuming"); + expect(req.method).toBe("PATCH"); + expect(req.requestHeaders["Tus-Resumable"]).toBe("1.0.0"); + expect(req.requestHeaders["Upload-Offset"]).toBe(3); + expect(req.contentType()).toBe("application/offset+octet-stream"); + expect(req.params.size).toBe(11 - 3); + + req.respondWith({ + status: 204, + responseHeaders: { + "Upload-Offset": 11 + } + }); + + done(); + }); + }, 200); + }); }); }); @@ -197,7 +255,8 @@ function tickTillNewReq(oldReq, cb, level) { const allowedLevels = 5; level = level || 0; if (level >= allowedLevels) { - throw new Error("call level exceeded"); + fail(new Error("call level exceeded")); + return; } process.nextTick(function () { const req = jasmine.Ajax.requests.mostRecent();