tus-js-client
Version:
A pure JavaScript client for the tus resumable upload protocol
1,343 lines (1,288 loc) • 58.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _jsBase = require("js-base64");
var _urlParse = _interopRequireDefault(require("url-parse"));
var _error = _interopRequireDefault(require("./error.js"));
var _logger = require("./logger.js");
var _uuid = _interopRequireDefault(require("./uuid.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _regeneratorRuntime() {
"use strict";
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
_regeneratorRuntime = function _regeneratorRuntime() {
return e;
};
var t,
e = {},
r = Object.prototype,
n = r.hasOwnProperty,
o = Object.defineProperty || function (t, e, r) {
t[e] = r.value;
},
i = "function" == typeof Symbol ? Symbol : {},
a = i.iterator || "@@iterator",
c = i.asyncIterator || "@@asyncIterator",
u = i.toStringTag || "@@toStringTag";
function define(t, e, r) {
return Object.defineProperty(t, e, {
value: r,
enumerable: !0,
configurable: !0,
writable: !0
}), t[e];
}
try {
define({}, "");
} catch (t) {
define = function define(t, e, r) {
return t[e] = r;
};
}
function wrap(t, e, r, n) {
var i = e && e.prototype instanceof Generator ? e : Generator,
a = Object.create(i.prototype),
c = new Context(n || []);
return o(a, "_invoke", {
value: makeInvokeMethod(t, r, c)
}), a;
}
function tryCatch(t, e, r) {
try {
return {
type: "normal",
arg: t.call(e, r)
};
} catch (t) {
return {
type: "throw",
arg: t
};
}
}
e.wrap = wrap;
var h = "suspendedStart",
l = "suspendedYield",
f = "executing",
s = "completed",
y = {};
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}
var p = {};
define(p, a, function () {
return this;
});
var d = Object.getPrototypeOf,
v = d && d(d(values([])));
v && v !== r && n.call(v, a) && (p = v);
var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p);
function defineIteratorMethods(t) {
["next", "throw", "return"].forEach(function (e) {
define(t, e, function (t) {
return this._invoke(e, t);
});
});
}
function AsyncIterator(t, e) {
function invoke(r, o, i, a) {
var c = tryCatch(t[r], t, o);
if ("throw" !== c.type) {
var u = c.arg,
h = u.value;
return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) {
invoke("next", t, i, a);
}, function (t) {
invoke("throw", t, i, a);
}) : e.resolve(h).then(function (t) {
u.value = t, i(u);
}, function (t) {
return invoke("throw", t, i, a);
});
}
a(c.arg);
}
var r;
o(this, "_invoke", {
value: function value(t, n) {
function callInvokeWithMethodAndArg() {
return new e(function (e, r) {
invoke(t, n, e, r);
});
}
return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();
}
});
}
function makeInvokeMethod(e, r, n) {
var o = h;
return function (i, a) {
if (o === f) throw Error("Generator is already running");
if (o === s) {
if ("throw" === i) throw a;
return {
value: t,
done: !0
};
}
for (n.method = i, n.arg = a;;) {
var c = n.delegate;
if (c) {
var u = maybeInvokeDelegate(c, n);
if (u) {
if (u === y) continue;
return u;
}
}
if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) {
if (o === h) throw o = s, n.arg;
n.dispatchException(n.arg);
} else "return" === n.method && n.abrupt("return", n.arg);
o = f;
var p = tryCatch(e, r, n);
if ("normal" === p.type) {
if (o = n.done ? s : l, p.arg === y) continue;
return {
value: p.arg,
done: n.done
};
}
"throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg);
}
};
}
function maybeInvokeDelegate(e, r) {
var n = r.method,
o = e.iterator[n];
if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y;
var i = tryCatch(o, e.iterator, r.arg);
if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y;
var a = i.arg;
return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y);
}
function pushTryEntry(t) {
var e = {
tryLoc: t[0]
};
1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e);
}
function resetTryEntry(t) {
var e = t.completion || {};
e.type = "normal", delete e.arg, t.completion = e;
}
function Context(t) {
this.tryEntries = [{
tryLoc: "root"
}], t.forEach(pushTryEntry, this), this.reset(!0);
}
function values(e) {
if (e || "" === e) {
var r = e[a];
if (r) return r.call(e);
if ("function" == typeof e.next) return e;
if (!isNaN(e.length)) {
var o = -1,
i = function next() {
for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next;
return next.value = t, next.done = !0, next;
};
return i.next = i;
}
}
throw new TypeError(_typeof(e) + " is not iterable");
}
return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", {
value: GeneratorFunctionPrototype,
configurable: !0
}), o(GeneratorFunctionPrototype, "constructor", {
value: GeneratorFunction,
configurable: !0
}), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) {
var e = "function" == typeof t && t.constructor;
return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name));
}, e.mark = function (t) {
return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t;
}, e.awrap = function (t) {
return {
__await: t
};
}, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () {
return this;
}), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) {
void 0 === i && (i = Promise);
var a = new AsyncIterator(wrap(t, r, n, o), i);
return e.isGeneratorFunction(r) ? a : a.next().then(function (t) {
return t.done ? t.value : a.next();
});
}, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () {
return this;
}), define(g, "toString", function () {
return "[object Generator]";
}), e.keys = function (t) {
var e = Object(t),
r = [];
for (var n in e) r.push(n);
return r.reverse(), function next() {
for (; r.length;) {
var t = r.pop();
if (t in e) return next.value = t, next.done = !1, next;
}
return next.done = !0, next;
};
}, e.values = values, Context.prototype = {
constructor: Context,
reset: function reset(e) {
if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t);
},
stop: function stop() {
this.done = !0;
var t = this.tryEntries[0].completion;
if ("throw" === t.type) throw t.arg;
return this.rval;
},
dispatchException: function dispatchException(e) {
if (this.done) throw e;
var r = this;
function handle(n, o) {
return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o;
}
for (var o = this.tryEntries.length - 1; o >= 0; --o) {
var i = this.tryEntries[o],
a = i.completion;
if ("root" === i.tryLoc) return handle("end");
if (i.tryLoc <= this.prev) {
var c = n.call(i, "catchLoc"),
u = n.call(i, "finallyLoc");
if (c && u) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
} else if (c) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
} else {
if (!u) throw Error("try statement without catch or finally");
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
}
}
}
},
abrupt: function abrupt(t, e) {
for (var r = this.tryEntries.length - 1; r >= 0; --r) {
var o = this.tryEntries[r];
if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) {
var i = o;
break;
}
}
i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null);
var a = i ? i.completion : {};
return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a);
},
complete: function complete(t, e) {
if ("throw" === t.type) throw t.arg;
return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y;
},
finish: function finish(t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y;
}
},
"catch": function _catch(t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.tryLoc === t) {
var n = r.completion;
if ("throw" === n.type) {
var o = n.arg;
resetTryEntry(r);
}
return o;
}
}
throw Error("illegal catch attempt");
},
delegateYield: function delegateYield(e, r, n) {
return this.delegate = {
iterator: values(e),
resultName: r,
nextLoc: n
}, "next" === this.method && (this.arg = t), y;
}
}, e;
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function F() {};
return {
s: F,
n: function n() {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function e(_e) {
throw _e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function s() {
it = it.call(o);
},
n: function n() {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function e(_e2) {
didErr = true;
err = _e2;
},
f: function f() {
try {
if (!normalCompletion && it["return"] != null) it["return"]();
} finally {
if (didErr) throw err;
}
}
};
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var PROTOCOL_TUS_V1 = 'tus-v1';
var PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03';
var PROTOCOL_IETF_DRAFT_05 = 'ietf-draft-05';
var defaultOptions = {
endpoint: null,
uploadUrl: null,
metadata: {},
metadataForPartialUploads: {},
fingerprint: null,
uploadSize: null,
onProgress: null,
onChunkComplete: null,
onSuccess: null,
onError: null,
onUploadUrlAvailable: null,
overridePatchMethod: false,
headers: {},
addRequestId: false,
onBeforeRequest: null,
onAfterResponse: null,
onShouldRetry: defaultOnShouldRetry,
chunkSize: Number.POSITIVE_INFINITY,
retryDelays: [0, 1000, 3000, 5000],
parallelUploads: 1,
parallelUploadBoundaries: null,
storeFingerprintForResuming: true,
removeFingerprintOnSuccess: false,
uploadLengthDeferred: false,
uploadDataDuringCreation: false,
urlStorage: null,
fileReader: null,
httpStack: null,
protocol: PROTOCOL_TUS_V1
};
var BaseUpload = /*#__PURE__*/function () {
function BaseUpload(file, options) {
_classCallCheck(this, BaseUpload);
// Warn about removed options from previous versions
if ('resume' in options) {
console.log('tus: The `resume` option has been removed in tus-js-client v2. Please use the URL storage API instead.');
}
// The default options will already be added from the wrapper classes.
this.options = options;
// Cast chunkSize to integer
this.options.chunkSize = Number(this.options.chunkSize);
// The storage module used to store URLs
this._urlStorage = this.options.urlStorage;
// The underlying File/Blob object
this.file = file;
// The URL against which the file will be uploaded
this.url = null;
// The underlying request object for the current PATCH request
this._req = null;
// The fingerpinrt for the current file (set after start())
this._fingerprint = null;
// The key that the URL storage returned when saving an URL with a fingerprint,
this._urlStorageKey = null;
// The offset used in the current PATCH request
this._offset = null;
// True if the current PATCH request has been aborted
this._aborted = false;
// The file's size in bytes
this._size = null;
// The Source object which will wrap around the given file and provides us
// with a unified interface for getting its size and slice chunks from its
// content allowing us to easily handle Files, Blobs, Buffers and Streams.
this._source = null;
// The current count of attempts which have been made. Zero indicates none.
this._retryAttempt = 0;
// The timeout's ID which is used to delay the next retry
this._retryTimeout = null;
// The offset of the remote upload before the latest attempt was started.
this._offsetBeforeRetry = 0;
// An array of BaseUpload instances which are used for uploading the different
// parts, if the parallelUploads option is used.
this._parallelUploads = null;
// An array of upload URLs which are used for uploading the different
// parts, if the parallelUploads option is used.
this._parallelUploadUrls = null;
}
/**
* Use the Termination extension to delete an upload from the server by sending a DELETE
* request to the specified upload URL. This is only possible if the server supports the
* Termination extension. If the `options.retryDelays` property is set, the method will
* also retry if an error ocurrs.
*
* @param {String} url The upload's URL which will be terminated.
* @param {object} options Optional options for influencing HTTP requests.
* @return {Promise} The Promise will be resolved/rejected when the requests finish.
*/
return _createClass(BaseUpload, [{
key: "findPreviousUploads",
value: function findPreviousUploads() {
var _this = this;
return this.options.fingerprint(this.file, this.options).then(function (fingerprint) {
return _this._urlStorage.findUploadsByFingerprint(fingerprint);
});
}
}, {
key: "resumeFromPreviousUpload",
value: function resumeFromPreviousUpload(previousUpload) {
this.url = previousUpload.uploadUrl || null;
this._parallelUploadUrls = previousUpload.parallelUploadUrls || null;
this._urlStorageKey = previousUpload.urlStorageKey;
}
}, {
key: "start",
value: function start() {
var _this2 = this;
var file = this.file;
if (!file) {
this._emitError(new Error('tus: no file or stream to upload provided'));
return;
}
if (![PROTOCOL_TUS_V1, PROTOCOL_IETF_DRAFT_03, PROTOCOL_IETF_DRAFT_05].includes(this.options.protocol)) {
this._emitError(new Error("tus: unsupported protocol ".concat(this.options.protocol)));
return;
}
if (!this.options.endpoint && !this.options.uploadUrl && !this.url) {
this._emitError(new Error('tus: neither an endpoint or an upload URL is provided'));
return;
}
var retryDelays = this.options.retryDelays;
if (retryDelays != null && Object.prototype.toString.call(retryDelays) !== '[object Array]') {
this._emitError(new Error('tus: the `retryDelays` option must either be an array or null'));
return;
}
if (this.options.parallelUploads > 1) {
// Test which options are incompatible with parallel uploads.
for (var _i = 0, _arr = ['uploadUrl', 'uploadSize', 'uploadLengthDeferred']; _i < _arr.length; _i++) {
var optionName = _arr[_i];
if (this.options[optionName]) {
this._emitError(new Error("tus: cannot use the ".concat(optionName, " option when parallelUploads is enabled")));
return;
}
}
}
if (this.options.parallelUploadBoundaries) {
if (this.options.parallelUploads <= 1) {
this._emitError(new Error('tus: cannot use the `parallelUploadBoundaries` option when `parallelUploads` is disabled'));
return;
}
if (this.options.parallelUploads !== this.options.parallelUploadBoundaries.length) {
this._emitError(new Error('tus: the `parallelUploadBoundaries` must have the same length as the value of `parallelUploads`'));
return;
}
}
this.options.fingerprint(file, this.options).then(function (fingerprint) {
if (fingerprint == null) {
(0, _logger.log)('No fingerprint was calculated meaning that the upload cannot be stored in the URL storage.');
} else {
(0, _logger.log)("Calculated fingerprint: ".concat(fingerprint));
}
_this2._fingerprint = fingerprint;
if (_this2._source) {
return _this2._source;
}
return _this2.options.fileReader.openFile(file, _this2.options.chunkSize);
}).then(function (source) {
_this2._source = source;
// First, we look at the uploadLengthDeferred option.
// Next, we check if the caller has supplied a manual upload size.
// Finally, we try to use the calculated size from the source object.
if (_this2.options.uploadLengthDeferred) {
_this2._size = null;
} else if (_this2.options.uploadSize != null) {
_this2._size = Number(_this2.options.uploadSize);
if (Number.isNaN(_this2._size)) {
_this2._emitError(new Error('tus: cannot convert `uploadSize` option into a number'));
return;
}
} else {
_this2._size = _this2._source.size;
if (_this2._size == null) {
_this2._emitError(new Error("tus: cannot automatically derive upload's size from input. Specify it manually using the `uploadSize` option or use the `uploadLengthDeferred` option"));
return;
}
}
// If the upload was configured to use multiple requests or if we resume from
// an upload which used multiple requests, we start a parallel upload.
if (_this2.options.parallelUploads > 1 || _this2._parallelUploadUrls != null) {
_this2._startParallelUpload();
} else {
_this2._startSingleUpload();
}
})["catch"](function (err) {
_this2._emitError(err);
});
}
/**
* Initiate the uploading procedure for a parallelized upload, where one file is split into
* multiple request which are run in parallel.
*
* @api private
*/
}, {
key: "_startParallelUpload",
value: function _startParallelUpload() {
var _this$options$paralle,
_this3 = this;
var totalSize = this._size;
var totalProgress = 0;
this._parallelUploads = [];
var partCount = this._parallelUploadUrls != null ? this._parallelUploadUrls.length : this.options.parallelUploads;
// The input file will be split into multiple slices which are uploaded in separate
// requests. Here we get the start and end position for the slices.
var parts = (_this$options$paralle = this.options.parallelUploadBoundaries) !== null && _this$options$paralle !== void 0 ? _this$options$paralle : splitSizeIntoParts(this._source.size, partCount);
// Attach URLs from previous uploads, if available.
if (this._parallelUploadUrls) {
parts.forEach(function (part, index) {
part.uploadUrl = _this3._parallelUploadUrls[index] || null;
});
}
// Create an empty list for storing the upload URLs
this._parallelUploadUrls = new Array(parts.length);
// Generate a promise for each slice that will be resolve if the respective
// upload is completed.
var uploads = parts.map(function (part, index) {
var lastPartProgress = 0;
return _this3._source.slice(part.start, part.end).then(function (_ref) {
var value = _ref.value;
return new Promise(function (resolve, reject) {
// Merge with the user supplied options but overwrite some values.
var options = _objectSpread(_objectSpread({}, _this3.options), {}, {
// If available, the partial upload should be resumed from a previous URL.
uploadUrl: part.uploadUrl || null,
// We take manually care of resuming for partial uploads, so they should
// not be stored in the URL storage.
storeFingerprintForResuming: false,
removeFingerprintOnSuccess: false,
// Reset the parallelUploads option to not cause recursion.
parallelUploads: 1,
// Reset this option as we are not doing a parallel upload.
parallelUploadBoundaries: null,
metadata: _this3.options.metadataForPartialUploads,
// Add the header to indicate the this is a partial upload.
headers: _objectSpread(_objectSpread({}, _this3.options.headers), {}, {
'Upload-Concat': 'partial'
}),
// Reject or resolve the promise if the upload errors or completes.
onSuccess: resolve,
onError: reject,
// Based in the progress for this partial upload, calculate the progress
// for the entire final upload.
onProgress: function onProgress(newPartProgress) {
totalProgress = totalProgress - lastPartProgress + newPartProgress;
lastPartProgress = newPartProgress;
_this3._emitProgress(totalProgress, totalSize);
},
// Wait until every partial upload has an upload URL, so we can add
// them to the URL storage.
onUploadUrlAvailable: function onUploadUrlAvailable() {
_this3._parallelUploadUrls[index] = upload.url;
// Test if all uploads have received an URL
if (_this3._parallelUploadUrls.filter(function (u) {
return Boolean(u);
}).length === parts.length) {
_this3._saveUploadInUrlStorage();
}
}
});
var upload = new BaseUpload(value, options);
upload.start();
// Store the upload in an array, so we can later abort them if necessary.
_this3._parallelUploads.push(upload);
});
});
});
var req;
// Wait until all partial uploads are finished and we can send the POST request for
// creating the final upload.
Promise.all(uploads).then(function () {
req = _this3._openRequest('POST', _this3.options.endpoint);
req.setHeader('Upload-Concat', "final;".concat(_this3._parallelUploadUrls.join(' ')));
// Add metadata if values have been added
var metadata = encodeMetadata(_this3.options.metadata);
if (metadata !== '') {
req.setHeader('Upload-Metadata', metadata);
}
return _this3._sendRequest(req, null);
}).then(function (res) {
if (!inStatusCategory(res.getStatus(), 200)) {
_this3._emitHttpError(req, res, 'tus: unexpected response while creating upload');
return;
}
var location = res.getHeader('Location');
if (location == null) {
_this3._emitHttpError(req, res, 'tus: invalid or missing Location header');
return;
}
_this3.url = resolveUrl(_this3.options.endpoint, location);
(0, _logger.log)("Created upload at ".concat(_this3.url));
_this3._emitSuccess(res);
})["catch"](function (err) {
_this3._emitError(err);
});
}
/**
* Initiate the uploading procedure for a non-parallel upload. Here the entire file is
* uploaded in a sequential matter.
*
* @api private
*/
}, {
key: "_startSingleUpload",
value: function _startSingleUpload() {
// Reset the aborted flag when the upload is started or else the
// _performUpload will stop before sending a request if the upload has been
// aborted previously.
this._aborted = false;
// The upload had been started previously and we should reuse this URL.
if (this.url != null) {
(0, _logger.log)("Resuming upload from previous URL: ".concat(this.url));
this._resumeUpload();
return;
}
// A URL has manually been specified, so we try to resume
if (this.options.uploadUrl != null) {
(0, _logger.log)("Resuming upload from provided URL: ".concat(this.options.uploadUrl));
this.url = this.options.uploadUrl;
this._resumeUpload();
return;
}
// An upload has not started for the file yet, so we start a new one
(0, _logger.log)('Creating a new upload');
this._createUpload();
}
/**
* Abort any running request and stop the current upload. After abort is called, no event
* handler will be invoked anymore. You can use the `start` method to resume the upload
* again.
* If `shouldTerminate` is true, the `terminate` function will be called to remove the
* current upload from the server.
*
* @param {boolean} shouldTerminate True if the upload should be deleted from the server.
* @return {Promise} The Promise will be resolved/rejected when the requests finish.
*/
}, {
key: "abort",
value: function abort(shouldTerminate) {
var _this4 = this;
// Stop any parallel partial uploads, that have been started in _startParallelUploads.
if (this._parallelUploads != null) {
var _iterator = _createForOfIteratorHelper(this._parallelUploads),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var upload = _step.value;
upload.abort(shouldTerminate);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
// Stop any current running request.
if (this._req !== null) {
this._req.abort();
// Note: We do not close the file source here, so the user can resume in the future.
}
this._aborted = true;
// Stop any timeout used for initiating a retry.
if (this._retryTimeout != null) {
clearTimeout(this._retryTimeout);
this._retryTimeout = null;
}
if (!shouldTerminate || this.url == null) {
return Promise.resolve();
}
return BaseUpload.terminate(this.url, this.options)
// Remove entry from the URL storage since the upload URL is no longer valid.
.then(function () {
return _this4._removeFromUrlStorage();
});
}
}, {
key: "_emitHttpError",
value: function _emitHttpError(req, res, message, causingErr) {
this._emitError(new _error.default(message, causingErr, req, res));
}
}, {
key: "_emitError",
value: function _emitError(err) {
var _this5 = this;
// Do not emit errors, e.g. from aborted HTTP requests, if the upload has been stopped.
if (this._aborted) return;
// Check if we should retry, when enabled, before sending the error to the user.
if (this.options.retryDelays != null) {
// We will reset the attempt counter if
// - we were already able to connect to the server (offset != null) and
// - we were able to upload a small chunk of data to the server
var shouldResetDelays = this._offset != null && this._offset > this._offsetBeforeRetry;
if (shouldResetDelays) {
this._retryAttempt = 0;
}
if (shouldRetry(err, this._retryAttempt, this.options)) {
var delay = this.options.retryDelays[this._retryAttempt++];
this._offsetBeforeRetry = this._offset;
this._retryTimeout = setTimeout(function () {
_this5.start();
}, delay);
return;
}
}
if (typeof this.options.onError === 'function') {
this.options.onError(err);
} else {
throw err;
}
}
/**
* Publishes notification if the upload has been successfully completed.
*
* @param {object} lastResponse Last HTTP response.
* @api private
*/
}, {
key: "_emitSuccess",
value: function _emitSuccess(lastResponse) {
if (this.options.removeFingerprintOnSuccess) {
// Remove stored fingerprint and corresponding endpoint. This causes
// new uploads of the same file to be treated as a different file.
this._removeFromUrlStorage();
}
if (typeof this.options.onSuccess === 'function') {
this.options.onSuccess({
lastResponse: lastResponse
});
}
}
/**
* Publishes notification when data has been sent to the server. This
* data may not have been accepted by the server yet.
*
* @param {number} bytesSent Number of bytes sent to the server.
* @param {number} bytesTotal Total number of bytes to be sent to the server.
* @api private
*/
}, {
key: "_emitProgress",
value: function _emitProgress(bytesSent, bytesTotal) {
if (typeof this.options.onProgress === 'function') {
this.options.onProgress(bytesSent, bytesTotal);
}
}
/**
* Publishes notification when a chunk of data has been sent to the server
* and accepted by the server.
* @param {number} chunkSize Size of the chunk that was accepted by the server.
* @param {number} bytesAccepted Total number of bytes that have been
* accepted by the server.
* @param {number} bytesTotal Total number of bytes to be sent to the server.
* @api private
*/
}, {
key: "_emitChunkComplete",
value: function _emitChunkComplete(chunkSize, bytesAccepted, bytesTotal) {
if (typeof this.options.onChunkComplete === 'function') {
this.options.onChunkComplete(chunkSize, bytesAccepted, bytesTotal);
}
}
/**
* Create a new upload using the creation extension by sending a POST
* request to the endpoint. After successful creation the file will be
* uploaded
*
* @api private
*/
}, {
key: "_createUpload",
value: function _createUpload() {
var _this6 = this;
if (!this.options.endpoint) {
this._emitError(new Error('tus: unable to create upload because no endpoint is provided'));
return;
}
var req = this._openRequest('POST', this.options.endpoint);
if (this.options.uploadLengthDeferred) {
req.setHeader('Upload-Defer-Length', '1');
} else {
req.setHeader('Upload-Length', "".concat(this._size));
}
// Add metadata if values have been added
var metadata = encodeMetadata(this.options.metadata);
if (metadata !== '') {
req.setHeader('Upload-Metadata', metadata);
}
var promise;
if (this.options.uploadDataDuringCreation && !this.options.uploadLengthDeferred) {
this._offset = 0;
promise = this._addChunkToRequest(req);
} else {
if (this.options.protocol === PROTOCOL_IETF_DRAFT_03 || this.options.protocol === PROTOCOL_IETF_DRAFT_05) {
req.setHeader('Upload-Complete', '?0');
}
promise = this._sendRequest(req, null);
}
promise.then(function (res) {
if (!inStatusCategory(res.getStatus(), 200)) {
_this6._emitHttpError(req, res, 'tus: unexpected response while creating upload');
return;
}
var location = res.getHeader('Location');
if (location == null) {
_this6._emitHttpError(req, res, 'tus: invalid or missing Location header');
return;
}
_this6.url = resolveUrl(_this6.options.endpoint, location);
(0, _logger.log)("Created upload at ".concat(_this6.url));
if (typeof _this6.options.onUploadUrlAvailable === 'function') {
_this6.options.onUploadUrlAvailable();
}
if (_this6._size === 0) {
// Nothing to upload and file was successfully created
_this6._emitSuccess(res);
_this6._source.close();
return;
}
_this6._saveUploadInUrlStorage().then(function () {
if (_this6.options.uploadDataDuringCreation) {
_this6._handleUploadResponse(req, res);
} else {
_this6._offset = 0;
_this6._performUpload();
}
});
})["catch"](function (err) {
_this6._emitHttpError(req, null, 'tus: failed to create upload', err);
});
}
/*
* Try to resume an existing upload. First a HEAD request will be sent
* to retrieve the offset. If the request fails a new upload will be
* created. In the case of a successful response the file will be uploaded.
*
* @api private
*/
}, {
key: "_resumeUpload",
value: function _resumeUpload() {
var _this7 = this;
var req = this._openRequest('HEAD', this.url);
var promise = this._sendRequest(req, null);
promise.then(function (res) {
var status = res.getStatus();
if (!inStatusCategory(status, 200)) {
// If the upload is locked (indicated by the 423 Locked status code), we
// emit an error instead of directly starting a new upload. This way the
// retry logic can catch the error and will retry the upload. An upload
// is usually locked for a short period of time and will be available
// afterwards.
if (status === 423) {
_this7._emitHttpError(req, res, 'tus: upload is currently locked; retry later');
return;
}
if (inStatusCategory(status, 400)) {
// Remove stored fingerprint and corresponding endpoint,
// on client errors since the file can not be found
_this7._removeFromUrlStorage();
}
if (!_this7.options.endpoint) {
// Don't attempt to create a new upload if no endpoint is provided.
_this7._emitHttpError(req, res, 'tus: unable to resume upload (new upload cannot be created without an endpoint)');
return;
}
// Try to create a new upload
_this7.url = null;
_this7._createUpload();
return;
}
var offset = Number.parseInt(res.getHeader('Upload-Offset'), 10);
if (Number.isNaN(offset)) {
_this7._emitHttpError(req, res, 'tus: invalid or missing offset value');
return;
}
var length = Number.parseInt(res.getHeader('Upload-Length'), 10);
if (Number.isNaN(length) && !_this7.options.uploadLengthDeferred && _this7.options.protocol === PROTOCOL_TUS_V1) {
_this7._emitHttpError(req, res, 'tus: invalid or missing length value');
return;
}
if (typeof _this7.options.onUploadUrlAvailable === 'function') {
_this7.options.onUploadUrlAvailable();
}
_this7._saveUploadInUrlStorage().then(function () {
// Upload has already been completed and we do not need to send additional
// data to the server
if (offset === length) {
_this7._emitProgress(length, length);
_this7._emitSuccess(res);
return;
}
_this7._offset = offset;
_this7._performUpload();
});
})["catch"](function (err) {
_this7._emitHttpError(req, null, 'tus: failed to resume upload', err);
});
}
/**
* Start uploading the file using PATCH requests. The file will be divided
* into chunks as specified in the chunkSize option. During the upload
* the onProgress event handler may be invoked multiple times.
*
* @api private
*/
}, {
key: "_performUpload",
value: function _performUpload() {
var _this8 = this;
// If the upload has been aborted, we will not send the next PATCH request.
// This is important if the abort method was called during a callback, such
// as onChunkComplete or onProgress.
if (this._aborted) {
return;
}
var req;
// Some browser and servers may not support the PATCH method. For those
// cases, you can tell tus-js-client to use a POST request with the
// X-HTTP-Method-Override header for simulating a PATCH request.
if (this.options.overridePatchMethod) {
req = this._openRequest('POST', this.url);
req.setHeader('X-HTTP-Method-Override', 'PATCH');
} else {
req = this._openRequest('PATCH', this.url);
}
req.setHeader('Upload-Offset', "".concat(this._offset));
var promise = this._addChunkToRequest(req);
promise.then(function (res) {
if (!inStatusCategory(res.getStatus(), 200)) {
_this8._emitHttpError(req, res, 'tus: unexpected response while uploading chunk');
return;
}
_this8._handleUploadResponse(req, res);
})["catch"](function (err) {
// Don't emit an error if the upload was aborted manually
if (_this8._aborted) {
return;
}
_this8._emitHttpError(req, null, "tus: failed to upload chunk at offset ".concat(_this8._offset), err);
});
}
/**
* _addChunktoRequest reads a chunk from the source and sends it using the
* supplied request object. It will not handle the response.
*
* @api private
*/
}, {
key: "_addChunkToRequest",
value: function _addChunkToRequest(req) {
var _this9 = this;
var start = this._offset;
var end = this._offset + this.options.chunkSize;
req.setProgressHandler(function (bytesSent) {
_this9._emitProgress(start + bytesSent, _this9._size);
});
if (this.options.protocol === PROTOCOL_TUS_V1) {
req.setHeader('Content-Type', 'application/offset+octet-stream');
} else if (this.options.protocol === PROTOCOL_IETF_DRAFT_05) {
req.setHeader('Content-Type', 'application/partial-upload');
}
// The specified chunkSize may be Infinity or the calcluated end position
// may exceed the file's size. In both cases, we limit the end position to
// the input's total size for simpler calculations and correctness.
if ((end === Number.POSITIVE_INFINITY || end > this._size) && !this.options.uploadLengthDeferred) {
end = this._size;
}
return this._source.slice(start, end).then(function (_ref2) {
var value = _ref2.value,
done = _ref2.done;
var valueSize = value !== null && value !== void 0 && value.size ? value.size : 0;
// If the upload length is deferred, the upload size was not specified during
// upload creation. So, if the file reader is done reading, we know the total
// upload size and can tell the tus server.
if (_this9.options.uploadLengthDeferred && done) {
_this9._size = _this9._offset + valueSize;
req.setHeader('Upload-Length', "".concat(_this9._size));
}
// The specified uploadSize might not match the actual amount of data that a source
// provides. In these cases, we cannot successfully complete the upload, so we
// rather error out and let the user know. If not, tus-js-client will be stuck
// in a loop of repeating empty PATCH requests.
// See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13
var newSize = _this9._offset + valueSize;
if (!_this9.options.uploadLengthDeferred && done && newSize !== _this9._size) {
return Promise.reject(new Error("upload was configured with a size of ".concat(_this9._size, " bytes, but the source is done after ").concat(newSize, " bytes")));
}
if (value === null) {
return _this9._sendRequest(req);
}
if (_this9.options.protocol === PROTOCOL_IETF_DRAFT_03 || _this9.options.protocol === PROTOCOL_IETF_DRAFT_05) {
req.setHeader('Upload-Complete', done ? '?1' : '?0');
}
_this9._emitProgress(_this9._offset, _this9._size);
return _this9._sendRequest(req, value);
});
}
/**
* _handleUploadResponse is used by requests that haven been sent using _addChunkToRequest
* and already have received a response.
*
* @api private
*/
}, {
key: "_handleUploadResponse",
value: function _handleUploadResponse(req, res) {
var offset = Number.parseInt(res.getHeader('Upload-Offset'), 10);
if (Number.isNaN(offset)) {
this._emitHttpError(req, res, 'tus: invalid or missing offset value');
return;
}
this._emitProgress(offset, this._size);
this._emitChunkComplete(offset - this._offset, offset, this._size);
this._offset = offset;
if (offset === this._size) {
// Yay, finally done :)
this._emitSuccess(res);
this._source.close();
return;
}
this._performUpload();
}
/**
* Create a new HTTP request object with the given method and URL.
*
* @api private
*/
}, {
key: "_openRequest",
value: function _openRequest(method, url) {
var req = openRequest(method, url, this.options);
this._req = req;
return req;
}
/**
* Remove the entry in the URL storage, if it has been saved before.
*
* @api private
*/
}, {
key: "_removeFromUrlStorage",
value: function _removeFromUrlStorage() {
var _this10 = this;
if (!this._urlStorageKey) return;
this._urlStorage.removeUpload(this._urlStorageKey)["catch"](function (err) {
_this10._emitError(err);
});
this._urlStorageKey = null;
}
/**
* Add the upload URL to the URL storage, if possible.
*
* @api private
*/
}, {
key: "_saveUploadInUrlStorage",
value: function _saveUploadInUrlStorage() {
var _this11 = this;
// We do not store the upload URL
// - if it was disabled in the option, or
// - if no fingerprint was calculated for the input (i.e. a stream), or
// - if the URL is already stored (i.e. key is set alread).
if (!this.options.storeFingerprintForResuming || !this._fingerprint || this._urlStorageKey !== null) {
return Promise.