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:
@@ -7,43 +7,56 @@ const Storage = {
|
||||
init() {
|
||||
// Initialize Dexie database
|
||||
this.db = new Dexie('nextsnap');
|
||||
|
||||
|
||||
// Define schema with compound indexes
|
||||
this.db.version(1).stores({
|
||||
photos: '++id, username, timestamp, filename, targetPath, status, [username+status], [username+timestamp]',
|
||||
settings: '++id, username, [username+key]'
|
||||
});
|
||||
|
||||
|
||||
// Request persistent storage so the browser won't evict our data
|
||||
this.requestPersistence();
|
||||
|
||||
return this.db.open();
|
||||
},
|
||||
|
||||
async requestPersistence() {
|
||||
if (navigator.storage && navigator.storage.persist) {
|
||||
const granted = await navigator.storage.persist();
|
||||
console.log('[STORAGE] Persistence:', granted ? 'granted' : 'denied');
|
||||
}
|
||||
},
|
||||
|
||||
async savePhoto(photoData) {
|
||||
/**
|
||||
* Save a photo to IndexedDB
|
||||
* photoData: {
|
||||
* username: string,
|
||||
* timestamp: number,
|
||||
* filename: string,
|
||||
* targetPath: string,
|
||||
* blob: Blob,
|
||||
* status: 'pending' | 'uploading' | 'uploaded' | 'verified'
|
||||
* retryCount: number,
|
||||
* lastError: string
|
||||
* }
|
||||
*/
|
||||
// Convert Blob to ArrayBuffer for storage resilience.
|
||||
// iOS Safari can evict Blob file-backed data from IndexedDB,
|
||||
// but ArrayBuffer is stored inline and won't be evicted.
|
||||
let imageData = photoData.blob;
|
||||
if (imageData instanceof Blob) {
|
||||
imageData = await imageData.arrayBuffer();
|
||||
}
|
||||
|
||||
const id = await this.db.photos.add({
|
||||
username: photoData.username,
|
||||
timestamp: photoData.timestamp,
|
||||
filename: photoData.filename,
|
||||
targetPath: photoData.targetPath,
|
||||
blob: photoData.blob,
|
||||
blob: imageData,
|
||||
status: photoData.status || 'pending',
|
||||
retryCount: photoData.retryCount || 0,
|
||||
lastError: photoData.lastError || null
|
||||
});
|
||||
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
// Convert stored photo data back to a Blob for upload or display.
|
||||
// Handles both legacy Blob storage and new ArrayBuffer storage.
|
||||
getBlob(photo) {
|
||||
if (!photo || !photo.blob) return null;
|
||||
if (photo.blob instanceof Blob) return photo.blob;
|
||||
return new Blob([photo.blob], { type: 'image/jpeg' });
|
||||
},
|
||||
|
||||
async getPhoto(id) {
|
||||
return await this.db.photos.get(id);
|
||||
|
||||
Reference in New Issue
Block a user