/** * Composable para gestionar eventos de Frigate * Usa proxy backend para evitar problemas de CORS/cookies entre subdominios * API Proxy: /api/frigate/event, /api/frigate/events */ export interface FrigateEventParams { label: string sub_label?: string duration?: number include_recording?: boolean draw?: { boxes?: Array<{ box: [number, number, number, number] color?: [number, number, number] score?: number }> } } export interface FrigateEventResponse { success: boolean event_id?: string message?: string } export interface FrigateEvent { id: string label: string sub_label?: string camera: string start_time: number end_time?: number has_clip: boolean has_snapshot: boolean zones: string[] } export const useFrigateEvents = () => { // Ya no usamos URL publica, todo va por proxy interno const isCreating = useState('frigate_creating', () => false) const isLoadingEvents = useState('frigate_loading_events', () => false) const error = useState('frigate_error', () => null) const lastEventId = useState('frigate_last_event', () => null) const events = useState('frigate_events', () => []) /** * Obtiene los eventos recientes de una camara */ const fetchEvents = async (camera?: string, limit: number = 10): Promise => { isLoadingEvents.value = true error.value = null try { const params = new URLSearchParams() if (camera) { params.set('camera', camera) } params.set('limit', limit.toString()) const response = await $fetch(`/api/frigate/events?${params.toString()}`) events.value = response return response } catch (err: unknown) { const errorMessage = (err as Error)?.message || 'Error al obtener eventos' error.value = errorMessage console.error('[Frigate] Error fetching events:', err) return [] } finally { isLoadingEvents.value = false } } /** * Genera la URL del clip de un evento (via proxy interno) */ const getEventClipUrl = (eventId: string): string => { return `/api/frigate/events/${eventId}/clip` } /** * Genera la URL del snapshot de un evento (via proxy interno) */ const getEventSnapshotUrl = (eventId: string): string => { return `/api/frigate/events/${eventId}/snapshot` } /** * Formatea la fecha de un evento */ const formatEventTime = (timestamp: number): string => { const date = new Date(timestamp * 1000) return date.toLocaleString('es-ES', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) } /** * Crea un evento en Frigate para una camara especifica via proxy */ const createEvent = async ( camera: string, params: FrigateEventParams ): Promise => { isCreating.value = true error.value = null try { const response = await $fetch('/api/frigate/event', { method: 'POST', body: { camera, ...params } }) if (response.event_id) { lastEventId.value = response.event_id } return { success: true, ...response } } catch (err: unknown) { const errorMessage = (err as Error)?.message || 'Error al crear evento' error.value = errorMessage console.error('[Frigate] Error creating event:', err) return { success: false, message: errorMessage } } finally { isCreating.value = false } } /** * Crea un evento rapido "eventoWhisper" de 1 minuto */ const createQuickEvent = async (camera: string): Promise => { return createEvent(camera, { label: 'eventoWhisper', duration: 60, include_recording: true }) } /** * Limpia el error */ const clearError = () => { error.value = null } return { // Estado isCreating: readonly(isCreating), isLoadingEvents: readonly(isLoadingEvents), error: readonly(error), lastEventId: readonly(lastEventId), events: readonly(events), // Métodos fetchEvents, getEventClipUrl, getEventSnapshotUrl, formatEventTime, createEvent, createQuickEvent, clearError } }