- Refactor useNostr composable to utilize a centralized Pinia store for connection state and client management. - Update NostrFeed and App components to leverage the new store-based approach for relay configuration and client instantiation. - Remove direct relay URL handling from components, improving maintainability and consistency across the application.
158 lines
No EOL
4.3 KiB
TypeScript
158 lines
No EOL
4.3 KiB
TypeScript
import { ref } from 'vue'
|
|
import { NostrClient, type NostrNote } from '@/lib/nostr/client'
|
|
import { createTextNote, createReaction, createProfileMetadata } from '@/lib/nostr/events'
|
|
import { identity } from '@/composables/useIdentity'
|
|
import { toast } from 'vue-sonner'
|
|
|
|
import { useNostr } from './useNostr'
|
|
|
|
export function useSocial(relayUrls?: string[]) {
|
|
const { getClient } = useNostr(relayUrls ? { relays: relayUrls } : undefined)
|
|
const client = getClient()
|
|
const isPublishing = ref(false)
|
|
const profiles = ref(new Map<string, any>())
|
|
|
|
/**
|
|
* Publish a text note
|
|
*/
|
|
async function publishNote(content: string, replyTo?: string): Promise<void> {
|
|
if (!identity.isAuthenticated.value || !identity.currentIdentity.value) {
|
|
throw new Error('Must be logged in to publish notes')
|
|
}
|
|
|
|
try {
|
|
isPublishing.value = true
|
|
|
|
await client.connect()
|
|
const event = createTextNote(content, identity.currentIdentity.value, replyTo)
|
|
await client.publishEvent(event)
|
|
|
|
toast.success(replyTo ? 'Reply published!' : 'Note published!')
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to publish note'
|
|
toast.error(message)
|
|
throw error
|
|
} finally {
|
|
isPublishing.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Publish a reaction to a note
|
|
*/
|
|
async function publishReaction(targetEventId: string, targetAuthor: string, reaction: string = '👍'): Promise<void> {
|
|
if (!identity.isAuthenticated.value || !identity.currentIdentity.value) {
|
|
throw new Error('Must be logged in to react to notes')
|
|
}
|
|
|
|
try {
|
|
await client.connect()
|
|
const event = createReaction(targetEventId, targetAuthor, reaction, identity.currentIdentity.value)
|
|
await client.publishEvent(event)
|
|
|
|
toast.success('Reaction added!')
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to add reaction'
|
|
toast.error(message)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Publish profile metadata
|
|
*/
|
|
async function publishProfile(profileData: any): Promise<void> {
|
|
if (!identity.isAuthenticated.value || !identity.currentIdentity.value) {
|
|
throw new Error('Must be logged in to update profile')
|
|
}
|
|
|
|
try {
|
|
isPublishing.value = true
|
|
|
|
await client.connect()
|
|
const event = createProfileMetadata(profileData, identity.currentIdentity.value)
|
|
await client.publishEvent(event)
|
|
|
|
toast.success('Profile updated on Nostr!')
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to update profile'
|
|
toast.error(message)
|
|
throw error
|
|
} finally {
|
|
isPublishing.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch replies to a note
|
|
*/
|
|
async function fetchReplies(noteId: string): Promise<NostrNote[]> {
|
|
try {
|
|
await client.connect()
|
|
return await client.fetchReplies(noteId)
|
|
} catch (error) {
|
|
console.error('Failed to fetch replies:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch and cache user profiles
|
|
*/
|
|
async function fetchProfiles(pubkeys: string[]): Promise<void> {
|
|
// Filter out already cached profiles
|
|
const uncachedPubkeys = pubkeys.filter(pubkey => !profiles.value.has(pubkey))
|
|
|
|
if (uncachedPubkeys.length === 0) return
|
|
|
|
try {
|
|
await client.connect()
|
|
const fetchedProfiles = await client.fetchProfiles(uncachedPubkeys)
|
|
|
|
// Update cache
|
|
fetchedProfiles.forEach((profile, pubkey) => {
|
|
profiles.value.set(pubkey, profile)
|
|
})
|
|
} catch (error) {
|
|
console.error('Failed to fetch profiles:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached profile or return default
|
|
*/
|
|
function getProfile(pubkey: string) {
|
|
return profiles.value.get(pubkey) || {
|
|
name: pubkey.slice(0, 8) + '...',
|
|
display_name: undefined,
|
|
about: undefined,
|
|
picture: undefined
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get display name for a pubkey
|
|
*/
|
|
function getDisplayName(pubkey: string): string {
|
|
const profile = getProfile(pubkey)
|
|
return profile.display_name || profile.name || pubkey.slice(0, 8) + '...'
|
|
}
|
|
|
|
return {
|
|
// State
|
|
isPublishing,
|
|
profiles,
|
|
|
|
// Actions
|
|
publishNote,
|
|
publishReaction,
|
|
publishProfile,
|
|
fetchReplies,
|
|
fetchProfiles,
|
|
getProfile,
|
|
getDisplayName
|
|
}
|
|
}
|
|
|
|
// Export singleton instance for global use
|
|
export const social = useSocial() |