fix: prevent possible race condition on upload flows/folders (#4114)

* ✨ (create-file-upload.ts): improve file upload functionality by adding cleanup logic and handling edge cases for resolving file selection

* 🐛 (create-file-upload.ts): fix removing input element from the DOM by checking if it is contained in the document body before removal
💡 (create-file-upload.ts): add a comment to clarify the purpose of the setTimeout function for a fallback timeout of 1 minute

* ✨ (create-file-upload.ts): change createFileUpload function to be asynchronous to support Promise return type for better handling of file upload operations

* 📝 (create-file-upload.ts): improve error handling when removing input element from the DOM
📝 (create-file-upload.ts): remove unnecessary comment about timeout value in the code
This commit is contained in:
Cristhian Zanforlin Lousa 2024-10-11 13:17:20 -03:00 • committed by GitHub
commit 6bad987f38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2,34 +2,59 @@ export async function createFileUpload(props?: {
accept?: string; accept?: string;
multiple?: boolean; multiple?: boolean;
}): Promise<File[]> { }): Promise<File[]> {
let lock = false;
return new Promise((resolve) => { return new Promise((resolve) => {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = props?.accept ?? ".json"; input.accept = props?.accept ?? ".json";
input.multiple = props?.multiple ?? true; input.multiple = props?.multiple ?? true;
input.style.display = "none"; input.style.display = "none";
// add a change event listener to the file input
input.onchange = async (e: Event) => { let isResolved = false;
lock = true;
resolve(Array.from((e.target as HTMLInputElement).files!)); const cleanup = () => {
// Check if the input element still exists in the DOM before attempting to remove it
if (input && document.body.contains(input)) {
try {
document.body.removeChild(input); document.body.removeChild(input);
} catch (error) {
console.warn("Error removing input element:", error);
}
}
window.removeEventListener("focus", handleFocus);
}; };
window.addEventListener(
"focus", const handleChange = (e: Event) => {
() => { if (!isResolved) {
isResolved = true;
const files = Array.from((e.target as HTMLInputElement).files!);
cleanup();
resolve(files);
}
};
const handleFocus = () => {
setTimeout(() => { setTimeout(() => {
if (!lock) { if (!isResolved) {
isResolved = true;
cleanup();
resolve([]); resolve([]);
document.body.removeChild(input);
} }
}, 300); }, 300);
}, };
{ once: true },
); input.addEventListener("change", handleChange);
// add the input element to the body to ensure it is part of the DOM window.addEventListener("focus", handleFocus);
document.body.appendChild(input); document.body.appendChild(input);
// trigger the file input click event to open the file dialog
input.click(); input.click();
// Fallback timeout to ensure resolution
setTimeout(() => {
if (!isResolved) {
isResolved = true;
cleanup();
resolve([]);
}
}, 60000);
}); });
} }