···270270 * On web, converts blob URLs to data URIs immediately to prevent revocation issues.
271271 */
272272async function copyToCache(from: string): Promise<string> {
273273- // Handle web blob URLs - convert to data URI immediately before they can be revoked
274274- if (IS_WEB && from.startsWith('blob:')) {
275275- try {
276276- const response = await fetch(from)
277277- const blob = await response.blob()
278278- return await blobToDataUri(blob)
279279- } catch (e) {
280280- // If fetch fails, the blob URL was likely already revoked
281281- // Return as-is and let downstream code handle the error
282282- return from
283283- }
284284- }
285285-286273 // Data URIs don't need any conversion
287274 if (from.startsWith('data:')) {
288275 return from
289276 }
290277291291- const cacheDir = IS_WEB && getImageCacheDirectory()
278278+ if (IS_WEB) {
279279+ // Web: convert blob URLs to data URIs before they can be revoked
280280+ if (from.startsWith('blob:')) {
281281+ try {
282282+ const response = await fetch(from)
283283+ const blob = await response.blob()
284284+ return await blobToDataUri(blob)
285285+ } catch (e) {
286286+ // Blob URL was likely revoked, return as-is for downstream error handling
287287+ return from
288288+ }
289289+ }
290290+ // Other URLs on web don't need conversion
291291+ return from
292292+ }
292293293293- // On web (non-blob URLs) or if already in cache dir, no need to copy
294294+ // Native: copy to cache directory to survive OS temp file cleanup
295295+ const cacheDir = getImageCacheDirectory()
294296 if (!cacheDir || from.startsWith(cacheDir)) {
295297 return from
296298 }
···298300 const to = joinPath(cacheDir, nanoid(36))
299301 await makeDirectoryAsync(cacheDir, {intermediates: true})
300302301301- // Normalize the source path for expo-file-system
302303 let normalizedFrom = from
303304 if (!from.startsWith('file://') && from.startsWith('/')) {
304305 normalizedFrom = `file://${from}`