273 lines
8.7 KiB
JavaScript
273 lines
8.7 KiB
JavaScript
/**
|
|
* IndexedDB Failed Uploads Storage
|
|
* Handles storing and retrying failed uploads with background sync
|
|
*/
|
|
|
|
const DB_NAME = 'SpeakrFailedUploads';
|
|
const DB_VERSION = 1;
|
|
const STORE_NAME = 'failedUploads';
|
|
|
|
let dbInstance = null;
|
|
|
|
/**
|
|
* Initialize IndexedDB
|
|
*/
|
|
export const initDB = () => {
|
|
return new Promise((resolve, reject) => {
|
|
if (dbInstance) {
|
|
resolve(dbInstance);
|
|
return;
|
|
}
|
|
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to open database:', request.error);
|
|
reject(request.error);
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
dbInstance = request.result;
|
|
console.log('[FailedUploadsDB] Database opened successfully');
|
|
resolve(dbInstance);
|
|
};
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result;
|
|
|
|
// Create object store for failed uploads
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
const objectStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
|
|
objectStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
objectStore.createIndex('clientId', 'clientId', { unique: false });
|
|
console.log('[FailedUploadsDB] Object store created');
|
|
}
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Store a failed upload for later retry
|
|
*/
|
|
export const storeFailedUpload = async (uploadData) => {
|
|
try {
|
|
const db = await initDB();
|
|
|
|
// Convert File to ArrayBuffer BEFORE opening transaction.
|
|
// IDB transactions auto-close when inactive — the async arrayBuffer()
|
|
// call would cause the transaction to expire before add().
|
|
let fileData = uploadData.fileData || null;
|
|
if (uploadData.file && !fileData) {
|
|
fileData = await uploadData.file.arrayBuffer();
|
|
}
|
|
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
const failedUpload = {
|
|
timestamp: Date.now(),
|
|
clientId: uploadData.clientId || `client-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
fileName: uploadData.file?.name || uploadData.fileName || 'unknown',
|
|
fileSize: uploadData.file?.size || uploadData.fileSize || 0,
|
|
notes: uploadData.notes || '',
|
|
tags: uploadData.tags || [],
|
|
asrOptions: uploadData.asrOptions || {},
|
|
retryCount: uploadData.retryCount || 0,
|
|
lastError: uploadData.error || '',
|
|
fileData: fileData,
|
|
mimeType: uploadData.file?.type || uploadData.mimeType || 'audio/webm'
|
|
};
|
|
|
|
const request = objectStore.add(failedUpload);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
request.onsuccess = () => {
|
|
console.log('[FailedUploadsDB] Upload stored for retry:', failedUpload.fileName);
|
|
resolve(request.result); // Returns the ID
|
|
};
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to store upload:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error storing failed upload:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get all failed uploads waiting to retry
|
|
*/
|
|
export const getFailedUploads = async () => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.getAll();
|
|
|
|
request.onsuccess = () => {
|
|
console.log(`[FailedUploadsDB] Retrieved ${request.result.length} failed uploads`);
|
|
resolve(request.result);
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to retrieve uploads:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error getting failed uploads:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a specific failed upload by ID
|
|
*/
|
|
export const getFailedUpload = async (id) => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.get(id);
|
|
|
|
request.onsuccess = () => {
|
|
resolve(request.result);
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to get upload:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error getting failed upload:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update retry count for a failed upload
|
|
*/
|
|
export const updateRetryCount = async (id, retryCount, error = null) => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
const upload = await getFailedUpload(id);
|
|
if (!upload) {
|
|
console.warn('[FailedUploadsDB] Upload not found for retry count update');
|
|
return;
|
|
}
|
|
|
|
upload.retryCount = retryCount;
|
|
upload.lastRetry = Date.now();
|
|
if (error) {
|
|
upload.lastError = error;
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.put(upload);
|
|
|
|
request.onsuccess = () => {
|
|
console.log(`[FailedUploadsDB] Updated retry count for upload ${id}: ${retryCount}`);
|
|
resolve();
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to update retry count:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error updating retry count:', error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete a failed upload (after successful retry)
|
|
*/
|
|
export const deleteFailedUpload = async (id) => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.delete(id);
|
|
|
|
request.onsuccess = () => {
|
|
console.log('[FailedUploadsDB] Deleted successful upload:', id);
|
|
resolve();
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to delete upload:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error deleting failed upload:', error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clear all failed uploads
|
|
*/
|
|
export const clearAllFailedUploads = async () => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.clear();
|
|
|
|
request.onsuccess = () => {
|
|
console.log('[FailedUploadsDB] Cleared all failed uploads');
|
|
resolve();
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to clear uploads:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error clearing failed uploads:', error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get count of failed uploads
|
|
*/
|
|
export const getFailedUploadCount = async () => {
|
|
try {
|
|
const db = await initDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const objectStore = transaction.objectStore(STORE_NAME);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = objectStore.count();
|
|
|
|
request.onsuccess = () => {
|
|
resolve(request.result);
|
|
};
|
|
|
|
request.onerror = () => {
|
|
console.error('[FailedUploadsDB] Failed to count uploads:', request.error);
|
|
reject(request.error);
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('[FailedUploadsDB] Error counting failed uploads:', error);
|
|
return 0;
|
|
}
|
|
};
|