Refactor authentication architecture to eliminate dual auth complexity

This major refactor consolidates the authentication system to use a single
source of truth, eliminating timing issues and architectural complexity
that was causing chat and payment functionality problems.

Key Changes:
• Remove old global useAuth composable and replace with useAuthService wrapper
• Update all 25+ files to use consistent auth pattern via dependency injection
• Eliminate dual auth detection workarounds from services (ChatService, PaymentService, etc.)
• Fix TypeScript errors and add proper Uint8Array conversion for Nostr private keys
• Consolidate auth state management to AuthService as single source of truth

Benefits:
• Resolves chat peer loading and message subscription timing issues
• Fixes wallet detection problems for Lightning payments
• Eliminates race conditions between global and injected auth
• Maintains API compatibility while improving architecture
• Reduces code complexity and improves maintainability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-09-07 00:47:02 +02:00
parent 5633aa154b
commit 4feb5459cc
27 changed files with 210 additions and 518 deletions

View file

@ -7,7 +7,7 @@ import LoginDialog from '@/components/auth/LoginDialog.vue'
import { Toaster } from '@/components/ui/sonner' import { Toaster } from '@/components/ui/sonner'
import 'vue-sonner/style.css' import 'vue-sonner/style.css'
import { useMarketPreloader } from '@/modules/market/composables/useMarketPreloader' import { useMarketPreloader } from '@/modules/market/composables/useMarketPreloader'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
const route = useRoute() const route = useRoute()

View file

