Some checks failed
Automated Container Build / build-and-push (push) Failing after 12s
1081 lines
No EOL
54 KiB
JavaScript
1081 lines
No EOL
54 KiB
JavaScript
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); }
|
|
import { Base64 } from 'js-base64';
|
|
import URL from 'url-parse';
|
|
import DetailedError from './error.js';
|
|
import { log } from './logger.js';
|
|
import uuid from './uuid.js';
|
|
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) {
|
|
log('No fingerprint was calculated meaning that the upload cannot be stored in the URL storage.');
|
|
} else {
|
|
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);
|
|
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) {
|
|
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) {
|
|
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
|
|
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 DetailedError(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);
|
|
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.resolve();
|
|
}
|
|
var storedUpload = {
|
|
size: this._size,
|
|
metadata: this.options.metadata,
|
|
creationTime: new Date().toString()
|
|
};
|
|
if (this._parallelUploads) {
|
|
// Save multiple URLs if the parallelUploads option is used ...
|
|
storedUpload.parallelUploadUrls = this._parallelUploadUrls;
|
|
} else {
|
|
// ... otherwise we just save the one available URL.
|
|
storedUpload.uploadUrl = this.url;
|
|
}
|
|
return this._urlStorage.addUpload(this._fingerprint, storedUpload).then(function (urlStorageKey) {
|
|
_this11._urlStorageKey = urlStorageKey;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Send a request with the provided body.
|
|
*
|
|
* @api private
|
|
*/
|
|
}, {
|
|
key: "_sendRequest",
|
|
value: function _sendRequest(req) {
|
|
var body = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
return sendRequest(req, body, this.options);
|
|
}
|
|
}], [{
|
|
key: "terminate",
|
|
value: function terminate(url) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
var req = openRequest('DELETE', url, options);
|
|
return sendRequest(req, null, options).then(function (res) {
|
|
// A 204 response indicates a successfull request
|
|
if (res.getStatus() === 204) {
|
|
return;
|
|
}
|
|
throw new DetailedError('tus: unexpected response while terminating upload', null, req, res);
|
|
})["catch"](function (err) {
|
|
if (!(err instanceof DetailedError)) {
|
|
err = new DetailedError('tus: failed to terminate upload', err, req, null);
|
|
}
|
|
if (!shouldRetry(err, 0, options)) {
|
|
throw err;
|
|
}
|
|
|
|
// Instead of keeping track of the retry attempts, we remove the first element from the delays
|
|
// array. If the array is empty, all retry attempts are used up and we will bubble up the error.
|
|
// We recursively call the terminate function will removing elements from the retryDelays array.
|
|
var delay = options.retryDelays[0];
|
|
var remainingDelays = options.retryDelays.slice(1);
|
|
var newOptions = _objectSpread(_objectSpread({}, options), {}, {
|
|
retryDelays: remainingDelays
|
|
});
|
|
return new Promise(function (resolve) {
|
|
return setTimeout(resolve, delay);
|
|
}).then(function () {
|
|
return BaseUpload.terminate(url, newOptions);
|
|
});
|
|
});
|
|
}
|
|
}]);
|
|
}();
|
|
function encodeMetadata(metadata) {
|
|
return Object.entries(metadata).map(function (_ref3) {
|
|
var _ref4 = _slicedToArray(_ref3, 2),
|
|
key = _ref4[0],
|
|
value = _ref4[1];
|
|
return "".concat(key, " ").concat(Base64.encode(String(value)));
|
|
}).join(',');
|
|
}
|
|
|
|
/**
|
|
* Checks whether a given status is in the range of the expected category.
|
|
* For example, only a status between 200 and 299 will satisfy the category 200.
|
|
*
|
|
* @api private
|
|
*/
|
|
function inStatusCategory(status, category) {
|
|
return status >= category && status < category + 100;
|
|
}
|
|
|
|
/**
|
|
* Create a new HTTP request with the specified method and URL.
|
|
* The necessary headers that are included in every request
|
|
* will be added, including the request ID.
|
|
*
|
|
* @api private
|
|
*/
|
|
function openRequest(method, url, options) {
|
|
var req = options.httpStack.createRequest(method, url);
|
|
if (options.protocol === PROTOCOL_IETF_DRAFT_03) {
|
|
req.setHeader('Upload-Draft-Interop-Version', '5');
|
|
} else if (options.protocol === PROTOCOL_IETF_DRAFT_05) {
|
|
req.setHeader('Upload-Draft-Interop-Version', '6');
|
|
} else {
|
|
req.setHeader('Tus-Resumable', '1.0.0');
|
|
}
|
|
var headers = options.headers || {};
|
|
for (var _i2 = 0, _Object$entries = Object.entries(headers); _i2 < _Object$entries.length; _i2++) {
|
|
var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
|
|
name = _Object$entries$_i[0],
|
|
value = _Object$entries$_i[1];
|
|
req.setHeader(name, value);
|
|
}
|
|
if (options.addRequestId) {
|
|
var requestId = uuid();
|
|
req.setHeader('X-Request-ID', requestId);
|
|
}
|
|
return req;
|
|
}
|
|
|
|
/**
|
|
* Send a request with the provided body while invoking the onBeforeRequest
|
|
* and onAfterResponse callbacks.
|
|
*
|
|
* @api private
|
|
*/
|
|
function sendRequest(_x, _x2, _x3) {
|
|
return _sendRequest2.apply(this, arguments);
|
|
}
|
|
/**
|
|
* Checks whether the browser running this code has internet access.
|
|
* This function will always return true in the node.js environment
|
|
*
|
|
* @api private
|
|
*/
|
|
function _sendRequest2() {
|
|
_sendRequest2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(req, body, options) {
|
|
var res;
|
|
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
while (1) switch (_context.prev = _context.next) {
|
|
case 0:
|
|
if (!(typeof options.onBeforeRequest === 'function')) {
|
|
_context.next = 3;
|
|
break;
|
|
}
|
|
_context.next = 3;
|
|
return options.onBeforeRequest(req);
|
|
case 3:
|
|
_context.next = 5;
|
|
return req.send(body);
|
|
case 5:
|
|
res = _context.sent;
|
|
if (!(typeof options.onAfterResponse === 'function')) {
|
|
_context.next = 9;
|
|
break;
|
|
}
|
|
_context.next = 9;
|
|
return options.onAfterResponse(req, res);
|
|
case 9:
|
|
return _context.abrupt("return", res);
|
|
case 10:
|
|
case "end":
|
|
return _context.stop();
|
|
}
|
|
}, _callee);
|
|
}));
|
|
return _sendRequest2.apply(this, arguments);
|
|
}
|
|
function isOnline() {
|
|
var online = true;
|
|
// Note: We don't reference `window` here because the navigator object also exists
|
|
// in a Web Worker's context.
|
|
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
|
|
online = false;
|
|
}
|
|
return online;
|
|
}
|
|
|
|
/**
|
|
* Checks whether or not it is ok to retry a request.
|
|
* @param {Error|DetailedError} err the error returned from the last request
|
|
* @param {number} retryAttempt the number of times the request has already been retried
|
|
* @param {object} options tus Upload options
|
|
*
|
|
* @api private
|
|
*/
|
|
function shouldRetry(err, retryAttempt, options) {
|
|
// We only attempt a retry if
|
|
// - retryDelays option is set
|
|
// - we didn't exceed the maxium number of retries, yet, and
|
|
// - this error was caused by a request or it's response and
|
|
// - the error is server error (i.e. not a status 4xx except a 409 or 423) or
|
|
// a onShouldRetry is specified and returns true
|
|
// - the browser does not indicate that we are offline
|
|
if (options.retryDelays == null || retryAttempt >= options.retryDelays.length || err.originalRequest == null) {
|
|
return false;
|
|
}
|
|
if (options && typeof options.onShouldRetry === 'function') {
|
|
return options.onShouldRetry(err, retryAttempt, options);
|
|
}
|
|
return defaultOnShouldRetry(err);
|
|
}
|
|
|
|
/**
|
|
* determines if the request should be retried. Will only retry if not a status 4xx except a 409 or 423
|
|
* @param {DetailedError} err
|
|
* @returns {boolean}
|
|
*/
|
|
function defaultOnShouldRetry(err) {
|
|
var status = err.originalResponse ? err.originalResponse.getStatus() : 0;
|
|
return (!inStatusCategory(status, 400) || status === 409 || status === 423) && isOnline();
|
|
}
|
|
|
|
/**
|
|
* Resolve a relative link given the origin as source. For example,
|
|
* if a HTTP request to http://example.com/files/ returns a Location
|
|
* header with the value /upload/abc, the resolved URL will be:
|
|
* http://example.com/upload/abc
|
|
*/
|
|
function resolveUrl(origin, link) {
|
|
return new URL(link, origin).toString();
|
|
}
|
|
|
|
/**
|
|
* Calculate the start and end positions for the parts if an upload
|
|
* is split into multiple parallel requests.
|
|
*
|
|
* @param {number} totalSize The byte size of the upload, which will be split.
|
|
* @param {number} partCount The number in how many parts the upload will be split.
|
|
* @return {object[]}
|
|
* @api private
|
|
*/
|
|
function splitSizeIntoParts(totalSize, partCount) {
|
|
var partSize = Math.floor(totalSize / partCount);
|
|
var parts = [];
|
|
for (var i = 0; i < partCount; i++) {
|
|
parts.push({
|
|
start: partSize * i,
|
|
end: partSize * (i + 1)
|
|
});
|
|
}
|
|
parts[partCount - 1].end = totalSize;
|
|
return parts;
|
|
}
|
|
BaseUpload.defaultOptions = defaultOptions;
|
|
export default BaseUpload; |