diff --git a/src/lnurlcash/storage/currentOwner.ts b/src/lnurlcash/storage/currentOwner.ts new file mode 100644 index 0000000..9b3a64b --- /dev/null +++ b/src/lnurlcash/storage/currentOwner.ts @@ -0,0 +1,20 @@ +// Mutators must not let a still-running old tab overwrite namespaces after a +// successor has installed its saved-key owner marker. Ownerless data may only +// move through explicit migration APIs after their own proof checks succeed. + +import {savedKeyOwnerId} from '../keys' + +export class WalletOwnerMismatchError extends Error { + override readonly name = 'WalletOwnerMismatchError' + constructor() { + super('The active wallet owner no longer matches the saved wallet.') + } +} + +export const savedKeyOwnerAllows = (ownerId: string): boolean => { + return savedKeyOwnerId() === ownerId +} + +export const assertSavedKeyOwner = (ownerId: string): void => { + if (!savedKeyOwnerAllows(ownerId)) throw new WalletOwnerMismatchError() +} diff --git a/src/lnurlcash/storage/walletOwnerEvents.ts b/src/lnurlcash/storage/walletOwnerEvents.ts new file mode 100644 index 0000000..ba0b366 --- /dev/null +++ b/src/lnurlcash/storage/walletOwnerEvents.ts @@ -0,0 +1,17 @@ +// Storage events only wake an owning store to reread its saved-key marker. +// Their payload may be stale when writes arrive faster than delivery. + +export const LINKING_KEY_STORAGE_KEY = 'sattle_linking_key' + +// 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 onSavedKeyStorageChange = (listener: () => void): (() => void) => { + const target = typeof window === 'undefined' ? null : window + const onStorage = (event: StorageEvent): void => { + if (event.key !== LINKING_KEY_STORAGE_KEY && event.key !== null) return + listener() + } + target?.addEventListener('storage', onStorage) + return () => target?.removeEventListener('storage', onStorage) +}