@ -124,7 +124,7 @@ export async function createAppInstance() {
await pluginManager.installAll() await pluginManager.installAll()
// Initialize auth before setting up router guards // Initialize auth before setting up router guards
const { auth } = await import('@/composables/useAuth') const { auth } = await import('@/composables/useAuthService')
await auth.initialize() await auth.initialize()
console.log('Auth initialized, isAuthenticated:', auth.isAuthenticated.value) console.log('Auth initialized, isAuthenticated:', auth.isAuthenticated.value)

View file

@ -7,7 +7,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { User } from 'lucide-vue-next' import { User } from 'lucide-vue-next'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { useToast } from '@/core/composables/useToast' import { useToast } from '@/core/composables/useToast'
interface Props { interface Props {

View file

@ -6,7 +6,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog' import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog'
import { User, LogOut, Settings, Key, Wallet, ExternalLink } from 'lucide-vue-next' import { User, LogOut, Settings, Key, Wallet, ExternalLink } from 'lucide-vue-next'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
interface Props { interface Props {

View file

@ -6,7 +6,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog' import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog'
import { User, LogOut, Settings } from 'lucide-vue-next' import { User, LogOut, Settings } from 'lucide-vue-next'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
const router = useRouter() const router = useRouter()

View file

@ -85,7 +85,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { useDemoAccountGenerator } from '@/composables/useDemoAccountGenerator' import { useDemoAccountGenerator } from '@/composables/useDemoAccountGenerator'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'

View file

@ -12,7 +12,7 @@ import LoginDialog from '@/components/auth/LoginDialog.vue'
import ProfileDialog from '@/components/auth/ProfileDialog.vue' import ProfileDialog from '@/components/auth/ProfileDialog.vue'
import CurrencyDisplay from '@/components/ui/CurrencyDisplay.vue' import CurrencyDisplay from '@/components/ui/CurrencyDisplay.vue'
import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog' import { LogoutConfirmDialog } from '@/components/ui/LogoutConfirmDialog'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { useMarketPreloader } from '@/modules/market/composables/useMarketPreloader' import { useMarketPreloader } from '@/modules/market/composables/useMarketPreloader'
import { useMarketStore } from '@/stores/market' import { useMarketStore } from '@/stores/market'
import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container' import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container'

View file

@ -1,168 +0,0 @@
import { ref, computed } from 'vue'
import { lnbitsAPI, type User, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits'
import { useMultiAsyncOperation } from '@/core/composables/useAsyncOperation'
const currentUser = ref<User | null>(null)
// Shared async operations for auth actions
const authOperations = useMultiAsyncOperation<{
initialize: User | null
login: User
register: User
logout: void
}>()
export function useAuth() {
const isAuthenticated = computed(() => !!currentUser.value)
// Get operation instances
const initializeOp = authOperations.createOperation('initialize')
const loginOp = authOperations.createOperation('login')
const registerOp = authOperations.createOperation('register')
const logoutOp = authOperations.createOperation('logout')
/**
* Initialize authentication on app start
*/
async function initialize(): Promise<void> {
try {
await initializeOp.execute(async () => {
if (lnbitsAPI.isAuthenticated()) {
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}
return null
}, {
errorMessage: 'Failed to initialize authentication',
showToast: false // Don't show toast for initialization errors
})
} catch {
// Clear invalid token on error
await logout()
}
}
/**
* Login with username and password
*/
async function login(credentials: LoginCredentials): Promise<void> {
await loginOp.execute(async () => {
await lnbitsAPI.login(credentials)
// Get user details
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}, {
errorMessage: 'Login failed'
})
}
/**
* Register new user
*/
async function register(data: RegisterData): Promise<void> {
await registerOp.execute(async () => {
await lnbitsAPI.register(data)
// Get user details
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}, {
errorMessage: 'Registration failed'
})
}
/**
* Logout and clear user data
*/
async function logout(): Promise<void> {
await logoutOp.execute(async () => {
// Clear local state
lnbitsAPI.logout()
currentUser.value = null
// Clear all auth operation states
authOperations.clearAll()
}, {
showToast: false // Don't show toast for logout
})
}
/**
* Update user password
*/
async function updatePassword(currentPassword: string, newPassword: string): Promise<void> {
const updatePasswordOp = authOperations.createOperation('updatePassword' as any)
return await updatePasswordOp.execute(async () => {
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword)
currentUser.value = updatedUser
return updatedUser
}, {
errorMessage: 'Failed to update password'
})
}
/**
* Update user profile
*/
async function updateProfile(data: Partial<User>): Promise<void> {
const updateProfileOp = authOperations.createOperation('updateProfile' as any)
return await updateProfileOp.execute(async () => {
const updatedUser = await lnbitsAPI.updateProfile(data)
currentUser.value = updatedUser
return updatedUser
}, {
errorMessage: 'Failed to update profile'
})
}
/**
* Check if user is authenticated
*/
function checkAuth(): boolean {
return lnbitsAPI.isAuthenticated()
}
/**
* Get user display info
*/
const userDisplay = computed(() => {
if (!currentUser.value) return null
return {
name: currentUser.value.username || currentUser.value.email || 'Anonymous',
username: currentUser.value.username,
email: currentUser.value.email,
id: currentUser.value.id,
shortId: currentUser.value.id.slice(0, 8) + '...' + currentUser.value.id.slice(-8)
}
})
return {
// State
currentUser: computed(() => currentUser.value),
isAuthenticated,
isLoading: computed(() => authOperations.isAnyLoading()),
error: computed(() => authOperations.hasAnyError() ?
(initializeOp.error.value || loginOp.error.value || registerOp.error.value || logoutOp.error.value) : null),
userDisplay,
// Actions
initialize,
login,
register,
logout,
updatePassword,
updateProfile,
checkAuth
}
}
// Export singleton instance for global state
export const auth = useAuth()

View file

@ -0,0 +1,72 @@
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { AuthService } from '@/modules/base/auth/auth-service'
/**
* Composable to access the injected auth service
* This replaces the global auth composable with the injected service
*/
export function useAuth() {
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
if (!authService) {
throw new Error('AuthService not available. Make sure base module is installed.')
}
return {
// State
currentUser: authService.currentUser,
isAuthenticated: authService.isAuthenticated,
isLoading: authService.isLoading,
error: authService.error,
userDisplay: authService.userDisplay,
// Compatibility aliases
user: authService.user,
// Actions
initialize: () => authService.initialize(),
login: (credentials: any) => authService.login(credentials),
register: (data: any) => authService.register(data),
logout: () => authService.logout(),
updatePassword: (current: string, newPass: string) => authService.updatePassword(current, newPass),
updateProfile: (data: any) => authService.updateProfile(data),
checkAuth: () => authService.checkAuth(),
refresh: () => authService.refresh()
}
}
// Export singleton reference for compatibility
export function getAuthService() {
return injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
}
// For files that import { auth } directly
export const auth = {
get currentUser() {
const service = getAuthService()
return service?.currentUser
},
get isAuthenticated() {
const service = getAuthService()
return service?.isAuthenticated
},
get isLoading() {
const service = getAuthService()
return service?.isLoading
},
get error() {
const service = getAuthService()
return service?.error
},
get userDisplay() {
const service = getAuthService()
return service?.userDisplay
},
initialize: () => getAuthService()?.initialize(),
login: (credentials: any) => getAuthService()?.login(credentials),
register: (data: any) => getAuthService()?.register(data),
logout: () => getAuthService()?.logout(),
updatePassword: (current: string, newPass: string) => getAuthService()?.updatePassword(current, newPass),
updateProfile: (data: any) => getAuthService()?.updateProfile(data),
checkAuth: () => getAuthService()?.checkAuth()
}

View file

@ -51,82 +51,23 @@ export class PaymentService extends BaseService {
this.debug('PaymentService initialized with clean state') this.debug('PaymentService initialized with clean state')
} }
/**
* Get global auth composable
*/
private async getGlobalAuth() {
try {
// Use async dynamic import to avoid circular dependencies
const { auth } = await import('@/composables/useAuth')
return auth
} catch (error) {
this.debug('Could not access global auth:', error)
return null
}
}
/** /**
* Get user wallets from authenticated user (using dual auth detection) * Get user wallets from authenticated user
*/
async getUserWalletsAsync() {
// Check both injected auth service AND global auth composable
const hasAuthService = this.authService?.user?.value?.wallets
const globalAuth = await this.getGlobalAuth()
const hasGlobalAuth = globalAuth?.currentUser?.value?.wallets
const wallets = hasAuthService || hasGlobalAuth || []
this.debug('Getting user wallets:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth,
walletsCount: Array.isArray(wallets) ? wallets.length : 0,
walletsSource: hasAuthService ? 'authService' : (hasGlobalAuth ? 'globalAuth' : 'none')
})
return wallets
}
/**
* Get user wallets from authenticated user (synchronous fallback)
*/ */
get userWallets() { get userWallets() {
// Fallback to just auth service for synchronous access
return this.authService?.user?.value?.wallets || [] return this.authService?.user?.value?.wallets || []
} }
/** /**
* Check if user has any wallet with balance (async version) * Check if user has any wallet with balance
*/
async hasWalletWithBalanceAsync(): Promise<boolean> {
const wallets = await this.getUserWalletsAsync()
return wallets.some((wallet: any) => wallet.balance_msat > 0)
}
/**
* Check if user has any wallet with balance (synchronous fallback)
*/ */
get hasWalletWithBalance(): boolean { get hasWalletWithBalance(): boolean {
return this.userWallets.some((wallet: any) => wallet.balance_msat > 0) return this.userWallets.some((wallet: any) => wallet.balance_msat > 0)
} }
/** /**
* Find wallet with sufficient balance for payment (async version) * Find wallet with sufficient balance for payment
*/
async getWalletWithBalanceAsync(requiredAmountSats?: number): Promise<any | null> {
const wallets = await this.getUserWalletsAsync()
if (!wallets.length) return null
if (requiredAmountSats) {
// Convert sats to msat for comparison
const requiredMsat = requiredAmountSats * 1000
return wallets.find((wallet: any) => wallet.balance_msat >= requiredMsat)
}
return wallets.find((wallet: any) => wallet.balance_msat > 0)
}
/**
* Find wallet with sufficient balance for payment (synchronous fallback)
*/ */
getWalletWithBalance(requiredAmountSats?: number): any | null { getWalletWithBalance(requiredAmountSats?: number): any | null {
const wallets = this.userWallets const wallets = this.userWallets
@ -197,26 +138,14 @@ export class PaymentService extends BaseService {
requiredAmountSats?: number, requiredAmountSats?: number,
options: PaymentOptions = {} options: PaymentOptions = {}
): Promise<PaymentResult> { ): Promise<PaymentResult> {
// Check authentication using dual auth detection // Check authentication
const hasAuthService = this.authService?.isAuthenticated?.value && this.authService?.user?.value if (!this.authService?.isAuthenticated?.value || !this.authService?.user?.value) {
const globalAuth = await this.getGlobalAuth()
const hasGlobalAuth = globalAuth?.isAuthenticated?.value && globalAuth?.currentUser?.value
if (!hasAuthService && !hasGlobalAuth) {
this.debug('Payment failed - user not authenticated:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth
})
throw new Error('User must be authenticated to pay with wallet') throw new Error('User must be authenticated to pay with wallet')
} }
// Find suitable wallet using async version for proper dual auth detection // Find suitable wallet
const wallet = await this.getWalletWithBalanceAsync(requiredAmountSats) const wallet = this.getWalletWithBalance(requiredAmountSats)
if (!wallet) { if (!wallet) {
this.debug('No wallet with sufficient balance found:', {
requiredAmountSats,
walletsAvailable: (await this.getUserWalletsAsync()).length
})
throw new Error('No wallet with sufficient balance found') throw new Error('No wallet with sufficient balance found')
} }
@ -307,8 +236,8 @@ export class PaymentService extends BaseService {
return null return null
} }
// Try wallet payment first if user has balance (use async check for proper dual auth detection) // Try wallet payment first if user has balance
if (await this.hasWalletWithBalanceAsync()) { if (this.hasWalletWithBalance) {
try { try {
return await this.payWithWallet( return await this.payWithWallet(
paymentRequest, paymentRequest,
@ -319,8 +248,6 @@ export class PaymentService extends BaseService {
this.debug('Wallet payment failed, offering external wallet option:', error) this.debug('Wallet payment failed, offering external wallet option:', error)
// Don't throw here, continue to external wallet option // Don't throw here, continue to external wallet option
} }
} else {
this.debug('No wallet with balance available, skipping wallet payment')
} }
// Fallback to external wallet // Fallback to external wallet

View file

@ -1,8 +1,8 @@
// Auth service for LNbits integration // Auth service for LNbits integration
import { ref } from 'vue' import { ref, computed } from 'vue'
import { BaseService } from '@/core/base/BaseService' import { BaseService } from '@/core/base/BaseService'
import { eventBus } from '@/core/event-bus' import { eventBus } from '@/core/event-bus'
import { lnbitsAPI, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits' import { lnbitsAPI, type LoginCredentials, type RegisterData, type User } from '@/lib/api/lnbits'
export class AuthService extends BaseService { export class AuthService extends BaseService {
// Service metadata // Service metadata
@ -14,8 +14,23 @@ export class AuthService extends BaseService {
// Public state // Public state
public isAuthenticated = ref(false) public isAuthenticated = ref(false)
public user = ref<any>(null) public user = ref<User | null>(null)
public isLoading = ref(false) public isLoading = ref(false)
public error = ref<Error | null>(null)
// Computed properties for compatibility with global auth
public currentUser = computed(() => this.user.value)
public userDisplay = computed(() => {
if (!this.user.value) return null
return {
name: this.user.value.username || this.user.value.email || 'Anonymous',
username: this.user.value.username,
email: this.user.value.email,
id: this.user.value.id,
shortId: this.user.value.id.slice(0, 8) + '...' + this.user.value.id.slice(-8)
}
})
/** /**
* Service-specific initialization (called by BaseService) * Service-specific initialization (called by BaseService)
@ -104,10 +119,11 @@ export class AuthService extends BaseService {
} }
} }
logout(): void { async logout(): Promise<void> {
lnbitsAPI.logout() lnbitsAPI.logout()
this.user.value = null this.user.value = null
this.isAuthenticated.value = false this.isAuthenticated.value = false
this.error.value = null
eventBus.emit('auth:logout', {}, 'auth-service') eventBus.emit('auth:logout', {}, 'auth-service')
} }
@ -116,6 +132,37 @@ export class AuthService extends BaseService {
// Re-fetch user data from API // Re-fetch user data from API
await this.checkAuth() await this.checkAuth()
} }
async initialize(): Promise<void> {
// Alias for checkAuth for compatibility
await this.checkAuth()
}
async updatePassword(currentPassword: string, newPassword: string): Promise<void> {
try {
this.isLoading.value = true
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword)
this.user.value = updatedUser
} catch (error) {
const err = this.handleError(error, 'updatePassword')
throw err
} finally {
this.isLoading.value = false
}
}
async updateProfile(data: Partial<User>): Promise<void> {
try {
this.isLoading.value = true
const updatedUser = await lnbitsAPI.updateProfile(data)
this.user.value = updatedUser
} catch (error) {
const err = this.handleError(error, 'updateProfile')
throw err
} finally {
this.isLoading.value = false
}
}
/** /**
* Cleanup when service is disposed * Cleanup when service is disposed

View file

@ -5,8 +5,6 @@ import { nip04, finalizeEvent, type Event, type EventTemplate } from 'nostr-tool
import type { ChatMessage, ChatPeer, UnreadMessageData, ChatConfig } from '../types' import type { ChatMessage, ChatPeer, UnreadMessageData, ChatConfig } from '../types'
import { getAuthToken } from '@/lib/config/lnbits' import { getAuthToken } from '@/lib/config/lnbits'
import { config } from '@/lib/config' import { config } from '@/lib/config'
export class ChatService extends BaseService { export class ChatService extends BaseService {
// Service metadata // Service metadata
protected readonly metadata = { protected readonly metadata = {
@ -14,7 +12,6 @@ export class ChatService extends BaseService {
version: '1.0.0', version: '1.0.0',
dependencies: ['RelayHub', 'AuthService', 'VisibilityService', 'StorageService'] dependencies: ['RelayHub', 'AuthService', 'VisibilityService', 'StorageService']
} }
// Service-specific state // Service-specific state
private messages = ref<Map<string, ChatMessage[]>>(new Map()) private messages = ref<Map<string, ChatMessage[]>>(new Map())
private peers = ref<Map<string, ChatPeer>>(new Map()) private peers = ref<Map<string, ChatPeer>>(new Map())
@ -24,80 +21,59 @@ export class ChatService extends BaseService {
private visibilityUnsubscribe?: () => void private visibilityUnsubscribe?: () => void
private isFullyInitialized = false private isFullyInitialized = false
private authCheckInterval?: ReturnType<typeof setInterval> private authCheckInterval?: ReturnType<typeof setInterval>
constructor(config: ChatConfig) { constructor(config: ChatConfig) {
super() super()
this.config = config this.config = config
this.loadPeersFromStorage() this.loadPeersFromStorage()
} }
// Register market message handler for forwarding market-related DMs // Register market message handler for forwarding market-related DMs
setMarketMessageHandler(handler: (event: any) => Promise<void>) { setMarketMessageHandler(handler: (event: any) => Promise<void>) {
this.marketMessageHandler = handler this.marketMessageHandler = handler
} }
/** /**
* Service-specific initialization (called by BaseService) * Service-specific initialization (called by BaseService)
*/ */
protected async onInitialize(): Promise<void> { protected async onInitialize(): Promise<void> {
this.debug('Chat service onInitialize called') this.debug('Chat service onInitialize called')
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
this.debug('Auth detection:', { this.debug('Auth detection:', {
hasAuthService: !!hasAuthService, hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth, authServicePubkey: hasAuthService ? hasAuthService.substring(0, 10) : 'none',
authServicePubkey: hasAuthService ? hasAuthService.substring(0, 10) + '...' : null,
globalAuthPubkey: hasGlobalAuth ? hasGlobalAuth.substring(0, 10) + '...' : null
}) })
if (!this.authService?.user?.value) {
if (!hasAuthService && !hasGlobalAuth) {
this.debug('User not authenticated yet, deferring full initialization with periodic check') this.debug('User not authenticated yet, deferring full initialization with periodic check')
// Listen for auth events to complete initialization when user logs in // Listen for auth events to complete initialization when user logs in
const unsubscribe = eventBus.on('auth:login', async () => { const unsubscribe = eventBus.on('auth:login', async () => {
this.debug('Auth login detected, completing chat initialization...') this.debug('Auth login detected, completing chat initialization...')
unsubscribe() unsubscribe()
if (this.authCheckInterval) { if (this.authCheckInterval) {
clearInterval(this.authCheckInterval) clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined this.authCheckInterval = undefined
} }
// Re-inject dependencies and complete initialization // Re-inject dependencies and complete initialization
await this.waitForDependencies() await this.waitForDependencies()
await this.completeInitialization() await this.completeInitialization()
}) })
// Also check periodically in case we missed the auth event // Also check periodically in case we missed the auth event
this.authCheckInterval = setInterval(async () => { this.authCheckInterval = setInterval(async () => {
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey if (this.authService?.user?.value?.pubkey) {
const hasGlobalAuth = auth.currentUser.value?.pubkey
if (hasAuthService || hasGlobalAuth) {
this.debug('Auth detected via periodic check, completing initialization') this.debug('Auth detected via periodic check, completing initialization')
if (this.authCheckInterval) { if (this.authCheckInterval) {
clearInterval(this.authCheckInterval) clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined this.authCheckInterval = undefined
} }
unsubscribe() unsubscribe()
await this.waitForDependencies() await this.waitForDependencies()
await this.completeInitialization() await this.completeInitialization()
} }
}, 2000) // Check every 2 seconds }, 2000) // Check every 2 seconds
return return
} }
await this.completeInitialization() await this.completeInitialization()
} }
/** /**
* Complete the initialization once all dependencies are available * Complete the initialization once all dependencies are available
*/ */
@ -106,68 +82,52 @@ export class ChatService extends BaseService {
this.debug('Chat service already fully initialized, skipping') this.debug('Chat service already fully initialized, skipping')
return return
} }
this.debug('Completing chat service initialization...') this.debug('Completing chat service initialization...')
// Load peers from storage first // Load peers from storage first
this.loadPeersFromStorage() this.loadPeersFromStorage()
// Load peers from API // Load peers from API
await this.loadPeersFromAPI().catch(error => { await this.loadPeersFromAPI().catch(error => {
console.warn('Failed to load peers from API:', error) console.warn('Failed to load peers from API:', error)
}) })
// Initialize message handling (subscription + history loading) // Initialize message handling (subscription + history loading)
await this.initializeMessageHandling() await this.initializeMessageHandling()
// Register with visibility service // Register with visibility service
this.registerWithVisibilityService() this.registerWithVisibilityService()
this.isFullyInitialized = true this.isFullyInitialized = true
this.debug('Chat service fully initialized and ready!') this.debug('Chat service fully initialized and ready!')
} }
private isFullyInitialized = false
// Initialize message handling (subscription + history loading) // Initialize message handling (subscription + history loading)
async initializeMessageHandling(): Promise<void> { async initializeMessageHandling(): Promise<void> {
// Set up real-time subscription // Set up real-time subscription
await this.setupMessageSubscription() await this.setupMessageSubscription()
// Load message history for known peers // Load message history for known peers
await this.loadMessageHistory() await this.loadMessageHistory()
} }
// Computed properties // Computed properties
get allPeers() { get allPeers() {
return computed(() => Array.from(this.peers.value.values())) return computed(() => Array.from(this.peers.value.values()))
} }
get totalUnreadCount() { get totalUnreadCount() {
return computed(() => { return computed(() => {
return Array.from(this.peers.value.values()) return Array.from(this.peers.value.values())
.reduce((total, peer) => total + peer.unreadCount, 0) .reduce((total, peer) => total + peer.unreadCount, 0)
}) })
} }
get isReady() { get isReady() {
return this.isInitialized return this.isInitialized
} }
// Get messages for a specific peer // Get messages for a specific peer
getMessages(peerPubkey: string): ChatMessage[] { getMessages(peerPubkey: string): ChatMessage[] {
return this.messages.value.get(peerPubkey) || [] return this.messages.value.get(peerPubkey) || []
} }
// Get peer by pubkey // Get peer by pubkey
getPeer(pubkey: string): ChatPeer | undefined { getPeer(pubkey: string): ChatPeer | undefined {
return this.peers.value.get(pubkey) return this.peers.value.get(pubkey)
} }
// Add or update a peer // Add or update a peer
addPeer(pubkey: string, name?: string): ChatPeer { addPeer(pubkey: string, name?: string): ChatPeer {
let peer = this.peers.value.get(pubkey) let peer = this.peers.value.get(pubkey)
if (!peer) { if (!peer) {
peer = { peer = {
pubkey, pubkey,
@ -175,61 +135,48 @@ export class ChatService extends BaseService {
unreadCount: 0, unreadCount: 0,
lastSeen: Date.now() lastSeen: Date.now()
} }
this.peers.value.set(pubkey, peer) this.peers.value.set(pubkey, peer)
this.savePeersToStorage() this.savePeersToStorage()
eventBus.emit('chat:peer-added', { peer }, 'chat-service') eventBus.emit('chat:peer-added', { peer }, 'chat-service')
} else if (name && name !== peer.name) { } else if (name && name !== peer.name) {
peer.name = name peer.name = name
this.savePeersToStorage() this.savePeersToStorage()
} }
return peer return peer
} }
// Add a message // Add a message
addMessage(peerPubkey: string, message: ChatMessage): void { addMessage(peerPubkey: string, message: ChatMessage): void {
if (!this.messages.value.has(peerPubkey)) { if (!this.messages.value.has(peerPubkey)) {
this.messages.value.set(peerPubkey, []) this.messages.value.set(peerPubkey, [])
} }
const peerMessages = this.messages.value.get(peerPubkey)! const peerMessages = this.messages.value.get(peerPubkey)!
// Avoid duplicates // Avoid duplicates
if (!peerMessages.some(m => m.id === message.id)) { if (!peerMessages.some(m => m.id === message.id)) {
peerMessages.push(message) peerMessages.push(message)
// Sort by timestamp // Sort by timestamp
peerMessages.sort((a, b) => a.created_at - b.created_at) peerMessages.sort((a, b) => a.created_at - b.created_at)
// Limit message count // Limit message count
if (peerMessages.length > this.config.maxMessages) { if (peerMessages.length > this.config.maxMessages) {
peerMessages.splice(0, peerMessages.length - this.config.maxMessages) peerMessages.splice(0, peerMessages.length - this.config.maxMessages)
} }
// Update peer info // Update peer info
const peer = this.addPeer(peerPubkey) const peer = this.addPeer(peerPubkey)
peer.lastMessage = message peer.lastMessage = message
peer.lastSeen = Date.now() peer.lastSeen = Date.now()
// Update unread count if message is not sent by us // Update unread count if message is not sent by us
if (!message.sent) { if (!message.sent) {
this.updateUnreadCount(peerPubkey, message) this.updateUnreadCount(peerPubkey, message)
} }
// Emit events // Emit events
const eventType = message.sent ? 'chat:message-sent' : 'chat:message-received' const eventType = message.sent ? 'chat:message-sent' : 'chat:message-received'
eventBus.emit(eventType, { message, peerPubkey }, 'chat-service') eventBus.emit(eventType, { message, peerPubkey }, 'chat-service')
} }
} }
// Mark messages as read for a peer // Mark messages as read for a peer
markAsRead(peerPubkey: string): void { markAsRead(peerPubkey: string): void {
const peer = this.peers.value.get(peerPubkey) const peer = this.peers.value.get(peerPubkey)
if (peer && peer.unreadCount > 0) { if (peer && peer.unreadCount > 0) {
peer.unreadCount = 0 peer.unreadCount = 0
// Save unread state // Save unread state
const unreadData: UnreadMessageData = { const unreadData: UnreadMessageData = {
lastReadTimestamp: Date.now(), lastReadTimestamp: Date.now(),
@ -237,7 +184,6 @@ export class ChatService extends BaseService {
processedMessageIds: new Set() processedMessageIds: new Set()
} }
this.saveUnreadData(peerPubkey, unreadData) this.saveUnreadData(peerPubkey, unreadData)
eventBus.emit('chat:unread-count-changed', { eventBus.emit('chat:unread-count-changed', {
peerPubkey, peerPubkey,
count: 0, count: 0,
@ -245,61 +191,46 @@ export class ChatService extends BaseService {
}, 'chat-service') }, 'chat-service')
} }
} }
// Refresh peers from API // Refresh peers from API
async refreshPeers(): Promise<void> { async refreshPeers(): Promise<void> {
// Check if we should trigger full initialization // Check if we should trigger full initialization
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuth = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey const hasAuth = this.authService?.user?.value?.pubkey
if (!this.isFullyInitialized && hasAuth) { if (!this.isFullyInitialized && hasAuth) {
console.log('💬 Refresh peers triggered full initialization') console.log('💬 Refresh peers triggered full initialization')
await this.completeInitialization() await this.completeInitialization()
} }
return this.loadPeersFromAPI() return this.loadPeersFromAPI()
} }
// Check if services are available for messaging // Check if services are available for messaging
private async checkServicesAvailable(): Promise<{ relayHub: any; authService: any; userPubkey: string; userPrivkey: string } | null> { private async checkServicesAvailable(): Promise<{ relayHub: any; authService: any; userPubkey: string; userPrivkey: string } | null> {
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.prvkey const userPubkey = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.prvkey const userPrivkey = this.authService?.user?.value?.prvkey
if (!this.relayHub || (!this.authService?.user?.value)) {
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
return null return null
} }
if (!this.relayHub.isConnected) { if (!this.relayHub.isConnected) {
return null return null
} }
return { return {
relayHub: this.relayHub, relayHub: this.relayHub,
authService: this.authService || auth, authService: this.authService,
userPubkey: userPubkey!, userPubkey: userPubkey!,
userPrivkey: userPrivkey! userPrivkey: userPrivkey!
} }
} }
// Send a message // Send a message
async sendMessage(peerPubkey: string, content: string): Promise<void> { async sendMessage(peerPubkey: string, content: string): Promise<void> {
try { try {
const services = await this.checkServicesAvailable() const services = await this.checkServicesAvailable()
if (!services) { if (!services) {
throw new Error('Chat services not ready. Please wait for connection to establish.') throw new Error('Chat services not ready. Please wait for connection to establish.')
} }
const { relayHub, userPrivkey, userPubkey } = services const { relayHub, userPrivkey, userPubkey } = services
// Encrypt the message using NIP-04 // Encrypt the message using NIP-04
const encryptedContent = await nip04.encrypt(userPrivkey, peerPubkey, content) const encryptedContent = await nip04.encrypt(userPrivkey, peerPubkey, content)
// Create Nostr event for the encrypted message (kind 4 = encrypted direct message) // Create Nostr event for the encrypted message (kind 4 = encrypted direct message)
const eventTemplate: EventTemplate = { const eventTemplate: EventTemplate = {
kind: 4, kind: 4,
@ -307,10 +238,9 @@ export class ChatService extends BaseService {
tags: [['p', peerPubkey]], tags: [['p', peerPubkey]],
content: encryptedContent content: encryptedContent
} }
// Finalize the event with signature
// Finalize the event with signature const privkeyBytes = this.hexToUint8Array(userPrivkey)
const signedEvent = finalizeEvent(eventTemplate, userPrivkey) const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
// Create local message for immediate display // Create local message for immediate display
const message: ChatMessage = { const message: ChatMessage = {
id: signedEvent.id, id: signedEvent.id,
@ -319,36 +249,40 @@ export class ChatService extends BaseService {
sent: true, sent: true,
pubkey: userPubkey pubkey: userPubkey
} }
// Add to local messages immediately // Add to local messages immediately
this.addMessage(peerPubkey, message) this.addMessage(peerPubkey, message)
// Publish to Nostr relays // Publish to Nostr relays
const result = await relayHub.publishEvent(signedEvent) const result = await relayHub.publishEvent(signedEvent)
console.log('Message published to relays:', { success: result.success, total: result.total }) console.log('Message published to relays:', { success: result.success, total: result.total })
} catch (error) { } catch (error) {
console.error('Failed to send message:', error) console.error('Failed to send message:', error)
throw error throw error
} }
} }
// Private methods // Private methods
/**
* Convert hex string to Uint8Array (browser-compatible)
*/
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
}
private updateUnreadCount(peerPubkey: string, message: ChatMessage): void { private updateUnreadCount(peerPubkey: string, message: ChatMessage): void {
const unreadData = this.getUnreadData(peerPubkey) const unreadData = this.getUnreadData(peerPubkey)
if (!unreadData.processedMessageIds.has(message.id)) { if (!unreadData.processedMessageIds.has(message.id)) {
unreadData.processedMessageIds.add(message.id) unreadData.processedMessageIds.add(message.id)
unreadData.unreadCount++ unreadData.unreadCount++
const peer = this.peers.value.get(peerPubkey) const peer = this.peers.value.get(peerPubkey)
if (peer) { if (peer) {
peer.unreadCount = unreadData.unreadCount peer.unreadCount = unreadData.unreadCount
this.savePeersToStorage() this.savePeersToStorage()
} }
this.saveUnreadData(peerPubkey, unreadData) this.saveUnreadData(peerPubkey, unreadData)
eventBus.emit('chat:unread-count-changed', { eventBus.emit('chat:unread-count-changed', {
peerPubkey, peerPubkey,
count: unreadData.unreadCount, count: unreadData.unreadCount,
@ -356,20 +290,17 @@ export class ChatService extends BaseService {
}, 'chat-service') }, 'chat-service')
} }
} }
private getUnreadData(peerPubkey: string): UnreadMessageData { private getUnreadData(peerPubkey: string): UnreadMessageData {
const data = this.storageService.getUserData(`chat-unread-messages-${peerPubkey}`, { const data = this.storageService.getUserData(`chat-unread-messages-${peerPubkey}`, {
lastReadTimestamp: 0, lastReadTimestamp: 0,
unreadCount: 0, unreadCount: 0,
processedMessageIds: [] processedMessageIds: []
}) })
return { return {
...data, ...data,
processedMessageIds: new Set(data.processedMessageIds || []) processedMessageIds: new Set(data.processedMessageIds || [])
} }
} }
private saveUnreadData(peerPubkey: string, data: UnreadMessageData): void { private saveUnreadData(peerPubkey: string, data: UnreadMessageData): void {
const serializable = { const serializable = {
...data, ...data,
@ -377,7 +308,6 @@ export class ChatService extends BaseService {
} }
this.storageService.setUserData(`chat-unread-messages-${peerPubkey}`, serializable) this.storageService.setUserData(`chat-unread-messages-${peerPubkey}`, serializable)
} }
// Load peers from API // Load peers from API
async loadPeersFromAPI(): Promise<void> { async loadPeersFromAPI(): Promise<void> {
try { try {
@ -386,38 +316,31 @@ export class ChatService extends BaseService {
console.warn('💬 No authentication token found for loading peers from API') console.warn('💬 No authentication token found for loading peers from API')
throw new Error('No authentication token found') throw new Error('No authentication token found')
} }
const API_BASE_URL = config.api.baseUrl || 'http://localhost:5006' const API_BASE_URL = config.api.baseUrl || 'http://localhost:5006'
console.log('💬 Loading peers from API:', `${API_BASE_URL}/api/v1/auth/nostr/pubkeys`) console.log('💬 Loading peers from API:', `${API_BASE_URL}/api/v1/auth/nostr/pubkeys`)
const response = await fetch(`${API_BASE_URL}/api/v1/auth/nostr/pubkeys`, { const response = await fetch(`${API_BASE_URL}/api/v1/auth/nostr/pubkeys`, {
headers: { headers: {
'Authorization': `Bearer ${authToken}`, 'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) })
if (!response.ok) { if (!response.ok) {
const errorText = await response.text() const errorText = await response.text()
console.error('💬 API response error:', response.status, errorText) console.error('💬 API response error:', response.status, errorText)
throw new Error(`Failed to load peers: ${response.status} - ${errorText}`) throw new Error(`Failed to load peers: ${response.status} - ${errorText}`)
} }
const data = await response.json() const data = await response.json()
console.log('💬 API returned', data?.length || 0, 'peers') console.log('💬 API returned', data?.length || 0, 'peers')
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
console.warn('💬 Invalid API response format - expected array, got:', typeof data) console.warn('💬 Invalid API response format - expected array, got:', typeof data)
return return
} }
// Don't clear existing peers - merge instead // Don't clear existing peers - merge instead
data.forEach((peer: any) => { data.forEach((peer: any) => {
if (!peer.pubkey) { if (!peer.pubkey) {
console.warn('💬 Skipping peer without pubkey:', peer) console.warn('💬 Skipping peer without pubkey:', peer)
return return
} }
const chatPeer: ChatPeer = { const chatPeer: ChatPeer = {
pubkey: peer.pubkey, pubkey: peer.pubkey,
name: peer.username || `User ${peer.pubkey.slice(0, 8)}`, name: peer.username || `User ${peer.pubkey.slice(0, 8)}`,
@ -426,18 +349,14 @@ export class ChatService extends BaseService {
} }
this.peers.value.set(peer.pubkey, chatPeer) this.peers.value.set(peer.pubkey, chatPeer)
}) })
// Save to storage // Save to storage
this.savePeersToStorage() this.savePeersToStorage()
console.log(`✅ Loaded ${data.length} peers from API, total peers now: ${this.peers.value.size}`) console.log(`✅ Loaded ${data.length} peers from API, total peers now: ${this.peers.value.size}`)
} catch (error) { } catch (error) {
console.error('❌ Failed to load peers from API:', error) console.error('❌ Failed to load peers from API:', error)
// Don't re-throw - peers from storage are still available // Don't re-throw - peers from storage are still available
} }
} }
private loadPeersFromStorage(): void { private loadPeersFromStorage(): void {
// Skip loading peers in constructor as StorageService may not be available yet // Skip loading peers in constructor as StorageService may not be available yet
// This will be called later during initialization when dependencies are ready // This will be called later during initialization when dependencies are ready
@ -445,7 +364,6 @@ export class ChatService extends BaseService {
this.debug('Skipping peer loading from storage - not initialized or storage unavailable') this.debug('Skipping peer loading from storage - not initialized or storage unavailable')
return return
} }
try { try {
const peersArray = this.storageService.getUserData('chat-peers', []) as ChatPeer[] const peersArray = this.storageService.getUserData('chat-peers', []) as ChatPeer[]
console.log('💬 Loading', peersArray.length, 'peers from storage') console.log('💬 Loading', peersArray.length, 'peers from storage')
@ -456,41 +374,32 @@ export class ChatService extends BaseService {
console.warn('💬 Failed to load peers from storage:', error) console.warn('💬 Failed to load peers from storage:', error)
} }
} }
private savePeersToStorage(): void { private savePeersToStorage(): void {
const peersArray = Array.from(this.peers.value.values()) const peersArray = Array.from(this.peers.value.values())
this.storageService.setUserData('chat-peers', peersArray) this.storageService.setUserData('chat-peers', peersArray)
} }
// Load message history for known peers // Load message history for known peers
private async loadMessageHistory(): Promise<void> { private async loadMessageHistory(): Promise<void> {
try { try {
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey // const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey const userPubkey = this.authService?.user?.value?.pubkey
const userPrivkey = this.authService?.user?.value?.prvkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey if (!this.relayHub || (!this.authService?.user?.value)) {
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
console.warn('Cannot load message history: missing services') console.warn('Cannot load message history: missing services')
return return
} }
if (!userPubkey || !userPrivkey) { if (!userPubkey || !userPrivkey) {
console.warn('Cannot load message history: missing user keys') console.warn('Cannot load message history: missing user keys')
return return
} }
const peerPubkeys = Array.from(this.peers.value.keys()) const peerPubkeys = Array.from(this.peers.value.keys())
if (peerPubkeys.length === 0) { if (peerPubkeys.length === 0) {
console.log('No peers to load message history for') console.log('No peers to load message history for')
return return
} }
console.log('Loading message history for', peerPubkeys.length, 'peers') console.log('Loading message history for', peerPubkeys.length, 'peers')
// Query historical messages (kind 4) to/from known peers // Query historical messages (kind 4) to/from known peers
// We need separate queries for sent vs received messages due to different tagging // We need separate queries for sent vs received messages due to different tagging
const receivedEvents = await this.relayHub.queryEvents([ const receivedEvents = await this.relayHub.queryEvents([
@ -501,7 +410,6 @@ export class ChatService extends BaseService {
limit: 100 limit: 100
} }
]) ])
const sentEvents = await this.relayHub.queryEvents([ const sentEvents = await this.relayHub.queryEvents([
{ {
kinds: [4], kinds: [4],
@ -510,12 +418,9 @@ export class ChatService extends BaseService {
limit: 100 limit: 100
} }
]) ])
const events = [...receivedEvents, ...sentEvents] const events = [...receivedEvents, ...sentEvents]
.sort((a, b) => a.created_at - b.created_at) // Sort by timestamp .sort((a, b) => a.created_at - b.created_at) // Sort by timestamp
console.log('Found', events.length, 'historical messages:', receivedEvents.length, 'received,', sentEvents.length, 'sent') console.log('Found', events.length, 'historical messages:', receivedEvents.length, 'received,', sentEvents.length, 'sent')
// Process historical messages // Process historical messages
for (const event of events) { for (const event of events) {
try { try {
@ -523,12 +428,9 @@ export class ChatService extends BaseService {
const peerPubkey = isFromUs const peerPubkey = isFromUs
? event.tags.find((tag: string[]) => tag[0] === 'p')?.[1] // Get recipient from tag ? event.tags.find((tag: string[]) => tag[0] === 'p')?.[1] // Get recipient from tag
: event.pubkey // Sender is the peer : event.pubkey // Sender is the peer
if (!peerPubkey || peerPubkey === userPubkey) continue if (!peerPubkey || peerPubkey === userPubkey) continue
// Decrypt the message // Decrypt the message
const decryptedContent = await nip04.decrypt(userPrivkey, peerPubkey, event.content) const decryptedContent = await nip04.decrypt(userPrivkey, peerPubkey, event.content)
// Create a chat message // Create a chat message
const message: ChatMessage = { const message: ChatMessage = {
id: event.id, id: event.id,
@ -537,46 +439,36 @@ export class ChatService extends BaseService {
sent: isFromUs, sent: isFromUs,
pubkey: event.pubkey pubkey: event.pubkey
} }
// Add the message (will avoid duplicates) // Add the message (will avoid duplicates)
this.addMessage(peerPubkey, message) this.addMessage(peerPubkey, message)
} catch (error) { } catch (error) {
console.error('Failed to decrypt historical message:', error) console.error('Failed to decrypt historical message:', error)
} }
} }
console.log('Message history loaded successfully') console.log('Message history loaded successfully')
} catch (error) { } catch (error) {
console.error('Failed to load message history:', error) console.error('Failed to load message history:', error)
} }
} }
// Setup subscription for incoming messages // Setup subscription for incoming messages
private async setupMessageSubscription(): Promise<void> { private async setupMessageSubscription(): Promise<void> {
try { try {
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey // const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey const userPubkey = this.authService?.user?.value?.pubkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
this.debug('Setup message subscription auth check:', { this.debug('Setup message subscription auth check:', {
hasAuthService: !!hasAuthService, hasAuthService: !!this.authService?.user?.value,
hasGlobalAuth: !!hasGlobalAuth,
hasRelayHub: !!this.relayHub, hasRelayHub: !!this.relayHub,
relayHubConnected: this.relayHub?.isConnected, relayHubConnected: this.relayHub?.isConnected,
userPubkey: userPubkey ? userPubkey.substring(0, 10) + '...' : null userPubkey: userPubkey ? userPubkey.substring(0, 10) : 'none',
}) })
if (!this.relayHub || (!this.authService?.user?.value)) {
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
console.warn('💬 Cannot setup message subscription: missing services') console.warn('💬 Cannot setup message subscription: missing services')
// Retry after 2 seconds // Retry after 2 seconds
setTimeout(() => this.setupMessageSubscription(), 2000) setTimeout(() => this.setupMessageSubscription(), 2000)
return return
} }
if (!this.relayHub.isConnected) { if (!this.relayHub.isConnected) {
console.warn('💬 RelayHub not connected, waiting for connection...') console.warn('💬 RelayHub not connected, waiting for connection...')
// Listen for connection event // Listen for connection event
@ -588,13 +480,11 @@ export class ChatService extends BaseService {
setTimeout(() => this.setupMessageSubscription(), 5000) setTimeout(() => this.setupMessageSubscription(), 5000)
return return
} }
if (!userPubkey) { if (!userPubkey) {
console.warn('💬 No user pubkey available for subscription') console.warn('💬 No user pubkey available for subscription')
setTimeout(() => this.setupMessageSubscription(), 2000) setTimeout(() => this.setupMessageSubscription(), 2000)
return return
} }
// Subscribe to encrypted direct messages (kind 4) addressed to this user // Subscribe to encrypted direct messages (kind 4) addressed to this user
this.subscriptionUnsubscriber = this.relayHub.subscribe({ this.subscriptionUnsubscriber = this.relayHub.subscribe({
id: 'chat-messages', id: 'chat-messages',
@ -609,23 +499,19 @@ export class ChatService extends BaseService {
if (event.pubkey === userPubkey) { if (event.pubkey === userPubkey) {
return return
} }
await this.processIncomingMessage(event) await this.processIncomingMessage(event)
}, },
onEose: () => { onEose: () => {
console.log('💬 Chat message subscription EOSE received') console.log('💬 Chat message subscription EOSE received')
} }
}) })
console.log('💬 Chat message subscription set up successfully for pubkey:', userPubkey.substring(0, 10) + '...') console.log('💬 Chat message subscription set up successfully for pubkey:', userPubkey.substring(0, 10) + '...')
} catch (error) { } catch (error) {
console.error('💬 Failed to setup message subscription:', error) console.error('💬 Failed to setup message subscription:', error)
// Retry after delay // Retry after delay
setTimeout(() => this.setupMessageSubscription(), 3000) setTimeout(() => this.setupMessageSubscription(), 3000)
} }
} }
/** /**
* Register with VisibilityService for connection management * Register with VisibilityService for connection management
*/ */
@ -634,42 +520,34 @@ export class ChatService extends BaseService {
this.debug('VisibilityService not available') this.debug('VisibilityService not available')
return return
} }
this.visibilityUnsubscribe = this.visibilityService.registerService( this.visibilityUnsubscribe = this.visibilityService.registerService(
this.metadata.name, this.metadata.name,
async () => this.handleAppResume(), async () => this.handleAppResume(),
async () => this.handleAppPause() async () => this.handleAppPause()
) )
this.debug('Registered with VisibilityService') this.debug('Registered with VisibilityService')
} }
/** /**
* Handle app resuming from visibility change * Handle app resuming from visibility change
*/ */
private async handleAppResume(): Promise<void> { private async handleAppResume(): Promise<void> {
this.debug('App resumed - checking chat connections') this.debug('App resumed - checking chat connections')
// Check if subscription is still active // Check if subscription is still active
if (!this.subscriptionUnsubscriber) { if (!this.subscriptionUnsubscriber) {
this.debug('Chat subscription lost, re-establishing...') this.debug('Chat subscription lost, re-establishing...')
this.setupMessageSubscription() this.setupMessageSubscription()
} }
// Check if we need to sync missed messages // Check if we need to sync missed messages
await this.syncMissedMessages() await this.syncMissedMessages()
} }
/** /**
* Handle app pausing from visibility change * Handle app pausing from visibility change
*/ */
private async handleAppPause(): Promise<void> { private async handleAppPause(): Promise<void> {
this.debug('App paused - chat subscription will be maintained for quick resume') this.debug('App paused - chat subscription will be maintained for quick resume')
// Don't immediately unsubscribe - let RelayHub handle connection management // Don't immediately unsubscribe - let RelayHub handle connection management
// Subscriptions will be restored automatically on resume if needed // Subscriptions will be restored automatically on resume if needed
} }
/** /**
* Sync any messages that might have been missed while app was hidden * Sync any messages that might have been missed while app was hidden
*/ */
@ -678,39 +556,30 @@ export class ChatService extends BaseService {
// For each peer, try to load recent messages // For each peer, try to load recent messages
const peers = Array.from(this.peers.value.values()) const peers = Array.from(this.peers.value.values())
const syncPromises = peers.map(peer => this.loadRecentMessagesForPeer(peer.pubkey)) const syncPromises = peers.map(peer => this.loadRecentMessagesForPeer(peer.pubkey))
await Promise.allSettled(syncPromises) await Promise.allSettled(syncPromises)
this.debug('Missed messages sync completed') this.debug('Missed messages sync completed')
} catch (error) { } catch (error) {
console.warn('Failed to sync missed messages:', error) console.warn('Failed to sync missed messages:', error)
} }
} }
/** /**
* Process an incoming message event * Process an incoming message event
*/ */
private async processIncomingMessage(event: any): Promise<void> { private async processIncomingMessage(event: any): Promise<void> {
try { try {
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey // const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey const userPubkey = this.authService?.user?.value?.pubkey
const userPrivkey = this.authService?.user?.value?.prvkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!userPubkey || !userPrivkey) { if (!userPubkey || !userPrivkey) {
console.warn('Cannot process message: user not authenticated') console.warn('Cannot process message: user not authenticated')
return return
} }
// Get sender pubkey from event // Get sender pubkey from event
const senderPubkey = event.pubkey const senderPubkey = event.pubkey
// Decrypt the message content // Decrypt the message content
const decryptedContent = await nip04.decrypt(userPrivkey, senderPubkey, event.content) const decryptedContent = await nip04.decrypt(userPrivkey, senderPubkey, event.content)
// Check if this is a market-related message // Check if this is a market-related message
let isMarketMessage = false let isMarketMessage = false
try { try {
@ -718,7 +587,6 @@ export class ChatService extends BaseService {
if (parsedContent.type === 1 || parsedContent.type === 2) { if (parsedContent.type === 1 || parsedContent.type === 2) {
// This is a market order message // This is a market order message
isMarketMessage = true isMarketMessage = true
// Forward to market handler // Forward to market handler
if (this.marketMessageHandler) { if (this.marketMessageHandler) {
await this.marketMessageHandler(event) await this.marketMessageHandler(event)
@ -730,12 +598,10 @@ export class ChatService extends BaseService {
} catch (e) { } catch (e) {
// Not JSON or not a market message, treat as regular chat // Not JSON or not a market message, treat as regular chat
} }
// Process as chat message regardless (market messages should also appear in chat) // Process as chat message regardless (market messages should also appear in chat)
{ {
// Format the content for display based on whether it's a market message // Format the content for display based on whether it's a market message
let displayContent = decryptedContent let displayContent = decryptedContent
if (isMarketMessage) { if (isMarketMessage) {
try { try {
const parsedContent = JSON.parse(decryptedContent) const parsedContent = JSON.parse(decryptedContent)
@ -749,14 +615,12 @@ export class ChatService extends BaseService {
else if (parsedContent.paid === false) status.push('⏳ Payment Pending') else if (parsedContent.paid === false) status.push('⏳ Payment Pending')
if (parsedContent.shipped === true) status.push('📦 Shipped') if (parsedContent.shipped === true) status.push('📦 Shipped')
else if (parsedContent.shipped === false) status.push('🔄 Processing') else if (parsedContent.shipped === false) status.push('🔄 Processing')
displayContent = `📋 Order Update: ${parsedContent.id}\n${status.join(' | ')}\n${parsedContent.message || ''}` displayContent = `📋 Order Update: ${parsedContent.id}\n${status.join(' | ')}\n${parsedContent.message || ''}`
} }
} catch (e) { } catch (e) {
// Fallback to raw content if parsing fails // Fallback to raw content if parsing fails
} }
} }
// Create a chat message // Create a chat message
const message: ChatMessage = { const message: ChatMessage = {
id: event.id, id: event.id,
@ -765,37 +629,27 @@ export class ChatService extends BaseService {
sent: false, sent: false,
pubkey: senderPubkey pubkey: senderPubkey
} }
// Ensure we have a peer record for the sender // Ensure we have a peer record for the sender
this.addPeer(senderPubkey) this.addPeer(senderPubkey)
// Add the message // Add the message
this.addMessage(senderPubkey, message) this.addMessage(senderPubkey, message)
console.log('Received encrypted chat message from:', senderPubkey.slice(0, 8)) console.log('Received encrypted chat message from:', senderPubkey.slice(0, 8))
} }
} catch (error) { } catch (error) {
console.error('Failed to process incoming message:', error) console.error('Failed to process incoming message:', error)
} }
} }
/** /**
* Load recent messages for a specific peer * Load recent messages for a specific peer
*/ */
private async loadRecentMessagesForPeer(peerPubkey: string): Promise<void> { private async loadRecentMessagesForPeer(peerPubkey: string): Promise<void> {
// Check both injected auth service AND global auth composable // Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth') // Removed dual auth import
const hasAuthService = this.authService?.user?.value?.pubkey const userPubkey = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
if (!userPubkey || !this.relayHub) return if (!userPubkey || !this.relayHub) return
try { try {
// Get last 10 messages from the last hour for this peer // Get last 10 messages from the last hour for this peer
const oneHourAgo = Math.floor(Date.now() / 1000) - 3600 const oneHourAgo = Math.floor(Date.now() / 1000) - 3600
const events = await this.relayHub.queryEvents([ const events = await this.relayHub.queryEvents([
{ {
kinds: [4], // Encrypted DMs kinds: [4], // Encrypted DMs
@ -812,17 +666,14 @@ export class ChatService extends BaseService {
limit: 10 limit: 10
} }
]) ])
// Process any new messages // Process any new messages
for (const event of events) { for (const event of events) {
await this.processIncomingMessage(event) await this.processIncomingMessage(event)
} }
} catch (error) { } catch (error) {
this.debug(`Failed to load recent messages for peer ${peerPubkey.slice(0, 8)}:`, error) this.debug(`Failed to load recent messages for peer ${peerPubkey.slice(0, 8)}:`, error)
} }
} }
/** /**
* Cleanup when service is disposed (overrides BaseService) * Cleanup when service is disposed (overrides BaseService)
*/ */
@ -832,26 +683,21 @@ export class ChatService extends BaseService {
clearInterval(this.authCheckInterval) clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined this.authCheckInterval = undefined
} }
// Unregister from visibility service // Unregister from visibility service
if (this.visibilityUnsubscribe) { if (this.visibilityUnsubscribe) {
this.visibilityUnsubscribe() this.visibilityUnsubscribe()
this.visibilityUnsubscribe = undefined this.visibilityUnsubscribe = undefined
} }
// Unsubscribe from message subscription // Unsubscribe from message subscription
if (this.subscriptionUnsubscriber) { if (this.subscriptionUnsubscriber) {
this.subscriptionUnsubscriber() this.subscriptionUnsubscriber()
this.subscriptionUnsubscriber = undefined this.subscriptionUnsubscriber = undefined
} }
this.messages.value.clear() this.messages.value.clear()
this.peers.value.clear() this.peers.value.clear()
this.isFullyInitialized = false this.isFullyInitialized = false
this.debug('Chat service disposed') this.debug('Chat service disposed')
} }
/** /**
* Legacy destroy method for backward compatibility * Legacy destroy method for backward compatibility
*/ */

View file

@ -1,11 +1,11 @@
<!-- eslint-disable vue/multi-word-component-names --> <!-- eslint-disable vue/multi-word-component-names -->
<script setup lang="ts"> <script setup lang="ts">
import { onUnmounted, watch } from 'vue' import { onUnmounted } from 'vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { useTicketPurchase } from '../composables/useTicketPurchase' import { useTicketPurchase } from '../composables/useTicketPurchase'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { User, Wallet, CreditCard, Zap, Ticket } from 'lucide-vue-next' import { User, Wallet, CreditCard, Zap, Ticket } from 'lucide-vue-next'
import { formatEventPrice, formatWalletBalance } from '@/lib/utils/formatting' import { formatEventPrice, formatWalletBalance } from '@/lib/utils/formatting'
@ -44,8 +44,7 @@ const {
cleanup, cleanup,
ticketQRCode, ticketQRCode,
purchasedTicketId, purchasedTicketId,
showTicketQR, showTicketQR
loadWallets
} = useTicketPurchase() } = useTicketPurchase()
async function handlePurchase() { async function handlePurchase() {
@ -63,13 +62,6 @@ function handleClose() {
resetPaymentState() resetPaymentState()
} }
// Reload wallets when dialog opens
watch(() => props.isOpen, async (newVal) => {
if (newVal && isAuthenticated.value) {
await loadWallets()
}
})
// Cleanup on unmount // Cleanup on unmount
onUnmounted(() => { onUnmounted(() => {
cleanup() cleanup()

View file

@ -1,6 +1,6 @@
import { ref, computed, onUnmounted, onMounted } from 'vue' import { ref, computed, onUnmounted } from 'vue'
import { purchaseTicket, checkPaymentStatus } from '@/lib/api/events' import { purchaseTicket, checkPaymentStatus } from '@/lib/api/events'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { useAsyncOperation } from '@/core/composables/useAsyncOperation' import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
@ -24,9 +24,6 @@ export function useTicketPurchase() {
const purchasedTicketId = ref<string | null>(null) const purchasedTicketId = ref<string | null>(null)
const showTicketQR = ref(false) const showTicketQR = ref(false)
// Wallet state (loaded asynchronously)
const userWallets = ref<any[]>([])
const hasWalletWithBalance = ref(false)
// Computed properties // Computed properties
const canPurchase = computed(() => isAuthenticated.value && currentUser.value) const canPurchase = computed(() => isAuthenticated.value && currentUser.value)
@ -38,30 +35,10 @@ export function useTicketPurchase() {
} }
}) })
// Delegate to PaymentService for processing status // Delegate to PaymentService
const isPayingWithWallet = computed(() => paymentService.isProcessingPayment.value) // computed ref const userWallets = computed(() => paymentService.userWallets)
const hasWalletWithBalance = computed(() => paymentService.hasWalletWithBalance)
// Load wallets asynchronously const isPayingWithWallet = computed(() => paymentService.isProcessingPayment.value)
async function loadWallets() {
try {
const wallets = await paymentService.getUserWalletsAsync()
userWallets.value = wallets
hasWalletWithBalance.value = await paymentService.hasWalletWithBalanceAsync()
console.log('Loaded wallets for ticket purchase:', {
count: wallets.length,
hasBalance: hasWalletWithBalance.value
})
} catch (error) {
console.error('Failed to load wallets:', error)
userWallets.value = []
hasWalletWithBalance.value = false
}
}
// Load wallets on mount
onMounted(() => {
loadWallets()
})
// Generate QR code for Lightning payment - delegate to PaymentService // Generate QR code for Lightning payment - delegate to PaymentService
async function generateQRCode(bolt11: string) { async function generateQRCode(bolt11: string) {
@ -241,7 +218,6 @@ export function useTicketPurchase() {
handleOpenLightningWallet, handleOpenLightningWallet,
resetPaymentState, resetPaymentState,
cleanup, cleanup,
generateTicketQRCode, generateTicketQRCode
loadWallets
} }
} }

View file

@ -2,7 +2,7 @@ import { computed } from 'vue'
import { useAsyncState } from '@vueuse/core' import { useAsyncState } from '@vueuse/core'
import type { Ticket } from '@/lib/types/event' import type { Ticket } from '@/lib/types/event'
import { fetchUserTickets } from '@/lib/api/events' import { fetchUserTickets } from '@/lib/api/events'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
interface GroupedTickets { interface GroupedTickets {
eventId: string eventId: string

View file

@ -3,7 +3,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useModuleReady } from '@/composables/useModuleReady' import { useModuleReady } from '@/composables/useModuleReady'
import { useEvents } from '../composables/useEvents' import { useEvents } from '../composables/useEvents'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'

View file

@ -2,7 +2,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { useUserTickets } from '../composables/useUserTickets' import { useUserTickets } from '../composables/useUserTickets'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'

View file

@ -218,7 +218,7 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useMarketStore } from '../stores/market' import { useMarketStore } from '../stores/market'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { useMarket } from '../composables/useMarket' import { useMarket } from '../composables/useMarket'
// import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module // import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'

View file

@ -321,7 +321,7 @@ import {
} from 'lucide-vue-next' } from 'lucide-vue-next'
import type { OrderStatus } from '@/stores/market' import type { OrderStatus } from '@/stores/market'
import { nostrmarketService } from '../services/nostrmarketService' import { nostrmarketService } from '../services/nostrmarketService'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
const router = useRouter() const router = useRouter()
const marketStore = useMarketStore() const marketStore = useMarketStore()

View file

@ -247,7 +247,7 @@ import { useRouter } from 'vue-router'
import { useMarketStore } from '../stores/market' import { useMarketStore } from '../stores/market'
// import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module // import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { useLightningPayment } from '../composables/useLightningPayment' import { useLightningPayment } from '../composables/useLightningPayment'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'

View file

@ -98,6 +98,7 @@
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import QRCode from 'qrcode' import QRCode from 'qrcode'
import { useAuth } from '@/composables/useAuthService'
import type { NostrmarketPaymentRequest } from '../services/nostrmarketService' import type { NostrmarketPaymentRequest } from '../services/nostrmarketService'
import { import {
Dialog, Dialog,
@ -125,6 +126,9 @@ const emit = defineEmits<{
'payment-completed': [orderId: string] 'payment-completed': [orderId: string]
}>() }>()
// Auth
const auth = useAuth()
// Computed // Computed
const lightningInvoice = computed(() => { const lightningInvoice = computed(() => {
if (!props.paymentRequest) return null if (!props.paymentRequest) return null
@ -251,10 +255,6 @@ const payWithWallet = async () => {
// Import the payment API // Import the payment API
const { payInvoiceWithWallet } = await import('@/lib/api/events') const { payInvoiceWithWallet } = await import('@/lib/api/events')
// Get the current user's wallet info
const { useAuth } = await import('@/composables/useAuth')
const auth = useAuth()
if (!auth.currentUser.value?.wallets?.[0]?.id || !auth.currentUser.value?.wallets?.[0]?.adminkey) { if (!auth.currentUser.value?.wallets?.[0]?.id || !auth.currentUser.value?.wallets?.[0]?.adminkey) {
toast.error('Please connect your wallet to pay') toast.error('Please connect your wallet to pay')
return return

View file

@ -5,7 +5,7 @@ import { config } from '@/lib/config'
import { nostrmarketService } from '../services/nostrmarketService' import { nostrmarketService } from '../services/nostrmarketService'
import { nip04 } from 'nostr-tools' import { nip04 } from 'nostr-tools'
import { useAsyncOperation } from '@/core/composables/useAsyncOperation' import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
// Nostr event kinds for market functionality // Nostr event kinds for market functionality
const MARKET_EVENT_KINDS = { const MARKET_EVENT_KINDS = {

View file

@ -1,5 +1,5 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { useMarketStore } from '../stores/market' import { useMarketStore } from '../stores/market'
// Simplified bolt11 parser to extract payment hash // Simplified bolt11 parser to extract payment hash

View file

@ -1,7 +1,7 @@
import { finalizeEvent, type EventTemplate, nip04 } from 'nostr-tools' import { finalizeEvent, type EventTemplate, nip04 } from 'nostr-tools'
import { BaseService } from '@/core/base/BaseService' import { BaseService } from '@/core/base/BaseService'
import type { Stall, Product, Order } from '@/stores/market' import type { Stall, Product, Order } from '@/stores/market'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
export interface NostrmarketStall { export interface NostrmarketStall {
id: string id: string

View file

@ -3,7 +3,7 @@ import { ref, computed, readonly, watch } from 'vue'
import { invoiceService } from '@/core/services/invoiceService' import { invoiceService } from '@/core/services/invoiceService'
import { paymentMonitor } from '../services/paymentMonitor' import { paymentMonitor } from '../services/paymentMonitor'
import { nostrmarketService } from '../services/nostrmarketService' import { nostrmarketService } from '../services/nostrmarketService'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuthService'
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { LightningInvoice } from '@/core/services/invoiceService' import type { LightningInvoice } from '@/core/services/invoiceService'

View file

@ -272,7 +272,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useMarketStore } from '@/stores/market' import { useMarketStore } from '@/stores/market'
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { import {
Card, Card,
CardHeader, CardHeader,

View file

@ -187,7 +187,7 @@ import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { auth } from '@/composables/useAuth' import { auth } from '@/composables/useAuthService'
import { useDemoAccountGenerator } from '@/composables/useDemoAccountGenerator' import { useDemoAccountGenerator } from '@/composables/useDemoAccountGenerator'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'