import { ref, onMounted, onUnmounted } from 'vue' export interface PollingOptions { interval?: number // milliseconds immediate?: boolean // whether to execute immediately on start maxRetries?: number // maximum number of retries on error retryDelay?: number // delay between retries in milliseconds onError?: (error: Error) => void // error handler } export interface PollingState { isPolling: boolean isFetching: boolean error: string | null retryCount: number lastFetchTime: Date | null } /** * Composable for implementing polling/real-time updates * * @param callback - Function to execute on each poll * @param options - Polling configuration options * @returns Polling controls and state */ export const usePolling = ( callback: () => Promise | T, options: PollingOptions = {} ) => { const { interval = 3000, // 3 seconds default immediate = true, maxRetries = 3, retryDelay = 1000, onError } = options // State const state = ref({ isPolling: false, isFetching: false, error: null, retryCount: 0, lastFetchTime: null }) // Polling interval reference let pollInterval: NodeJS.Timeout | null = null let retryTimeout: NodeJS.Timeout | null = null // Execute the polling callback const executePoll = async (): Promise => { if (state.value.isFetching) { return null // Skip if already fetching } state.value.isFetching = true state.value.error = null try { const result = await callback() state.value.lastFetchTime = new Date() state.value.retryCount = 0 // Reset retry count on success return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' state.value.error = errorMessage state.value.retryCount++ // Call error handler if provided if (onError) { onError(error instanceof Error ? error : new Error(errorMessage)) } // Handle retries if (state.value.retryCount <= maxRetries) { console.warn(`Polling error (retry ${state.value.retryCount}/${maxRetries}):`, errorMessage) // Schedule retry if (retryTimeout) { clearTimeout(retryTimeout) } retryTimeout = setTimeout(() => { executePoll() }, retryDelay) } else { console.error(`Polling failed after ${maxRetries} retries:`, errorMessage) stopPolling() // Stop polling after max retries } return null } finally { state.value.isFetching = false } } // Start polling const startPolling = () => { if (state.value.isPolling) { return // Already polling } state.value.isPolling = true state.value.error = null // Execute immediately if requested if (immediate) { executePoll() } // Set up interval pollInterval = setInterval(() => { executePoll() }, interval) } // Stop polling const stopPolling = () => { if (pollInterval) { clearInterval(pollInterval) pollInterval = null } if (retryTimeout) { clearTimeout(retryTimeout) retryTimeout = null } state.value.isPolling = false state.value.isFetching = false } // Toggle polling const togglePolling = () => { if (state.value.isPolling) { stopPolling() } else { startPolling() } } // Force immediate execution const forcePoll = async (): Promise => { return await executePoll() } // Update polling interval const updateInterval = (newInterval: number) => { const wasPolling = state.value.isPolling if (wasPolling) { stopPolling() } // Update interval in options (for next start) options.interval = newInterval if (wasPolling) { startPolling() } } // Cleanup on unmount onUnmounted(() => { stopPolling() }) // Auto-start on mount if immediate is true onMounted(() => { if (immediate) { startPolling() } }) return { // State state: state.value, isPolling: state.value.isPolling, isFetching: state.value.isFetching, error: state.value.error, retryCount: state.value.retryCount, lastFetchTime: state.value.lastFetchTime, // Controls startPolling, stopPolling, togglePolling, forcePoll, updateInterval, // Helper resetError: () => { state.value.error = null state.value.retryCount = 0 } } } export default usePolling