mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 00:03:15 +00:00
fix(mobile): retry the legacy tab-strip purge on every load, and make host deletion await it
A session that only loads (offline) never reached the retry, which lived in the memoized file read. Forgetting a host now fails until the older build's blob is gone too.
This commit is contained in:
@@ -275,6 +275,26 @@ describe('session tab strip cache', () => {
|
||||
expect(asyncStorage.removeItem).toHaveBeenCalledWith(LEGACY_STORAGE_KEY)
|
||||
})
|
||||
|
||||
it('retries a failed legacy removal on a later load, even with no write in between', async () => {
|
||||
// Why: an offline session only ever loads; the memoized file read must not be the
|
||||
// only place the retry lives.
|
||||
const key = getSessionTabStripCacheKey('host-1', 'wt-1')
|
||||
asyncStorage.removeItem.mockRejectedValueOnce(new Error('bridge down'))
|
||||
await loadCachedSessionTabStrip(key)
|
||||
expect(asyncStorage.removeItem).toHaveBeenCalledTimes(1)
|
||||
await loadCachedSessionTabStrip(key)
|
||||
expect(asyncStorage.removeItem).toHaveBeenCalledTimes(2)
|
||||
await loadCachedSessionTabStrip(key)
|
||||
expect(asyncStorage.removeItem).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('does not report a host forgotten while its legacy blob is still on disk', async () => {
|
||||
asyncStorage.removeItem.mockRejectedValue(new Error('bridge down'))
|
||||
await expect(deleteCachedSessionTabStripForHost('host-1')).rejects.toThrow(/bridge down/)
|
||||
asyncStorage.removeItem.mockResolvedValue(undefined)
|
||||
await expect(deleteCachedSessionTabStripForHost('host-1')).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('retries a failed legacy removal on the next write, and stops once it lands', async () => {
|
||||
const key = getSessionTabStripCacheKey('host-1', 'wt-1')
|
||||
asyncStorage.removeItem.mockRejectedValueOnce(new Error('bridge down'))
|
||||
|
||||
+21
-10
@@ -79,6 +79,9 @@ export async function loadCachedSessionTabStrip(
|
||||
if (!key) {
|
||||
return null
|
||||
}
|
||||
// Every load, not just the first file read: a removal that failed on launch must be
|
||||
// retried by an offline session that only ever loads.
|
||||
removeLegacyBlobsBestEffort()
|
||||
const cache = await loadFile()
|
||||
return cache.get(key) ?? null
|
||||
}
|
||||
@@ -136,6 +139,9 @@ export async function deleteCachedSessionTabStripForHost(hostId: string): Promis
|
||||
}
|
||||
// Queued, not raced: the purge is the last write, and its failure is the caller's.
|
||||
await enqueueWrite(cache)
|
||||
// The forgotten host's titles may still sit in the blob an older build wrote. A deletion
|
||||
// the user asked for is not done until that is gone too, so this one is awaited and thrown.
|
||||
await removeLegacyBlobs()
|
||||
}
|
||||
|
||||
export function resetSessionTabStripCacheForTests(): void {
|
||||
@@ -193,19 +199,24 @@ async function loadFile(): Promise<Map<string, MobileSessionTabStripPreview>> {
|
||||
return loadPromise
|
||||
}
|
||||
|
||||
// Not awaited: the plaintext left by an older build must go, but a failed removal is no reason
|
||||
// to withhold the strip this build can draw. A failure keeps the key queued for the next try.
|
||||
function removeLegacyBlobs(): void {
|
||||
for (const key of pendingLegacyRemovals) {
|
||||
void AsyncStorage.removeItem(key).then(
|
||||
() => pendingLegacyRemovals.delete(key),
|
||||
() => {}
|
||||
// Loads and ordinary writes do not await this: the plaintext left by an older build must go,
|
||||
// but a failed removal is no reason to withhold the strip this build can draw. A failure keeps
|
||||
// the key queued for the next try. A host deletion does await it, and throws on failure.
|
||||
function removeLegacyBlobs(): Promise<void> {
|
||||
return Promise.all(
|
||||
[...pendingLegacyRemovals].map((key) =>
|
||||
AsyncStorage.removeItem(key).then(() => {
|
||||
pendingLegacyRemovals.delete(key)
|
||||
})
|
||||
)
|
||||
}
|
||||
).then(() => {})
|
||||
}
|
||||
|
||||
function removeLegacyBlobsBestEffort(): void {
|
||||
void removeLegacyBlobs().catch(() => {})
|
||||
}
|
||||
|
||||
async function readStoredFile(): Promise<StoredWorkspace[]> {
|
||||
removeLegacyBlobs()
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(STORAGE_KEY)
|
||||
if (!raw) {
|
||||
@@ -250,7 +261,7 @@ function enqueueWrite(cache: Map<string, MobileSessionTabStripPreview>): Promise
|
||||
}
|
||||
|
||||
async function writeFile(cache: Map<string, MobileSessionTabStripPreview>): Promise<void> {
|
||||
removeLegacyBlobs()
|
||||
removeLegacyBlobsBestEffort()
|
||||
const workspaces: StoredWorkspace[] = [...cache].map(([key, preview]) => ({ key, preview }))
|
||||
// Throws on purpose: a deletion that only removed the in-memory rows must not be
|
||||
// reported as a deletion, or the forgotten host's titles stay in plaintext on disk.
|
||||
|
||||
Reference in New Issue
Block a user