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:
parent
5633aa154b
commit
4feb5459cc
27 changed files with 210 additions and 518 deletions
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
72
src/composables/useAuthService.ts
Normal file
72
src/composables/useAuthService.ts
Normal 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()
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue