lots of login dialog and nostr subsription relate updates
This commit is contained in:
parent
ed1b4cb22a
commit
d694f9b645
8 changed files with 323 additions and 189 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { NostrEvent, NostrProfile, NostrAccount, DirectMessage } from '../types/nostr'
|
||||
import { isValidPrivateKey, formatPrivateKey } from '@/lib/nostr'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -41,12 +42,21 @@ const DEFAULT_RELAYS = [
|
|||
const SUPPORT_NPUB = import.meta.env.VITE_SUPPORT_NPUB
|
||||
|
||||
// Helper functions
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number = 10000): Promise<T> {
|
||||
return Promise.race([
|
||||
promise,
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
async function connectToRelay(url: string) {
|
||||
console.log(`Attempting to connect to relay: ${url}`)
|
||||
const relay = window.NostrTools.relayInit(url)
|
||||
try {
|
||||
console.log(`Initializing connection to ${url}...`)
|
||||
await relay.connect()
|
||||
await withTimeout(relay.connect())
|
||||
console.log(`Successfully connected to ${url}`)
|
||||
return relay
|
||||
} catch (err) {
|
||||
|
|
@ -114,46 +124,88 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
|
||||
// Initialize connection if account exists
|
||||
async function init() {
|
||||
if (account.value) {
|
||||
if (!account.value) return
|
||||
|
||||
try {
|
||||
// Clear existing state
|
||||
messages.value.clear()
|
||||
profiles.value.clear()
|
||||
processedMessageIds.value.clear()
|
||||
|
||||
// Close existing connections
|
||||
relayPool.value.forEach(relay => relay.close())
|
||||
relayPool.value.forEach(relay => {
|
||||
try {
|
||||
relay.close()
|
||||
} catch (err) {
|
||||
console.error('Error closing relay:', err)
|
||||
}
|
||||
})
|
||||
relayPool.value = []
|
||||
|
||||
// Connect to relays
|
||||
const connectedRelays = await Promise.all(
|
||||
account.value.relays.map(relay => connectToRelay(relay.url))
|
||||
)
|
||||
|
||||
relayPool.value = connectedRelays.filter(relay => relay !== null)
|
||||
// Connect to relays with timeout
|
||||
const connectionPromises = account.value.relays.map(async relay => {
|
||||
try {
|
||||
return await withTimeout(connectToRelay(relay.url), 5000)
|
||||
} catch (err) {
|
||||
console.error(`Timeout connecting to ${relay.url}:`, err)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// Subscribe to messages and load history
|
||||
await Promise.all([
|
||||
subscribeToMessages(),
|
||||
loadProfiles()
|
||||
])
|
||||
const connectedRelays = await Promise.all(connectionPromises)
|
||||
relayPool.value = connectedRelays.filter((relay): relay is NonNullable<typeof relay> => relay !== null)
|
||||
|
||||
// Set active chat to support agent
|
||||
if (relayPool.value.length === 0) {
|
||||
throw new Error('Failed to connect to any relays')
|
||||
}
|
||||
|
||||
// Set active chat to support agent first
|
||||
activeChat.value = SUPPORT_NPUB
|
||||
|
||||
// Subscribe to messages with shorter timeout
|
||||
try {
|
||||
await withTimeout(subscribeToMessages(), 10000)
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe to messages:', err)
|
||||
// Continue even if subscription fails
|
||||
}
|
||||
|
||||
// Load profiles with shorter timeout
|
||||
try {
|
||||
await withTimeout(loadProfiles(), 5000)
|
||||
} catch (err) {
|
||||
console.error('Failed to load profiles:', err)
|
||||
// Continue even if profile loading fails
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize:', err)
|
||||
throw new Error('Failed to connect to the network. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
async function login(privkey: string) {
|
||||
const pubkey = window.NostrTools.getPublicKey(privkey)
|
||||
if (!isValidPrivateKey(privkey)) {
|
||||
throw new Error('Invalid private key')
|
||||
}
|
||||
|
||||
const formattedKey = formatPrivateKey(privkey)
|
||||
const pubkey = window.NostrTools.getPublicKey(formattedKey)
|
||||
|
||||
account.value = {
|
||||
pubkey,
|
||||
privkey,
|
||||
privkey: formattedKey,
|
||||
relays: DEFAULT_RELAYS.map(url => ({ url, read: true, write: true }))
|
||||
}
|
||||
|
||||
// Initialize connection and load messages
|
||||
await init()
|
||||
try {
|
||||
// Initialize connection with a global timeout
|
||||
await withTimeout(init(), 30000) // 30 second total timeout for the entire login process
|
||||
} catch (err) {
|
||||
// If initialization fails, clear the account
|
||||
account.value = null
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProfiles() {
|
||||
|
|
@ -254,125 +306,168 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
unsubscribeFromMessages()
|
||||
}
|
||||
|
||||
// Filter for received messages with history
|
||||
const receivedFilter = {
|
||||
kinds: [4],
|
||||
'#p': [account.value.pubkey],
|
||||
since: 0 // Get all historical messages
|
||||
}
|
||||
let hasReceivedMessage = false
|
||||
const subscriptionTimeout = setTimeout(() => {
|
||||
if (!hasReceivedMessage) {
|
||||
console.log('No messages received, considering subscription successful')
|
||||
hasReceivedMessage = true
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
// Filter for sent messages with history
|
||||
const sentFilter = {
|
||||
kinds: [4],
|
||||
authors: [account.value.pubkey],
|
||||
since: 0 // Get all historical messages
|
||||
}
|
||||
try {
|
||||
const subscribeToRelay = (relay: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let subs: any[] = []
|
||||
let resolved = false
|
||||
|
||||
const subscribeToRelay = (relay: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let eoseCount = 0
|
||||
// Set a timeout for the entire subscription
|
||||
const timeout = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
console.log('Subscription timeout, but continuing...')
|
||||
resolved = true
|
||||
resolve(true)
|
||||
}
|
||||
}, 8000)
|
||||
|
||||
// Subscribe to received messages
|
||||
const receivedSub = relay.sub([receivedFilter])
|
||||
|
||||
receivedSub.on('event', async (event: NostrEvent) => {
|
||||
try {
|
||||
// Skip if we've already processed this message
|
||||
if (processedMessageIds.value.has(event.id)) {
|
||||
return
|
||||
// Filter for received messages with history
|
||||
const receivedFilter = {
|
||||
kinds: [4],
|
||||
'#p': [account.value!.pubkey],
|
||||
since: 0
|
||||
}
|
||||
|
||||
const decrypted = await window.NostrTools.nip04.decrypt(
|
||||
account.value!.privkey,
|
||||
event.pubkey,
|
||||
event.content
|
||||
)
|
||||
|
||||
const dm: DirectMessage = {
|
||||
id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
content: decrypted,
|
||||
created_at: event.created_at,
|
||||
sent: false
|
||||
// Filter for sent messages with history
|
||||
const sentFilter = {
|
||||
kinds: [4],
|
||||
authors: [account.value!.pubkey],
|
||||
since: 0
|
||||
}
|
||||
|
||||
await addMessage(event.pubkey, dm)
|
||||
// Subscribe to received messages
|
||||
const receivedSub = relay.sub([receivedFilter])
|
||||
subs.push(receivedSub)
|
||||
|
||||
receivedSub.on('event', async (event: NostrEvent) => {
|
||||
hasReceivedMessage = true
|
||||
try {
|
||||
if (processedMessageIds.value.has(event.id)) return
|
||||
|
||||
const decrypted = await window.NostrTools.nip04.decrypt(
|
||||
account.value!.privkey,
|
||||
event.pubkey,
|
||||
event.content
|
||||
)
|
||||
|
||||
// Load profile if not already loaded
|
||||
if (!profiles.value.has(event.pubkey)) {
|
||||
await loadProfiles()
|
||||
const dm: DirectMessage = {
|
||||
id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
content: decrypted,
|
||||
created_at: event.created_at,
|
||||
sent: false
|
||||
}
|
||||
|
||||
await addMessage(event.pubkey, dm)
|
||||
processedMessageIds.value.add(event.id)
|
||||
|
||||
if (!profiles.value.has(event.pubkey)) {
|
||||
await loadProfiles()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to decrypt received message:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// Subscribe to sent messages
|
||||
const sentSub = relay.sub([sentFilter])
|
||||
subs.push(sentSub)
|
||||
|
||||
sentSub.on('event', async (event: NostrEvent) => {
|
||||
hasReceivedMessage = true
|
||||
try {
|
||||
if (processedMessageIds.value.has(event.id)) return
|
||||
|
||||
const targetPubkey = event.tags.find(tag => tag[0] === 'p')?.[1]
|
||||
if (!targetPubkey) return
|
||||
|
||||
const decrypted = await window.NostrTools.nip04.decrypt(
|
||||
account.value!.privkey,
|
||||
targetPubkey,
|
||||
event.content
|
||||
)
|
||||
|
||||
const dm: DirectMessage = {
|
||||
id: event.id,
|
||||
pubkey: targetPubkey,
|
||||
content: decrypted,
|
||||
created_at: event.created_at,
|
||||
sent: true
|
||||
}
|
||||
|
||||
await addMessage(targetPubkey, dm)
|
||||
processedMessageIds.value.add(event.id)
|
||||
} catch (err) {
|
||||
console.error('Failed to decrypt sent message:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// Store subscriptions for cleanup
|
||||
currentSubscription.value = {
|
||||
unsub: () => {
|
||||
clearTimeout(timeout)
|
||||
subs.forEach(sub => {
|
||||
try {
|
||||
if (sub && typeof sub.unsub === 'function') {
|
||||
sub.unsub()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to unsubscribe:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Consider subscription successful immediately
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to decrypt received message:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// Subscribe to sent messages
|
||||
const sentSub = relay.sub([sentFilter])
|
||||
|
||||
sentSub.on('event', async (event: NostrEvent) => {
|
||||
try {
|
||||
// Skip if we've already processed this message
|
||||
if (processedMessageIds.value.has(event.id)) {
|
||||
return
|
||||
console.error('Error in subscription:', err)
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
// Find the target pubkey from the p tag
|
||||
const targetPubkey = event.tags.find(tag => tag[0] === 'p')?.[1]
|
||||
if (!targetPubkey) return
|
||||
|
||||
const decrypted = await window.NostrTools.nip04.decrypt(
|
||||
account.value!.privkey,
|
||||
targetPubkey,
|
||||
event.content
|
||||
)
|
||||
|
||||
const dm: DirectMessage = {
|
||||
id: event.id,
|
||||
pubkey: targetPubkey,
|
||||
content: decrypted,
|
||||
created_at: event.created_at,
|
||||
sent: true
|
||||
}
|
||||
|
||||
await addMessage(targetPubkey, dm)
|
||||
} catch (err) {
|
||||
console.error('Failed to decrypt sent message:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Listen for end of stored events
|
||||
receivedSub.on('eose', () => {
|
||||
eoseCount++
|
||||
if (eoseCount >= 2) { // Both subscriptions have finished
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
// Wait for relays
|
||||
await Promise.all(
|
||||
relayPool.value.map(relay =>
|
||||
withTimeout(subscribeToRelay(relay), 10000)
|
||||
.catch(() => true)
|
||||
)
|
||||
)
|
||||
|
||||
sentSub.on('eose', () => {
|
||||
eoseCount++
|
||||
if (eoseCount >= 2) { // Both subscriptions have finished
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
clearTimeout(subscriptionTimeout)
|
||||
return true
|
||||
|
||||
// Store subscriptions for cleanup
|
||||
currentSubscription.value = {
|
||||
unsub: () => {
|
||||
receivedSub.unsub()
|
||||
sentSub.unsub()
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
clearTimeout(subscriptionTimeout)
|
||||
console.error('Failed to subscribe to messages:', err)
|
||||
return true
|
||||
}
|
||||
|
||||
// Wait for all relays to load their historical messages
|
||||
await Promise.all(relayPool.value.map(relay => subscribeToRelay(relay)))
|
||||
}
|
||||
|
||||
function unsubscribeFromMessages() {
|
||||
if (currentSubscription.value) {
|
||||
currentSubscription.value.unsub()
|
||||
if (currentSubscription.value && typeof currentSubscription.value.unsub === 'function') {
|
||||
try {
|
||||
currentSubscription.value.unsub()
|
||||
} catch (err) {
|
||||
console.error('Failed to unsubscribe:', err)
|
||||
}
|
||||
currentSubscription.value = null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue