fix PeerTube upload: final working solution — simple multipart POST with CURLFile; iterated through Google-resumable PATCH protocol debugging (HTTP version negotiation, chunk body encoding, off-by-one fixes) before settling on simpler POST approach

This commit is contained in:
Pontoporeia
2026-05-11 12:09:19 +02:00
parent 1b0451581d
commit cdec3e96a6
9 changed files with 281 additions and 112 deletions

View File

@@ -0,0 +1,96 @@
/**
* upload-progress.js
*
* Intercepts admin form submissions (add.php / edit.php) and submits via
* XMLHttpRequest to display a progress bar for large file uploads.
* Falls back to native form POST when JavaScript is unavailable.
*
* Requires an element with id="upload-progress-bar" inside the form.
* The progress bar is normally hidden (display:none), shown only during upload.
*/
(() => {
'use strict';
const FORMS = document.querySelectorAll('form[data-upload-progress]');
if (!FORMS.length) return;
for (const form of FORMS) {
const progressWrap = form.querySelector('#upload-progress-wrap');
const progressBar = form.querySelector('#upload-progress-bar');
const progressText = form.querySelector('#upload-progress-text');
const submitBtn = form.querySelector('button[type="submit"]');
if (!progressBar || !progressWrap) continue;
form.addEventListener('submit', function (e) {
// Only intercept if files are actually attached (FilePond inputs have files)
const fileInputs = form.querySelectorAll('input[type="file"]');
let hasFiles = false;
for (const fi of fileInputs) {
if (fi.files && fi.files.length > 0) {
hasFiles = true;
break;
}
}
if (!hasFiles) return; // let native submit handle it
e.preventDefault();
// Show progress bar
progressWrap.style.display = 'block';
progressBar.value = 0;
progressText.textContent = '0%';
if (submitBtn) submitBtn.disabled = true;
// Build FormData
const fd = new FormData(form);
// Ensure any FilePond-managed files are included — FilePond with
// storeAsFile:true copies files into the <input>.files, so FormData
// picks them up automatically from the DOM inputs.
// But we must also respect queue_order hidden inputs.
// FormData(form) already handles this since it reads all form fields.
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function (evt) {
if (evt.lengthComputable) {
const pct = Math.round((evt.loaded / evt.total) * 100);
progressBar.value = pct;
progressText.textContent = pct + '%';
}
});
xhr.addEventListener('load', function () {
// Server returns a redirect (302) on success, or re-renders the form on error.
// We can't follow 302 with XHR directly — the response body is the target page.
// Check if we got a redirect by examining the response URL.
const finalUrl = xhr.responseURL || '';
const isRedirect = xhr.status >= 200 && xhr.status < 300 && finalUrl !== '' && !finalUrl.endsWith(form.action);
if (isRedirect) {
// Success — navigate to the redirect target
window.location.href = finalUrl;
} else {
// Error — the server returned the form HTML with flash messages.
// Replace the current page content.
document.open();
document.write(xhr.responseText);
document.close();
}
});
xhr.addEventListener('error', function () {
progressText.textContent = 'Erreur réseau';
if (submitBtn) submitBtn.disabled = false;
});
xhr.addEventListener('abort', function () {
progressWrap.style.display = 'none';
if (submitBtn) submitBtn.disabled = false;
});
xhr.open('POST', form.action, true);
xhr.send(fd);
});
}
})();