Nicolas Cantu cb7ee0cfd4 Replace nos2x and NostrConnect with Alby authentication
- Remove nos2x and NostrConnect support
- Create new NostrAuthService using Alby (window.nostr NIP-07)
- Replace useNostrConnect with useNostrAuth in all components
- Update NostrRemoteSigner to use Alby for signing
- Delete NostrConnect-related files (nostrconnect.ts, handlers, etc.)
- Update documentation to reflect Alby-only authentication
- Remove NOSTRCONNECT_BRIDGE environment variable
- All TypeScript checks pass
2025-12-27 23:54:34 +01:00

145 lines
3.5 KiB
TypeScript

import { nostrService } from './nostr'
import type { NostrConnectState } from '@/types/nostr'
/**
* Nostr authentication service using Alby (NIP-07)
* Alby exposes window.nostr API for Nostr authentication and signing
*/
export class NostrAuthService {
private state: NostrConnectState = {
connected: false,
pubkey: null,
profile: null,
}
private listeners: Set<(state: NostrConnectState) => void> = new Set()
constructor() {
if (typeof window !== 'undefined') {
this.loadStateFromStorage()
this.setupMessageListener()
}
}
subscribe(callback: (state: NostrConnectState) => void): () => void {
this.listeners.add(callback)
callback(this.state)
return () => {
this.listeners.delete(callback)
}
}
getState(): NostrConnectState {
return { ...this.state }
}
/**
* Check if Alby (window.nostr) is available
*/
isAvailable(): boolean {
return typeof window !== 'undefined' && typeof window.nostr !== 'undefined'
}
/**
* Connect using Alby (NIP-07)
*/
async connect(): Promise<void> {
if (!this.isAvailable()) {
throw new Error('Alby extension not available. Please install Alby browser extension.')
}
if (!window.nostr) {
throw new Error('window.nostr is not available. Please ensure Alby extension is installed and enabled.')
}
try {
const pubkey = await window.nostr.getPublicKey()
if (!pubkey) {
throw new Error('Failed to get public key from Alby')
}
this.state = {
connected: true,
pubkey,
profile: null,
}
nostrService.setPublicKey(pubkey)
this.saveStateToStorage()
this.notifyListeners()
void this.loadProfile()
} catch (e) {
console.error('Error connecting with Alby:', e)
throw new Error(`Failed to connect with Alby: ${e instanceof Error ? e.message : 'Unknown error'}`)
}
}
disconnect(): void {
this.state = {
connected: false,
pubkey: null,
profile: null,
}
this.saveStateToStorage()
this.notifyListeners()
}
private async loadProfile(): Promise<void> {
if (!this.state.pubkey) {
return
}
try {
const profile = await nostrService.getProfile(this.state.pubkey)
if (profile) {
this.state.profile = profile
this.saveStateToStorage()
this.notifyListeners()
}
} catch (e) {
console.error('Error loading profile:', e)
}
}
private setupMessageListener(): void {
window.addEventListener('storage', (e) => {
if (e.key === 'nostr_auth_state') {
this.loadStateFromStorage()
}
})
}
private loadStateFromStorage(): void {
try {
const stored = localStorage.getItem('nostr_auth_state')
if (stored) {
const parsed = JSON.parse(stored)
this.state = {
connected: parsed.connected ?? false,
pubkey: parsed.pubkey ?? null,
profile: parsed.profile ?? null,
}
if (this.state.pubkey) {
nostrService.setPublicKey(this.state.pubkey)
}
}
} catch (e) {
console.error('Error loading state from storage:', e)
}
}
private saveStateToStorage(): void {
try {
localStorage.setItem('nostr_auth_state', JSON.stringify(this.state))
} catch (e) {
console.error('Error saving state to storage:', e)
}
}
private notifyListeners(): void {
this.listeners.forEach((callback) => callback({ ...this.state }))
}
}
export const nostrAuthService = new NostrAuthService()