mirror of
https://github.com/ArthurDanjou/artsite.git
synced 2026-02-03 05:31:44 +01:00
Test visitors
This commit is contained in:
177
app/composables/visitors.ts
Normal file
177
app/composables/visitors.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { useState } from '#imports'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Composable for tracking real-time website visitors count via WebSocket
|
||||
*
|
||||
* Features:
|
||||
* - Real-time visitors count updates
|
||||
* - Automatic WebSocket connection management
|
||||
* - Connection status tracking
|
||||
* - Error handling
|
||||
* - Automatic cleanup on component unmount
|
||||
*
|
||||
* @returns {object} An object containing:
|
||||
* - visitors: Ref<number> - Current number of visitors
|
||||
* - isLoading: Ref<boolean> - Loading state indicator
|
||||
* - error: Ref<string | null> - Error message if any
|
||||
* - isConnected: Ref<boolean> - WebSocket connection status
|
||||
* - reconnect: () => void - Function to manually reconnect
|
||||
*/
|
||||
export function useVisitors() {
|
||||
// State management
|
||||
const visitors = useState<number>('visitors', () => 0) // Added default value
|
||||
const locations = ref<Array<{ latitude: number, longitude: number }>>([])
|
||||
const myLocation = useState('location', () => ({
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
}))
|
||||
const isLoading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
const wsRef = ref<WebSocket | null>(null)
|
||||
const isConnected = ref(false)
|
||||
const isMounted = ref(true)
|
||||
|
||||
// Constants
|
||||
const RECONNECTION_DELAY = 5000 // 5 seconds delay for reconnection
|
||||
const WS_NORMAL_CLOSURE = 1000
|
||||
|
||||
/**
|
||||
* Constructs the WebSocket URL based on the current protocol and host
|
||||
* @returns {string} WebSocket URL
|
||||
*/
|
||||
const getWebSocketUrl = (): string => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const baseUrl = window.location.host.replace(/^(http|https):\/\//, '')
|
||||
return `${protocol}//${baseUrl}/.nuxt-visitors/ws?latitude=${myLocation.value.latitude}&longitude=${myLocation.value.longitude}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up WebSocket connection and resets state
|
||||
*/
|
||||
const cleanup = () => {
|
||||
if (wsRef.value) {
|
||||
wsRef.value.close()
|
||||
wsRef.value = null
|
||||
}
|
||||
isConnected.value = false
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles WebSocket messages
|
||||
* @param {MessageEvent} event - WebSocket message event
|
||||
*/
|
||||
const handleMessage = async (event: MessageEvent) => {
|
||||
if (!isMounted.value)
|
||||
return
|
||||
|
||||
try {
|
||||
const data = typeof event.data === 'string' ? event.data : await event.data.text()
|
||||
locations.value = JSON.parse(data) as { latitude: number, longitude: number }[]
|
||||
const visitorCount = locations.value.length
|
||||
if (!Number.isNaN(visitorCount) && visitorCount >= 0) {
|
||||
visitors.value = visitorCount
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid visitor count received')
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Failed to parse visitors WebSocket data:', err)
|
||||
error.value = 'Invalid data received'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles WebSocket connection closure
|
||||
* @param {CloseEvent} event - WebSocket close event
|
||||
*/
|
||||
const handleClose = (event: CloseEvent) => {
|
||||
console.log('Visitors WebSocket closed:', event.code, event.reason)
|
||||
isConnected.value = false
|
||||
wsRef.value = null
|
||||
|
||||
if (isMounted.value && event.code !== WS_NORMAL_CLOSURE) {
|
||||
error.value = 'Connection lost'
|
||||
// Attempt to reconnect after delay
|
||||
setTimeout(() => reconnect(), RECONNECTION_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes WebSocket connection
|
||||
*/
|
||||
const initWebSocket = () => {
|
||||
if (!isMounted.value)
|
||||
return
|
||||
|
||||
cleanup()
|
||||
|
||||
try {
|
||||
const ws = new WebSocket(getWebSocketUrl())
|
||||
wsRef.value = ws
|
||||
|
||||
ws.onopen = () => {
|
||||
if (!isMounted.value) {
|
||||
ws.close()
|
||||
return
|
||||
}
|
||||
console.log('Stats WebSocket connected')
|
||||
isConnected.value = true
|
||||
isLoading.value = false
|
||||
error.value = null
|
||||
}
|
||||
|
||||
ws.onmessage = handleMessage
|
||||
ws.onclose = handleClose
|
||||
ws.onerror = (event: Event) => {
|
||||
if (!isMounted.value)
|
||||
return
|
||||
console.error('Visitors WebSocket error:', event)
|
||||
error.value = 'Connection error'
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
if (!isMounted.value)
|
||||
return
|
||||
console.error('Failed to initialize Visitors WebSocket:', err)
|
||||
error.value = 'Failed to initialize connection'
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually triggers WebSocket reconnection
|
||||
*/
|
||||
const reconnect = () => {
|
||||
if (!isMounted.value)
|
||||
return
|
||||
error.value = null
|
||||
isLoading.value = true
|
||||
initWebSocket()
|
||||
}
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
if (import.meta.client) {
|
||||
isMounted.value = true
|
||||
initWebSocket()
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
isMounted.value = false
|
||||
cleanup()
|
||||
})
|
||||
|
||||
return {
|
||||
visitors,
|
||||
locations,
|
||||
myLocation,
|
||||
isLoading,
|
||||
error,
|
||||
isConnected,
|
||||
reconnect,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user