(fileBlob: Blob, filename: string, primary: ServiceType)
| 1370 | } |
| 1371 | |
| 1372 | async function uploadWithFallbacks(fileBlob: Blob, filename: string, primary: ServiceType): Promise<string> { |
| 1373 | const uploadOrder = buildUploadOrder(primary, filename); |
| 1374 | const attempted: string[] = []; |
| 1375 | let lastError = "Unknown error"; |
| 1376 | |
| 1377 | setUploadState({ |
| 1378 | phase: "uploading", |
| 1379 | fileName: filename, |
| 1380 | totalAttempts: uploadOrder.length, |
| 1381 | attempt: 1, |
| 1382 | percent: 5, |
| 1383 | transferredBytes: 0, |
| 1384 | totalBytes: fileBlob.size, |
| 1385 | status: `Starting upload via ${serviceLabels[uploadOrder[0]]}...`, |
| 1386 | currentService: uploadOrder[0], |
| 1387 | currentServiceLabel: serviceLabels[uploadOrder[0]], |
| 1388 | canCancel: true |
| 1389 | }); |
| 1390 | |
| 1391 | for (const service of uploadOrder) { |
| 1392 | if (cancelRequested) throw new Error("Upload cancelled by user"); |
| 1393 | |
| 1394 | const configError = getServiceConfigError(service); |
| 1395 | if (configError) { |
| 1396 | showToast(`Skipped ${serviceLabels[service]}: ${configError}`, Toasts.Type.MESSAGE); |
| 1397 | lastError = `${serviceLabels[service]} is not configured (${configError})`; |
| 1398 | continue; |
| 1399 | } |
| 1400 | |
| 1401 | const attempt = attempted.length + 1; |
| 1402 | |
| 1403 | setUploadState({ |
| 1404 | phase: attempt === 1 ? "uploading" : "retrying", |
| 1405 | attempt, |
| 1406 | currentService: service, |
| 1407 | currentServiceLabel: serviceLabels[service], |
| 1408 | transferredBytes: 0, |
| 1409 | totalBytes: fileBlob.size, |
| 1410 | percent: 0, |
| 1411 | status: attempt === 1 |
| 1412 | ? `Uploading via ${serviceLabels[service]}...` |
| 1413 | : `Retrying with ${serviceLabels[service]} (${attempt}/${uploadOrder.length})...` |
| 1414 | }); |
| 1415 | |
| 1416 | const stopPolling = pollNativeUploadProgress(); |
| 1417 | try { |
| 1418 | const uploadedUrl = await uploadToService(service, fileBlob, filename); |
| 1419 | |
| 1420 | if (!/https?:\/\//i.test(uploadedUrl)) { |
| 1421 | throw new Error(`${serviceLabels[service]} returned no URL: ${uploadedUrl.slice(0, 100) || "(empty response)"}`); |
| 1422 | } |
| 1423 | |
| 1424 | if (attempted.length) { |
| 1425 | showToast(`Upload succeeded with ${serviceLabels[service]} after fallback`, Toasts.Type.SUCCESS); |
| 1426 | } |
| 1427 | |
| 1428 | setUploadState({ |
| 1429 | phase: "success", |
no test coverage detected