diff --git a/pkg/client/websocket.go b/pkg/client/websocket.go index 71d5018..36be370 100644 --- a/pkg/client/websocket.go +++ b/pkg/client/websocket.go @@ -563,7 +563,11 @@ func (ws *WebSocketClient) handleEvent(event *models.WebSocketEvent) { if !hasKnownEvent && handlers.OnUnknownEvent != nil { handlers.OnUnknownEvent(event) } else if !hasKnownEvent { - ws.logger.Printf("Received unknown event types: %v", eventTypes) + // Log the actual unmodeled element names (e.g. nowSelectionUpdated) + // rather than an empty list; skip frames that carry no child events. + if names := event.UnknownEventNames(); len(names) > 0 { + ws.logger.Printf("Received unhandled event types: %v", names) + } } } diff --git a/pkg/models/websocket.go b/pkg/models/websocket.go index d4378a9..30ec8a1 100644 --- a/pkg/models/websocket.go +++ b/pkg/models/websocket.go @@ -102,7 +102,28 @@ type WebSocketEvent struct { ErrorUpdated *ErrorUpdatedEvent `xml:"errorUpdated,omitempty"` RecentsUpdated *RecentsUpdatedEvent `xml:"recentsUpdated,omitempty"` LanguageUpdated *LanguageUpdatedEvent `xml:"languageUpdated,omitempty"` - Timestamp time.Time `json:"timestamp"` // Added by client for tracking + // UnknownElements captures children we don't model yet (e.g. + // nowSelectionUpdated), so callers can log them by name instead of an + // empty list when no known event matched. + UnknownElements []UnknownElement `xml:",any"` + Timestamp time.Time `json:"timestamp"` // Added by client for tracking +} + +// UnknownElement records the tag name of an child element that the +// WebSocketEvent struct does not (yet) model. +type UnknownElement struct { + XMLName xml.Name +} + +// UnknownEventNames returns the tag names of any unmodeled children, +// for diagnostic logging. +func (e *WebSocketEvent) UnknownEventNames() []string { + names := make([]string, 0, len(e.UnknownElements)) + for _, u := range e.UnknownElements { + names = append(names, u.XMLName.Local) + } + + return names } // GetEvents returns all events present in this WebSocket event diff --git a/pkg/models/websocket_test.go b/pkg/models/websocket_test.go index 8535880..e7d9d7b 100644 --- a/pkg/models/websocket_test.go +++ b/pkg/models/websocket_test.go @@ -540,3 +540,41 @@ func TestCreateMockWebSocketEvent(t *testing.T) { t.Errorf("Event types don't match expected values") } } + +// TestParseWebSocketEvent_UnknownElements verifies that an envelope +// whose only child is an unmodeled element (e.g. nowSelectionUpdated, observed +// on real SoundTouch 10 firmware) parses with no known event types but with +// the element captured by name, so callers can log something useful instead +// of an empty list. +func TestParseWebSocketEvent_UnknownElements(t *testing.T) { + raw := []byte(`Willy`) + + event, err := ParseWebSocketEvent(raw) + if err != nil { + t.Fatalf("ParseWebSocketEvent: %v", err) + } + + if got := event.GetEventTypes(); len(got) != 0 { + t.Errorf("expected no known event types, got %v", got) + } + + names := event.UnknownEventNames() + if len(names) != 1 || names[0] != "nowSelectionUpdated" { + t.Errorf("expected [nowSelectionUpdated], got %v", names) + } +} + +// TestParseWebSocketEvent_KnownEventNoUnknowns confirms a modeled event is not +// also captured as an unknown element. +func TestParseWebSocketEvent_KnownEventNoUnknowns(t *testing.T) { + raw := []byte(``) + + event, err := ParseWebSocketEvent(raw) + if err != nil { + t.Fatalf("ParseWebSocketEvent: %v", err) + } + + if names := event.UnknownEventNames(); len(names) != 0 { + t.Errorf("expected no unknown elements for a modeled event, got %v", names) + } +}