* refactor: extract relay auth manager into standalone package
Decouple NIP-42 relay authentication from Grimoire internals into a
generic, framework-agnostic package at packages/relay-auth-manager/.
The new package uses dependency injection for pool, signer, and storage
(localStorage-like interface), making it reusable in any applesauce-based
app. Fixes the bug where auth prompts appeared even when the signer
couldn't sign events - now only emits pending challenges when a signer
is available.
Key changes:
- New package with RelayAuthManager class, pure auth state machine,
and comprehensive test suite (103 tests)
- Grimoire's relay-state-manager now delegates all auth logic to the
package, retaining only connection/notice tracking
- Auth preferences moved from Dexie to localStorage via pluggable storage
- Reactive signer lifecycle: auto-auth re-evaluates when signer appears
- Pool relay lifecycle via add$/remove$ observables (no polling)
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* fix: prevent auth prompts when signer is unavailable
Three bugs fixed:
1. Race condition in emitState(): states$ was emitted before
pendingChallenges$, so relay-state-manager's states$ subscriber
would read stale pendingChallenges$.value. Now pendingChallenges$
is emitted first for consistent reads.
2. relay-state-manager only subscribed to states$, missing
pendingChallenges$ changes. Now subscribes to both.
3. canAccountSign used constructor.name which is fragile under
minification. Now uses account.type !== "readonly" (stable
property from applesauce-accounts).
Added 3 regression tests verifying pendingChallenges$.value
consistency when observed from states$ subscribers.
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* fix: migrate auth preferences from Dexie to localStorage
One-time migration preserves existing user preferences: reads from
Dexie relayAuthPreferences table, writes to localStorage, injects
into the running manager, then clears the Dexie table. Skips if
localStorage already has data (idempotent).
Removes the DexieAuthStorage adapter — sync/async impedance mismatch
made it fragile. localStorage is the right fit for the sync
getItem/setItem interface.
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* docs: add README for relay-auth-manager package
Covers constructor options, observables, methods, auth lifecycle,
preferences, storage interface, state machine, and dependency
interfaces.
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* fix: address 3 bugs in relay-auth-manager
1. State machine: authenticated + CHALLENGE_RECEIVED with "never"
preference now correctly auto-rejects instead of entering a dead
challenge_received state that nobody acts on.
2. Auto-auth error handlers now guard with `status === "authenticating"`
before overwriting state, preventing a late-resolving promise from
clobbering a valid state transition (e.g. disconnect → "none").
3. API no longer leaks mutable internal references. getRelayState(),
getAllStates(), getAllPreferences(), and states$ emissions all return
shallow copies. Previously, consumers holding a reference to a
previous emission would see it silently mutate.
All three fixes include regression tests (110 tests, up from 103).
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* feat: make relay-auth-manager production-ready
- authenticate() now waits for authenticated$ confirmation via
firstValueFrom(race(...)) — resolves only when relay confirms,
rejects on disconnect. Uses Promise.all to avoid unhandled rejections.
- Add retry() for relays stuck in "failed" state.
- Add removePreference() to prevent unbounded preference growth.
- Add normalizeUrl option for custom URL normalization — preferences and
session rejections now consistently normalized via normalizeForKey().
- Wire Grimoire's normalizeRelayURL as the normalizer in relay-auth.ts.
- Update README with new methods, normalizeUrl option, retry lifecycle.
- 123 package tests passing.
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
* chore: prepare relay-auth-manager for npm publishing
- Point exports/main/types to dist/ for correct npm resolution
- Add tsconfig.build.json for emitting ESM JS + declarations
- Add build/clean/prepublishOnly scripts
- Add LICENSE, description, keywords, author, files field
- Add Vite resolve alias + tsconfig paths for workspace dev
(resolves source directly, no pre-build needed for dev)
- Fix TypeScript strict errors in test file
- Clean npm pack output: dist/, README.md, LICENSE only
https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
---------
Co-authored-by: Claude <noreply@anthropic.com>