feat: integrate tus.io for resumable and chunked uploads (#2145)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
import { upload as postTus, useTus } from "./tus";
|
||||
|
||||
export async function fetch(url) {
|
||||
url = removePrefix(url);
|
||||
@@ -78,6 +79,22 @@ export function download(format, ...files) {
|
||||
}
|
||||
|
||||
export async function post(url, content = "", overwrite = false, onupload) {
|
||||
// Use the pre-existing API if:
|
||||
const useResourcesApi =
|
||||
// a folder is being created
|
||||
url.endsWith("/") ||
|
||||
// We're not using http(s)
|
||||
(content instanceof Blob &&
|
||||
!["http:", "https:"].includes(window.location.protocol)) ||
|
||||
// Tus is disabled / not applicable
|
||||
!(await useTus(content));
|
||||
|
||||
return useResourcesApi
|
||||
? postResources(url, content, overwrite, onupload)
|
||||
: postTus(url, content, overwrite, onupload);
|
||||
}
|
||||
|
||||
async function postResources(url, content = "", overwrite = false, onupload) {
|
||||
url = removePrefix(url);
|
||||
|
||||
let bufferContent;
|
||||
|
||||
85
frontend/src/api/tus.js
Normal file
85
frontend/src/api/tus.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import * as tus from "tus-js-client";
|
||||
import { tusEndpoint, tusSettings } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
import { removePrefix } from "@/api/utils";
|
||||
import { fetchURL } from "./utils";
|
||||
|
||||
const RETRY_BASE_DELAY = 1000;
|
||||
const RETRY_MAX_DELAY = 20000;
|
||||
|
||||
export async function upload(url, content = "", overwrite = false, onupload) {
|
||||
if (!tusSettings) {
|
||||
// Shouldn't happen as we check for tus support before calling this function
|
||||
throw new Error("Tus.io settings are not defined");
|
||||
}
|
||||
|
||||
url = removePrefix(url);
|
||||
let resourceUrl = `${tusEndpoint}${url}?override=${overwrite}`;
|
||||
|
||||
await createUpload(resourceUrl);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let upload = new tus.Upload(content, {
|
||||
uploadUrl: resourceUrl,
|
||||
chunkSize: tusSettings.chunkSize,
|
||||
retryDelays: computeRetryDelays(tusSettings),
|
||||
parallelUploads: 1,
|
||||
storeFingerprintForResuming: false,
|
||||
headers: {
|
||||
"X-Auth": store.state.jwt,
|
||||
},
|
||||
onError: function (error) {
|
||||
reject("Upload failed: " + error);
|
||||
},
|
||||
onProgress: function (bytesUploaded) {
|
||||
// Emulate ProgressEvent.loaded which is used by calling functions
|
||||
// loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded)
|
||||
if (typeof onupload === "function") {
|
||||
onupload({ loaded: bytesUploaded });
|
||||
}
|
||||
},
|
||||
onSuccess: function () {
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
upload.start();
|
||||
});
|
||||
}
|
||||
|
||||
async function createUpload(resourceUrl) {
|
||||
let headResp = await fetchURL(resourceUrl, {
|
||||
method: "POST",
|
||||
});
|
||||
if (headResp.status !== 201) {
|
||||
throw new Error(
|
||||
`Failed to create an upload: ${headResp.status} ${headResp.statusText}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function computeRetryDelays(tusSettings) {
|
||||
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
||||
// Disable retries altogether
|
||||
return null;
|
||||
}
|
||||
// The tus client expects our retries as an array with computed backoffs
|
||||
// E.g.: [0, 3000, 5000, 10000, 20000]
|
||||
const retryDelays = [];
|
||||
let delay = 0;
|
||||
|
||||
for (let i = 0; i < tusSettings.retryCount; i++) {
|
||||
retryDelays.push(Math.min(delay, RETRY_MAX_DELAY));
|
||||
delay =
|
||||
delay === 0 ? RETRY_BASE_DELAY : Math.min(delay * 2, RETRY_MAX_DELAY);
|
||||
}
|
||||
|
||||
return retryDelays;
|
||||
}
|
||||
|
||||
export async function useTus(content) {
|
||||
return isTusSupported() && content instanceof Blob;
|
||||
}
|
||||
|
||||
function isTusSupported() {
|
||||
return tus.isSupported === true;
|
||||
}
|
||||
Reference in New Issue
Block a user