- Création d'un nouveau composant `useSSE` pour centraliser la gestion des connexions SSE - Remplacement direct de l'import `sse` par le nouveau composable dans :useMediaServers:, useRenderers: et useWebRenderer: - Refactorisation des fonctions `ensureSSEConnected` → ensureSCEInitialized pour utiliser le composable centralisé - Ajout d'une méthode `connect()` explicite dans chaque usage pour garantir la connexion au bon moment - Suppression des variables `sseConnected` (doublons) et remplacement par un état partagé via `useSSE` - Ajout de hooks spécialisés : useRendererEvents et useMediaServerEvent pour simplifier les abonnements filtrées - Nettoyage des imports obsolètes et harmonisation de la gestion d'état SSE
244 lines
6.0 KiB
TypeScript
244 lines
6.0 KiB
TypeScript
/**
|
|
* Composable centralisé pour la gestion des Server-Sent Events
|
|
*
|
|
* Ce composable fournit une interface unifiée pour:
|
|
* - Une seule connexion SSE (évite les connexions multiples)
|
|
* - Des abonnements typés aux événements (renderers, servers)
|
|
* - Le suivi de l'état de connexion
|
|
*
|
|
* Usage:
|
|
* const { connected, onRendererEvent, onMediaServerEvent } = useSSE()
|
|
*/
|
|
import { ref, readonly, onUnmounted } from 'vue'
|
|
import { sse } from '../services/pmocontrol/sse'
|
|
import type {
|
|
RendererEventPayload,
|
|
MediaServerEventPayload
|
|
} from '../services/pmocontrol/types'
|
|
|
|
// État global partagé
|
|
const connected = ref(sse.isConnectedState())
|
|
const connectionCallbacks: Set<(connected: boolean) => void> = new Set()
|
|
|
|
// Abonnement à l'état de connexion global
|
|
function setupConnectionListener() {
|
|
// S'assurer qu'on ne s'abonne qu'une seule fois
|
|
if (connectionCallbacks.size === 0) {
|
|
sse.onConnectionChange((isConnected) => {
|
|
connected.value = isConnected
|
|
connectionCallbacks.forEach(cb => cb(isConnected))
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook principal pour utiliser SSE
|
|
*/
|
|
export function useSSE() {
|
|
// S'assurer que le listener de connexion est configuré
|
|
setupConnectionListener()
|
|
|
|
/**
|
|
* Abonnement aux événements de type renderer
|
|
* Retourne une fonction de cleanup
|
|
*/
|
|
function onRendererEvent(
|
|
callback: (event: RendererEventPayload) => void
|
|
): () => void {
|
|
return sse.onRendererEvent(callback)
|
|
}
|
|
|
|
/**
|
|
* Abonnement aux événements de type media server
|
|
* Retourne une fonction de cleanup
|
|
*/
|
|
function onMediaServerEvent(
|
|
callback: (event: MediaServerEventPayload) => void
|
|
): () => void {
|
|
return sse.onMediaServerEvent(callback)
|
|
}
|
|
|
|
/**
|
|
* Abonnement aux changements de connexion
|
|
* Retourne une fonction de cleanup
|
|
*/
|
|
function onConnectionChange(
|
|
callback: (connected: boolean) => void
|
|
): () => void {
|
|
connectionCallbacks.add(callback)
|
|
// Appeler immédiatement avec l'état actuel
|
|
callback(connected.value)
|
|
|
|
// Retourner fonction de cleanup
|
|
return () => {
|
|
connectionCallbacks.delete(callback)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force la connexion SSE
|
|
*/
|
|
function connect(): void {
|
|
sse.connect()
|
|
}
|
|
|
|
/**
|
|
* Force la déconnexion SSE
|
|
*/
|
|
function disconnect(): void {
|
|
sse.disconnect()
|
|
}
|
|
|
|
/**
|
|
* Vérifie si actuellement connecté
|
|
*/
|
|
function isConnected(): boolean {
|
|
return connected.value
|
|
}
|
|
|
|
return {
|
|
// État (readonly pour éviter les modifications directes)
|
|
connected: readonly(connected),
|
|
|
|
// Abonnements
|
|
onRendererEvent,
|
|
onMediaServerEvent,
|
|
onConnectionChange,
|
|
|
|
// Actions
|
|
connect,
|
|
disconnect,
|
|
isConnected,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook pour s'abonner à un type spécifique d'événement renderer
|
|
* avec filtrage optionnel par rendererId
|
|
*/
|
|
export function useRendererEvents(
|
|
rendererId: () => string | null,
|
|
options?: {
|
|
onStateChanged?: (event: RendererEventPayload) => void
|
|
onPositionChanged?: (event: RendererEventPayload) => void
|
|
onVolumeChanged?: (event: RendererEventPayload) => void
|
|
onMetadataChanged?: (event: RendererEventPayload) => void
|
|
onQueueUpdated?: (event: RendererEventPayload) => void
|
|
onBindingChanged?: (event: RendererEventPayload) => void
|
|
onTimerEvent?: (event: RendererEventPayload) => void
|
|
}
|
|
) {
|
|
const { onRendererEvent } = useSSE()
|
|
|
|
let cleanup: (() => void) | null = null
|
|
|
|
function setup() {
|
|
cleanup = onRendererEvent((event) => {
|
|
const currentId = rendererId()
|
|
|
|
// Si un rendererId est spécifié, filtrer
|
|
if (currentId && event.renderer_id !== currentId) {
|
|
return
|
|
}
|
|
|
|
// Dispatch vers le handler approprié
|
|
switch (event.type) {
|
|
case 'state_changed':
|
|
case 'online':
|
|
case 'offline':
|
|
options?.onStateChanged?.(event)
|
|
break
|
|
case 'position_changed':
|
|
options?.onPositionChanged?.(event)
|
|
break
|
|
case 'volume_changed':
|
|
case 'mute_changed':
|
|
options?.onVolumeChanged?.(event)
|
|
break
|
|
case 'metadata_changed':
|
|
options?.onMetadataChanged?.(event)
|
|
break
|
|
case 'queue_updated':
|
|
case 'queue_refreshing':
|
|
options?.onQueueUpdated?.(event)
|
|
break
|
|
case 'binding_changed':
|
|
options?.onBindingChanged?.(event)
|
|
break
|
|
case 'timer_started':
|
|
case 'timer_updated':
|
|
case 'timer_tick':
|
|
case 'timer_expired':
|
|
case 'timer_cancelled':
|
|
options?.onTimerEvent?.(event)
|
|
break
|
|
}
|
|
})
|
|
}
|
|
|
|
setup()
|
|
|
|
// Cleanup automatique au unmount du composant
|
|
onUnmounted(() => {
|
|
cleanup?.()
|
|
})
|
|
|
|
return {
|
|
refresh: setup, // Permet de recréer l'abonnement si besoin
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook pour s'abonner aux événements media server
|
|
* avec filtrage optionnel par serverId
|
|
*/
|
|
export function useMediaServerEvents(
|
|
serverId: () => string | null,
|
|
options?: {
|
|
onOnline?: (event: MediaServerEventPayload) => void
|
|
onOffline?: (event: MediaServerEventPayload) => void
|
|
onGlobalUpdated?: (event: MediaServerEventPayload) => void
|
|
onContainersUpdated?: (event: MediaServerEventPayload) => void
|
|
}
|
|
) {
|
|
const { onMediaServerEvent } = useSSE()
|
|
|
|
let cleanup: (() => void) | null = null
|
|
|
|
function setup() {
|
|
cleanup = onMediaServerEvent((event) => {
|
|
const currentId = serverId()
|
|
|
|
// Si un serverId est spécifié, filtrer
|
|
if (currentId && event.server_id !== currentId) {
|
|
return
|
|
}
|
|
|
|
// Dispatch vers le handler approprié
|
|
switch (event.type) {
|
|
case 'online':
|
|
options?.onOnline?.(event)
|
|
break
|
|
case 'offline':
|
|
options?.onOffline?.(event)
|
|
break
|
|
case 'global_updated':
|
|
options?.onGlobalUpdated?.(event)
|
|
break
|
|
case 'containers_updated':
|
|
options?.onContainersUpdated?.(event)
|
|
break
|
|
}
|
|
})
|
|
}
|
|
|
|
setup()
|
|
|
|
onUnmounted(() => {
|
|
cleanup?.()
|
|
})
|
|
|
|
return {
|
|
refresh: setup,
|
|
}
|
|
} |