Compare commits

...

8 Commits

Author SHA1 Message Date
Mohamed BEN HAMMOUDA 7ed6091331 (backend) add OpenShift-compatible recording download endpoint
Implement a Django-based download endpoint that streams recording files
2026-08-20 16:53:58 +02:00
lebaudantoine 87dbd8069d ⬆️(frontend) upgrade livekit-client and @livekit/components-react
Bump livekit-client 2.20.0 -> 2.21.0 and
@livekit/components-react 2.9.21 -> 2.9.23. Patch/minor upgrade
with data streams v2, data channel flow control and reliability
fixes, iOS Safari double-playback fix, and a build-time type
resolution fix for `skipLibCheck: false`.

No breaking API changes.
2026-08-20 11:08:27 +02:00
lebaudantoine c7420c59a3 ⬆️(frontend) upgrade posthog-js from 1.395.0 to 1.404.1
Minor version upgrade with bug fixes and internal improvements. No
breaking changes, API deprecations or required actions on our side.

Highlights:

* Better handling of `sendBeacon` quota rejections so events are no
  longer silently dropped.
* Improved error catching for synchronous throws from a
  monkey-patched `window.fetch`.
* Source map improvements for easier debugging.
2026-08-20 11:08:27 +02:00
lebaudantoine f46babfcbd ⬆️(frontend) upgrade i18next and react-i18next patch versions
Bump i18next from 26.3.1 to 26.3.6 and react-i18next from 17.0.8 to
17.0.10. Both are patch upgrades with bug fixes and no breaking
changes, requiring no code modifications on our side.

i18next:

* 26.3.6: widen the optional TypeScript peer dependency range to
  include v7.
* 26.3.1: fix a regression in type definitions related to
  `keyPrefix` to prevent incorrect type pollution.

react-i18next:

* 17.0.10: improve warning messages for `useTranslation` and
  `Trans` to help debug common issues, especially in monorepos.
  Add a new development-only warning when `useTranslation`
  suspends while loading translations.
* 17.0.9: add TypeScript 7 support and fix related type-checking
  issues with the `<Trans>` component.
2026-08-20 11:08:27 +02:00
lebaudantoine 7c465f2148 ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
Update sqlparse to 0.6.0 to pick up fixes for the following high
severity CVEs, all reported against versions prior to 0.6.0:

* CVE-2026-54284: https://avd.aquasec.com/nvd/cve-2026-54284
* CVE-2026-59893: https://avd.aquasec.com/nvd/cve-2026-59893
* CVE-2026-71491: https://avd.aquasec.com/nvd/cve-2026-71491
2026-08-19 13:30:25 +02:00
lebaudantoine d005f202c6 (backend) accept form-urlencoded on the user token endpoint
Accept `application/x-www-form-urlencoded` requests on the user
token endpoint, in addition to the existing JSON support.

