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:
Dorian
2026-02-12 12:24:58 +00:00
parent c970f5b29f
commit 725896673c
42 changed files with 3767 additions and 1329 deletions

76
src/lib/accounts.ts Normal file
View 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()

View File

@@ -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
View 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])