The fetch-room URL was missing its trailing slash, which caused the
backend to issue a 301 redirect. Query parameters were being dropped
in the process, leading to incorrect requests.
Append the trailing slash so the request hits the correct endpoint
directly, without going through a redirect.
Rapid toggles could persist a stale configuration: each PATCH
replaces the full room config, and every call site built it from a
render-time snapshot. A toggle issued before the previous one
resolved therefore overwrote the newer value with an older one.
Handle the cache centrally in usePatchRoom so the next toggle always
reads an up-to-date configuration.
Skip adding the username query parameter when its value is
undefined, so the request URL no longer ends up with an
`?username=undefined` (or similar) that the backend has to handle.
Introduce a room configuration popup opened from the SDK's
CreateMeetingButton, laid out like the Google Meet "call options"
dialog: logo header, grey section bands, and a footer bar with the
close action.
Like CreatePopup, it runs in a dedicated popup window so it can
access session cookies, which would be blocked in an iframe. If the
user is not authenticated, they are redirected to login and come
back to this popup afterwards.
Permissions are enforced server-side. The room is fetched with the
user's session, and settings are only shown when the room is
administrable by this user. Since #1482 removed the
is_administrable flag from the room serializer (roles now live in
the LiveKit participant attributes, only available in-meeting),
administrability is detected here through the presence of the
`accesses` field, which the backend only serializes for
administrators and owners. The PATCH endpoint enforces the same
permissions server-side regardless.
The settings mirror the in-room Admin panel. Unlike the Admin panel,
there is no LiveKit connection here, so changes are only persisted
in the room configuration (and applied when a session starts):
participants of an ongoing session are not live-synced or notified.
Refactor CONNECTION_TEST_ROOM_MAX_AGE_SECONDS so it is no longer an
independent setting but a quantity derived from (or added on top of)
the token TTL.
This prevents a misconfiguration where the token would outlive the
delete-room callback. In that case, an attacker holding a valid
token could recreate the room after the callback fired and escape
the intended cleanup.
Some characters span multiple UTF-16 code units. Taking a naive first
index for avatar initials can split them and show a broken glyph when
the camera is off.
Optically fix initials centering with a more complex approach.
Icons inside the Switch primitive were not properly centered.
Use relative sizes for the icons and switch to a grid-based
placement strategy so they stay centered regardless of the switch
size.
_generate_title returned a lazy gettext_lazy proxy in the
recording_datetime is None branch, which json.dumps cannot
serialize.
This crashed requests.post(json=payload) with "Object of type
__proxy__ is not JSON serializable" whenever the LiveKit egress
lookup failed (started_at=None).
Force evaluation with a non-lazy method.
Add a regression test asserting the v2 payload is a real str and
is JSON-serializable when timestamps are unavailable.
The existing without_metadata test missed this: mocked
requests.post never serialized, and a lazy proxy compares equal
to its string.
Add a custom diagnostic step that reports which ICE candidate pair
was selected on the WebRTC connection, as well as all working pairs
observed during the check.
Experimental and vibe-coded for now; the output is meant to help
debugging and will likely be revisited.
Introduce a new connection test page to allow users to verify
their device and network compatibility with the application.
The feature also supports generating and downloading a detailed report
of the test results.
Currently users have no way to reliably test their connection before
joining a room. To address this, we plan to build a connection-test
page.
The testing requires a dedicated LiveKit token, issued without going
through the room API, which is tied to registered meetings, lobby
rules, and longer-lived access tokens.
Introduce a new viewset for all diagnostics-related features. The
first route issues a token for diagnostics, even for anonymous
users. Each request creates a new dedicated room so users never
share the same LiveKit room during tests. Tokens are short-lived
(default 10 minutes) to limit reuse, and the endpoint is throttled
to prevent abuse.
A Celery worker also schedules a callback that deletes the room
after a certain delay, in every case.
site.webmanifest declared no start_url, so the page that linked
it became one and an install started inside a room reopened that
room on every launch. It now declares "/", moves out of public/
and takes VITE_APP_TITLE for name and short_name, which shipped
empty and leaned on the browser falling back to the title.
The frontend Dockerfile now declares that build argument, so the
value compose.yml passes stops being dropped.
Out of precaution, also revert the previous GridLayout re-render
optimization to avoid any layout regression alongside the
CarouselLayout revert.
The useSize-based re-render optimization will be reintroduced in a
dedicated small PR and release. That will also be a good occasion to
polish the layout code along the way.
The previous optimization of the CarouselLayout was broken: the
approach did not hold in practice, and strict-mode rendering was
hiding the issue during development.
Revert the change for now and revisit the optimization later with a
sounder approach.
Add a hidden div in the DOM that reflects the current state of the
microphone and camera, so that external SIP media gateways (e.g. the
Renater one) can observe it and keep an accurate view of the media
state.
Also emit a custom event from the page whenever the microphone or
camera state changes, so external consumers can subscribe to updates
instead of polling the DOM.
Update the API so that, when a user creates a new meeting without
passing an explicit configuration, the user's persisted preferences
are applied as defaults.
This allows a user to, for example, enable the waiting room by
default on every meeting they create.
Add attributes on the User model to persist per-user preferences for
the default link access level and the default room configuration.
The frontend will let users update these preferences and then reuse
them when generating a link through the webapp.
Persisting them on the backend (rather than in application memory
only) ensures the preferences survive across sessions and devices.
Expose the default access level for rooms in the backend settings
response, so the frontend can initialize the global room preferences
UI with the current default value.
The SonarCloud gate fails on main, so every commit lands red, and
gh run list hides it: it lists only Actions workflows, and the
failure is an app check run.
Reliability rests on one bug, in test_file_service.py, which wrapped
an assertion in an except Exception re-raised through pytest.fail.
Removing it takes the rating from D to A.
Two pieces of debt ride along. The SDK callback id now comes from
crypto.getRandomValues, since it guards an endpoint with no auth. And
core/tasks gets the __init__.py that lets pylint see it, with the
debt that exposes, which is why #1533 fails lint-back.
The roomkit can now create a SIP dispatch rule before the LiveKit
webhook that used to trigger this creation is fired. In practice,
when the roomkit connects to the room, it also triggers the
webhook, leading to a duplicated dispatch rule.
Switch from "create dispatch rule" to "ensure dispatch rule exists"
semantics, so subsequent calls are idempotent and no duplicate rule
is created.
Rename the telephony service to a more descriptive name,
SIPManagementService, which clearly states what the service is used
for.
It is no longer used only by the telephony feature; the roomkit
feature also relies on it now.
Introduce a new viewset that lets the roomkit start a room even when
no WebRTC participant has joined yet.
This is a first entry point that will be extended over time with
more actions a roomkit needs to be able to trigger.
Known limitations:
* The responsibility around SIP rules is currently split between
the telephony feature and the roomkit one. This may need a
refactor later on to consolidate ownership in a single place.
* The default throttle might be too low for production usage and
will likely need to be revisited.
LiveKit was declared as an app-dev dependency, which caused it
(along with its egress) to be started whenever we ran unrelated
commands such as tests, migrate or makemigrations.
Drop that dependency and start LiveKit explicitly only when it is
actually needed, i.e. when calling run-backend.
The tests were failing when the Django settings did not disable
recording events, which was the case by default.
We do not rely on these events anymore by default, so set the
corresponding environment variable to false in the env file to make
the tests pass out of the box.
The linting workflow was unnecessarily building Docker dependencies and
creating containers multiple times. Optimize the Makefile to fix both
issues, for faster and lighter linting.
Display two initials in the Avatar whenever the participant's name
allows it, instead of a single letter.
A single initial makes it too hard to distinguish participants when
their cameras are off, especially in larger
Rework how the participant name is displayed in the participant
list to show as much of the name as possible before truncating.
When the name has to be truncated, add a tooltip so users can hover
to see the full name.
Requested by users.
Add a visual badge on participants who are not authenticated, so it
is immediately clear who could be an anonymous participant. This is
a small but explicit security signal in the participant list.
Beyond that, the badge also plays a functional role: since only
authenticated participants can be promoted or demoted, the badge
helps users see at a glance who is eligible for a role change.
Since the username refactoring, the username in the store could be
undefined when the join input was pre-filled from user.full_name,
because no keystroke was needed to populate the store.
This led to a 400 error on the request-entry endpoint whenever the
user joined without editing the pre-filled name.
Fall back to user.full_name when the store username is missing, so
the endpoint always receives a value.
Acknowledged as a somewhat wobbly fix, but ships as-is until the
underlying flow is reworked.
Listen to role changes in the admin panel and close the side panel
if the current user is demoted while it is open. Without this,
unprivileged users could still see the admin side panel until they
closed it manually.
I checked the other features that could be affected by hot role
changes; this was the only one still exposing admin-only UI after a
demotion. Everything else already handles live permission updates
correctly.
Introduce a new feature that lets a user promote one of the
authenticated participants of the meeting to a role with additional
privileges.
Known limitations:
* Only authenticated participants can be promoted, but there is no
visual indicator yet distinguishing authenticated from anonymous
participants. This will be added in a follow-up commit.
* The resource_access data fetched in the initial API call becomes
stale after a promotion. It is not currently used in the product,
so this is not visible, but it should either be refreshed later
or removed from the initial fetch.
* Demoting a promoted user turns them into a member, which is still
a privileged role. This is a deliberate choice until we introduce
finer-grained tuning of participant roles.
Extract the logic that closes the side panel into a utility function
declared at the store module level, as recommended by Valtio.
This avoids re-creating the function on every render and prevents
extra re-renders in components that use it.
The API serializer was too restrictive on the `sub` field, expecting
a UUID. This worked in our development and production setups because
our Keycloak is configured to emit UUID subs, but it broke for other
providers.
Per the OIDC spec and the DB model, `sub` can be any string. Align
the serializer with this and accept arbitrary string values.
Fixes#1525.
Uppercase the initials rendered in the Avatar so their vertical
centering stays consistent.
With lowercase letters, the initials were slightly shifted toward
the bottom of the Avatar, which broke the alignment.
The is_administrable flag was previously read from the room API
response through the room serializer, giving the frontend static
information about the user's rights.
Refactor the frontend so it derives this flag from the participant
role carried in the participant metadata instead.
Two benefits:
* The flag now updates live along with the participant
attributes/metadata, so role changes are reflected immediately.
* It removes the duplication between the API response and the
metadata, which both used to determine the user's capabilities.