diff --git a/lib/storage/providers/IDBKeyValProvider/classifyError.ts b/lib/storage/providers/IDBKeyValProvider/classifyError.ts index 99f3c3e64..fc6ddbd9f 100644 --- a/lib/storage/providers/IDBKeyValProvider/classifyError.ts +++ b/lib/storage/providers/IDBKeyValProvider/classifyError.ts @@ -14,6 +14,13 @@ function classifyIDBError(error: unknown): ValueOf { return StorageErrorClass.INVALID_DATA; } + // A queued File/Blob whose backing bytes are gone — the source OS file was modified, deleted, or + // renamed after being picked, so the structured clone fails at write time. Chromium reports it as + // InvalidBlob (Windows) or IOError (macOS). Retrying re-reads the same dead blob and can never succeed. + if (message.includes('failed to write blobs')) { + return StorageErrorClass.INVALID_DATA; + } + // Browser quota exceeded. if (name.includes('quotaexceedederror') || message.includes('quotaexceedederror')) { return StorageErrorClass.CAPACITY; diff --git a/tests/unit/storage/providers/classifyErrorTest.ts b/tests/unit/storage/providers/classifyErrorTest.ts new file mode 100644 index 000000000..c563d9dde --- /dev/null +++ b/tests/unit/storage/providers/classifyErrorTest.ts @@ -0,0 +1,23 @@ +import classifyIDBError from '../../../../lib/storage/providers/IDBKeyValProvider/classifyError'; +import {StorageErrorClass} from '../../../../lib/storage/errors'; + +describe('classifyIDBError', () => { + it.each([ + // Dead File/Blob in the payload — the platform-specific dialects of "Failed to write blobs". + [new DOMException('Failed to write blobs (InvalidBlob)', 'DataError'), StorageErrorClass.INVALID_DATA], + [new DOMException('Failed to write blobs (IOError)', 'DataError'), StorageErrorClass.INVALID_DATA], + // Non-serializable payload. + [new TypeError("Failed to execute 'put' on 'IDBObjectStore': something could not be cloned."), StorageErrorClass.INVALID_DATA], + // Quota. + [new DOMException('The quota has been exceeded.', 'QuotaExceededError'), StorageErrorClass.CAPACITY], + // Backing-store corruption. + [new DOMException('Internal error opening backing store for indexedDB.open.', 'UnknownError'), StorageErrorClass.FATAL], + // Transient connection failures. + [new DOMException('Connection to Indexed Database server lost. Refresh the page to try again', 'UnknownError'), StorageErrorClass.TRANSIENT], + [new DOMException('IDB write transaction aborted without an error', 'AbortError'), StorageErrorClass.TRANSIENT], + // Anything else stays UNKNOWN. + [new Error('some brand new failure'), StorageErrorClass.UNKNOWN], + ])('classifies %s as %s', (error, expectedClass) => { + expect(classifyIDBError(error)).toBe(expectedClass); + }); +});