Files
whatsappNucleo/app/composables/usePresence.ts
josedario87 8f44826e64
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
Fix: Usar URL interna para debug webhook receiver (bypass authentik)
2025-12-02 21:27:45 -06:00

167 lines
4.4 KiB
TypeScript

/**
* Composable for managing presence (typing indicators, online status)
*/
export type PresenceState = 'available' | 'unavailable' | 'composing' | 'recording' | 'paused'
interface PresenceInfo {
presence: PresenceState | null
lastSeen: Date | null
}
interface PresenceStore {
[jid: string]: PresenceInfo
}
export function usePresence(instanceId: Ref<string | null>) {
const { on } = useRealtime()
// Store of presence states by JID
const presences = ref<PresenceStore>({})
// Debounce timer for composing presence
let composingTimeout: NodeJS.Timeout | null = null
// Subscribe to presence updates for a contact
const subscribeToPresence = async (jid: string): Promise<void> => {
if (!instanceId.value) return
try {
await $fetch(`/api/presence/${instanceId.value}/subscribe`, {
method: 'POST',
body: { jid }
})
} catch (error) {
console.error('[usePresence] Error subscribing:', error)
}
}
// Send own presence update
const sendPresence = async (jid: string, presence: PresenceState): Promise<void> => {
if (!instanceId.value) return
try {
await $fetch(`/api/presence/${instanceId.value}/send`, {
method: 'POST',
body: { jid, presence }
})
} catch (error) {
console.error('[usePresence] Error sending presence:', error)
}
}
// Send composing presence with automatic paused after timeout
const sendTyping = async (jid: string): Promise<void> => {
if (!instanceId.value) return
// Clear previous timeout
if (composingTimeout) {
clearTimeout(composingTimeout)
}
// Send composing
await sendPresence(jid, 'composing')
// Auto-pause after 5 seconds of no typing
composingTimeout = setTimeout(async () => {
await sendPresence(jid, 'paused')
}, 5000)
}
// Send recording presence
const sendRecording = async (jid: string): Promise<void> => {
await sendPresence(jid, 'recording')
}
// Stop typing/recording indicator
const stopIndicator = async (jid: string): Promise<void> => {
if (composingTimeout) {
clearTimeout(composingTimeout)
composingTimeout = null
}
await sendPresence(jid, 'paused')
}
// Get presence for a JID
const getPresence = (jid: string): PresenceInfo => {
return presences.value[jid] || { presence: null, lastSeen: null }
}
// Get presence text for display
const getPresenceText = (jid: string): string | null => {
const presence = getPresence(jid)
if (!presence.presence) return null
switch (presence.presence) {
case 'composing':
return 'escribiendo...'
case 'recording':
return 'grabando audio...'
case 'available':
return 'en línea'
case 'unavailable':
if (presence.lastSeen) {
return `última vez ${formatLastSeen(presence.lastSeen)}`
}
return null
case 'paused':
return null
default:
return null
}
}
// Format last seen date
const formatLastSeen = (date: Date): string => {
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / 60000)
if (minutes < 1) return 'hace un momento'
if (minutes < 60) return `hace ${minutes} min`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `hace ${hours}h`
const days = Math.floor(hours / 24)
if (days === 1) return 'ayer'
if (days < 7) return `hace ${days} días`
return date.toLocaleDateString('es', { day: 'numeric', month: 'short' })
}
// Listen for presence updates from SSE
onMounted(() => {
on('presence.update', (data: any) => {
if (data.instanceId !== instanceId.value) return
// Update presence store
for (const [participantJid, presence] of Object.entries(data.presences)) {
const presenceData = presence as { lastKnownPresence: string; lastSeen?: number }
presences.value[participantJid] = {
presence: presenceData.lastKnownPresence as PresenceState,
lastSeen: presenceData.lastSeen ? new Date(presenceData.lastSeen * 1000) : null
}
}
})
})
// Cleanup on unmount
onUnmounted(() => {
if (composingTimeout) {
clearTimeout(composingTimeout)
}
})
return {
presences: readonly(presences),
subscribeToPresence,
sendPresence,
sendTyping,
sendRecording,
stopIndicator,
getPresence,
getPresenceText
}
}