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 { 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 { 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()