fix: normalize storage lock failures

This commit is contained in:
2026-08-22 16:55:00 +02:00
parent 12e4ec9199
commit 1dbf5788f9
2 changed files with 38 additions and 7 deletions
+30
View File
@@ -0,0 +1,30 @@
import {describe, expect, it, vi} from 'vitest'
import {withStorageLock} from './storageLock'
describe('withStorageLock fallback', () => {
it('runs unlocked when Web Locks are unavailable without promising serialization', async () => {
vi.stubGlobal('navigator', {})
const entered: string[] = []
let releaseFirst: (() => void) | undefined
try {
const first = withStorageLock('registry', async () => {
entered.push('first')
await new Promise<void>((resolve) => {
releaseFirst = resolve
})
})
const second = withStorageLock('registry', () => {
entered.push('second')
})
await second
expect(entered).toEqual(['first', 'second'])
releaseFirst?.()
await first
} finally {
vi.unstubAllGlobals()
}
})
})
+8 -7
View File
@@ -3,12 +3,13 @@
// lose each other's records (worst case: a stale tab overwrites a freshly
// persisted rotated note after its old k1 was burned). Falls back to
// running unlocked where Web Locks is unavailable (plain-Node tests, very
// old browsers).
export const withStorageLock = <T>(
name: string,
fn: () => T | Promise<T>
): Promise<T> => {
// old browsers). That fallback provides no cross-tab serialization
// guarantee; the promise hop only normalizes synchronous callback errors.
export const withStorageLock = <T>(name: string, fn: () => T | Promise<T>): Promise<T> => {
const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined
if (locks) return locks.request(name, fn)
return Promise.resolve(fn())
if (locks) return locks.request(name, () => Promise.resolve().then(fn))
return Promise.resolve().then(fn)
}
export const storageLocksAvailable = (): boolean =>
typeof navigator !== 'undefined' && navigator.locks !== undefined