diff --git a/CLAUDE.md b/CLAUDE.md
index 2ef2826..6fd5d02 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -10,7 +10,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `npm run preview` - Preview production build locally
- `npm run analyze` - Build with bundle analysis (opens visualization)
-**Electron Development**
+**Electron Development**
- `npm run electron:dev` - Run both Vite dev server and Electron concurrently
- `npm run electron:build` - Full build and package for Electron
- `npm run start` - Start Electron using Forge
@@ -26,7 +26,7 @@ This is a modular Vue 3 + TypeScript + Vite application with Electron support, f
The application uses a plugin-based modular architecture with dependency injection for service management:
**Core Modules:**
-- **Base Module** (`src/modules/base/`) - Core infrastructure (Nostr, Auth, PWA, Image Upload)
+- **Base Module** (`src/modules/base/`) - Core infrastructure (Nostr, Auth, PWA)
- **Wallet Module** (`src/modules/wallet/`) - Lightning wallet management with real-time balance updates
- **Nostr Feed Module** (`src/modules/nostr-feed/`) - Social feed functionality
- **Chat Module** (`src/modules/chat/`) - Encrypted Nostr chat
@@ -90,12 +90,6 @@ const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
- `SERVICE_TOKENS.VISIBILITY_SERVICE` - App visibility and connection management
- `SERVICE_TOKENS.WALLET_SERVICE` - Wallet operations (send, receive, transactions)
- `SERVICE_TOKENS.WALLET_WEBSOCKET_SERVICE` - Real-time wallet balance updates via WebSocket
-- `SERVICE_TOKENS.STORAGE_SERVICE` - Local storage management
-- `SERVICE_TOKENS.TOAST_SERVICE` - Toast notification system
-- `SERVICE_TOKENS.INVOICE_SERVICE` - Lightning invoice creation and management
-- `SERVICE_TOKENS.LNBITS_API` - LNbits API client
-- `SERVICE_TOKENS.IMAGE_UPLOAD_SERVICE` - Image upload to pictrs server
-- `SERVICE_TOKENS.NOSTR_METADATA_SERVICE` - Nostr user metadata (NIP-01 kind 0)
**Core Stack:**
- Vue 3 with Composition API (`
-
-
-
-
diff --git a/src/components/ui/sheet/SheetTitle.vue b/src/components/ui/sheet/SheetTitle.vue
deleted file mode 100644
index 5870787..0000000
--- a/src/components/ui/sheet/SheetTitle.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/ui/sheet/SheetTrigger.vue b/src/components/ui/sheet/SheetTrigger.vue
deleted file mode 100644
index a4fc3ee..0000000
--- a/src/components/ui/sheet/SheetTrigger.vue
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/ui/sheet/index.ts b/src/components/ui/sheet/index.ts
deleted file mode 100644
index a370633..0000000
--- a/src/components/ui/sheet/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { VariantProps } from "class-variance-authority"
-import { cva } from "class-variance-authority"
-
-export { default as Sheet } from "./Sheet.vue"
-export { default as SheetClose } from "./SheetClose.vue"
-export { default as SheetContent } from "./SheetContent.vue"
-export { default as SheetDescription } from "./SheetDescription.vue"
-export { default as SheetFooter } from "./SheetFooter.vue"
-export { default as SheetHeader } from "./SheetHeader.vue"
-export { default as SheetTitle } from "./SheetTitle.vue"
-export { default as SheetTrigger } from "./SheetTrigger.vue"
-
-export const sheetVariants = cva(
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
- {
- variants: {
- side: {
- top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
- bottom:
- "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
- left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
- right:
- "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
- },
- },
- defaultVariants: {
- side: "right",
- },
- },
-)
-
-export type SheetVariants = VariantProps
diff --git a/src/composables/useModularNavigation.ts b/src/composables/useModularNavigation.ts
index b03bbe8..af47f66 100644
--- a/src/composables/useModularNavigation.ts
+++ b/src/composables/useModularNavigation.ts
@@ -42,19 +42,11 @@ export function useModularNavigation() {
})
}
- if (appConfig.modules.tasks?.enabled) {
- items.push({
- name: 'Tasks',
- href: '/tasks',
- requiresAuth: true
- })
- }
-
if (appConfig.modules.chat.enabled) {
- items.push({
- name: t('nav.chat'),
- href: '/chat',
- requiresAuth: true
+ items.push({
+ name: t('nav.chat'),
+ href: '/chat',
+ requiresAuth: true
})
}
@@ -69,40 +61,30 @@ export function useModularNavigation() {
// Events module items
if (appConfig.modules.events.enabled) {
- items.push({
- name: 'My Tickets',
- href: '/my-tickets',
+ items.push({
+ name: 'My Tickets',
+ href: '/my-tickets',
icon: 'Ticket',
- requiresAuth: true
+ requiresAuth: true
})
}
- // Market module items
+ // Market module items
if (appConfig.modules.market.enabled) {
- items.push({
- name: 'Market Dashboard',
- href: '/market-dashboard',
+ items.push({
+ name: 'Market Dashboard',
+ href: '/market-dashboard',
icon: 'Store',
- requiresAuth: true
- })
- }
-
- // Expenses module items
- if (appConfig.modules.expenses.enabled) {
- items.push({
- name: 'My Transactions',
- href: '/expenses/transactions',
- icon: 'Receipt',
- requiresAuth: true
+ requiresAuth: true
})
}
// Base module items (always available)
- items.push({
- name: 'Relay Hub Status',
- href: '/relay-hub-status',
+ items.push({
+ name: 'Relay Hub Status',
+ href: '/relay-hub-status',
icon: 'Activity',
- requiresAuth: true
+ requiresAuth: true
})
return items
diff --git a/src/composables/useQuickActions.ts b/src/composables/useQuickActions.ts
deleted file mode 100644
index 7046fbb..0000000
--- a/src/composables/useQuickActions.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { computed } from 'vue'
-import { pluginManager } from '@/core/plugin-manager'
-import type { QuickAction } from '@/core/types'
-
-/**
- * Composable for dynamic quick actions based on enabled modules
- *
- * Quick actions are module-provided action buttons that appear in the floating
- * action button (FAB) menu. Each module can register its own quick actions
- * for common tasks like composing notes, sending payments, adding expenses, etc.
- *
- * @example
- * ```typescript
- * const { quickActions, getActionsByCategory } = useQuickActions()
- *
- * // Get all actions
- * const actions = quickActions.value
- *
- * // Get actions by category
- * const composeActions = getActionsByCategory('compose')
- * ```
- */
-export function useQuickActions() {
- /**
- * Get all quick actions from installed modules
- * Actions are sorted by order (lower = higher priority)
- */
- const quickActions = computed(() => {
- const actions: QuickAction[] = []
-
- // Iterate through installed modules
- const installedModules = pluginManager.getInstalledModules()
-
- for (const moduleName of installedModules) {
- const module = pluginManager.getModule(moduleName)
- if (module?.plugin.quickActions) {
- actions.push(...module.plugin.quickActions)
- }
- }
-
- // Sort by order (lower = higher priority), then by label
- return actions.sort((a, b) => {
- const orderA = a.order ?? 999
- const orderB = b.order ?? 999
- if (orderA !== orderB) {
- return orderA - orderB
- }
- return a.label.localeCompare(b.label)
- })
- })
-
- /**
- * Get actions filtered by category
- */
- const getActionsByCategory = (category: string) => {
- return computed(() => {
- return quickActions.value.filter(action => action.category === category)
- })
- }
-
- /**
- * Get a specific action by ID
- */
- const getActionById = (id: string) => {
- return computed(() => {
- return quickActions.value.find(action => action.id === id)
- })
- }
-
- /**
- * Check if any actions are available
- */
- const hasActions = computed(() => quickActions.value.length > 0)
-
- /**
- * Get all unique categories
- */
- const categories = computed(() => {
- const cats = new Set()
- quickActions.value.forEach(action => {
- if (action.category) {
- cats.add(action.category)
- }
- })
- return Array.from(cats).sort()
- })
-
- return {
- quickActions,
- getActionsByCategory,
- getActionById,
- hasActions,
- categories
- }
-}
diff --git a/src/core/di-container.ts b/src/core/di-container.ts
index f2cf091..32221f5 100644
--- a/src/core/di-container.ts
+++ b/src/core/di-container.ts
@@ -137,15 +137,6 @@ export const SERVICE_TOKENS = {
PROFILE_SERVICE: Symbol('profileService'),
REACTION_SERVICE: Symbol('reactionService'),
- // Tasks services
- TASK_SERVICE: Symbol('taskService'),
- /** @deprecated Use TASK_SERVICE instead */
- SCHEDULED_EVENT_SERVICE: Symbol('scheduledEventService'),
-
- // Links services
- SUBMISSION_SERVICE: Symbol('submissionService'),
- LINK_PREVIEW_SERVICE: Symbol('linkPreviewService'),
-
// Nostr metadata services
NOSTR_METADATA_SERVICE: Symbol('nostrMetadataService'),
@@ -168,9 +159,6 @@ export const SERVICE_TOKENS = {
// Image upload services
IMAGE_UPLOAD_SERVICE: Symbol('imageUploadService'),
-
- // Expenses services
- EXPENSES_API: Symbol('expensesAPI'),
} as const
// Type-safe injection helpers
diff --git a/src/core/types.ts b/src/core/types.ts
index c866653..9ce1e47 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -1,64 +1,37 @@
import type { App, Component } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
-// Quick action interface for modular action buttons
-export interface QuickAction {
- /** Unique action ID */
- id: string
-
- /** Display label for the action */
- label: string
-
- /** Lucide icon name */
- icon: string
-
- /** Component to render when action is selected */
- component: Component
-
- /** Display order (lower = higher priority) */
- order?: number
-
- /** Action category (e.g., 'compose', 'wallet', 'utilities') */
- category?: string
-
- /** Whether action requires authentication */
- requiresAuth?: boolean
-}
-
// Base module plugin interface
export interface ModulePlugin {
/** Unique module name */
name: string
-
+
/** Module version */
version: string
-
+
/** Required dependencies (other module names) */
dependencies?: string[]
-
+
/** Module configuration */
config?: Record
-
+
/** Install the module */
install(app: App, options?: any): Promise | void
-
+
/** Uninstall the module (cleanup) */
uninstall?(): Promise | void
-
+
/** Routes provided by this module */
routes?: RouteRecordRaw[]
-
+
/** Components provided by this module */
components?: Record
-
+
/** Services provided by this module */
services?: Record
-
+
/** Composables provided by this module */
composables?: Record
-
- /** Quick actions provided by this module */
- quickActions?: QuickAction[]
}
// Module configuration for app setup
diff --git a/src/modules/base/composables/useImageOptimizer.ts b/src/modules/base/composables/useImageOptimizer.ts
deleted file mode 100644
index d7383e9..0000000
--- a/src/modules/base/composables/useImageOptimizer.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ImageUploadService } from '../services/ImageUploadService'
-
-/**
- * Composable for generating optimized image URLs via pict-rs
- * Handles both file aliases and full URLs
- */
-export function useImageOptimizer() {
- const imageService = tryInjectService(SERVICE_TOKENS.IMAGE_UPLOAD_SERVICE)
-
- /**
- * Get a thumbnail URL (fast, lower quality - good for cards/lists)
- */
- const thumbnail = (url: string | undefined, size = 256): string => {
- if (!url) return ''
- if (!imageService) return url
- return imageService.getThumbnailUrl(url, size)
- }
-
- /**
- * Get a resized URL (Lanczos2 filter - better quality for larger displays)
- */
- const resized = (url: string | undefined, size = 800): string => {
- if (!url) return ''
- if (!imageService) return url
- return imageService.getResizedUrl(url, size)
- }
-
- /**
- * Get a blurred placeholder URL (for loading states)
- */
- const blurred = (url: string | undefined, blur = 5): string => {
- if (!url) return ''
- if (!imageService) return url
- return imageService.getBlurredUrl(url, blur)
- }
-
- return {
- thumbnail,
- resized,
- blurred
- }
-}
diff --git a/src/modules/base/composables/useProfiles.ts b/src/modules/base/composables/useProfiles.ts
deleted file mode 100644
index c79472f..0000000
--- a/src/modules/base/composables/useProfiles.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { ref, computed } from 'vue'
-import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ProfileService } from '../nostr/ProfileService'
-
-/**
- * Composable for managing user profiles
- */
-export function useProfiles() {
- const profileService = injectService(SERVICE_TOKENS.PROFILE_SERVICE)
-
- // Reactive state
- const isLoading = ref(false)
- const error = ref(null)
-
- /**
- * Get display name for a pubkey
- */
- const getDisplayName = (pubkey: string): string => {
- if (!profileService) return formatPubkey(pubkey)
- return profileService.getDisplayName(pubkey)
- }
-
- /**
- * Fetch profiles for a list of pubkeys
- */
- const fetchProfiles = async (pubkeys: string[]): Promise => {
- if (!profileService || pubkeys.length === 0) return
-
- try {
- isLoading.value = true
- error.value = null
- await profileService.fetchProfiles(pubkeys)
- } catch (err) {
- error.value = err instanceof Error ? err.message : 'Failed to fetch profiles'
- console.error('Failed to fetch profiles:', err)
- } finally {
- isLoading.value = false
- }
- }
-
- /**
- * Subscribe to profile updates for active users
- */
- const subscribeToProfileUpdates = async (pubkeys: string[]): Promise => {
- if (!profileService) return
-
- try {
- await profileService.subscribeToProfileUpdates(pubkeys)
- } catch (err) {
- console.error('Failed to subscribe to profile updates:', err)
- }
- }
-
- /**
- * Get full profile for a pubkey
- */
- const getProfile = async (pubkey: string) => {
- if (!profileService) return null
- return await profileService.getProfile(pubkey)
- }
-
- /**
- * Format pubkey as fallback display name
- */
- const formatPubkey = (pubkey: string): string => {
- return `${pubkey.slice(0, 8)}...${pubkey.slice(-4)}`
- }
-
- /**
- * Get all cached profiles
- */
- const profiles = computed(() => {
- if (!profileService) return new Map()
- return profileService.profiles
- })
-
- return {
- // State
- isLoading,
- error,
- profiles,
-
- // Methods
- getDisplayName,
- fetchProfiles,
- subscribeToProfileUpdates,
- getProfile,
- formatPubkey
- }
-}
diff --git a/src/modules/base/composables/useReactions.ts b/src/modules/base/composables/useReactions.ts
deleted file mode 100644
index a67fcb1..0000000
--- a/src/modules/base/composables/useReactions.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { computed } from 'vue'
-import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ReactionService, EventReactions } from '../nostr/ReactionService'
-import { useToast } from '@/core/composables/useToast'
-
-/**
- * Composable for managing reactions
- */
-export function useReactions() {
- const reactionService = injectService(SERVICE_TOKENS.REACTION_SERVICE)
- const toast = useToast()
-
- /**
- * Get reactions for a specific event
- */
- const getEventReactions = (eventId: string): EventReactions => {
- if (!reactionService) {
- return {
- eventId,
- likes: 0,
- dislikes: 0,
- totalReactions: 0,
- userHasLiked: false,
- userHasDisliked: false,
- reactions: []
- }
- }
- return reactionService.getEventReactions(eventId)
- }
-
- /**
- * Subscribe to reactions for a list of event IDs
- */
- const subscribeToReactions = async (eventIds: string[]): Promise => {
- if (!reactionService || eventIds.length === 0) return
-
- try {
- await reactionService.subscribeToReactions(eventIds)
- } catch (error) {
- console.error('Failed to subscribe to reactions:', error)
- }
- }
-
- /**
- * Toggle like on an event - like if not liked, unlike if already liked
- */
- const toggleLike = async (eventId: string, eventPubkey: string, eventKind: number): Promise => {
- if (!reactionService) {
- toast.error('Reaction service not available')
- return
- }
-
- try {
- await reactionService.toggleLikeEvent(eventId, eventPubkey, eventKind)
-
- // Check if we liked or unliked
- const eventReactions = reactionService.getEventReactions(eventId)
- if (eventReactions.userHasLiked) {
- toast.success('Post liked!')
- } else {
- toast.success('Like removed')
- }
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to toggle reaction'
-
- if (message.includes('authenticated')) {
- toast.error('Please sign in to react to posts')
- } else if (message.includes('Not connected')) {
- toast.error('Not connected to relays')
- } else {
- toast.error(message)
- }
-
- console.error('Failed to toggle like:', error)
- }
- }
-
- /**
- * Get loading state
- */
- const isLoading = computed(() => {
- return reactionService?.isLoading ?? false
- })
-
- /**
- * Get all event reactions (for debugging)
- */
- const allEventReactions = computed(() => {
- return reactionService?.eventReactions ?? new Map()
- })
-
- return {
- // Methods
- getEventReactions,
- subscribeToReactions,
- toggleLike,
-
- // State
- isLoading,
- allEventReactions
- }
-}
diff --git a/src/modules/base/index.ts b/src/modules/base/index.ts
index ad9cf20..d70a81f 100644
--- a/src/modules/base/index.ts
+++ b/src/modules/base/index.ts
@@ -3,8 +3,6 @@ import type { ModulePlugin } from '@/core/types'
import { container, SERVICE_TOKENS } from '@/core/di-container'
import { relayHub } from './nostr/relay-hub'
import { NostrMetadataService } from './nostr/nostr-metadata-service'
-import { ProfileService } from './nostr/ProfileService'
-import { ReactionService } from './nostr/ReactionService'
// Import auth services
import { auth } from './auth/auth-service'
@@ -30,8 +28,6 @@ const invoiceService = new InvoiceService()
const lnbitsAPI = new LnbitsAPI()
const imageUploadService = new ImageUploadService()
const nostrMetadataService = new NostrMetadataService()
-const profileService = new ProfileService()
-const reactionService = new ReactionService()
/**
* Base Module Plugin
@@ -72,10 +68,6 @@ export const baseModule: ModulePlugin = {
// Register image upload service
container.provide(SERVICE_TOKENS.IMAGE_UPLOAD_SERVICE, imageUploadService)
- // Register shared Nostr services (used by multiple modules)
- container.provide(SERVICE_TOKENS.PROFILE_SERVICE, profileService)
- container.provide(SERVICE_TOKENS.REACTION_SERVICE, reactionService)
-
// Register PWA service
container.provide('pwaService', pwaService)
@@ -114,14 +106,6 @@ export const baseModule: ModulePlugin = {
waitForDependencies: true, // NostrMetadataService depends on AuthService and RelayHub
maxRetries: 3
})
- await profileService.initialize({
- waitForDependencies: true, // ProfileService depends on RelayHub
- maxRetries: 3
- })
- await reactionService.initialize({
- waitForDependencies: true, // ReactionService depends on RelayHub and AuthService
- maxRetries: 3
- })
// InvoiceService doesn't need initialization as it's not a BaseService
console.log('✅ Base module installed successfully')
@@ -139,8 +123,6 @@ export const baseModule: ModulePlugin = {
await toastService.dispose()
await imageUploadService.dispose()
await nostrMetadataService.dispose()
- await profileService.dispose()
- await reactionService.dispose()
// InvoiceService doesn't need disposal as it's not a BaseService
await lnbitsAPI.dispose()
@@ -149,8 +131,6 @@ export const baseModule: ModulePlugin = {
container.remove(SERVICE_TOKENS.INVOICE_SERVICE)
container.remove(SERVICE_TOKENS.IMAGE_UPLOAD_SERVICE)
container.remove(SERVICE_TOKENS.NOSTR_METADATA_SERVICE)
- container.remove(SERVICE_TOKENS.PROFILE_SERVICE)
- container.remove(SERVICE_TOKENS.REACTION_SERVICE)
console.log('✅ Base module uninstalled')
},
@@ -165,9 +145,7 @@ export const baseModule: ModulePlugin = {
invoiceService,
pwaService,
imageUploadService,
- nostrMetadataService,
- profileService,
- reactionService
+ nostrMetadataService
},
// No routes - base module is pure infrastructure
diff --git a/src/modules/base/nostr/ProfileService.ts b/src/modules/base/nostr/ProfileService.ts
deleted file mode 100644
index 18b3301..0000000
--- a/src/modules/base/nostr/ProfileService.ts
+++ /dev/null
@@ -1,274 +0,0 @@
-import { reactive } from 'vue'
-import { BaseService } from '@/core/base/BaseService'
-import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { Event as NostrEvent, Filter } from 'nostr-tools'
-
-export interface UserProfile {
- pubkey: string
- name?: string
- display_name?: string
- about?: string
- picture?: string
- nip05?: string
- updated_at: number
-}
-
-export class ProfileService extends BaseService {
- protected readonly metadata = {
- name: 'ProfileService',
- version: '1.0.0',
- dependencies: []
- }
-
- protected relayHub: any = null
-
- // Profile cache - reactive for UI updates
- private _profiles = reactive(new Map())
- private currentSubscription: string | null = null
- private currentUnsubscribe: (() => void) | null = null
-
- // Track which profiles we've requested to avoid duplicate requests
- private requestedProfiles = new Set()
-
- protected async onInitialize(): Promise {
- console.log('ProfileService: Starting initialization...')
-
- this.relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
-
- if (!this.relayHub) {
- throw new Error('RelayHub service not available')
- }
-
- console.log('ProfileService: Initialization complete')
- }
-
- /**
- * Get profile for a pubkey, fetching if not cached
- */
- async getProfile(pubkey: string): Promise {
- // Return cached profile if available
- if (this._profiles.has(pubkey)) {
- return this._profiles.get(pubkey)!
- }
-
- // If not requested yet, fetch it
- if (!this.requestedProfiles.has(pubkey)) {
- await this.fetchProfile(pubkey)
- }
-
- return this._profiles.get(pubkey) || null
- }
-
- /**
- * Get display name for a pubkey (returns formatted pubkey if no profile)
- */
- getDisplayName(pubkey: string): string {
- const profile = this._profiles.get(pubkey)
- if (profile?.display_name) return profile.display_name
- if (profile?.name) return profile.name
-
- // Return formatted pubkey as fallback
- return `${pubkey.slice(0, 8)}...${pubkey.slice(-4)}`
- }
-
- /**
- * Fetch profile for specific pubkey
- */
- private async fetchProfile(pubkey: string): Promise {
- if (!this.relayHub || this.requestedProfiles.has(pubkey)) {
- return
- }
-
- this.requestedProfiles.add(pubkey)
-
- try {
- if (!this.relayHub.isConnected) {
- await this.relayHub.connect()
- }
-
- const subscriptionId = `profile-${pubkey}-${Date.now()}`
-
- const filter: Filter = {
- kinds: [0], // Profile metadata
- authors: [pubkey],
- limit: 1
- }
-
- console.log(`ProfileService: Fetching profile for ${pubkey.slice(0, 8)}...`)
-
- const unsubscribe = this.relayHub.subscribe({
- id: subscriptionId,
- filters: [filter],
- onEvent: (event: NostrEvent) => {
- this.handleProfileEvent(event)
- },
- onEose: () => {
- console.log(`Profile subscription ${subscriptionId} complete`)
- // Clean up subscription after getting the profile
- if (unsubscribe) {
- unsubscribe()
- }
- }
- })
-
- } catch (error) {
- console.error(`Failed to fetch profile for ${pubkey}:`, error)
- this.requestedProfiles.delete(pubkey) // Allow retry
- }
- }
-
- /**
- * Handle incoming profile event
- */
- private handleProfileEvent(event: NostrEvent): void {
- try {
- const metadata = JSON.parse(event.content)
-
- const profile: UserProfile = {
- pubkey: event.pubkey,
- name: metadata.name,
- display_name: metadata.display_name,
- about: metadata.about,
- picture: metadata.picture,
- nip05: metadata.nip05,
- updated_at: event.created_at
- }
-
- // Only update if this is newer than what we have
- const existing = this._profiles.get(event.pubkey)
- if (!existing || event.created_at > existing.updated_at) {
- this._profiles.set(event.pubkey, profile)
- console.log(`ProfileService: Updated profile for ${event.pubkey.slice(0, 8)}...`, profile.display_name || profile.name)
- }
-
- } catch (error) {
- console.error('Failed to parse profile metadata:', error)
- }
- }
-
- /**
- * Bulk fetch profiles for multiple pubkeys
- */
- async fetchProfiles(pubkeys: string[]): Promise {
- const unfetchedPubkeys = pubkeys.filter(pubkey =>
- !this._profiles.has(pubkey) && !this.requestedProfiles.has(pubkey)
- )
-
- if (unfetchedPubkeys.length === 0) return
-
- console.log(`ProfileService: Bulk fetching ${unfetchedPubkeys.length} profiles`)
-
- try {
- if (!this.relayHub?.isConnected) {
- await this.relayHub?.connect()
- }
-
- const subscriptionId = `profiles-bulk-${Date.now()}`
-
- // Mark all as requested
- unfetchedPubkeys.forEach(pubkey => this.requestedProfiles.add(pubkey))
-
- const filter: Filter = {
- kinds: [0],
- authors: unfetchedPubkeys,
- limit: unfetchedPubkeys.length
- }
-
- const unsubscribe = this.relayHub.subscribe({
- id: subscriptionId,
- filters: [filter],
- onEvent: (event: NostrEvent) => {
- this.handleProfileEvent(event)
- },
- onEose: () => {
- console.log(`Bulk profile subscription ${subscriptionId} complete`)
- if (unsubscribe) {
- unsubscribe()
- }
- }
- })
-
- } catch (error) {
- console.error('Failed to bulk fetch profiles:', error)
- // Remove from requested so they can be retried
- unfetchedPubkeys.forEach(pubkey => this.requestedProfiles.delete(pubkey))
- }
- }
-
- /**
- * Subscribe to real-time profile updates for active users
- */
- async subscribeToProfileUpdates(pubkeys: string[]): Promise {
- if (this.currentSubscription) {
- await this.unsubscribeFromProfiles()
- }
-
- if (pubkeys.length === 0) return
-
- try {
- if (!this.relayHub?.isConnected) {
- await this.relayHub?.connect()
- }
-
- const subscriptionId = `profile-updates-${Date.now()}`
-
- const filter: Filter = {
- kinds: [0],
- authors: pubkeys
- }
-
- console.log(`ProfileService: Subscribing to profile updates for ${pubkeys.length} users`)
-
- const unsubscribe = this.relayHub.subscribe({
- id: subscriptionId,
- filters: [filter],
- onEvent: (event: NostrEvent) => {
- this.handleProfileEvent(event)
- },
- onEose: () => {
- console.log(`Profile updates subscription ${subscriptionId} ready`)
- }
- })
-
- this.currentSubscription = subscriptionId
- this.currentUnsubscribe = unsubscribe
-
- } catch (error) {
- console.error('Failed to subscribe to profile updates:', error)
- }
- }
-
- /**
- * Unsubscribe from profile updates
- */
- async unsubscribeFromProfiles(): Promise {
- if (this.currentUnsubscribe) {
- this.currentUnsubscribe()
- this.currentSubscription = null
- this.currentUnsubscribe = null
- }
- }
-
- /**
- * Clear profile cache
- */
- clearCache(): void {
- this._profiles.clear()
- this.requestedProfiles.clear()
- }
-
- /**
- * Get all cached profiles
- */
- get profiles(): Map {
- return this._profiles
- }
-
- /**
- * Cleanup
- */
- protected async onDestroy(): Promise {
- await this.unsubscribeFromProfiles()
- this.clearCache()
- }
-}
diff --git a/src/modules/base/nostr/ReactionService.ts b/src/modules/base/nostr/ReactionService.ts
deleted file mode 100644
index 6058c70..0000000
--- a/src/modules/base/nostr/ReactionService.ts
+++ /dev/null
@@ -1,581 +0,0 @@
-import { ref, reactive } from 'vue'
-import { BaseService } from '@/core/base/BaseService'
-import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import { finalizeEvent, type EventTemplate } from 'nostr-tools'
-import type { Event as NostrEvent } from 'nostr-tools'
-
-export interface Reaction {
- id: string
- eventId: string // The event being reacted to
- pubkey: string // Who reacted
- content: string // The reaction content ('+', '-', emoji)
- created_at: number
-}
-
-export interface EventReactions {
- eventId: string
- likes: number
- dislikes: number
- totalReactions: number
- userHasLiked: boolean
- userHasDisliked: boolean
- userReactionId?: string // Track the user's reaction ID for deletion
- reactions: Reaction[]
-}
-
-export class ReactionService extends BaseService {
- protected readonly metadata = {
- name: 'ReactionService',
- version: '1.0.0',
- dependencies: []
- }
-
- protected relayHub: any = null
- protected authService: any = null
-
- // Reaction state - indexed by event ID
- private _eventReactions = reactive(new Map())
- private _isLoading = ref(false)
-
- // Track reaction subscription
- private currentSubscription: string | null = null
- private currentUnsubscribe: (() => void) | null = null
-
- // Track which events we're monitoring
- private monitoredEvents = new Set()
-
- // Track deleted reactions to hide them
- private deletedReactions = new Set()
-
- protected async onInitialize(): Promise {
- console.log('ReactionService: Starting initialization...')
-
- this.relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
- this.authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
-
- if (!this.relayHub) {
- throw new Error('RelayHub service not available')
- }
-
- console.log('ReactionService: Initialization complete')
- }
-
- /**
- * Get reactions for a specific event
- */
- getEventReactions(eventId: string): EventReactions {
- if (!this._eventReactions.has(eventId)) {
- this._eventReactions.set(eventId, {
- eventId,
- likes: 0,
- dislikes: 0,
- totalReactions: 0,
- userHasLiked: false,
- userHasDisliked: false,
- reactions: []
- })
- }
- return this._eventReactions.get(eventId)!
- }
-
- /**
- * Subscribe to reactions for a list of event IDs
- */
- async subscribeToReactions(eventIds: string[]): Promise {
- if (eventIds.length === 0) return
-
- // Filter out events we're already monitoring
- const newEventIds = eventIds.filter(id => !this.monitoredEvents.has(id))
- if (newEventIds.length === 0) return
-
- console.log(`ReactionService: Subscribing to reactions for ${newEventIds.length} events`)
-
- try {
- if (!this.relayHub?.isConnected) {
- await this.relayHub?.connect()
- }
-
- // Add to monitored set
- newEventIds.forEach(id => this.monitoredEvents.add(id))
-
- const subscriptionId = `reactions-${Date.now()}`
-
- // Subscribe to reactions (kind 7) for these events
- const filters = [
- {
- kinds: [7], // Reactions
- '#e': newEventIds, // Events being reacted to
- limit: 1000
- }
- ]
-
- const unsubscribe = this.relayHub.subscribe({
- id: subscriptionId,
- filters: filters,
- onEvent: (event: NostrEvent) => {
- this.handleReactionEvent(event)
- },
- onEose: () => {
- console.log(`ReactionService: Subscription ${subscriptionId} ready`)
- }
- })
-
- // Store subscription info (we can have multiple)
- if (!this.currentSubscription) {
- this.currentSubscription = subscriptionId
- this.currentUnsubscribe = unsubscribe
- }
-
- } catch (error) {
- console.error('Failed to subscribe to reactions:', error)
- }
- }
-
- /**
- * Handle incoming reaction event
- */
- public handleReactionEvent(event: NostrEvent): void {
- try {
- // Find the event being reacted to
- const eTag = event.tags.find(tag => tag[0] === 'e')
- if (!eTag || !eTag[1]) {
- console.warn('Reaction event missing e tag:', event.id)
- return
- }
-
- const eventId = eTag[1]
- const content = event.content.trim()
-
- // Create reaction object
- const reaction: Reaction = {
- id: event.id,
- eventId,
- pubkey: event.pubkey,
- content,
- created_at: event.created_at
- }
-
- // Update event reactions
- const eventReactions = this.getEventReactions(eventId)
-
- // Check if this reaction already exists (deduplication) or is deleted
- const existingIndex = eventReactions.reactions.findIndex(r => r.id === reaction.id)
- if (existingIndex >= 0) {
- return // Already have this reaction
- }
-
- // Check if this reaction has been deleted
- if (this.deletedReactions.has(reaction.id)) {
- return // This reaction was deleted
- }
-
- // IMPORTANT: Remove any previous reaction from the same user
- // This ensures one reaction per user per event, even if deletion events aren't processed
- const previousReactionIndex = eventReactions.reactions.findIndex(r =>
- r.pubkey === reaction.pubkey &&
- r.content === reaction.content
- )
-
- if (previousReactionIndex >= 0) {
- // Replace the old reaction with the new one
- eventReactions.reactions[previousReactionIndex] = reaction
- } else {
- // Add as new reaction
- eventReactions.reactions.push(reaction)
- }
-
- // Recalculate counts and user state
- this.recalculateEventReactions(eventId)
-
- } catch (error) {
- console.error('Failed to handle reaction event:', error)
- }
- }
-
- /**
- * Handle deletion event (called when a kind 5 event with k=7 is received)
- */
- public handleDeletionEvent(event: NostrEvent): void {
- try {
- // Process each deleted event
- const eTags = event.tags.filter(tag => tag[0] === 'e')
- const deletionAuthor = event.pubkey
-
- for (const eTag of eTags) {
- const deletedEventId = eTag[1]
- if (deletedEventId) {
- // Add to deleted set
- this.deletedReactions.add(deletedEventId)
-
- // Find and remove the reaction from all event reactions
- for (const [eventId, eventReactions] of this._eventReactions) {
- const reactionIndex = eventReactions.reactions.findIndex(r => r.id === deletedEventId)
-
- if (reactionIndex >= 0) {
- const reaction = eventReactions.reactions[reactionIndex]
-
- // IMPORTANT: Only process deletion if it's from the same user who created the reaction
- // This follows NIP-09 spec: "Relays SHOULD delete or stop publishing any referenced events
- // that have an identical `pubkey` as the deletion request"
- if (reaction.pubkey === deletionAuthor) {
- eventReactions.reactions.splice(reactionIndex, 1)
- // Recalculate counts for this event
- this.recalculateEventReactions(eventId)
- }
- }
- }
- }
- }
- } catch (error) {
- console.error('Failed to handle deletion event:', error)
- }
- }
-
- /**
- * Recalculate reaction counts and user state for an event
- */
- private recalculateEventReactions(eventId: string): void {
- const eventReactions = this.getEventReactions(eventId)
- const userPubkey = this.authService?.user?.value?.pubkey
-
- // Use Sets to track unique users who liked/disliked
- const likedUsers = new Set()
- const dislikedUsers = new Set()
- let userHasLiked = false
- let userHasDisliked = false
- let userReactionId: string | undefined
-
- // Group reactions by user, keeping only the most recent
- const latestReactionsByUser = new Map()
-
- for (const reaction of eventReactions.reactions) {
- // Skip deleted reactions
- if (this.deletedReactions.has(reaction.id)) {
- continue
- }
-
- // Keep only the latest reaction from each user
- const existing = latestReactionsByUser.get(reaction.pubkey)
- if (!existing || reaction.created_at > existing.created_at) {
- latestReactionsByUser.set(reaction.pubkey, reaction)
- }
- }
-
- // Now count unique reactions
- for (const reaction of latestReactionsByUser.values()) {
- const isLike = reaction.content === '+' || reaction.content === '❤️' || reaction.content === ''
- const isDislike = reaction.content === '-'
-
- if (isLike) {
- likedUsers.add(reaction.pubkey)
- if (userPubkey && reaction.pubkey === userPubkey) {
- userHasLiked = true
- userReactionId = reaction.id
- }
- } else if (isDislike) {
- dislikedUsers.add(reaction.pubkey)
- if (userPubkey && reaction.pubkey === userPubkey) {
- userHasDisliked = true
- userReactionId = reaction.id
- }
- }
- }
-
- // Update the reactive state with unique user counts
- eventReactions.likes = likedUsers.size
- eventReactions.dislikes = dislikedUsers.size
- eventReactions.totalReactions = latestReactionsByUser.size
- eventReactions.userHasLiked = userHasLiked
- eventReactions.userHasDisliked = userHasDisliked
- eventReactions.userReactionId = userReactionId
- }
-
- /**
- * Send a heart reaction (like) to an event
- */
- async likeEvent(eventId: string, eventPubkey: string, eventKind: number): Promise {
- if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to react')
- }
-
- if (!this.relayHub?.isConnected) {
- throw new Error('Not connected to relays')
- }
-
- const userPubkey = this.authService.user.value?.pubkey
- const userPrivkey = this.authService.user.value?.prvkey
-
- if (!userPubkey || !userPrivkey) {
- throw new Error('User keys not available')
- }
-
- // Check if user already liked this event
- const eventReactions = this.getEventReactions(eventId)
- if (eventReactions.userHasLiked) {
- throw new Error('Already liked this event')
- }
-
- try {
- this._isLoading.value = true
-
- // Create reaction event template according to NIP-25
- const eventTemplate: EventTemplate = {
- kind: 7, // Reaction
- content: '+', // Like reaction
- tags: [
- ['e', eventId, '', eventPubkey], // Event being reacted to
- ['p', eventPubkey], // Author of the event being reacted to
- ['k', eventKind.toString()] // Kind of the event being reacted to
- ],
- created_at: Math.floor(Date.now() / 1000)
- }
-
- // Sign the event
- const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
-
- // Publish the reaction
- await this.relayHub.publishEvent(signedEvent)
-
- // Optimistically update local state
- this.handleReactionEvent(signedEvent)
-
- } catch (error) {
- console.error('Failed to like event:', error)
- throw error
- } finally {
- this._isLoading.value = false
- }
- }
-
- /**
- * Send a dislike reaction to an event
- */
- async dislikeEvent(eventId: string, eventPubkey: string, eventKind: number): Promise {
- if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to react')
- }
-
- if (!this.relayHub?.isConnected) {
- throw new Error('Not connected to relays')
- }
-
- const userPubkey = this.authService.user.value?.pubkey
- const userPrivkey = this.authService.user.value?.prvkey
-
- if (!userPubkey || !userPrivkey) {
- throw new Error('User keys not available')
- }
-
- const eventReactions = this.getEventReactions(eventId)
- if (eventReactions.userHasDisliked) {
- throw new Error('Already disliked this event')
- }
-
- try {
- this._isLoading.value = true
-
- const eventTemplate: EventTemplate = {
- kind: 7,
- content: '-', // Dislike reaction
- tags: [
- ['e', eventId, '', eventPubkey],
- ['p', eventPubkey],
- ['k', eventKind.toString()]
- ],
- created_at: Math.floor(Date.now() / 1000)
- }
-
- const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
-
- await this.relayHub.publishEvent(signedEvent)
- this.handleReactionEvent(signedEvent)
-
- } catch (error) {
- console.error('Failed to dislike event:', error)
- throw error
- } finally {
- this._isLoading.value = false
- }
- }
-
- /**
- * Remove a like from an event (unlike) using NIP-09 deletion events
- */
- async unlikeEvent(eventId: string): Promise {
- if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to remove reaction')
- }
-
- if (!this.relayHub?.isConnected) {
- throw new Error('Not connected to relays')
- }
-
- const userPubkey = this.authService.user.value?.pubkey
- const userPrivkey = this.authService.user.value?.prvkey
-
- if (!userPubkey || !userPrivkey) {
- throw new Error('User keys not available')
- }
-
- // Get the user's reaction ID to delete
- const eventReactions = this.getEventReactions(eventId)
-
- if (!eventReactions.userHasLiked || !eventReactions.userReactionId) {
- throw new Error('No reaction to remove')
- }
-
- try {
- this._isLoading.value = true
-
- // Create deletion event according to NIP-09
- const eventTemplate: EventTemplate = {
- kind: 5, // Deletion request
- content: '', // Empty content or reason
- tags: [
- ['e', eventReactions.userReactionId], // The reaction event to delete
- ['k', '7'] // Kind of event being deleted (reaction)
- ],
- created_at: Math.floor(Date.now() / 1000)
- }
-
- // Sign the event
- const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
-
- // Publish the deletion
- const result = await this.relayHub.publishEvent(signedEvent)
-
- console.log(`ReactionService: Deletion published to ${result.success}/${result.total} relays`)
-
- // Optimistically update local state
- this.handleDeletionEvent(signedEvent)
-
- } catch (error) {
- console.error('Failed to unlike event:', error)
- throw error
- } finally {
- this._isLoading.value = false
- }
- }
-
- /**
- * Remove a dislike from an event using NIP-09 deletion events
- */
- async undislikeEvent(eventId: string): Promise {
- if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to remove reaction')
- }
-
- if (!this.relayHub?.isConnected) {
- throw new Error('Not connected to relays')
- }
-
- const userPubkey = this.authService.user.value?.pubkey
- const userPrivkey = this.authService.user.value?.prvkey
-
- if (!userPubkey || !userPrivkey) {
- throw new Error('User keys not available')
- }
-
- const eventReactions = this.getEventReactions(eventId)
-
- if (!eventReactions.userHasDisliked || !eventReactions.userReactionId) {
- throw new Error('No dislike to remove')
- }
-
- try {
- this._isLoading.value = true
-
- const eventTemplate: EventTemplate = {
- kind: 5,
- content: '',
- tags: [
- ['e', eventReactions.userReactionId],
- ['k', '7']
- ],
- created_at: Math.floor(Date.now() / 1000)
- }
-
- const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
-
- const result = await this.relayHub.publishEvent(signedEvent)
- console.log(`ReactionService: Dislike deletion published to ${result.success}/${result.total} relays`)
-
- this.handleDeletionEvent(signedEvent)
-
- } catch (error) {
- console.error('Failed to undislike event:', error)
- throw error
- } finally {
- this._isLoading.value = false
- }
- }
-
- /**
- * Toggle like on an event - like if not liked, unlike if already liked
- */
- async toggleLikeEvent(eventId: string, eventPubkey: string, eventKind: number): Promise {
- const eventReactions = this.getEventReactions(eventId)
-
- if (eventReactions.userHasLiked) {
- // Unlike the event
- await this.unlikeEvent(eventId)
- } else {
- // Like the event
- await this.likeEvent(eventId, eventPubkey, eventKind)
- }
- }
-
- /**
- * Toggle dislike on an event
- */
- async toggleDislikeEvent(eventId: string, eventPubkey: string, eventKind: number): Promise {
- const eventReactions = this.getEventReactions(eventId)
-
- if (eventReactions.userHasDisliked) {
- await this.undislikeEvent(eventId)
- } else {
- await this.dislikeEvent(eventId, eventPubkey, eventKind)
- }
- }
-
- /**
- * Helper function to convert hex string to Uint8Array
- */
- private hexToUint8Array(hex: string): Uint8Array {
- const bytes = new Uint8Array(hex.length / 2)
- for (let i = 0; i < hex.length; i += 2) {
- bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
- }
- return bytes
- }
-
- /**
- * Get all event reactions
- */
- get eventReactions(): Map {
- return this._eventReactions
- }
-
- /**
- * Check if currently loading
- */
- get isLoading(): boolean {
- return this._isLoading.value
- }
-
- /**
- * Cleanup
- */
- protected async onDestroy(): Promise {
- if (this.currentUnsubscribe) {
- this.currentUnsubscribe()
- }
- this._eventReactions.clear()
- this.monitoredEvents.clear()
- this.deletedReactions.clear()
- }
-}
diff --git a/src/modules/base/nostr/relay-hub.ts b/src/modules/base/nostr/relay-hub.ts
index 7cdd00f..486d3f2 100644
--- a/src/modules/base/nostr/relay-hub.ts
+++ b/src/modules/base/nostr/relay-hub.ts
@@ -540,12 +540,8 @@ export class RelayHub extends BaseService {
const successful = results.filter(result => result.status === 'fulfilled').length
const total = results.length
- this.emit('eventPublished', { eventId: event.id, success: successful, total })
- // Throw error if no relays accepted the event
- if (successful === 0) {
- throw new Error(`Failed to publish event - none of the ${total} relay(s) accepted it`)
- }
+ this.emit('eventPublished', { eventId: event.id, success: successful, total })
return { success: successful, total }
}
diff --git a/src/modules/chat/components/ChatComponent.vue b/src/modules/chat/components/ChatComponent.vue
index 82ab49f..f893e2d 100644
--- a/src/modules/chat/components/ChatComponent.vue
+++ b/src/modules/chat/components/ChatComponent.vue
@@ -158,7 +158,7 @@
: 'bg-muted'
]"
>
-
+
{{ message.content }}
{{ formatTime(message.created_at) }}
@@ -325,7 +325,7 @@
: 'bg-muted'
]"
>
-
+
{{ message.content }}
{{ formatTime(message.created_at) }}
@@ -376,7 +376,6 @@ import { Badge } from '@/components/ui/badge'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { useChat } from '../composables/useChat'
-import ChatMessageContent from './ChatMessageContent.vue'
import { useFuzzySearch } from '@/composables/useFuzzySearch'
diff --git a/src/modules/chat/components/ChatMessageContent.vue b/src/modules/chat/components/ChatMessageContent.vue
deleted file mode 100644
index f6d1ef9..0000000
--- a/src/modules/chat/components/ChatMessageContent.vue
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
- Order Placed
-
-
-
-
-
-
- {{ item.quantity }}x
- Item
-
-
-
-
-
-
-
-
-
- {{ shippingLabel }}
-
-
-
-
- #{{ shortOrderId }}
-
-
-
-
-
-
{{ content }}
-
-
-
diff --git a/src/modules/chat/services/chat-service.ts b/src/modules/chat/services/chat-service.ts
index 767636b..37cb14c 100644
--- a/src/modules/chat/services/chat-service.ts
+++ b/src/modules/chat/services/chat-service.ts
@@ -702,8 +702,7 @@ export class ChatService extends BaseService {
}
}
/**
- * Process a message event (incoming or outgoing)
- * Note: This is called for both directions from loadRecentMessagesForPeer
+ * Process an incoming message event
*/
private async processIncomingMessage(event: any): Promise {
try {
@@ -716,29 +715,10 @@ export class ChatService extends BaseService {
console.warn('Cannot process message: user not authenticated')
return
}
-
- // Determine if this is an outgoing message (sent by us)
- const isOutgoing = event.pubkey === userPubkey
-
- // For NIP-04 decryption, we need the OTHER party's pubkey
- // - For incoming messages: sender is the other party (event.pubkey)
- // - For outgoing messages: recipient is the other party (from p-tag)
- let otherPartyPubkey: string
- if (isOutgoing) {
- // Outgoing: get recipient from p-tag
- const pTag = event.tags.find((tag: string[]) => tag[0] === 'p')
- if (!pTag || !pTag[1]) {
- console.warn('Cannot process outgoing message: no recipient p-tag')
- return
- }
- otherPartyPubkey = pTag[1]
- } else {
- // Incoming: sender is the other party
- otherPartyPubkey = event.pubkey
- }
-
- // Decrypt the message content using the other party's pubkey
- const decryptedContent = await nip04.decrypt(userPrivkey, otherPartyPubkey, event.content)
+ // Get sender pubkey from event
+ const senderPubkey = event.pubkey
+ // Decrypt the message content
+ const decryptedContent = await nip04.decrypt(userPrivkey, senderPubkey, event.content)
// Check if this is a market-related message
let isMarketMessage = false
try {
@@ -784,13 +764,13 @@ export class ChatService extends BaseService {
id: event.id,
content: displayContent,
created_at: event.created_at,
- sent: isOutgoing,
- pubkey: event.pubkey
+ sent: false,
+ pubkey: senderPubkey
}
- // Ensure we have a peer record for the other party (the peer we're chatting with)
- this.addPeer(otherPartyPubkey)
- // Add the message to the peer's conversation
- this.addMessage(otherPartyPubkey, message)
+ // Ensure we have a peer record for the sender
+ this.addPeer(senderPubkey)
+ // Add the message
+ this.addMessage(senderPubkey, message)
}
} catch (error) {
console.error('Failed to process incoming message:', error)
diff --git a/src/modules/expenses/components/AccountSelector.vue b/src/modules/expenses/components/AccountSelector.vue
deleted file mode 100644
index 573a66a..0000000
--- a/src/modules/expenses/components/AccountSelector.vue
+++ /dev/null
@@ -1,378 +0,0 @@
-
-