updates
This commit is contained in:
parent
d694f9b645
commit
d27f66e95d
5 changed files with 230 additions and 162 deletions
|
|
@ -79,7 +79,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
*/
|
||||
workbox.precacheAndRoute([{
|
||||
"url": "index.html",
|
||||
"revision": "0.dc5rn1lchj8"
|
||||
"revision": "0.36o4mscev7"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ import { useNostrStore } from '@/stores/nostr'
|
|||
import { KeyRound, Copy, Check, Eye, EyeOff, Loader2 } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { CardDescription, CardTitle } from '@/components/ui/card'
|
||||
import { isValidPrivateKey, formatPrivateKey } from '@/lib/nostr'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
|
@ -139,7 +139,7 @@ const generateKey = () => {
|
|||
privkey.value = newKey
|
||||
error.value = ''
|
||||
showRecoveryMessage.value = true
|
||||
showKey.value = true // Show the key when generated
|
||||
showKey.value = false
|
||||
} catch (err) {
|
||||
console.error('Failed to generate key:', err)
|
||||
error.value = t('login.error')
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const input = ref('')
|
|||
const isSending = ref(false)
|
||||
const error = ref('')
|
||||
const messagesEndRef = ref<HTMLDivElement | null>(null)
|
||||
const isSubscribing = ref(false)
|
||||
const SUPPORT_NPUB = import.meta.env.VITE_SUPPORT_NPUB
|
||||
|
||||
if (!SUPPORT_NPUB) {
|
||||
|
|
@ -71,11 +72,22 @@ onMounted(async () => {
|
|||
|
||||
const supportPubkeyHex = npubToHex(SUPPORT_NPUB)
|
||||
nostrStore.activeChat = supportPubkeyHex
|
||||
await nostrStore.subscribeToMessages()
|
||||
|
||||
// Try to subscribe in the background
|
||||
isSubscribing.value = true
|
||||
nostrStore.subscribeToMessages()
|
||||
.catch(err => {
|
||||
console.debug('Support chat subscription error:', err)
|
||||
// Continue anyway - messages will come through when connection succeeds
|
||||
})
|
||||
.finally(() => {
|
||||
isSubscribing.value = false
|
||||
})
|
||||
|
||||
scrollToBottom()
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize support chat:', err)
|
||||
error.value = 'Failed to connect to support. Please try again later.'
|
||||
console.debug('Support chat setup error:', err)
|
||||
// Continue anyway
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -90,10 +102,13 @@ onUnmounted(() => {
|
|||
watch(() => nostrStore.activeChat, async (newChat) => {
|
||||
if (newChat) {
|
||||
try {
|
||||
isSubscribing.value = true
|
||||
await nostrStore.subscribeToMessages()
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe to messages:', err)
|
||||
error.value = 'Failed to connect to chat. Please try again later.'
|
||||
console.debug('Chat subscription error:', err)
|
||||
// Continue anyway
|
||||
} finally {
|
||||
isSubscribing.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -235,6 +250,12 @@ const getMessageGroupClasses = (sent: boolean) => {
|
|||
</Button>
|
||||
</form>
|
||||
</CardFooter>
|
||||
|
||||
<!-- Add loading indicator if needed -->
|
||||
<div v-if="isSubscribing" class="absolute top-4 right-4 flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<div class="h-2 w-2 rounded-full bg-primary animate-pulse"></div>
|
||||
Connecting...
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useNostrStore } from '@/stores/nostr'
|
||||
import SupportChat from '@/components/SupportChat.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog'
|
||||
import Login from '@/components/Login.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const nostrStore = useNostrStore()
|
||||
const showLoginDialog = ref(!nostrStore.isLoggedIn)
|
||||
|
||||
// Redirect to home if not logged in
|
||||
if (!nostrStore.isLoggedIn) {
|
||||
router.push('/')
|
||||
const handleLoginSuccess = () => {
|
||||
showLoginDialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -20,6 +21,13 @@ if (!nostrStore.isLoggedIn) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login Dialog -->
|
||||
<Dialog v-model:open="showLoginDialog">
|
||||
<DialogContent class="sm:max-w-md">
|
||||
<Login @success="handleLoginSuccess" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ const DEFAULT_RELAYS = [
|
|||
'wss://nostr.atitlan.io'
|
||||
]
|
||||
|
||||
// Get support agent's public key from environment variable
|
||||
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([
|
||||
|
|
@ -132,54 +129,33 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
profiles.value.clear()
|
||||
processedMessageIds.value.clear()
|
||||
|
||||
// Close existing connections
|
||||
relayPool.value.forEach(relay => {
|
||||
try {
|
||||
relay.close()
|
||||
} catch (err) {
|
||||
console.error('Error closing relay:', err)
|
||||
// Connect to relays
|
||||
relayPool.value = (await Promise.all(
|
||||
account.value.relays.map(async relay => {
|
||||
console.log('Connecting to relay:', relay.url)
|
||||
const connection = await connectToRelay(relay.url)
|
||||
if (!connection) {
|
||||
console.error('Failed to connect to relay:', relay.url)
|
||||
}
|
||||
return connection
|
||||
})
|
||||
relayPool.value = []
|
||||
|
||||
// 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
|
||||
}
|
||||
})
|
||||
|
||||
const connectedRelays = await Promise.all(connectionPromises)
|
||||
relayPool.value = connectedRelays.filter((relay): relay is NonNullable<typeof relay> => relay !== null)
|
||||
)).filter((relay): relay is any => relay !== null)
|
||||
|
||||
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
|
||||
// Setup visibility change handler
|
||||
setupVisibilityHandler()
|
||||
|
||||
// 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
|
||||
}
|
||||
// Subscribe to messages in the background
|
||||
subscribeToMessages().catch(err => {
|
||||
console.error('Background subscription failed:', err)
|
||||
})
|
||||
|
||||
// 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.')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,43 +174,9 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
relays: DEFAULT_RELAYS.map(url => ({ url, read: true, write: true }))
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!account.value) return
|
||||
|
||||
const filter = {
|
||||
kinds: [0],
|
||||
authors: Array.from(messages.value.keys())
|
||||
}
|
||||
|
||||
if (filter.authors.length === 0) return
|
||||
|
||||
relayPool.value.forEach(relay => {
|
||||
const sub = relay.sub([filter])
|
||||
|
||||
sub.on('event', (event: NostrEvent) => {
|
||||
try {
|
||||
const profile = JSON.parse(event.content)
|
||||
profiles.value.set(event.pubkey, {
|
||||
pubkey: event.pubkey,
|
||||
name: profile.name,
|
||||
picture: profile.picture,
|
||||
about: profile.about,
|
||||
nip05: profile.nip05
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to parse profile:', err)
|
||||
}
|
||||
})
|
||||
// Initialize connection in the background
|
||||
init().catch(err => {
|
||||
console.error('Background initialization failed:', err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -301,55 +243,42 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
async function subscribeToMessages() {
|
||||
if (!account.value) return
|
||||
|
||||
// Cleanup existing subscription if any
|
||||
if (currentSubscription.value) {
|
||||
// Cleanup existing subscription
|
||||
unsubscribeFromMessages()
|
||||
}
|
||||
|
||||
let hasReceivedMessage = false
|
||||
const subscriptionTimeout = setTimeout(() => {
|
||||
if (!hasReceivedMessage) {
|
||||
console.log('No messages received, considering subscription successful')
|
||||
hasReceivedMessage = true
|
||||
}
|
||||
}, 5000)
|
||||
// Get timestamp from 24 hours ago
|
||||
const since = Math.floor(Date.now() / 1000) - (24 * 60 * 60)
|
||||
|
||||
try {
|
||||
const subscribeToRelay = (relay: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let subs: any[] = []
|
||||
let resolved = false
|
||||
|
||||
// Set a timeout for the entire subscription
|
||||
const timeout = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
console.log('Subscription timeout, but continuing...')
|
||||
resolved = true
|
||||
resolve(true)
|
||||
}
|
||||
}, 8000)
|
||||
const subs: any[] = []
|
||||
let messageReceived = false
|
||||
let isResolved = false
|
||||
|
||||
try {
|
||||
// Filter for received messages with history
|
||||
const receivedFilter = {
|
||||
kinds: [4],
|
||||
'#p': [account.value!.pubkey],
|
||||
since: 0
|
||||
}
|
||||
|
||||
// Filter for sent messages with history
|
||||
const sentFilter = {
|
||||
kinds: [4],
|
||||
authors: [account.value!.pubkey],
|
||||
since: 0
|
||||
}
|
||||
console.log('Setting up subscriptions for relay...')
|
||||
|
||||
// Subscribe to received messages
|
||||
const receivedSub = relay.sub([receivedFilter])
|
||||
const receivedSub = relay.sub([{
|
||||
kinds: [4],
|
||||
'#p': [account.value!.pubkey],
|
||||
since,
|
||||
}])
|
||||
subs.push(receivedSub)
|
||||
|
||||
// Subscribe to sent messages
|
||||
const sentSub = relay.sub([{
|
||||
kinds: [4],
|
||||
authors: [account.value!.pubkey],
|
||||
since,
|
||||
}])
|
||||
subs.push(sentSub)
|
||||
|
||||
// Handle received messages
|
||||
receivedSub.on('event', async (event: NostrEvent) => {
|
||||
hasReceivedMessage = true
|
||||
messageReceived = true
|
||||
if (isResolved) return // Don't process events after resolution
|
||||
try {
|
||||
if (processedMessageIds.value.has(event.id)) return
|
||||
|
||||
|
|
@ -369,21 +298,15 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
|
||||
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)
|
||||
|
||||
// Handle sent messages
|
||||
sentSub.on('event', async (event: NostrEvent) => {
|
||||
hasReceivedMessage = true
|
||||
messageReceived = true
|
||||
if (isResolved) return // Don't process events after resolution
|
||||
try {
|
||||
if (processedMessageIds.value.has(event.id)) return
|
||||
|
||||
|
|
@ -414,50 +337,56 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
// 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)
|
||||
console.debug('Failed to unsubscribe:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Consider subscription successful immediately
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
// Consider subscription successful after a short delay
|
||||
setTimeout(() => {
|
||||
if (!isResolved) {
|
||||
isResolved = true
|
||||
console.debug(messageReceived ?
|
||||
'Subscription successful with messages' :
|
||||
'Subscription successful, no messages yet'
|
||||
)
|
||||
resolve(true)
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error in subscription:', err)
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
resolve(true)
|
||||
console.debug('Error in subscription setup:', err)
|
||||
if (!isResolved) {
|
||||
isResolved = true
|
||||
resolve(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for relays
|
||||
await Promise.all(
|
||||
relayPool.value.map(relay =>
|
||||
withTimeout(subscribeToRelay(relay), 10000)
|
||||
.catch(() => true)
|
||||
)
|
||||
// Wait for all relays to set up subscriptions
|
||||
const results = await Promise.all(
|
||||
relayPool.value.map(relay => subscribeToRelay(relay))
|
||||
)
|
||||
|
||||
clearTimeout(subscriptionTimeout)
|
||||
// Consider success if at least one relay worked
|
||||
const success = results.some(result => result)
|
||||
if (!success) {
|
||||
console.debug('No relays successfully subscribed')
|
||||
return false // Return false instead of throwing
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
} catch (err) {
|
||||
clearTimeout(subscriptionTimeout)
|
||||
console.error('Failed to subscribe to messages:', err)
|
||||
return true
|
||||
console.debug('Subscription process failed:', err)
|
||||
return false // Return false instead of throwing
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,6 +401,115 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadProfiles() {
|
||||
if (!account.value) return
|
||||
|
||||
const pubkeysToLoad = new Set<string>()
|
||||
|
||||
// Collect all unique pubkeys from messages
|
||||
for (const [pubkey] of messages.value.entries()) {
|
||||
if (!profiles.value.has(pubkey)) {
|
||||
pubkeysToLoad.add(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
if (pubkeysToLoad.size === 0) return
|
||||
|
||||
try {
|
||||
const filter = {
|
||||
kinds: [0],
|
||||
authors: Array.from(pubkeysToLoad)
|
||||
}
|
||||
|
||||
const loadFromRelay = (relay: any) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const sub = relay.sub([filter])
|
||||
|
||||
sub.on('event', (event: NostrEvent) => {
|
||||
try {
|
||||
const profile = JSON.parse(event.content)
|
||||
profiles.value.set(event.pubkey, {
|
||||
pubkey: event.pubkey,
|
||||
name: profile.name,
|
||||
picture: profile.picture,
|
||||
about: profile.about,
|
||||
nip05: profile.nip05
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to parse profile:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// Resolve after receiving EOSE (End of Stored Events)
|
||||
sub.on('eose', () => {
|
||||
resolve()
|
||||
})
|
||||
|
||||
// Set a timeout in case EOSE is not received
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
|
||||
// Load profiles from all relays concurrently
|
||||
await Promise.all(relayPool.value.map(relay => loadFromRelay(relay)))
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to load profiles:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a reconnection function
|
||||
async function reconnectToRelays() {
|
||||
if (!account.value) return
|
||||
|
||||
console.log('Attempting to reconnect to relays...')
|
||||
|
||||
// Close existing connections
|
||||
relayPool.value.forEach(relay => {
|
||||
try {
|
||||
relay.close()
|
||||
} catch (err) {
|
||||
console.error('Error closing relay:', err)
|
||||
}
|
||||
})
|
||||
relayPool.value = []
|
||||
|
||||
// Reconnect
|
||||
relayPool.value = (await Promise.all(
|
||||
account.value.relays.map(async relay => {
|
||||
console.log('Reconnecting to relay:', relay.url)
|
||||
const connection = await connectToRelay(relay.url)
|
||||
if (!connection) {
|
||||
console.error('Failed to reconnect to relay:', relay.url)
|
||||
}
|
||||
return connection
|
||||
})
|
||||
)).filter((relay): relay is any => relay !== null)
|
||||
|
||||
if (relayPool.value.length === 0) {
|
||||
throw new Error('Failed to connect to any relays')
|
||||
}
|
||||
|
||||
// Resubscribe to messages
|
||||
await subscribeToMessages()
|
||||
}
|
||||
|
||||
// Add visibility change handler
|
||||
function setupVisibilityHandler() {
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
if (document.visibilityState === 'visible' && account.value) {
|
||||
console.log('Page became visible, reconnecting...')
|
||||
try {
|
||||
await reconnectToRelays()
|
||||
} catch (err) {
|
||||
console.error('Failed to reconnect:', err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
account,
|
||||
profiles,
|
||||
|
|
@ -484,6 +522,7 @@ export const useNostrStore = defineStore('nostr', () => {
|
|||
logout,
|
||||
sendMessage,
|
||||
subscribeToMessages,
|
||||
unsubscribeFromMessages
|
||||
unsubscribeFromMessages,
|
||||
loadProfiles
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue