mirror of
https://github.com/suitenumerique/meet.git
synced 2026-09-09 17:05:56 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0ab7f191f | |||
| 455b315dbb | |||
| 3089b03062 | |||
| 60febb3b57 | |||
| bf76ab1ddf |
@@ -10,8 +10,15 @@ and this project adheres to
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
|
||||||
- 🔒️(backend) enforce display name setting on rename API
|
- 🔒️(backend) enforce display name setting on rename API
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 📈(frontend) include LiveKit SIDs in the connection analytics event
|
||||||
|
- 🔇(backend) silence expected 401 warnings on /me
|
||||||
|
- 🔇(backend) silence noisy request summary info logs
|
||||||
|
|
||||||
## [1.31.0] - 2026-09-08
|
## [1.31.0] - 2026-09-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""Logging filters for the core application."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class SilenceExpected401(logging.Filter):
|
||||||
|
"""Drop the expected 401 from anonymous hits on the /me endpoint.
|
||||||
|
|
||||||
|
The frontend probes `/users/me/` to check authentication; a 401 for
|
||||||
|
anonymous users is normal, not a warning worth logging.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
"""Return False for a 401 on a silenced path, True otherwise."""
|
||||||
|
if getattr(record, "status_code", None) != 401:
|
||||||
|
return True
|
||||||
|
|
||||||
|
request = getattr(record, "request", None)
|
||||||
|
path = getattr(request, "path", None)
|
||||||
|
if not path:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return path not in settings.LOGGING_SILENCED_401_PATHS
|
||||||
@@ -52,12 +52,6 @@ class InvalidPayloadError(LiveKitWebhookError):
|
|||||||
status_code = 400
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedEventTypeError(LiveKitWebhookError):
|
|
||||||
"""Unsupported event type."""
|
|
||||||
|
|
||||||
status_code = 422
|
|
||||||
|
|
||||||
|
|
||||||
class ActionFailedError(LiveKitWebhookError):
|
class ActionFailedError(LiveKitWebhookError):
|
||||||
"""Webhook action fails to process or complete."""
|
"""Webhook action fails to process or complete."""
|
||||||
|
|
||||||
@@ -74,6 +68,7 @@ class LiveKitWebhookEventType(Enum):
|
|||||||
# Participant events
|
# Participant events
|
||||||
PARTICIPANT_JOINED = "participant_joined"
|
PARTICIPANT_JOINED = "participant_joined"
|
||||||
PARTICIPANT_LEFT = "participant_left"
|
PARTICIPANT_LEFT = "participant_left"
|
||||||
|
PARTICIPANT_CONNECTION_ABORTED = "participant_connection_aborted"
|
||||||
|
|
||||||
# Track events
|
# Track events
|
||||||
TRACK_PUBLISHED = "track_published"
|
TRACK_PUBLISHED = "track_published"
|
||||||
@@ -153,10 +148,13 @@ class LiveKitEventsService:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
webhook_type = LiveKitWebhookEventType(data.event)
|
webhook_type = LiveKitWebhookEventType(data.event)
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
raise UnsupportedEventTypeError(
|
logger.warning(
|
||||||
f"Unknown webhook type: {data.event}"
|
"Ignoring unknown LiveKit webhook event type '%s' for room '%s'",
|
||||||
) from e
|
data.event,
|
||||||
|
room_name,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# Handle according to received webhook type
|
# Handle according to received webhook type
|
||||||
handler = self._webhook_handlers.get(webhook_type.value)
|
handler = self._webhook_handlers.get(webhook_type.value)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def test_invalid_payload(client, auth_token, mock_livekit_config):
|
|||||||
|
|
||||||
|
|
||||||
def test_unknown_event_type(client, mock_livekit_config):
|
def test_unknown_event_type(client, mock_livekit_config):
|
||||||
"""Should return 422 for unknown event type."""
|
"""Should acknowledge (200) an unknown event type rather than reject it."""
|
||||||
event_data = json.dumps({"event": "unknown_event_type"})
|
event_data = json.dumps({"event": "unknown_event_type"})
|
||||||
|
|
||||||
# Generate auth token for this specific payload
|
# Generate auth token for this specific payload
|
||||||
@@ -112,10 +112,8 @@ def test_unknown_event_type(client, mock_livekit_config):
|
|||||||
HTTP_AUTHORIZATION=auth_token,
|
HTTP_AUTHORIZATION=auth_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 422
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == {"status": "success"}
|
||||||
"status": "error",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(LiveKitEventsService, "_handle_room_finished")
|
@mock.patch.object(LiveKitEventsService, "_handle_room_finished")
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from core.services.livekit_events import (
|
|||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
InvalidPayloadError,
|
InvalidPayloadError,
|
||||||
LiveKitEventsService,
|
LiveKitEventsService,
|
||||||
UnsupportedEventTypeError,
|
|
||||||
api,
|
api,
|
||||||
)
|
)
|
||||||
from core.services.lobby import LobbyService
|
from core.services.lobby import LobbyService
|
||||||
@@ -665,22 +664,27 @@ def test_receive_missing_auth(service):
|
|||||||
|
|
||||||
|
|
||||||
@mock.patch.object(api.WebhookReceiver, "receive")
|
@mock.patch.object(api.WebhookReceiver, "receive")
|
||||||
def test_receive_unsupported_event(mock_receive, service):
|
def test_receive_unknown_event_is_acknowledged(mock_receive, service, caplog):
|
||||||
"""Should raise LiveKitWebhookError for unsupported events."""
|
"""Unknown event types are logged and ignored, not rejected.
|
||||||
|
|
||||||
|
LiveKit adds event types over time and does not retry 4xx responses, so
|
||||||
|
raising here would silently drop the event.
|
||||||
|
"""
|
||||||
mock_request = mock.MagicMock()
|
mock_request = mock.MagicMock()
|
||||||
mock_request.headers = {"Authorization": "test_token"}
|
mock_request.headers = {"Authorization": "test_token"}
|
||||||
mock_request.body = b"{}"
|
mock_request.body = b"{}"
|
||||||
|
|
||||||
# Mock returned data with unsupported event type
|
|
||||||
mock_data = mock.MagicMock()
|
mock_data = mock.MagicMock()
|
||||||
mock_data.room.name = str(uuid.uuid4())
|
mock_data.room.name = str(uuid.uuid4())
|
||||||
mock_data.event = "unsupported_event"
|
mock_data.event = "some_future_event"
|
||||||
mock_receive.return_value = mock_data
|
mock_receive.return_value = mock_data
|
||||||
|
|
||||||
with pytest.raises(
|
with caplog.at_level("WARNING", logger="core.services.livekit_events"):
|
||||||
UnsupportedEventTypeError, match="Unknown webhook type: unsupported_event"
|
service.receive(mock_request) # must not raise
|
||||||
):
|
|
||||||
service.receive(mock_request)
|
assert "Ignoring unknown LiveKit webhook event type 'some_future_event'" in (
|
||||||
|
caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(api.WebhookReceiver, "receive")
|
@mock.patch.object(api.WebhookReceiver, "receive")
|
||||||
|
|||||||
@@ -1110,6 +1110,12 @@ class Base(Configuration):
|
|||||||
environ_prefix=None,
|
environ_prefix=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LOGGING_SILENCED_401_PATHS = values.ListValue(
|
||||||
|
default=["/api/v1.0/users/me/"],
|
||||||
|
environ_name="LOGGING_SILENCED_401_PATHS",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
# We want to make it easy to log to console but by default we log production
|
# We want to make it easy to log to console but by default we log production
|
||||||
# to Sentry and don't want to log to console.
|
# to Sentry and don't want to log to console.
|
||||||
@@ -1122,10 +1128,16 @@ class Base(Configuration):
|
|||||||
"style": "{",
|
"style": "{",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"filters": {
|
||||||
|
"silence_expected_401": {
|
||||||
|
"()": "core.logging_filters.SilenceExpected401",
|
||||||
|
},
|
||||||
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"console": {
|
"console": {
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "simple",
|
"formatter": "simple",
|
||||||
|
"filters": ["silence_expected_401"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
# Override root logger to send it to console
|
# Override root logger to send it to console
|
||||||
@@ -1136,6 +1148,13 @@ class Base(Configuration):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
|
"request.summary": {
|
||||||
|
"level": values.Value(
|
||||||
|
"WARNING",
|
||||||
|
environ_name="LOGGING_LEVEL_REQUEST_SUMMARY",
|
||||||
|
environ_prefix="",
|
||||||
|
)
|
||||||
|
},
|
||||||
"core": {
|
"core": {
|
||||||
"handlers": ["console"],
|
"handlers": ["console"],
|
||||||
"level": values.Value(
|
"level": values.Value(
|
||||||
|
|||||||
@@ -71,20 +71,30 @@ export const ConnectionObserver = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAnalyticsEnabled) return
|
if (!isAnalyticsEnabled) return
|
||||||
|
|
||||||
const handleConnection = () => {
|
const handleConnection = async () => {
|
||||||
// Preserve original connection timestamp across reconnections to measure
|
// Preserve original connection timestamp across reconnections to measure
|
||||||
// total session duration from first connect to final disconnect.
|
// total session duration from first connect to final disconnect.
|
||||||
if (connectionStartTimeRef.current != null) return
|
if (connectionStartTimeRef.current != null) return
|
||||||
connectionStartTimeRef.current = Date.now()
|
connectionStartTimeRef.current = Date.now()
|
||||||
void captureMediaEvent('connection-event', {})
|
const participantSid = room.localParticipant.sid
|
||||||
|
const roomSid = await room.getSid().catch(() => undefined)
|
||||||
|
void captureMediaEvent('connection-event', {
|
||||||
|
livekit_room_sid: roomSid,
|
||||||
|
livekit_participant_sid: participantSid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReconnect = () => {
|
const handleReconnect = () => {
|
||||||
captureEvent('reconnect-event')
|
captureEvent('reconnect-event')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReconnected = () => {
|
const handleReconnected = async () => {
|
||||||
captureEvent('reconnected-event')
|
const participantSid = room.localParticipant.sid
|
||||||
|
const roomSid = await room.getSid().catch(() => undefined)
|
||||||
|
captureEvent('reconnected-event', {
|
||||||
|
livekit_room_sid: roomSid,
|
||||||
|
livekit_participant_sid: participantSid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSignalingConnect = () => {
|
const handleSignalingConnect = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user