Update package dependencies and enhance application structure
- Added several new dependencies related to the Applesauce library, including 'applesauce-accounts', 'applesauce-common', 'applesauce-core', 'applesauce-loaders', 'applesauce-relay', and 'applesauce-signers', all at version 5.1.0. - Updated the development script in package.json to specify a port for Vite and added new seed scripts for profiles and activity. - Removed outdated image files from the public directory to clean up unused assets. - Enhanced the App.vue structure by integrating shared components like AppHeader and AuthModal for improved user experience. - Refactored ContentDetailModal and MobileNav components to support new features and improve usability. These changes improve the overall functionality and maintainability of the application while ensuring it utilizes the latest libraries for better performance.
This commit is contained in:
76
src/lib/accounts.ts
Normal file
76
src/lib/accounts.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { AccountManager, Accounts } from 'applesauce-accounts'
|
||||
import type { NostrEvent } from 'applesauce-core/helpers/event'
|
||||
import { NostrConnectSigner } from 'applesauce-signers'
|
||||
import { filter, map } from 'rxjs'
|
||||
import { pool } from './relay'
|
||||
|
||||
const STORAGE_KEY = 'indeedhub-accounts'
|
||||
const ACTIVE_KEY = 'indeedhub-active-account'
|
||||
|
||||
// Create singleton account manager
|
||||
export const accountManager = new AccountManager()
|
||||
|
||||
// Register common account types (Extension, PrivateKey, NostrConnect, etc.)
|
||||
Accounts.registerCommonAccountTypes(accountManager)
|
||||
|
||||
// Wire NostrConnect signer to use our relay pool
|
||||
NostrConnectSigner.subscriptionMethod = (relays, filters) => {
|
||||
return pool.subscription(relays, filters).pipe(
|
||||
filter((res): res is NostrEvent => res !== 'EOSE'),
|
||||
map((event) => event as NostrEvent),
|
||||
)
|
||||
}
|
||||
|
||||
NostrConnectSigner.publishMethod = (relays, event) => {
|
||||
return pool.publish(relays, event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load saved accounts from localStorage
|
||||
*/
|
||||
export function loadAccounts() {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved) {
|
||||
const accounts = JSON.parse(saved)
|
||||
accountManager.fromJSON(accounts, true)
|
||||
|
||||
// Restore active account
|
||||
const activeId = localStorage.getItem(ACTIVE_KEY)
|
||||
if (activeId) {
|
||||
const account = accountManager.getAccount(activeId)
|
||||
if (account) {
|
||||
accountManager.setActive(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load accounts:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist accounts to localStorage on changes
|
||||
*/
|
||||
export function setupPersistence() {
|
||||
accountManager.accounts$.subscribe(() => {
|
||||
try {
|
||||
const json = accountManager.toJSON(true)
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(json))
|
||||
} catch (e) {
|
||||
console.error('Failed to save accounts:', e)
|
||||
}
|
||||
})
|
||||
|
||||
accountManager.active$.subscribe((active) => {
|
||||
if (active) {
|
||||
localStorage.setItem(ACTIVE_KEY, active.id)
|
||||
} else {
|
||||
localStorage.removeItem(ACTIVE_KEY)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize on module load
|
||||
loadAccounts()
|
||||
setupPersistence()
|
||||
220
src/lib/nostr.ts
220
src/lib/nostr.ts
@@ -1,184 +1,46 @@
|
||||
import { SimplePool, nip19, type Event as NostrEvent, type Filter } from 'nostr-tools'
|
||||
import { nostrConfig } from '../config/api.config'
|
||||
import { EventFactory, EventStore } from 'applesauce-core'
|
||||
import {
|
||||
createEventLoaderForStore,
|
||||
createReactionsLoader,
|
||||
} from 'applesauce-loaders/loaders'
|
||||
|
||||
/**
|
||||
* Nostr Client
|
||||
* Handles Nostr relay connections and event management
|
||||
*/
|
||||
class NostrClient {
|
||||
private pool: SimplePool
|
||||
private relays: string[]
|
||||
private lookupRelays: string[]
|
||||
private eventCache: Map<string, NostrEvent>
|
||||
import { accountManager } from './accounts'
|
||||
import { pool, APP_RELAYS, LOOKUP_RELAYS, appRelays } from './relay'
|
||||
|
||||
constructor() {
|
||||
this.pool = new SimplePool()
|
||||
this.relays = nostrConfig.relays
|
||||
this.lookupRelays = nostrConfig.lookupRelays
|
||||
this.eventCache = new Map()
|
||||
// Re-export relay primitives so other modules can import from nostr.ts
|
||||
export { pool, APP_RELAYS, LOOKUP_RELAYS, appRelays }
|
||||
|
||||
// Add blueprints for structured comment/reaction handling
|
||||
import 'applesauce-common/blueprints/comment'
|
||||
import 'applesauce-common/blueprints/reaction'
|
||||
|
||||
// Setup event store (in-memory cache of all seen events)
|
||||
export const eventStore = new EventStore()
|
||||
|
||||
// Setup event factory with our app identity
|
||||
export const factory = new EventFactory({
|
||||
client: {
|
||||
name: 'indeedhub-prototype',
|
||||
address: {
|
||||
identifier: 'indeedhub-prototype',
|
||||
pubkey:
|
||||
'0f193d51fd76ef21f870bcc94f5561df675b64260c7e2aaf79a816a8fd6cda3d',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Sync signer from active account so factory can sign events
|
||||
accountManager.active$.subscribe(async (account) => {
|
||||
if (account) {
|
||||
factory.setSigner(account.signer)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Subscribe to events with filters
|
||||
*/
|
||||
subscribe(
|
||||
filters: Filter | Filter[],
|
||||
onEvent: (event: NostrEvent) => void,
|
||||
onEose?: () => void
|
||||
) {
|
||||
const filterArray = Array.isArray(filters) ? filters : [filters]
|
||||
const sub = this.pool.subscribeMany(
|
||||
this.relays,
|
||||
filterArray as any, // Type workaround for nostr-tools
|
||||
{
|
||||
onevent: (event) => {
|
||||
this.eventCache.set(event.id, event)
|
||||
onEvent(event)
|
||||
},
|
||||
oneose: () => {
|
||||
onEose?.()
|
||||
},
|
||||
}
|
||||
)
|
||||
// Setup event loader for fetching missing events (profiles, etc.)
|
||||
createEventLoaderForStore(eventStore, pool, {
|
||||
lookupRelays: LOOKUP_RELAYS,
|
||||
extraRelays: APP_RELAYS,
|
||||
})
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch events (one-time query)
|
||||
*/
|
||||
async fetchEvents(filters: Filter): Promise<NostrEvent[]> {
|
||||
const events = await this.pool.querySync(this.relays, filters)
|
||||
events.forEach((event) => {
|
||||
this.eventCache.set(event.id, event)
|
||||
})
|
||||
return events
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish event to relays
|
||||
*/
|
||||
async publishEvent(event: NostrEvent): Promise<void> {
|
||||
const results = this.pool.publish(this.relays, event)
|
||||
// Wait for at least one successful publish
|
||||
await Promise.race(results)
|
||||
this.eventCache.set(event.id, event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profile metadata (kind 0)
|
||||
*/
|
||||
async getProfile(pubkey: string): Promise<NostrEvent | null> {
|
||||
const events = await this.pool.querySync(this.lookupRelays, {
|
||||
kinds: [0],
|
||||
authors: [pubkey],
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
return events[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments for content (kind 1)
|
||||
*/
|
||||
async getComments(contentIdentifier: string): Promise<NostrEvent[]> {
|
||||
const filter: Filter = {
|
||||
kinds: [1],
|
||||
'#i': [contentIdentifier],
|
||||
}
|
||||
|
||||
return this.fetchEvents(filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reactions for content (kind 17)
|
||||
*/
|
||||
async getReactions(contentIdentifier: string): Promise<NostrEvent[]> {
|
||||
const filter: Filter = {
|
||||
kinds: [17],
|
||||
'#i': [contentIdentifier],
|
||||
}
|
||||
|
||||
return this.fetchEvents(filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to comments in real-time
|
||||
*/
|
||||
subscribeToComments(
|
||||
contentIdentifier: string,
|
||||
onComment: (event: NostrEvent) => void,
|
||||
onEose?: () => void
|
||||
) {
|
||||
return this.subscribe(
|
||||
[{
|
||||
kinds: [1],
|
||||
'#i': [contentIdentifier],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
}],
|
||||
onComment,
|
||||
onEose
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to reactions in real-time
|
||||
*/
|
||||
subscribeToReactions(
|
||||
contentIdentifier: string,
|
||||
onReaction: (event: NostrEvent) => void,
|
||||
onEose?: () => void
|
||||
) {
|
||||
return this.subscribe(
|
||||
[{
|
||||
kinds: [17],
|
||||
'#i': [contentIdentifier],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
}],
|
||||
onReaction,
|
||||
onEose
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event from cache or fetch
|
||||
*/
|
||||
async getEvent(eventId: string): Promise<NostrEvent | null> {
|
||||
// Check cache first
|
||||
if (this.eventCache.has(eventId)) {
|
||||
return this.eventCache.get(eventId)!
|
||||
}
|
||||
|
||||
// Fetch from relays
|
||||
const events = await this.fetchEvents({ ids: [eventId] })
|
||||
return events[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections
|
||||
*/
|
||||
close() {
|
||||
this.pool.close(this.relays)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert npub to hex pubkey
|
||||
*/
|
||||
npubToHex(npub: string): string {
|
||||
const decoded = nip19.decode(npub)
|
||||
if (decoded.type === 'npub') {
|
||||
return decoded.data
|
||||
}
|
||||
throw new Error('Invalid npub')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex pubkey to npub
|
||||
*/
|
||||
hexToNpub(hex: string): string {
|
||||
return nip19.npubEncode(hex)
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const nostrClient = new NostrClient()
|
||||
// Reactions loader for fetching reactions on events
|
||||
export const reactionsLoader = createReactionsLoader(pool, { eventStore })
|
||||
|
||||
19
src/lib/relay.ts
Normal file
19
src/lib/relay.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Relay pool and relay constants.
|
||||
* Extracted into its own module to avoid circular dependencies
|
||||
* between nostr.ts and accounts.ts.
|
||||
*/
|
||||
import { BehaviorSubject } from 'applesauce-core'
|
||||
import { RelayPool } from 'applesauce-relay'
|
||||
|
||||
// Relay pool for all WebSocket connections
|
||||
export const pool = new RelayPool()
|
||||
|
||||
// App relays (local dev relay)
|
||||
export const APP_RELAYS = ['ws://localhost:7777']
|
||||
|
||||
// Lookup relays for profile metadata
|
||||
export const LOOKUP_RELAYS = ['wss://purplepag.es']
|
||||
|
||||
// Observable relay list for reactive subscriptions
|
||||
export const appRelays = new BehaviorSubject<string[]>([...APP_RELAYS])
|
||||
Reference in New Issue
Block a user