This aligns the endpoint with the OAuth 2.0 specification for token
endpoint requests (RFC 6749, sections 3.2 and 4.4.2), so standard
OAuth 2.0 client libraries can call it without any customization,
while keeping backward compatibility with existing JSON clients.
2026-08-19 13:30:25 +02:00
chaitanyaphatak a82023f8b0 📝(docs) fix minor typos in comments and docstrings 2026-08-17 10:30:16 +02:00
lebaudantoine cc9dae66db 🔖(minor) bump release to 1.27.0 2026-08-14 15:22:06 +02:00
22 changed files with 484 additions and 93 deletions
+18
View File
@@ -8,6 +8,24 @@ and this project adheres to
## [Unreleased]
### Added
- ✨(backend) add OpenShift-compatible recording download endpoint
### Changed
- ✨(backend) accept form-urlencoded on the user token endpoint
- ⬆️(frontend) upgrade i18next and react-i18next patch versions
- ⬆️(frontend) upgrade posthog-js from 1.395.0 to 1.404.1
- ⬆️(frontend) upgrade livekit-client and @livekit/components-react
### Fixed
- 📝(docs) fix minor typos in comments and docstrings
- ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
## [1.27.0] - 2026-08-14
### Changed
- 🔥(frontend) drop unused vendored ConnectionObserver
+1 -1
View File
@@ -4,7 +4,7 @@
Security is very important to us.
If you have any issue regarding security, please disclose the information responsibly submiting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at visio@numerique.gouv.fr
If you have any issue regarding security, please disclose the information responsibly by submitting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at visio@numerique.gouv.fr
We appreciate your effort to make Visio more secure.
+27
View File
@@ -50,10 +50,24 @@ paths:
The application must be authorized for the user's email domain.
The returned token expires after a configured duration and must be refreshed by calling this endpoint again.
Request parameters may be sent either as "application/x-www-form-urlencoded"
(as specified by RFC 6749 for OAuth 2.0 token endpoints) or as "application/json".
operationId: generateToken
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/TokenRequest'
examples:
tokenRequest:
summary: Request token for user delegation
value:
client_id: "550e8400-e29b-41d4-a716-446655440000"
client_secret: "1234567890abcdefghijklmnopqrstuvwxyz"
grant_type: "client_credentials"
scope: "user@example.com"
application/json:
schema:
$ref: '#/components/schemas/TokenRequest'
@@ -117,6 +131,19 @@ paths:
summary: Domain not authorized
value:
error: "This application is not authorized for this email domain."
'415':
description: |
Unsupported media type. The request body must be sent as
"application/x-www-form-urlencoded" or "application/json".
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
unsupportedMediaType:
summary: Unsupported request content type
value:
detail: 'Unsupported media type "text/plain" in request.'
/rooms:
get:
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.26.0"
version = "1.27.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.6.7",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "agents"
version = "1.26.0"
version = "1.27.0"
source = { virtual = "." }
dependencies = [
{ name = "livekit-agents" },
+91 -1
View File
@@ -12,12 +12,13 @@ from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.files.storage import default_storage
from django.db import IntegrityError, transaction
from django.db.models import Q
from django.http import Http404
from django.http import Http404, StreamingHttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from botocore.exceptions import BotoCoreError, ClientError
from django_filters import rest_framework as django_filters
from rest_framework import (
decorators,
@@ -113,6 +114,14 @@ from .feature_flag import FeatureFlag
logger = getLogger(__name__)
class StorageUnavailable(drf_exceptions.APIException):
"""Exception raised when recording storage cannot be reached."""
status_code = drf_status.HTTP_503_SERVICE_UNAVAILABLE
default_detail = "The recording storage is temporarily unavailable."
default_code = "storage_unavailable"
class NestedGenericViewSet(viewsets.GenericViewSet):
"""
A generic Viewset aims to be used in a nested route context.
@@ -1220,6 +1229,87 @@ class RecordingViewSet(
return drf_response.Response("authorized", headers=request.headers, status=200)
@decorators.action(detail=False, methods=["get"], url_path="download")
def media_download(self, request, *args, **kwargs):
"""
Stream a recording file directly from S3 to the browser.
This endpoint is registered at /media/recordings/<uuid>.<ext> in core/urls.py
and handles the download flow on OpenShift where the Nginx auth_request
mechanism is not available.
Django acts as a streaming proxy: bytes flow from S3 (internal cluster URL)
directly to the client response. This avoids any page navigation or redirect,
so the browser triggers a native download without leaving the current page.
Access control mirrors media_auth: the user must have the "retrieve" ability
on the recording and the recording must be in a saved state.
"""
recording_id = self.kwargs.get("recording_id")
extension = self.kwargs.get("extension")
if extension not in [file.value for file in FileExtension]:
raise drf_exceptions.ValidationError({"detail": "Unsupported extension."})
try:
recording = models.Recording.objects.get(id=recording_id)
except models.Recording.DoesNotExist as e:
raise drf_exceptions.NotFound("No recording found for this id.") from e
if extension != recording.extension:
raise drf_exceptions.NotFound("No recording found with this extension.")
abilities = recording.get_abilities(request.user)
if not abilities["retrieve"]:
logger.debug(
"User '%s' lacks permission for recording download", request.user.id
)
raise drf_exceptions.PermissionDenied()
if not recording.is_saved:
logger.debug("Recording '%s' has not been saved", recording)
raise drf_exceptions.PermissionDenied()
s3_client = default_storage.connection.meta.client
try:
s3_object = s3_client.get_object(
Bucket=default_storage.bucket_name,
Key=recording.key,
)
except ClientError as error:
error_code = error.response.get("Error", {}).get("Code")
if error_code in {"404", "NoSuchKey", "NoSuchObject"}:
raise drf_exceptions.NotFound(
"No recording file found in storage."
) from error
logger.exception("Unable to retrieve recording from storage")
raise StorageUnavailable() from error
except BotoCoreError as error:
logger.exception("Unable to retrieve recording from storage")
raise StorageUnavailable() from error
content_type = s3_object.get("ContentType") or "video/mp4"
content_length = s3_object.get("ContentLength")
filename = f"{recording_id}.{extension}"
body = s3_object["Body"]
def stream():
try:
yield from body.iter_chunks(chunk_size=65536)
finally:
body.close()
streaming_response = StreamingHttpResponse(
stream(),
content_type=content_type,
)
streaming_response["Content-Disposition"] = f'attachment; filename="{filename}"'
if content_length:
streaming_response["Content-Length"] = content_length
return streaming_response
# pylint: disable=too-many-public-methods
class FileViewSet(
@@ -12,6 +12,9 @@ from rest_framework import decorators, mixins, viewsets
from rest_framework import (
exceptions as drf_exceptions,
)
from rest_framework import (
parsers as drf_parsers,
)
from rest_framework import (
response as drf_response,
)
@@ -41,6 +44,7 @@ class ApplicationViewSet(viewsets.ViewSet):
methods=["post"],
url_path="token",
url_name="token",
parser_classes=[drf_parsers.FormParser, drf_parsers.JSONParser],
)
@FeatureFlag.require("application")
def generate_jwt_access_token(self, request, *args, **kwargs):
@@ -3,8 +3,12 @@ Test recordings API endpoints in the Meet core app: retrieve.
"""
import random
from unittest import mock
from django.core.files.storage import default_storage
import pytest
from botocore.exceptions import ClientError, EndpointConnectionError
from freezegun import freeze_time
from rest_framework.test import APIClient
@@ -14,6 +18,11 @@ from ...models import RecordingStatusChoices
pytestmark = pytest.mark.django_db
def media_recording_url(recording):
"""Return the direct media URL for a recording."""
return f"/media/recordings/{recording.id!s}.{recording.extension}"
def test_api_recording_retrieve_anonymous():
"""Anonymous users should not be able to retrieve recordings."""
recording = RecordingFactory()
@@ -25,6 +34,9 @@ def test_api_recording_retrieve_anonymous():
"detail": "Authentication credentials were not provided."
}
response = client.get(media_recording_url(recording))
assert response.status_code == 401
def test_api_recording_retrieve_authenticated():
"""Authenticated users without access receive 404 when requesting recordings.
@@ -44,6 +56,9 @@ def test_api_recording_retrieve_authenticated():
assert response.status_code == 404
assert response.json() == {"detail": "No Recording matches the given query."}
response = client.get(media_recording_url(recording))
assert response.status_code == 403
def test_api_recording_retrieve_members():
"""
@@ -63,6 +78,11 @@ def test_api_recording_retrieve_members():
"detail": "You do not have permission to perform this action."
}
recording.status = RecordingStatusChoices.SAVED
recording.save(update_fields=["status"])
response = client.get(media_recording_url(recording))
assert response.status_code == 403
def test_api_recording_retrieve_administrators(settings):
"""A user who is an administrator of a recording should be able to retrieve it."""
@@ -137,6 +157,78 @@ def test_api_recording_retrieve_owners(settings):
}
@pytest.mark.parametrize("role", ["owner", "administrator"])
def test_api_recording_media_download_authorized(role):
"""Owners and administrators can download a saved recording."""
user = UserFactory()
recording = RecordingFactory(status=RecordingStatusChoices.SAVED)
UserRecordingAccessFactory(recording=recording, user=user, role=role)
client = APIClient()
client.force_login(user)
body = mock.Mock(iter_chunks=mock.Mock(return_value=[b"recording content"]))
s3_object = {
"Body": body,
"ContentType": "video/mp4",
"ContentLength": len(b"recording content"),
}
with mock.patch.object(
default_storage.connection.meta.client,
"get_object",
return_value=s3_object,
) as get_object:
response = client.get(media_recording_url(recording))
assert response.status_code == 200
get_object.assert_called_once_with(
Bucket=default_storage.bucket_name,
Key=recording.key,
)
assert response["Content-Type"] == "video/mp4"
assert response["Content-Disposition"] == (
f'attachment; filename="{recording.id}.{recording.extension}"'
)
assert b"".join(response.streaming_content) == b"recording content"
body.close.assert_called_once_with()
def test_api_recording_media_download_missing_object():
"""A missing recording object should return 404."""
user = UserFactory()
recording = RecordingFactory(status=RecordingStatusChoices.SAVED)
UserRecordingAccessFactory(recording=recording, user=user, role="owner")
client = APIClient()
client.force_login(user)
error = ClientError(
{"Error": {"Code": "NoSuchKey", "Message": "The object does not exist."}},
"GetObject",
)
with mock.patch.object(
default_storage.connection.meta.client, "get_object", side_effect=error
):
response = client.get(media_recording_url(recording))
assert response.status_code == 404
def test_api_recording_media_download_storage_unavailable():
"""A storage connection failure should return 503."""
user = UserFactory()
recording = RecordingFactory(status=RecordingStatusChoices.SAVED)
UserRecordingAccessFactory(recording=recording, user=user, role="owner")
client = APIClient()
client.force_login(user)
error = EndpointConnectionError(endpoint_url="https://storage.test")
with mock.patch.object(
default_storage.connection.meta.client, "get_object", side_effect=error
):
response = client.get(media_recording_url(recording))
assert response.status_code == 503
@freeze_time("2023-01-15 12:00:00")
def test_api_recording_retrieve_compute_expiration_date_correctly(settings):
"""Test that the API returns the correct expiration date for a non-expired recording."""
@@ -5,6 +5,7 @@ Tests for external API /token endpoint
# pylint: disable=W0621
from unittest import mock
from urllib.parse import urlencode
import jwt
import pytest
@@ -88,6 +89,155 @@ def test_api_applications_generate_token_success(settings):
}
def test_api_applications_generate_token_form_urlencoded(settings):
"""The token endpoint should accept "application/x-www-form-urlencoded"
requests, as mandated by RFC 6749 (sections 3.2 and 4.4.2) for OAuth 2.0
token endpoints, so that standard OAuth 2.0 client libraries work
out of the box."""
UserFactory(email="user@example.com")
application = ApplicationFactory(
is_active=True,
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
)
plain_secret = "test-secret-123"
application.client_secret = plain_secret
application.save()
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
(
f"client_id={application.client_id}"
f"&client_secret={plain_secret}"
"&grant_type=client_credentials"
"&scope=user%40example.com"
),
content_type="application/x-www-form-urlencoded",
)
assert response.status_code == 200
assert "access_token" in response.data
response.data.pop("access_token")
assert response.data == {
"token_type": "Bearer",
"expires_in": settings.APPLICATION_JWT_EXPIRATION_SECONDS,
"scope": "rooms:list rooms:create",
}
def test_api_applications_generate_token_form_urlencoded_invalid_credentials():
"""Invalid credentials sent as form-urlencoded should be parsed and
rejected with 401, proving the request body is properly decoded."""
user = UserFactory(email="user@example.com")
application = ApplicationFactory(is_active=True)
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
urlencode(
{
"client_id": application.client_id,
"client_secret": "wrong-secret",
"grant_type": "client_credentials",
"scope": user.email,
}
),
content_type="application/x-www-form-urlencoded",
)
assert response.status_code == 401
assert "Invalid credentials" in str(response.data)
def test_api_applications_generate_token_form_urlencoded_missing_fields():
"""Missing required fields in a form-urlencoded request should return
a 400 validation error, like for JSON requests."""
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
urlencode({"grant_type": "client_credentials"}),
content_type="application/x-www-form-urlencoded",
)
assert response.status_code == 400
for field in ("client_id", "client_secret", "scope"):
assert field in response.data
def test_api_applications_generate_token_form_urlencoded_invalid_grant_type():
"""An unsupported grant_type sent as form-urlencoded should return 400."""
user = UserFactory(email="user@example.com")
application = ApplicationFactory(is_active=True)
plain_secret = "test-secret-123"
application.client_secret = plain_secret
application.save()
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
urlencode(
{
"client_id": application.client_id,
"client_secret": plain_secret,
"grant_type": "authorization_code",
"scope": user.email,
}
),
content_type="application/x-www-form-urlencoded",
)
assert response.status_code == 400
assert "grant_type" in response.data
def test_api_applications_generate_token_form_urlencoded_special_characters():
"""Percent-encoded reserved characters ("&", "=", "+", "%") in the
client_secret should survive form-urlencoded decoding."""
UserFactory(email="user@example.com")
application = ApplicationFactory(
is_active=True,
scopes=[ApplicationScope.ROOMS_LIST],
)
plain_secret = "s3cr3t&with=special+chars%42"
application.client_secret = plain_secret
application.save()
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
urlencode(
{
"client_id": application.client_id,
"client_secret": plain_secret,
"grant_type": "client_credentials",
"scope": "user@example.com",
}
),
content_type="application/x-www-form-urlencoded",
)
assert response.status_code == 200
assert "access_token" in response.data
def test_api_applications_generate_token_unsupported_media_type():
"""Content types other than JSON and form-urlencoded should still be
rejected with 415 Unsupported Media Type."""
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
"client_id=x&client_secret=y&grant_type=client_credentials&scope=a@b.co",
content_type="text/plain",
)
assert response.status_code == 415
def test_api_applications_generate_token_invalid_client_id():
"""Invalid client_id should return 401."""
user = UserFactory(email="user@example.com")
+7
View File
@@ -60,6 +60,13 @@ urlpatterns = [
]
),
),
# The request is handled by RecordingViewSet.media_download which validates
# access rights and streams the recording file directly from MinIO to the browser.
path(
"media/recordings/<uuid:recording_id>.<slug:extension>",
viewsets.RecordingViewSet.as_view({"get": "media_download"}),
name="recording_media_download",
),
]
if settings.EXTERNAL_API_ENABLED:
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project]
name = "meet"
version = "1.26.0"
version = "1.27.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+4 -4
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.26.0"
version = "1.27.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -2252,11 +2252,11 @@ wheels = [
[[package]]
name = "sqlparse"
version = "0.5.5"
version = "0.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
{ url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" },
]
[[package]]
+70 -67
View File
@@ -1,18 +1,18 @@
{
"name": "meet",
"version": "1.26.0",
"version": "1.27.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.26.0",
"version": "1.27.0",
"dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11",
"@fontsource/opendyslexic": "5.2.5",
"@libreaudio/la-call": "0.1.4",
"@livekit/components-react": "2.9.21",
"@livekit/components-react": "2.9.23",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.14",
@@ -24,17 +24,17 @@
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.33.2",
"i18next": "26.3.1",
"i18next": "26.3.6",
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.21.0",
"posthog-js": "1.404.1",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
"react-dom": "18.3.1",
"react-i18next": "17.0.8",
"react-i18next": "17.0.10",
"react-stately": "3.48.0",
"use-sound": "5.0.0",
"valtio": "2.3.2",
@@ -742,28 +742,28 @@
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.11"
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
"@floating-ui/core": "^1.7.5",
"@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT"
},
"node_modules/@fontsource-variable/atkinson-hyperlegible-next": {
@@ -966,12 +966,12 @@
"license": "GPL-3.0+"
},
"node_modules/@livekit/components-core": {
"version": "0.12.13",
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.13.tgz",
"integrity": "sha512-DQmi84afHoHjZ62wm8y+XPNIDHTwFHAltjd3lmyXj8UZHOY7wcza4vFt1xnghJOD5wLRY58L1dkAgAw59MgWvw==",
"version": "0.12.14",
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.14.tgz",
"integrity": "sha512-6OKP/1Ok2fCZewDLKd3SzaTb7KvfZl/6hjggI+TgUdhp0Y7HBg3+tHA7YmhZRc0BO8wIyVy4VgTPn7qkLHt2nQ==",
"license": "Apache-2.0",
"dependencies": {
"@floating-ui/dom": "1.7.4",
"@floating-ui/dom": "1.7.6",
"loglevel": "1.9.1",
"rxjs": "7.8.2"
},
@@ -979,17 +979,17 @@
"node": ">=18"
},
"peerDependencies": {
"livekit-client": "^2.17.2",
"livekit-client": "^2.20.1",
"tslib": "^2.6.2"
}
},
"node_modules/@livekit/components-react": {
"version": "2.9.21",
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.21.tgz",
"integrity": "sha512-6hU9VucJJL+gAhilNGe4MBCDCZVk64qyjP9Ck86krvOIdVU76WeWksddg1MYUP10AlUwwrfD7davz41pJTcMJw==",
"version": "2.9.23",
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.23.tgz",
"integrity": "sha512-clO+0g/u3YBpuOvnAjUSAJBH/o7w+RpUAseswjiSML9rHrXlTUhwBNm8LPk+qN5GOU3A6lzsNnFYVpUHUBVOkg==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/components-core": "0.12.13",
"@livekit/components-core": "0.12.14",
"clsx": "2.1.1",
"events": "^3.3.0",
"jose": "^6.0.12",
@@ -999,8 +999,8 @@
"node": ">=18"
},
"peerDependencies": {
"@livekit/krisp-noise-filter": "^0.2.12 || ^0.3.0",
"livekit-client": "^2.18.2",
"@livekit/krisp-noise-filter": "^0.2.12 || ^0.3.0 || ^0.4.0",
"livekit-client": "^2.20.1",
"react": ">=18",
"react-dom": ">=18",
"tslib": "^2.6.2"
@@ -1027,9 +1027,9 @@
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.46.6",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.46.6.tgz",
"integrity": "sha512-upzlHP1vi/kZ/QqALZTFskQ0ifqc2f15RKucHYOsIHJsaXvEYanG75mAb7o+Yomfs4XhQ4BaRsdY+TFHXpaqrg==",
"version": "1.50.4",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.50.4.tgz",
"integrity": "sha512-L1uggNQAqyY21smQY8AllyOYbcv9Me9TaxwuLytL1R8ck9nbYPmQLNwEDi3pOFGAMa5F8I2nUi2Jc59W5awxlA==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
@@ -1720,18 +1720,18 @@
"license": "MIT"
},
"node_modules/@posthog/core": {
"version": "1.39.5",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.39.5.tgz",
"integrity": "sha512-M8Imv7ZmmrT6derMOFXhX3c5VKKO2oG6OX70ozLnNpKJV73sNIhYmRX/raa90saF7OA0YmWWJlpKje3irGAyQQ==",
"version": "1.48.3",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.48.3.tgz",
"integrity": "sha512-kwVDVvwtCTXctApA2tpnwDjDDan8LrkwCW1Wv6PABaVs/s5ahbQ9W3pvdXcqr71HCuqukSA1jp7MySczebubGg==",
"license": "MIT",
"dependencies": {
"@posthog/types": "^1.392.0"
"@posthog/types": "^1.405.0"
}
},
"node_modules/@posthog/types": {
"version": "1.392.0",
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.392.0.tgz",
"integrity": "sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==",
"version": "1.405.0",
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.405.0.tgz",
"integrity": "sha512-4rZ/taVXKQxs9Jrf7ZjlCRgrOSL69oKAgIWJQa5kRNJ6wll1UANbrJTSY+Su1e88LIG4zZVjKKKjyQHCkHdHcw==",
"license": "MIT"
},
"node_modules/@react-aria/overlays": {
@@ -4393,11 +4393,14 @@
}
},
"node_modules/core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"version": "3.50.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz",
"integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==",
"hasInstallScript": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
@@ -6400,9 +6403,9 @@
}
},
"node_modules/i18next": {
"version": "26.3.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz",
"integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==",
"version": "26.3.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz",
"integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==",
"funding": [
{
"type": "individual",
@@ -6419,7 +6422,7 @@
],
"license": "MIT",
"peerDependencies": {
"typescript": "^5 || ^6"
"typescript": "^5 || ^6 || ^7"
},
"peerDependenciesMeta": {
"typescript": {
@@ -8109,20 +8112,20 @@
"license": "MIT"
},
"node_modules/livekit-client": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.20.0.tgz",
"integrity": "sha512-RIJcpvBmOmwz3jTj3rmdY6Dzr55HrhcaJjMgY+HSmoEM+yIRyA40m7r8UKv0hnZWM3z/AYhP1q8C8ciz5UWFKQ==",
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.21.0.tgz",
"integrity": "sha512-RBUhPkV/sl1nzl8lokVlK5uATPwn0AlsudCBZXissw/kDl9yz8ac4pNJ43iPpMVoHOeBYH/BZ3vUC1adqa/zFQ==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
"@livekit/protocol": "1.46.6",
"@livekit/protocol": "1.50.4",
"events": "^3.3.0",
"jose": "^6.1.0",
"loglevel": "^1.9.2",
"sdp-transform": "^2.15.0",
"tslib": "2.8.1",
"typed-emitter": "^2.1.0",
"webrtc-adapter": "9.0.5"
"webrtc-adapter": "9.0.6"
},
"peerDependencies": {
"@types/dom-mediacapture-record": "^1"
@@ -9120,17 +9123,17 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.395.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.395.0.tgz",
"integrity": "sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==",
"license": "SEE LICENSE IN LICENSE",
"version": "1.404.1",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.404.1.tgz",
"integrity": "sha512-ck/HOMKuZ6yefUk5OX+h2s9mUWBsnu0mGDUAWjIwAmQQrloZPShzOhOWuEuZQuMnCKORBFRVGsBAuzsl66cBJA==",
"license": "(Apache-2.0 AND MIT)",
"dependencies": {
"@posthog/core": "^1.38.0",
"@posthog/types": "^1.391.1",
"core-js": "^3.38.1",
"@posthog/core": "^1.43.1",
"@posthog/types": "^1.397.0",
"core-js": "^3.49.0",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
"preact": "^10.29.2",
"preact": "^10.29.3",
"query-selector-shadow-dom": "^1.0.1",
"web-vitals": "^5.3.0"
}
@@ -9422,9 +9425,9 @@
}
},
"node_modules/react-i18next": {
"version": "17.0.8",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
"integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
"version": "17.0.10",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.10.tgz",
"integrity": "sha512-XneHftyYA774MJkkccSkZ5oKrUpCnXIPmxio3wemqrVzCRLWiGXOMbIzObrer03fNDEnm8g8R5yYls4HcE+esg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
@@ -9434,7 +9437,7 @@
"peerDependencies": {
"i18next": ">= 26.2.0",
"react": ">= 16.8.0",
"typescript": "^5 || ^6"
"typescript": "^5 || ^6 || ^7"
},
"peerDependenciesMeta": {
"react-dom": {
@@ -11645,9 +11648,9 @@
"license": "Apache-2.0"
},
"node_modules/webrtc-adapter": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.5.tgz",
"integrity": "sha512-U9vjByy/sK2OMXu5mmfuZFKTMIUQe34c0JXRO+oDrxJTsntdYT2iIFwYMOV7HhMTuktcZLGf2W1N/OcSf9ssWg==",
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.6.tgz",
"integrity": "sha512-CHbl2ZQbxx164IgWRgzJno4hWtM4tFbRam1QfI3Yxhs3w/DvqluVxVWeXs3oL5/fbGkSNLKo0Ty5MgUWceNhog==",
"license": "BSD-3-Clause",
"dependencies": {
"sdp": "^3.2.0"
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.26.0",
"version": "1.27.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -19,7 +19,7 @@
"@fontsource-variable/lexend": "5.2.11",
"@fontsource/opendyslexic": "5.2.5",
"@libreaudio/la-call": "0.1.4",
"@livekit/components-react": "2.9.21",
"@livekit/components-react": "2.9.23",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.14",
@@ -31,17 +31,17 @@
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.33.2",
"i18next": "26.3.1",
"i18next": "26.3.6",
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.21.0",
"posthog-js": "1.404.1",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
"react-dom": "18.3.1",
"react-i18next": "17.0.8",
"react-i18next": "17.0.10",
"react-stately": "3.48.0",
"use-sound": "5.0.0",
"valtio": "2.3.2",
@@ -23,7 +23,7 @@ export const ChatProvider = () => {
resetChatStore()
}, [])
// Tigger the message notification (temporary)
// Trigger the message notification (temporary)
useEffect(() => {
// TEMPORARY: This is a brittle workaround that relies on message count tracking
// due to recent LiveKit useChat changes breaking the previous implementation
@@ -44,7 +44,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
videoElement?: HTMLVideoElement
videoElementLoaded?: boolean
// Canvas containg the video processing result, of which we extract as stream.
// Canvas containing the video processing result, of which we extract as stream.
outputCanvas?: HTMLCanvasElement
outputCanvasCtx?: CanvasRenderingContext2D
@@ -55,7 +55,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
segmentationMaskCanvas?: HTMLCanvasElement
segmentationMaskCanvasCtx?: CanvasRenderingContext2D
// Mask containg the inference result.
// Mask containing the inference result.
segmentationMask?: ImageData
// The resized image of the video source.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "1.26.0",
"version": "1.27.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "1.26.0",
"version": "1.27.0",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "1.26.0",
"version": "1.27.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.26.0",
"version": "1.27.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.26.0",
"version": "1.27.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.26.0",
"version": "1.27.0",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.26.0"
version = "1.27.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
+1 -1
View File
@@ -26,7 +26,7 @@ class RecordingMetadata(BaseModel):
cloud_storage_url: Url = Field(
title="Cloud Storage URL",
description="The URL of the metadata file for speaker assignement.",
description="The URL of the metadata file for speaker assignment.",
)
started_at: AwareDatetime = Field(title="Start time of the recording to transcribe")
ended_at: AwareDatetime = Field(title="End time of the recording to transcribe")