- Remove title and description from pages/presentation.tsx - Keep title with user name in AuthorPresentationEditor form - Fix duplicate display issue
144 lines
3.5 KiB
TypeScript
144 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()
|