[Drafts] Storage fixes (#9790)

* delete media from existsCache when deleting

* revoke media URLs

* skip revoking objecturls until the composer is completely closed

* [Drafts] Metrics (#9794)

* metrics for drafts

* Nit: format

* nit: use new util for clarity

* nit: use new util for clarity

---------

Co-authored-by: Eric Bailey <git@esb.lol>

---------

Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Samuel Newman
2026-01-29 23:21:38 +02:00
committed by GitHub
parent 0babd0f475
commit 58f532a495
28 changed files with 258 additions and 29 deletions
@@ -356,6 +356,7 @@ export function draftViewToSummary(
hasMissingMedia,
mediaCount,
postCount: view.draft.posts.length,
createdAt: view.createdAt,
updatedAt: view.updatedAt,
posts,
}
@@ -62,6 +62,8 @@ export type DraftSummary = {
mediaCount: number
/** Number of posts in thread */
postCount: number
/** ISO timestamp of creation */
createdAt: string
/** ISO timestamp of last update */
updatedAt: string
/** All posts in the draft for full display */
@@ -91,6 +91,7 @@ export async function deleteMediaFromLocal(
if (file.exists) {
file.delete()
}
mediaExistsCache.delete(localRefPath)
}
/**
@@ -154,3 +155,17 @@ export function clearMediaCache(): void {
cachePopulated = false
populateCachePromise = null
}
/**
* Revoke a media URL (no-op on native - only needed for web blob URLs)
*/
export function revokeMediaUrl(_url: string): void {
// No-op on native - file URIs don't need revocation
}
/**
* Revoke all media URLs (no-op on native - only needed for web blob URLs)
*/
export function revokeAllMediaUrls(): void {
// No-op on native - file URIs don't need revocation
}
@@ -84,6 +84,11 @@ export async function saveMediaToLocal(
}
}
/**
* Track blob URLs created by loadMediaFromLocal for cleanup
*/
const createdBlobUrls = new Set<string>()
/**
* Load a media file from IndexedDB
* @returns A blob URL for the saved media
@@ -97,7 +102,10 @@ export async function loadMediaFromLocal(
throw new Error(`Media file not found: ${localRefPath}`)
}
return URL.createObjectURL(record.blob)
const url = URL.createObjectURL(record.blob)
logger.debug('Created blob URL', {url})
createdBlobUrls.add(url)
return url
}
/**
@@ -165,6 +173,20 @@ export function clearMediaCache(): void {
*/
export function revokeMediaUrl(url: string): void {
if (url.startsWith('blob:')) {
logger.debug('Revoking blob URL', {url})
URL.revokeObjectURL(url)
createdBlobUrls.delete(url)
}
}
/**
* Revoke all blob URLs created by loadMediaFromLocal.
* Call this when closing the drafts list dialog to prevent memory leaks.
*/
export function revokeAllMediaUrls(): void {
logger.debug(`Revoking ${createdBlobUrls.size} blob URLs`)
for (const url of createdBlobUrls) {
URL.revokeObjectURL(url)
}
createdBlobUrls.clear()
}