feat: add auto-retry (3 attempts) for file uploads on flaky connections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-07 20:10:52 -04:00
parent 97d7f9b625
commit 158eba416d

View File

@@ -166,7 +166,10 @@ export function RequirementUploadSlot({
requirementId: requirement.id, requirementId: requirement.id,
}) })
// Upload file with progress tracking // Upload file with progress tracking and auto-retry
const maxRetries = 3
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest() const xhr = new XMLHttpRequest()
xhr.upload.addEventListener('progress', (event) => { xhr.upload.addEventListener('progress', (event) => {
@@ -181,11 +184,28 @@ export function RequirementUploadSlot({
reject(new Error(`Upload failed with status ${xhr.status}`)) reject(new Error(`Upload failed with status ${xhr.status}`))
} }
}) })
xhr.addEventListener('error', () => reject(new Error('Upload failed'))) xhr.addEventListener('error', () =>
reject(new Error('Network error during upload'))
)
xhr.addEventListener('abort', () =>
reject(new Error('Upload was aborted'))
)
xhr.open('PUT', url) xhr.open('PUT', url)
xhr.setRequestHeader('Content-Type', file.type) xhr.setRequestHeader('Content-Type', file.type)
xhr.send(file) xhr.send(file)
}) })
break // Success — exit retry loop
} catch (uploadErr) {
if (attempt < maxRetries) {
const delay = attempt * 2000
toast.info(`Upload interrupted, retrying... (${attempt}/${maxRetries})`)
setProgress(0)
await new Promise((r) => setTimeout(r, delay))
} else {
throw uploadErr
}
}
}
// Save metadata // Save metadata
await saveFileMetadata.mutateAsync({ await saveFileMetadata.mutateAsync({