Fix uploads stuck pending due to page navigation killing sync
The sync engine was adding per-photo backoff delays (2-60s) inside the processing loop, during which the user would navigate to another page (browser -> queue -> browser), killing the sync mid-wait. The upload POST never fired because the delay ran out the clock. Changes: - Remove per-photo backoff delay from processQueue loop — uploads start immediately on page load without blocking - Move retry scheduling to after the full queue pass: if any uploads failed, schedule a new triggerSync() after 15s instead of blocking inline - Keep the duplicate check on retries (fast, catches interrupted uploads that actually succeeded server-side) - uploadPhoto now returns true/false so processQueue can track failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,7 @@ const Sync = {
|
||||
MAX_RETRIES: 15,
|
||||
VERIFY_DELAY_MS: 800,
|
||||
UPLOAD_TIMEOUT_MS: 120000,
|
||||
// Backoff delays in ms per retry attempt
|
||||
BACKOFF_DELAYS: [0, 2000, 5000, 10000, 15000, 30000, 30000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000],
|
||||
_retryTimerId: null,
|
||||
|
||||
init(username) {
|
||||
this.currentUsername = username;
|
||||
@@ -34,6 +33,10 @@ const Sync = {
|
||||
console.log('[SYNC] Network offline');
|
||||
this.isOnline = false;
|
||||
this.isSyncing = false;
|
||||
if (this._retryTimerId) {
|
||||
clearTimeout(this._retryTimerId);
|
||||
this._retryTimerId = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -63,14 +66,26 @@ const Sync = {
|
||||
console.log('[SYNC] Starting sync...');
|
||||
this.isSyncing = true;
|
||||
|
||||
let hadFailures = false;
|
||||
try {
|
||||
await this.processQueue();
|
||||
hadFailures = await this.processQueue();
|
||||
} catch (error) {
|
||||
console.error('[SYNC] Error:', error);
|
||||
hadFailures = true;
|
||||
} finally {
|
||||
this.isSyncing = false;
|
||||
this._setUploading(false);
|
||||
}
|
||||
|
||||
// If there were failures, schedule a retry cycle after a delay
|
||||
// so it doesn't block the current page load
|
||||
if (hadFailures && this.isOnline) {
|
||||
console.log('[SYNC] Scheduling retry in 15s...');
|
||||
this._retryTimerId = setTimeout(() => {
|
||||
this._retryTimerId = null;
|
||||
this.triggerSync();
|
||||
}, 15000);
|
||||
}
|
||||
},
|
||||
|
||||
async processQueue() {
|
||||
@@ -82,9 +97,11 @@ const Sync = {
|
||||
console.log('[SYNC] Found', pendingPhotos.length, 'photos to process');
|
||||
|
||||
if (pendingPhotos.length === 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let hadFailures = false;
|
||||
|
||||
for (const photo of pendingPhotos) {
|
||||
if (!this.isOnline) {
|
||||
console.log('[SYNC] Lost connection, stopping');
|
||||
@@ -99,21 +116,13 @@ const Sync = {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Backoff delay before retry attempts
|
||||
if (retryCount > 0) {
|
||||
const backoff = this.BACKOFF_DELAYS[Math.min(retryCount, this.BACKOFF_DELAYS.length - 1)];
|
||||
console.log('[SYNC] Waiting', backoff + 'ms before retry', retryCount + 1);
|
||||
await this.delay(backoff);
|
||||
|
||||
// Re-check connectivity after waiting
|
||||
if (!this.isOnline) {
|
||||
console.log('[SYNC] Lost connection during backoff, stopping');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.uploadPhoto(photo);
|
||||
// No delay — upload immediately. Retries are handled by
|
||||
// scheduling a new triggerSync() after the full queue pass.
|
||||
const success = await this.uploadPhoto(photo);
|
||||
if (!success) hadFailures = true;
|
||||
}
|
||||
|
||||
return hadFailures;
|
||||
},
|
||||
|
||||
async uploadPhoto(photo) {
|
||||
@@ -134,7 +143,8 @@ const Sync = {
|
||||
// Prevent page navigation during upload
|
||||
this._setUploading(true);
|
||||
|
||||
// Check for duplicates on retries (skip on first attempt to reduce latency)
|
||||
// On retries, first check if a previous attempt actually succeeded
|
||||
// (the upload may have completed but the verify/status update was interrupted)
|
||||
if (retryCount > 0) {
|
||||
const fullPath = photo.targetPath.replace(/\/$/, '') + '/' + photo.filename;
|
||||
const alreadyExists = await this.checkFileExists(fullPath);
|
||||
@@ -148,7 +158,7 @@ const Sync = {
|
||||
});
|
||||
await this.pruneHistory();
|
||||
this._setUploading(false);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +237,7 @@ const Sync = {
|
||||
|
||||
// Clear navigation guard after successful upload
|
||||
this._setUploading(false);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this._setUploading(false);
|
||||
@@ -237,6 +248,7 @@ const Sync = {
|
||||
lastError: error.message,
|
||||
error: error.message
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -264,7 +276,6 @@ const Sync = {
|
||||
const result = await response.json();
|
||||
return result.exists === true;
|
||||
} catch (e) {
|
||||
// If we can't check, assume it doesn't exist and proceed with upload
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user