diff --git a/nuxt4/app/composables/useFrigateEvents.ts b/nuxt4/app/composables/useFrigateEvents.ts index b1932e8..ff01dbe 100644 --- a/nuxt4/app/composables/useFrigateEvents.ts +++ b/nuxt4/app/composables/useFrigateEvents.ts @@ -1,6 +1,7 @@ /** * Composable para gestionar eventos de Frigate - * API: https://camaras.nucleoriofrio.com/api/events/{camera}/create + * Usa proxy backend para evitar problemas de CORS/cookies entre subdominios + * API Proxy: /api/frigate/event */ export interface FrigateEventParams { @@ -24,14 +25,12 @@ export interface FrigateEventResponse { } export const useFrigateEvents = () => { - const BASE_URL = 'https://camaras.nucleoriofrio.com' - const isCreating = useState('frigate_creating', () => false) const error = useState('frigate_error', () => null) const lastEventId = useState('frigate_last_event', () => null) /** - * Crea un evento en Frigate para una camara especifica + * Crea un evento en Frigate para una camara especifica via proxy */ const createEvent = async ( camera: string, @@ -41,17 +40,14 @@ export const useFrigateEvents = () => { error.value = null try { - // Extraer el nombre base de la camara (sin _main o _sub) - const cameraName = camera.replace(/_main$|_sub$/, '') - - const response = await $fetch( - `${BASE_URL}/api/events/${cameraName}/create`, - { - method: 'POST', - credentials: 'include', - body: params + // Usar proxy backend para evitar CORS/cookies issues + const response = await $fetch('/api/frigate/event', { + method: 'POST', + body: { + camera, + ...params } - ) + }) if (response.event_id) { lastEventId.value = response.event_id diff --git a/nuxt4/app/composables/useStreams.ts b/nuxt4/app/composables/useStreams.ts index 1f818f2..c78746d 100644 --- a/nuxt4/app/composables/useStreams.ts +++ b/nuxt4/app/composables/useStreams.ts @@ -1,6 +1,8 @@ /** * Composable para gestionar streams de video desde go2rtc - * API: https://streams.nucleoriofrio.com/api/streams + * Usa proxy backend para evitar problemas de CORS/cookies entre subdominios + * API Proxy: /api/streams/list + * Streaming: https://streams.nucleoriofrio.com (iframe con sesion propia) */ export type StreamType = 'webrtc' | 'mse' | 'mp4' | 'hls' | 'mjpeg' @@ -56,16 +58,15 @@ export const useStreams = () => { const error = useState('streams_error', () => null) /** - * Obtiene la lista de streams disponibles desde la API + * Obtiene la lista de streams disponibles via proxy backend */ const fetchStreams = async (): Promise => { isLoading.value = true error.value = null try { - const response = await $fetch>(`${BASE_URL}/api/streams`, { - credentials: 'include' - }) + // Usar proxy backend para evitar CORS/cookies issues + const response = await $fetch>('/api/streams/list') // Extraer nombres de streams del objeto streams.value = Object.keys(response).sort() diff --git a/nuxt4/server/api/frigate/event.post.ts b/nuxt4/server/api/frigate/event.post.ts new file mode 100644 index 0000000..1ab7833 --- /dev/null +++ b/nuxt4/server/api/frigate/event.post.ts @@ -0,0 +1,99 @@ +/** + * Proxy endpoint para crear eventos en Frigate + * POST /api/frigate/event + * Body: { camera: string, label: string, sub_label?: string, duration?: number, include_recording?: boolean } + */ + +interface EventRequestBody { + camera: string + label: string + sub_label?: string + duration?: number + include_recording?: boolean +} + +export default defineEventHandler(async (event) => { + // Verificar autenticación via headers de Authentik + const headers = getRequestHeaders(event) + const username = headers['x-authentik-username'] + + if (!username) { + throw createError({ + statusCode: 401, + message: 'No autenticado' + }) + } + + // Leer body + const body = await readBody(event) + + if (!body.camera || !body.label) { + throw createError({ + statusCode: 400, + message: 'Se requiere camera y label' + }) + } + + // Extraer nombre base de la cámara (sin _main o _sub) + const cameraName = body.camera.replace(/_main$|_sub$/, '') + + // Preparar payload para Frigate + const frigatePayload: Record = { + label: body.label + } + + if (body.sub_label) { + frigatePayload.sub_label = body.sub_label + } + + if (body.duration) { + frigatePayload.duration = body.duration + } + + if (body.include_recording !== undefined) { + frigatePayload.include_recording = body.include_recording + } + + try { + const response = await fetch( + `https://camaras.nucleoriofrio.com/api/events/${cameraName}/create`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(frigatePayload) + } + ) + + if (!response.ok) { + const errorText = await response.text() + console.error('[Frigate Proxy] Error response:', errorText) + throw createError({ + statusCode: response.status, + message: `Error al crear evento: ${response.statusText}` + }) + } + + const data = await response.json() + + console.log(`[Frigate Proxy] Evento creado por ${username}: ${body.label} en ${cameraName}`) + + return { + success: true, + ...data + } + } catch (error: unknown) { + console.error('[Frigate Proxy] Error:', error) + + if ((error as any).statusCode) { + throw error + } + + throw createError({ + statusCode: 500, + message: (error as Error)?.message || 'Error al crear evento' + }) + } +}) diff --git a/nuxt4/server/api/streams/list.get.ts b/nuxt4/server/api/streams/list.get.ts new file mode 100644 index 0000000..8610c27 --- /dev/null +++ b/nuxt4/server/api/streams/list.get.ts @@ -0,0 +1,48 @@ +/** + * Proxy endpoint para obtener la lista de streams de go2rtc + * Evita problemas de CORS/cookies entre subdominios + */ + +export default defineEventHandler(async (event) => { + // Verificar autenticación via headers de Authentik + const headers = getRequestHeaders(event) + const username = headers['x-authentik-username'] + + if (!username) { + throw createError({ + statusCode: 401, + message: 'No autenticado' + }) + } + + try { + const response = await fetch('https://streams.nucleoriofrio.com/api/streams', { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }) + + if (!response.ok) { + throw createError({ + statusCode: response.status, + message: `Error al obtener streams: ${response.statusText}` + }) + } + + const data = await response.json() + + return data + } catch (error: unknown) { + console.error('[Streams Proxy] Error:', error) + + if ((error as any).statusCode) { + throw error + } + + throw createError({ + statusCode: 500, + message: (error as Error)?.message || 'Error al obtener streams' + }) + } +})