Prevent blob eviction: store as ArrayBuffer + request persistence
iOS Safari evicts Blob file-backed data from IndexedDB under memory pressure, causing upload POSTs to throw 'Load failed' without ever reaching the server. Two-pronged fix: 1. Store photos as ArrayBuffer (inline bytes) instead of Blob (file reference) in IndexedDB — ArrayBuffers are not subject to eviction 2. Request navigator.storage.persist() to signal the browser not to evict our storage under pressure Also adds Storage.getBlob() helper for converting stored ArrayBuffer back to Blob at upload/display time, with backward compat for any existing Blob-stored photos. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -137,31 +137,18 @@ const Sync = {
|
||||
return true;
|
||||
}
|
||||
|
||||
const blob = freshPhoto.blob;
|
||||
// Convert stored data (ArrayBuffer or legacy Blob) to a Blob for upload
|
||||
const blob = Storage.getBlob(freshPhoto);
|
||||
|
||||
console.log('[SYNC] Uploading:', photo.filename,
|
||||
'(' + (blob ? (blob.size / 1024 / 1024).toFixed(1) + 'MB' : 'no blob') + ')',
|
||||
'attempt', retryCount + 1, 'of', this.MAX_RETRIES);
|
||||
|
||||
// Validate blob reference exists
|
||||
if (!blob || !(blob instanceof Blob) || blob.size === 0) {
|
||||
// Validate blob exists and has content
|
||||
if (!blob || blob.size === 0) {
|
||||
throw new Error('Photo data is missing or corrupted - please delete and re-capture');
|
||||
}
|
||||
|
||||
// Actually try to read the blob to verify data is accessible
|
||||
// (iOS Safari can evict blob data from IndexedDB while keeping the reference)
|
||||
const readable = await this.validateBlobReadable(blob);
|
||||
if (!readable) {
|
||||
// Blob data is gone — no amount of retrying will fix this
|
||||
console.error('[SYNC] Blob data is no longer readable:', photo.filename);
|
||||
await Storage.updatePhoto(photo.id, {
|
||||
status: 'failed',
|
||||
lastError: 'Photo data was lost by the browser - please re-capture',
|
||||
error: 'Photo data was lost by the browser - please re-capture'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
await Storage.updatePhoto(photo.id, { status: 'uploading' });
|
||||
|
||||
// Prevent page navigation during upload
|
||||
@@ -294,19 +281,6 @@ const Sync = {
|
||||
}
|
||||
},
|
||||
|
||||
async validateBlobReadable(blob) {
|
||||
try {
|
||||
// Try to read the first byte — if iOS has evicted the data,
|
||||
// this will throw even though the Blob reference looks valid
|
||||
const slice = blob.slice(0, 1);
|
||||
const buf = await slice.arrayBuffer();
|
||||
return buf.byteLength > 0;
|
||||
} catch (e) {
|
||||
console.error('[SYNC] Blob readability check failed:', e.message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async checkFileExists(path) {
|
||||
try {
|
||||
const verifyUrl = '/api/files/verify?path=' + encodeURIComponent(path);
|
||||
|
||||
Reference in New Issue
Block a user