feat: converge trusted mints on storage events

This commit is contained in:
2026-08-22 16:55:47 +02:00
parent d735aca885
commit 83c9e5b95f
2 changed files with 184 additions and 0 deletions
@@ -0,0 +1,157 @@
// Cross-tab events are hints, never authority: the current localStorage
// value is the only state a listener may project into a tab.
import {beforeEach, describe, expect, it, vi} from 'vitest'
import {linkingPubKeyHex} from './keys'
import {onTrustedMintsChange, readTrustedMints, type TrustedMint} from './trustedMints'
import {stubLocalStorage} from './test-utils'
const STORAGE_KEY = 'sattle_trusted_mints'
const OWNER_ID = linkingPubKeyHex(new Uint8Array(32).fill(7))
const MINT_KEY = '02' + 'aa'.repeat(32)
const mint = (server: string): TrustedMint => ({
server,
mintPubkey: MINT_KEY,
addedAt: 1,
locked: false,
})
const store = (mints: TrustedMint[]): void => {
localStorage.setItem(STORAGE_KEY, JSON.stringify({version: 1, ownerId: OWNER_ID, mints}))
}
const storageEvent = (key: string | null, newValue: string | null): Event => {
const event = new Event('storage')
Object.defineProperties(event, {
key: {value: key},
newValue: {value: newValue},
})
return event
}
beforeEach(() => {
vi.unstubAllGlobals()
stubLocalStorage()
})
describe('trusted-mint storage-event convergence', () => {
it('projects a replacement from current storage instead of event newValue', () => {
// Given a tab observing an existing trusted mint
const events = new EventTarget()
vi.stubGlobal('window', events)
store([mint('before.example')])
const observed: string[][] = []
const unsubscribe = onTrustedMintsChange(() => {
observed.push(readTrustedMints(OWNER_ID).map((entry) => entry.server))
})
try {
// When another tab replaces storage but a delayed event carries obsolete bytes
store([mint('current.example')])
events.dispatchEvent(
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('before.example')]})),
)
// Then the projection follows the live registry
expect(observed).toEqual([['current.example']])
} finally {
unsubscribe()
}
})
it('projects removal from current storage instead of a stale replacement event', () => {
// Given a tab observing a trusted mint
const events = new EventTarget()
vi.stubGlobal('window', events)
store([mint('removed.example')])
const observed: TrustedMint[][] = []
const unsubscribe = onTrustedMintsChange(() => {
observed.push(readTrustedMints(OWNER_ID))
})
try {
// When the registry is removed while the event claims it still exists
localStorage.removeItem(STORAGE_KEY)
events.dispatchEvent(
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('removed.example')]})),
)
// Then the live removal wins
expect(observed).toEqual([[]])
} finally {
unsubscribe()
}
})
it('projects a clear event by rereading current localStorage', () => {
// Given a tab observing a trusted mint
const events = new EventTarget()
vi.stubGlobal('window', events)
store([mint('cleared.example')])
const observed: TrustedMint[][] = []
const unsubscribe = onTrustedMintsChange(() => {
observed.push(readTrustedMints(OWNER_ID))
})
try {
// When another tab clears storage, which emits key null
localStorage.clear()
events.dispatchEvent(storageEvent(null, JSON.stringify({mints: [mint('cleared.example')]})))
// Then the live empty registry wins
expect(observed).toEqual([[]])
} finally {
unsubscribe()
}
})
it('ignores a delayed event after a newer live registry has replaced it', () => {
// Given an observer and an obsolete first registry
const events = new EventTarget()
vi.stubGlobal('window', events)
store([mint('obsolete.example')])
const observed: string[][] = []
const unsubscribe = onTrustedMintsChange(() => {
observed.push(readTrustedMints(OWNER_ID).map((entry) => entry.server))
})
try {
// When a newer registry is already live before the obsolete event arrives
store([mint('live.example')])
events.dispatchEvent(
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('obsolete.example')]})),
)
// Then the obsolete event cannot resurrect its value
expect(observed).toEqual([['live.example']])
} finally {
unsubscribe()
}
})
it('clears the projection for malformed current storage without throwing', () => {
// Given an observer with a previously valid registry
const events = new EventTarget()
vi.stubGlobal('window', events)
store([mint('valid.example')])
const observed: TrustedMint[][] = []
const unsubscribe = onTrustedMintsChange(() => {
observed.push(readTrustedMints(OWNER_ID))
})
try {
// When the current value is malformed and its event claims a valid old value
localStorage.setItem(STORAGE_KEY, '{')
events.dispatchEvent(
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('valid.example')]})),
)
// Then parsing the live value safely removes it from the projection
expect(observed).toEqual([[]])
} finally {
unsubscribe()
}
})
})
+27
View File
@@ -0,0 +1,27 @@
// Cross-tab storage events are hints only. Subscribers reread the live
// registry for their active owner rather than projecting event.newValue.
export const TRUSTED_MINTS_STORAGE_KEY = 'sattle_trusted_mints'
const listeners = new Set<() => void>()
export const notifyStoredTrustedMintsChange = (): void => {
for (const listener of listeners) listener()
}
// Each subscription binds its own storage listener and removes exactly it on
// unsubscribe: no shared reference counting, so an abandoned subscriber can
// never keep another subscriber's window listener (or runtime) alive.
export const onStoredTrustedMintsChange = (listener: () => void): (() => void) => {
listeners.add(listener)
const target = typeof window === 'undefined' ? null : window
const onStorage = (event: StorageEvent): void => {
if (event.key !== TRUSTED_MINTS_STORAGE_KEY && event.key !== null) return
listener()
}
target?.addEventListener('storage', onStorage)
return () => {
listeners.delete(listener)
target?.removeEventListener('storage', onStorage)
}
}