From 6bad987f38d297e2e7747c5c5dc5d43a77966077 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 11 Oct 2024 13:17:20 -0300 Subject: [PATCH] fix: prevent possible race condition on upload flows/folders (#4114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (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 --- .../src/helpers/create-file-upload.ts | 65 +++++++++++++------ 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/helpers/create-file-upload.ts b/src/frontend/src/helpers/create-file-upload.ts index b11363ce5..196716756 100644 --- a/src/frontend/src/helpers/create-file-upload.ts +++ b/src/frontend/src/helpers/create-file-upload.ts @@ -2,34 +2,59 @@ export async function createFileUpload(props?: { accept?: string; multiple?: boolean; }): Promise { - let lock = false; return new Promise((resolve) => { const input = document.createElement("input"); input.type = "file"; input.accept = props?.accept ?? ".json"; input.multiple = props?.multiple ?? true; input.style.display = "none"; - // add a change event listener to the file input - input.onchange = async (e: Event) => { - lock = true; - resolve(Array.from((e.target as HTMLInputElement).files!)); - document.body.removeChild(input); + + let isResolved = false; + + 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); + } catch (error) { + console.warn("Error removing input element:", error); + } + } + window.removeEventListener("focus", handleFocus); }; - window.addEventListener( - "focus", - () => { - setTimeout(() => { - if (!lock) { - resolve([]); - document.body.removeChild(input); - } - }, 300); - }, - { once: true }, - ); - // add the input element to the body to ensure it is part of the DOM + + const handleChange = (e: Event) => { + if (!isResolved) { + isResolved = true; + const files = Array.from((e.target as HTMLInputElement).files!); + cleanup(); + resolve(files); + } + }; + + const handleFocus = () => { + setTimeout(() => { + if (!isResolved) { + isResolved = true; + cleanup(); + resolve([]); + } + }, 300); + }; + + input.addEventListener("change", handleChange); + window.addEventListener("focus", handleFocus); + document.body.appendChild(input); - // trigger the file input click event to open the file dialog input.click(); + + // Fallback timeout to ensure resolution + setTimeout(() => { + if (!isResolved) { + isResolved = true; + cleanup(); + resolve([]); + } + }, 60000); }); }