package wifi import ( "log" "sync" "github.com/gorilla/websocket" "github.com/nemunaire/repeater/internal/models" ) // WifiBroadcaster manages WebSocket clients and broadcasts WiFi events type WifiBroadcaster struct { clients map[*websocket.Conn]bool clientsMu sync.RWMutex // State deduplication lastState string lastSSID string lastNetworks []models.WiFiNetwork stateMu sync.RWMutex } // NewWifiBroadcaster creates a new WiFi broadcaster func NewWifiBroadcaster() *WifiBroadcaster { return &WifiBroadcaster{ clients: make(map[*websocket.Conn]bool), } } // RegisterClient registers a new WebSocket client func (wb *WifiBroadcaster) RegisterClient(conn *websocket.Conn) { wb.clientsMu.Lock() wb.clients[conn] = true wb.clientsMu.Unlock() // Send initial state to the new client wb.sendInitialState(conn) } // UnregisterClient removes a WebSocket client func (wb *WifiBroadcaster) UnregisterClient(conn *websocket.Conn) { wb.clientsMu.Lock() delete(wb.clients, conn) wb.clientsMu.Unlock() } // sendInitialState sends the current WiFi state to a newly connected client func (wb *WifiBroadcaster) sendInitialState(conn *websocket.Conn) { wb.stateMu.RLock() lastState := wb.lastState lastSSID := wb.lastSSID lastNetworks := make([]models.WiFiNetwork, len(wb.lastNetworks)) copy(lastNetworks, wb.lastNetworks) wb.stateMu.RUnlock() // Send last known state if available if lastState != "" { event := NewStateChangeEvent(lastState, lastSSID, "") conn.WriteJSON(event) } // Send last known network list if available if len(lastNetworks) > 0 { event := NewScanUpdateEvent(lastNetworks) conn.WriteJSON(event) } } // BroadcastScanUpdate broadcasts a scan update event to all clients func (wb *WifiBroadcaster) BroadcastScanUpdate(networks []models.WiFiNetwork) { // Check for changes to avoid duplicate broadcasts wb.stateMu.Lock() if networksEqual(wb.lastNetworks, networks) { wb.stateMu.Unlock() return } wb.lastNetworks = make([]models.WiFiNetwork, len(networks)) copy(wb.lastNetworks, networks) wb.stateMu.Unlock() event := NewScanUpdateEvent(networks) wb.broadcast(event) } // BroadcastStateChange broadcasts a state change event to all clients func (wb *WifiBroadcaster) BroadcastStateChange(state, ssid string) { // Check for changes to avoid duplicate broadcasts wb.stateMu.Lock() if wb.lastState == state && wb.lastSSID == ssid { wb.stateMu.Unlock() return } previousState := wb.lastState wb.lastState = state wb.lastSSID = ssid wb.stateMu.Unlock() event := NewStateChangeEvent(state, ssid, previousState) wb.broadcast(event) } // BroadcastSignalUpdate broadcasts a signal update event to all clients func (wb *WifiBroadcaster) BroadcastSignalUpdate(ssid string, signal, dbm int) { event := NewSignalUpdateEvent(ssid, signal, dbm) wb.broadcast(event) } // broadcast sends an event to all connected clients func (wb *WifiBroadcaster) broadcast(event WifiEvent) { // Get list of clients with read lock wb.clientsMu.RLock() clients := make([]*websocket.Conn, 0, len(wb.clients)) for client := range wb.clients { clients = append(clients, client) } wb.clientsMu.RUnlock() // Broadcast to all clients for _, client := range clients { err := client.WriteJSON(event) if err != nil { log.Printf("Erreur lors de l'envoi WebSocket WiFi: %v", err) client.Close() wb.UnregisterClient(client) } } } // networksEqual compares two network slices for equality func networksEqual(a, b []models.WiFiNetwork) bool { if len(a) != len(b) { return false } for i := range a { if a[i].SSID != b[i].SSID || a[i].Signal != b[i].Signal { return false } } return true }