@@ -34,38 +34,50 @@ function uploadViaXhr(
|
||||
onProgress: (fraction: number) => void
|
||||
): Promise<{ url: string; key: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/upload/local");
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) onProgress(e.loaded / e.total);
|
||||
});
|
||||
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
if (json.url) resolve({ url: json.url, key: json.key ?? "" });
|
||||
else reject(new Error(json.error ?? "Upload failed"));
|
||||
} catch {
|
||||
reject(new Error("Invalid server response"));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
reject(new Error(json.error ?? `HTTP ${xhr.status}`));
|
||||
} catch {
|
||||
reject(new Error(`HTTP ${xhr.status}`));
|
||||
(async () => {
|
||||
// Ask the server for a presigned Hetzner PUT URL, then upload the
|
||||
// bytes directly from the browser — this bypasses this app's server
|
||||
// (and any reverse-proxy / CDN body size limit, e.g. Cloudflare's
|
||||
// 100MB edge cap) entirely for large footage plates.
|
||||
let presignedUrl: string;
|
||||
let key: string;
|
||||
let url: string;
|
||||
try {
|
||||
const presignRes = await fetch("/api/upload/presign", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ fileName: file.name, contentType: file.type, folder: "videos" }),
|
||||
});
|
||||
if (!presignRes.ok) {
|
||||
const json = await presignRes.json().catch(() => null);
|
||||
throw new Error(json?.error ?? `Failed to prepare upload (HTTP ${presignRes.status})`);
|
||||
}
|
||||
const presignJson = await presignRes.json();
|
||||
presignedUrl = presignJson.presignedUrl;
|
||||
key = presignJson.key;
|
||||
url = presignJson.url;
|
||||
} catch (err) {
|
||||
reject(err instanceof Error ? err : new Error("Failed to prepare upload"));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener("error", () => reject(new Error("Network error")));
|
||||
xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
||||
xhr.send(formData);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", presignedUrl);
|
||||
xhr.setRequestHeader("Content-Type", file.type);
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) onProgress(e.loaded / e.total);
|
||||
});
|
||||
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) resolve({ url, key });
|
||||
else reject(new Error(`Upload failed (HTTP ${xhr.status})`));
|
||||
});
|
||||
|
||||
xhr.addEventListener("error", () => reject(new Error("Network error")));
|
||||
xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
||||
xhr.send(file);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user