Reset the chat state on the first render of the ChatProvider, to
make sure no chat messages from a previous room leak into the new
one.
This covers SPA navigations where the user switches from one room
to another without a full page reload.
Introduce a tripwire component that listens to
RoomEvent.ActiveSpeakersChanged imperatively and forces a single
re-render of its host only when an active speaker has none of their
tiles within the visible span (maxVisibleTiles). That re-render
re-runs useVisualStableUpdate, which reads live isSpeaking state
and performs the actual tile swap.
Speakers already visible are ignored, so this costs zero React work
in the common case.
This lets us drop the ActiveSpeakersChanged subscription from
useTracks in the StageLayout upstream (updateOnlyOn: []), which was
re-rendering the whole stage on every speaker change.
Optimize the idle mouse handling and the FocusOverlay component so
they no longer trigger frequent re-renders of the ParticipantTile
focus.
State related to hover and focus is now scoped closer to where it
is used, keeping updates local instead of propagating up the tile.
Split the sub-components currently declared inside ParticipantTile
into dedicated files.
This makes the ParticipantTile file easier to read and lets each
sub-component be imported and reasoned about on its own.
Align how the participant name is retrieved and rendered inside the
ParticipantTile, so the different code paths use a single consistent
approach instead of a mix of ad hoc logic.
Stop using the useSize hook to compute the Avatar size in JS. Rely
on CSS to size the Avatar responsively instead.
This removes a set of unnecessary re-renders triggered by the
useSize subscription every time the container resized.
Stop using the useSize hook to compute the Avatar size in JS. Rely
on CSS to size the Avatar responsively instead.
This removes a set of unnecessary re-renders triggered by the
useSize subscription every time the container resized.
Apply the same pattern as previous layouts: push state and useSize
subscriptions down into child components.
The CarouselLayout now only re-renders when maxVisibles or
orientation actually change, instead of on every size update.
Move the useSize subscription into a dedicated child component of
MoreOptions.
The options now only re-render when they actually need to collapse,
instead of on every size change of the container.
Move the useSize observer into a leaf component of the GridLayout
tree. That leaf then propagates size changes back into the
GridLayout state through a callback.
This prevents every size change from re-rendering the whole
GridLayout tree, keeping the re-render local to the leaf that
actually observes the size.
Cut down the number of unnecessary re-renders happening in the
participant panel by narrowing subscriptions and isolating state to
the components that actually depend on it.
The Avatar was rendered as an image, which made the initials
centering unreliable across sizes and browsers.
Render the Avatar as an SVG instead, which allows the initials to be
properly centered by construction.
Align LowerAllHandsButton with the MuteEveryoneButton pattern by
wrapping it with the AdminOrOwnerOnly component.
This prevents mounting its hooks and rendering its logic for users
who are not admin or owner, instead of relying on an internal check
that still ran on every re-render.
Extract the participant count out of the participant badge into a
dedicated, optimized component.
This isolates the subscription to the participant count so it no
longer triggers a re-render of the whole toggle when the count
changes.
Reorganize all participant list related code elements into a
dedicated feature folder, so related components, hooks and helpers
are grouped together and easier to locate.
- Dissociate the chat observer (which persists messages in a store)
from chat rendering.
- Do not keep the chat mounted in the DOM anymore.
- Recompute the minimal amount of data when a participant renames.
- Memoize most of the layout.
- Drop the manual textarea row-size computation: recent React Aria
already handles it.
Known regressions still to solve: focus behavior on the input,
scroll behavior on message updates, and edge cases around list
sizing.
Not sure yet whether there is a better way to memoize the
virtualized list; open to feedback.
Memoize the ActiveSpeaker component used in the push-to-talk
component so it does not re-render on unrelated parent updates when
its props have not changed.
Reorganize all chat-related code elements into a dedicated feature
folder, so chat components, hooks and helpers are grouped together
and easier to locate.
Subscribing to the whole local participant caused re-renders on
every local participant update, even when the consumer only cared
about a specific attribute.
Narrow the subscription so only the relevant attributes trigger a
re-render.
Move the video resolution subscription down into a lower component
so it no longer re-renders the whole Videoconference component on
every resolution change.
The overall approach still needs validation, but this already avoids
the top-level re-render and is a clear improvement over the current
behavior.i
canPublishTrack was mistakenly subscribing to the whole local
participant changes, so every component using this hook re-rendered
on any local participant update, not only on permission changes.
Fix it to subscribe only to the permission attributes.
Restrict the full-screen warning to the local participant tile.
Previously, mounting it on every participant tile meant that when
the local participant toggled their camera or microphone, the
warning re-evaluated and triggered a re-render on every tile.
Push state down into the participant metadata subtree so a raised
hand change no longer re-renders the whole metadata block.
Use the children-as-props pattern so only the item actually related
to the raised hand re-renders when its state changes.
This also better encapsulates the color-update logic tied to the
raised-hand state.
useRaisedHandPosition is called on every participant tile, so any
unnecessary subscription in it multiplies re-renders across the
whole conference.
Reduce the number of events subscribed to for remote participants
to the strict minimum, while keeping the subscription to the
relevant attributes of the local participant intact.
Encapsulate the participant metadata rendering in a clear boundary
component, especially by pushing the raised-hand logic down into a
dedicated child.
This way, when a participant raises their hand, only the child
component re-renders instead of the whole ParticipantTile.
Reduce the set of events the chat subscribes to, keeping only those
that actually affect its rendering.
This avoids frequent re-renders triggered by unrelated events on
the room or participants.
In the participant tile, there is no need to read the pinned track
through a Valtio snapshot, which would trigger a re-render every
time the pinned track changes, for every participant tile.
Switch to an imperative read of the store to avoid these unnecessary
re-renders.
Reorganize all components related to the participant tile into a
dedicated feature folder, so the code organization is clearer and
related components are grouped together.
The useSettingsDialogs hook only encapsulated Valtio store
manipulation that should be declared at the module level. Keeping it
as a hook triggered unnecessary re-renders in components that used
it, subscribing to the store.
Remove the hook and use the store actions directly at the module
level.
Move the keyboard shortcut registration down into the
SettingsDialogProvider leaf component, so shortcut-related
re-renders no longer bubble up and re-render the whole
Videoconference component.
Reduce the set of events the ParticipantToggle subscribes to, so it
does not re-render on every LocalParticipant event but only on the
ones that actually affect its rendering.
Refactor the connection observer, previously a hook consumed inside
the Videoconference component, into a dedicated leaf component in
the tree.
This avoids re-rendering the whole Videoconference component every
time the connection state changes; only the leaf reacts to it.
Reduce the set of events that trigger an update of the participant
list in the chat, keeping only those relevant to display: attribute
and name changes.
This avoids unnecessary re-renders when other, unrelated participant
events fire.
At this stage, the chat is kept mounted in the DOM at all times to
preserve state and keep receiving new messages. As a consequence,
every chat message ends up rendered in the DOM, which can bloat it
significantly in large meetings with hundreds of participants
exchanging messages.
Virtualize the chat message list so only the few messages actually
visible are rendered in the DOM.
The fact that the chat is always mounted in the DOM will be
addressed in a later commit.
The InviteDialog open state was managed very high in the React tree,
which triggered a re-render of the whole conference tree whenever
the dialog was opened or closed.
Push the state down to a narrower component to limit the scope of
re-renders.
Part of a broader effort to isolate as much as possible what should
re-render, so we can then focus on tackling the real performance
issues.
Extract the code responsible for announcing pinned track changes
(for accessibility) into a well-scoped leaf component.
Previously, calling the useScreenReaderAnnounce hook inside
StageLayout triggered a re-render of the whole layout subtree.
Encapsulating the announcement logic in a narrow component keeps
those re-renders local to that leaf.
Vendor the pinned track context and its associated hook from
LiveKit, and switch the underlying state management to a Valtio
store.
The LiveKit ContextProvider was injected high in the tree, so any
pinned track change triggered a re-render of a large portion of the
app. With a Valtio store, only the parts of the app that actually
subscribe to the pinned track or need to pin one re-render when
present in the DOM.
Move track manipulation into a leaf component to avoid re-rendering
the whole videoconference tree when a track update occurs.
The new StageLayout component is now responsible for rendering the
video tracks in the relevant layout, while the Videoconference
component keeps the responsibility of building the overall
conference view.
We save re-rendering the ControlBar for every track/layout changes
for example.
In previous version, webhook may not be called in case of failure,
because the task wouldn't be actually retried.
We know check the exception raised against the auto_retry for
config.
Analytics capture also happens in case of definitive failure.
Request may fail for other reasons than HTTPError (ConnectionError for
instance). This change switches to the more generalized error for the
retry logic on call webhook & transcribe audio
* Consider that HTTP 400 errors are due to corrupted
audio files by default.
* Properly reraise the http error otherwise so that
the retry mechanism actually works.
The existing manifest was not exposing the add-in tools when
creating a meeting in a shared calendar. Amend the manifest with the
missing instructions so the add-in shows up in that context as well.
Manifest changes generated with Claude's assistance.
Copy the MediaPipe WASM modules into the frontend build output and
serve them locally, instead of loading them from the Google CDN at
runtime.
Mediapipe was a transitive dependency, add it explicitly.
Requested by some members of the community.
It closes#1168
Add vite-plugin-static-copy as a dependency. It will be used to copy
and serve the WASM modules shipped by the MediaPipe JS dependency
from the frontend build output.
Inline the model weights instead of fetching them from a Google
remote location at runtime. The weights do not update frequently, so
inlining removes an external dependency.
Update the calls to the background processors imported from
livekit-track-processor so they use the new instantiation method
instead of the previous, now-deprecated one.
The info panel was crashing when opening a room that was not
registered in the database (with ALLOW_UNREGISTERED_ROOMS=true),
because the API response did not include the room slug.
Instead of adding frontend fallbacks, update the unregistered-room
response to include the slug, keeping the API contract consistent
with registered rooms.
Note: this still relies on the unregistered-room response staying
aligned with the registered-room schema. Any future divergence
between the two responses could introduce similar issues.
A refactoring on the backend side is needed.
It closes#1441.
Add a new setting that controls whether an authenticated user can
rename or modify their display name, or if they must use the one
returned by the SSO.
When the setting is disabled, only anonymous users can set a display
name freely; authenticated users always use their SSO display name.
Requested by many self-hosters.
Create a new dedicated store for user choices, starting with the
username, to decouple this part from any LiveKit elements. This is
better for tree-shaking, and depending on LiveKit for storing the
username brings no real value.
The refactoring will be continued later for the other user choices
that can be persisted.
In the external API, applications authenticate with a
client_id/secret and can provision users with only an email. In that
flow, users are not identified to PostHog, so the user DB id alone
is not enough to identify them in analytics afterwards.
The issue does not exist in the public API, where users are
authenticated and therefore already identified.
Inspired by @flochehab's approach in summary.
Add the owner's email to the searchable fields of the recording
admin table. This makes it much more convenient for the support team
to look up recordings by user.
Avoids mounting the button's hooks and rendering its logic for users who
are not admin or owner, which previously happened on every re-render
of the parent.
Add Sentry observability to the `agents`. Introduce a dedicated
`observability.py` module. Refactor both agents to use the shared
observability layer and extract task helpers into a new `tasks.py` module,
making task execution easier to instrument and maintain.
Implement feature flags related functions in
Posthog analytics backend.
We cache the results in django cache to avoid
too frequent calls to Posthog.
Especially for when we are checking multiple features in
the same user request.
Extend the AnalyticsBackend class to support
user feature flags, without a breaking change to the
current implementation.
A flag value is considered to be a bool or a string.
Switch the analytics backend interface from a typing Protocol to an
abstract base class, so the contract that backends must implement
is explicit and enforced at instantiation time.
Created a shared class for handling Header based authentification.
This avoid duplicating logic and helps with a maintaining a signle
security related peace of code.
This commit introduces the compatibility with summary v2.
It tecnically doesn't break the compatibility with v1 as
v1 params are still sent. But we advise people using the
transcribe feature in their own deployments to adapt to the
new v2 API, as this compatibility will be removed in a
future major version.
* RecordingStatusChoices now has
EXTERNAL_PROCESS_SUCCESSFUL
& EXTERNAL_PROCESS_FAILED
Values, which are changed by a new webhook that
can be called by the transcribe service.
This webhook is protected by its own bearer token.
* Title for the document is computed in visio,
* Tests are added / updated accordingly
* Update the v2 create transcribe payload to support
publishing directly to docs and performing automatically
a summary if requested
* Note that title computation is to be handled by the caller
now as it makes more sense and avoids throwing a bunch
of parameters to the endpoint.
* All files are send as signed URL now, we don't read
directly from s3 anymore,
* Docs integration is now explicit in summary settings
* To make analytics work properly accross projects,
we use the user sub as distinct id,
this will require a new posthog project to be deployed
to work properly.
We create a post hog event at the creation request processing,
to make sure feature flags work properly after that.
* User email should now be provided to the API, it's not
mandatory to avoid a breaking change.
* Use a specific user agent for better tracking
Finally note that existing helpers don't always make use of
the pydantic models, so that's why there
are model_dump in some places. To avoid bigger changes.
This commit cleans up most of the code related to v1 route that
is used only by visio.
This is first step before introducing an update to the v2 route.
Add react-aria 3.49.0 + react-stately 3.47.0 as direct deps and bump
react-aria-components to 1.18.0 so react-stately resolves to one version.
Fixes the nominal Timer type clash and missing @react-stately/toast
imports introduced by the React Aria v1.17 monopackage consolidation.
Rarely media files may have one or multiple
empty streams when they are badly formatted.
The extract metadata code would crash when that happened.
We now avoid crashing and create a clean file
from the bad one to make sure API calls with that data
works properly (observed some failures otherwise
in my tests).
The management command that merges users with duplicate emails was
comparing emails in a case-insensitive manner, which left some
duplicates in the database when their emails only differed by case.
When using Makefile to launch tests, passing flags as well as specific
classes (using "::") was broken. This PR fixes this issue, by adding
an `ARGS` argument allowing to do
`make test ARGS="core/tests/foo.py::Test::x -vv"`.
joserfc versions before 1.6.8 accept an empty or nil HMAC key when
verifying HS256/HS384/HS512 tokens, which is a cross-language
sibling of CVE-2026-45363.
Bump joserfc from 1.6.4 to 1.6.8 to pick up the fix.
Ref: https://avd.aquasec.com/nvd/cve-2026-49852
Passing a list of hosts in the ingress was broken: the template
helpers were called with the current loop context (`.`) instead of
the root context (`$`), so the service name was rendered incorrectly
inside the hosts loop.
Track events whenever a new meeting link is generated, both through
the public API and through the external API.
The goal is twofold:
* Identify where the most links are generated from, so we can assess
which integration or entry point works best.
* Measure how many links are generated per user, so we can consider
a user truly active when they generate a link, rather than only
when they participate in a meeting.
Add an analytics abstraction that allows configuring which analytics
solution the app uses. PostHog is implemented as one backend, but by
default no analytics backend is activated.
The goal is to track events in a sufficiently organized way and to
let any developer implement their own backend as long as it follows
the same protocol.
Add the PostHog SDK to the backend so we can send analytics events
from server-side code. We currently lack data on events triggered
from the backend, and this closes that gap.
Ad blockers recently started blocking requests to our PostHog
feature flag API, leading to undesired behavior in the app.
Following PostHog's documentation, allow configuring a dedicated
domain for feature flags, isolated from the main PostHog domain.
The original implementation, introduced two years ago, was incorrect
and exposed the API to an undesired authentication mode: any user
access token obtained for a given user was being accepted as valid
credentials on the external API.
Restrict authentication to the intended mode so that user access
tokens are no longer accepted on this API.
Thanks @lunika spotting this.
Following internal feedback, rename the "Premium" wording to
"Advanced" across the transcription and recording sidepanels, so the
label no longer implies a paid tier.
Add a note in the documentation about La Suite Meet being
generalized by the French Prime Minister, along with a link to an
English-language source article.
Update the contribution documentation to make the conditions of
contribution to the project clearer.
Also encourage potential contributors to connect with the community
and get involved.
The favicon is bundled into the frontend image as static files served from
/usr/share/nginx/html. Rather than add a runtime config knob, document how to
overlay custom icons with a volume (ConfigMap + frontend.extraVolumes/
extraVolumeMounts). This serves the right icon from the first byte — no
rebuild, no favicon flash — and covers every variant, including the iOS
home-screen and Android/PWA icons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recording lifecycle previously relied exclusively on the MinIO storage-hook
endpoint to transition from STOPPED to SAVED, which made the system dependent
on MinIO bucket notifications and lifecycle configuration. Introduce support
for LiveKit egress_ended webhook (EGRESS_COMPLETE, EGRESS_LIMIT_REACHED) as
an alternative finalization mechanism for self-hosted deployments that do
not use MinIO / S3 hooks.
Change behavior of existing configuation RECORDING_STORAGE_EVENT_ENABLE.
When the LiveKit mechanism is enabled (RECORDING_STORAGE_EVENT_ENABLE=False),
RecordingEventsService.handle_complete is triggered from
LiveKitEventsService._handle_egress_ended.
We should be able to use other transcription services,
those usually relie on response_format="diarized_json"
to produce what we need.
Note that as part of this change, we stop using
openai library for making this call to avoid
casting the result to a payload that doesn't
contain the elements we used to rely on.
(setting this specific format auto cast the
results in openai lib). We keep the old
result class used.
When going through AlbertAPI, timestamp are not provided at the word level.
This adds default values so that the summary external contract stays the same,
while giving us compatibility wisht AlbertAPI.
Updates the help chart to be able to run a list of CronJobs.
By defaults it runs the clean_pending_files
and purge_deleted_files commands during the night.
Since we have added custom backgrounds, they could be
soft deleted but were never really deleted.
This commits introduces a command to actually delete
this files.
In introduces also a command to purge pending upload
.
Add a footer to transcription outputs linking to an external satisfaction
survey. The survey URL is built from TRANSCRIPTION_SATISFACTION_FORM_BASE_URL.
When TRANSCRIPTION_SATISFACTION_FORM_BASE_URL is unset or None, the
footer is omitted.
When joining a meeting that already has many participants, new
participants are now muted by default.
This is an empirical change, not directly requested by users but
informed by user experience: a lot of people joining large meetings
arrive with their microphone and/or camera open, which can be
painful for the host, who otherwise has to mute everyone or ask
everyone to mute. Many of these participants are inattentive but
still noisy.
If you are joining a room that is already at a certain size, you are
probably not the one expected to speak; presenters typically join
among the very first participants.
In particularly large rooms, with more than 5 or 10 participants,
the entry notification sound can quickly feel spammy.
A configuration to disable it exists, but new users do not discover
it easily. The sound notification is most useful while waiting for
the first few participants to arrive; once a few people are in, the
meeting usually starts and the sound becomes more disruptive than
helpful.
The visual notification remains in place, so users are still aware
when newcomers join. Roll this out and check whether users find the
change helpful.
Required by some self-hosters who want to present a link to
participants that are guests only, without access to their SSO. For
example, a meeting between a citizen and a public servant where the
guest is known not to have an account.
Also useful when rendering the join page inside an iframe.
Several clients ran into issues with the silent login, leading to
redirections that were not appropriate for their use case.
Offer a URL parameter to control this behavior and disable silent
login when needed.
The noise-suppression processor is now imported on demand with a
dynamic import() instead of a top-level static import, so it lands in
its own code-split chunk rather than the main bundle.
Why this is necessary:
* @libreaudio/la-call is fully self-contained. It inlines *everything*
as JavaScript: two WASM binaries (SIMD and non-SIMD variants), the
Emscripten glue, the worklet processor source, and the noise model
plus its weights — the model is compiled into the .wasm, so we ship
effectively two copies of it.
* The WASM is inlined as numeric array literals (new Uint8Array([...])),
the least compact representation possible (~2-4 source chars/byte) and
not meaningfully minifiable. The result is a large module that also
can't be stream-compiled the way an external .wasm asset would be.
* A static import would pull all of that into the initial bundle,
inflating critical-path download size and lengthening build time
(parsing/minifying the big literal) — for a feature that's only used
when the user actually turns on noise suppression.
* Dynamic import() isolates the whole payload in a separate,
content-hashed, browser-cached chunk. The cost (chunk fetch + WASM
instantiation) becomes a one-time hit deferred to first activation,
and is fully off the page's initial load path.
Notes:
* The library self-bundles its AudioWorklet at runtime from a Blob URL
and inlines the WASM, so it needs no build-tool asset plumbing
(no ?worker&url / ?url, no Vite asset config). The dynamic import is
therefore the only splitting mechanism required.
* Audio processing still runs off the main thread on the AudioWorklet
path (desktop/most browsers); only the Android ScriptProcessor
fallback runs on the main thread. Import style does not affect this.
* To hide the first-activation latency, the chunk can be prefetched
(e.g. import() on idle or <link rel="modulepreload">) so it's warm
before the user enables suppression.
Replace the basic RNN noise processor with a more advanced audio
pipeline powered by Big Blue Better Audio (BBBA), a project
supported by the Prototype Fund.
The new WASM-based pipeline introduced by @falkTX and
@trummerschlunk adds:
- voice isolation
- high-pass filtering
- spectral balancing
- multiband compression
- low-pass filtering
This significantly improves overall audio quality and speech
clarity.
More information about the pipeline is available on the
trummerschlunk/BigBlueBetterAudio repo.
Voice isolation still relies on RNNNoise, a widely used deep
learning-based denoising model. Audio quality could be further
improved in the future if DeepFilterNet becomes usable directly
in the browser.
Their code has been released in an NPM package under GPL license.
A previous CSP change suggested by CodeRabbit was not properly
tested and broke inline styling as well as the loading of the
ProConnect image.
Adjust the CSP directives to allow these resources again.
The CSP was missing a default-src directive (flagged by CodeRabbit).
Without it, styles, images, fonts, and media are completely
unrestricted, which undercuts the otherwise strict policy.
Set default-src to 'self' and add an explicit style-src directive
since we rely on inline styles (e.g. #close-msg and the view
toggling use inline style attributes), so style-src needs to allow
'self' and 'unsafe-inline'.
Mount the nginx configuration used by the frontend image as a volume
so the default one can be overridden by a custom configuration at
deployment time.
Based on suggestions from @rouja to help parametrize the
configuration at deployment time.
Add the possibility to display a feedback form link in the footer to
help collect feedback from the first users of the plugin.
Once the plugin is in production at scale, this will be replaced
with a link to the support page.
Offer the possibility to append a query parameter at the end of the
meeting URL to flag links created from the Outlook plugin, inspired
by Zoom.
The behavior is toggled via an environment variable.
From the task panel, change the "add" button to "remove" when a
meeting link is already present.
This will be updated to "update" once the external API supports
updating an existing room.
Detect whether a meeting link is already present in the calendar
event item or its body, and prevent generating a new link if one is
found.
Detection is based on the presence of the app URL in the text. This
is imperfect but covers the most naive scenarios.
Refactor the plugin to support internationalization and ship it in
three languages.
Internationalizing requires updating the manifest, which involves
touching the Helm chart and releasing a new version. Ship a beta
version of the plugin once the i18n work is done to bundle these
changes together.
In the ribbon, the "create a meeting link" action was displayed with
the generic add icon, which is fitting in the context of a menu but
not explicit enough on its own.
Following feedback from the social ministries, replace the add icon
with the app logo to make it clear that this button creates a
videoconference link.
Instead of appending the meeting link at the end of the email, insert
it where the user's cursor is. This ensures the link is not placed
after the email signature, or below the quoted thread in a reply.
Refactor the message builder so that rendered messages are displayed
in a nicer way. Based on user feedback from the social ministries.
Go for a hybrid approach: on the desktop client, insert plain text so
it picks up the default font configured by the client theme; on the
web client, insert HTML, as the editor there is rendered using HTML.
The dialog window does not close itself on Outlook Desktop, which
opens links in a webview that behaves like a different browser.
Calling window.close() fails because, for security reasons, JS is
prevented from closing the window. The browser considers that the JS
script is not the one that opened the dialog.
The dialog also loses its reference to the opener due to the
redirection, so there is no way to message the parent to trigger a
close.
Keeping the dialog reference on the parent side does not help either:
since the opener is lost, the parent cannot call close on the dialog.
After investigation, go with a temporary solution that shows an
explicit message hinting the user to close the dialog manually if it
does not close automatically.
Restrict arm64 builds to release tags only, instead of running them on
every build.
The arm64 pipeline was significantly slowing down CI with little
practical usage, degrading developer experience for regular workflows.
Keep arm64 builds for release validation, and handle occasional failures
manually if needed.
The LiveKit integration was still using RoomInputOptions and
RoomOutputOptions, which emit deprecation warnings.
Update the implementation to use the unified RoomOptions API.
Pin urllib3 to >=2.7.0 via uv constraint-dependencies to fix a moderate
severity decompression DoS vulnerability. Affected versions (2.6.0 to
<2.7.0) could fully decode a compressed response body in a single
operation, leading to excessive CPU and memory consumption.
Pin idna to >=3.15 via uv constraint-dependencies to fix a moderate
severity (CVSS 5.3) information disclosure vulnerability. The flaw is
network-exploitable with no authentication required.
Pin idna to >=3.15 via uv constraint-dependencies to fix a moderate
severity (CVSS 5.3) information disclosure vulnerability. The flaw is
network-exploitable with no authentication required.
Uninstall `vite-tsconfig-paths` as path resolution is now supported
natively by Vite's built-in configuration.
Remove the unused dependency and simplify the frontend tooling setup.
The upgrade introduced around 50 new linting errors. Fix the
low-hanging fruit and address straightforward violations.
Several of the new rules appear to target patterns intended for newer
React compiler capabilities. Since the project currently runs on
React 18 rather than React 19, disable these rules for now instead of
applying potentially inappropriate changes.
Follow-up work can address the new rules in a dedicated PR, potentially
alongside a future React version upgrade.
Removes Node.js type dependency from browser/React code by substituting
the non-portable `NodeJS.Timeout` type with the standard
`ReturnType<typeof setTimeout>` equivalent across all affected files.
Also replaces `process.env.NODE_ENV` in Icon.tsx with
`import.meta.env.MODE` for Vite compatibility.
With the addition of the ANALYSING state
files could be accessed in the short time they
were in that state.
We now require files to be in ready.
Also adds missing frontend types (no impact).
Manually upgrade the frontend codebase to ESLint 9.
Update the linting configuration and related code where required to
maintain compatibility with the new major version.
When analysing a file, the previous commit introduced a row level lock
to make sure we would analyse and promote a single file.
This commit changes the locking mechanism so that it happens with
the upload state which avoids long running db locks and potential
perf issues.
Before this commit, post a file check, the policy could be
reused to change the verified file.
Now, files are uplaoded to a temporary location, then inside
a transaction that prevents concurrent calls, the file is
copied to its final destination and the checks are run on that one.
A new file can still be updated with the policy but it will never be read, etc.
As part of this change, all files in the new tmp directory
on s3 should have an expiration policy.
Introduce a Kubernetes job to run the `merge_duplicate_users`
management command on the backend, as suggested by @rouja.
This provides a standard and reproducible way to execute the user merge
process in deployed environments without requiring manual access to
application containers.
The job can be used alongside the command's dry-run mode to validate
the impact of a merge operation before applying changes.
Add an email substring filter to the user merge management command,
allowing selection of a subset of users concerned by the merge process.
This enables safer incremental execution of the command by testing it
on a controlled group of users before applying it globally.
The goal is to validate behavior and ensure the merge process does not
introduce unexpected side effects or inconsistencies at scale.
Add a management command to merge duplicate users and reassign all
granted resources to the most recent user account in the database.
Support a dry-run mode to estimate the impact of the operation before
applying any changes. This helps validate the command and identify
potential issues in environments where realistic testing is difficult.
Add unit tests to verify the command behavior and ensure database
integrity is preserved during the merge process.
Move user provisioning logic out of the external token viewset into a
dedicated service to keep the viewset lightweight and easier to
maintain.
While extracting the logic, refactor user object handling to improve
robustness and make the provisioning workflow easier to reason about.
Defend against race conditions when concurrent requests attempt to
provision the same user. Rely on the existing database constraints to
guarantee uniqueness and gracefully handle integrity errors raised by
concurrent creations.
Update the test to document the actual contract without modifying the
underlying model behavior.
These tests act as non-regression coverage and explicitly assert that
users may have a null sub or email. They are intended to document the
current behavior of the initial user model rather than evolve or
constrain it.
Ghost rows have not been reported as an operational issue. For the sake
of simplicity, avoid changing the model unless required by a concrete
issue or unless the benefits clearly outweigh the added complexity.
Fix a race condition in the external viewset where concurrent requests
could create multiple pending users with the same email address.
No database constraint enforced email uniqueness for pending users,
allowing duplicates to be created under load.
This caused issues during user reconciliation, which expects a single
matching pending user and raises a warning when multiple records are
found.
Add a narrowly scoped migration to enforce the uniqueness constraint
and address the identified issue.
Backport the logging configuration from docs to LaSuite Meet after
discussion with @lunika.
Add a proper base logging setup to restore usable logs in production.
The initial repository bootstrap lacked a complete logging
configuration, resulting in limited operational visibility.
* Removed constraint on file extension
* Infer audio/video streams from the media with ffmpeg
* Infer the correct processed audio file extension based on actual
codec to avoid ffmpeg errors
We need to support more extensions and make audio extraction dynamic,
as we shipped transcript in production and it led to user complaints
requesting more formats.
Avoid stacking too many components in route/home. Keep only the
responsibility of orchestration and layout in the home route, and
move stateful components to their appropriate folder.
The homepage previously relied on the userChoices store, which loads
the persisted user choices from localStorage. However, this store is
widely used in the room feature, so code splitting ended up loading
the chunk containing the userChoices store at the initial render.
That chunk was bundled with unrelated parts that proved to be heavy.
Switch to a more straightforward approach reading the cached username
and verify the bundle output + intial loading.
Saves a few hundred ko on the initial load, now bundled in another
chunk that is loaded only when entering a room.
Material icons (and Symbols, the most recent ones from Google
Material) were initially adopted to align with the UI kit. Before
that, we relied on Remixicon, which loads only the icons used and
totals 40ko for the whole library.
To benefit from Material icons, the implementation matched the UI
kit, which uses two fonts: 150ko and 750ko. Unlike the UI kit, the
fonts were preloaded to prevent icon render blinks. On products using
UI kit, the fonts are loaded the first time an icon renders,
and because they are heavy, the icon swap is visible to the user.
Icons are replaced by their text label in the meantime, which also
caused screen-reader vocalization or i18n issues that had to be mitigated.
Preloading the fonts was a quick win that worked fine on high-speed
connections where fonts get loaded fast then cached. However, while
optimizing LCP for users on poor connections (a separate concern that
already affects videoconference joining), preloading proved harmful:
it stole network bandwidth at the most critical moment,
when the main JS chunk was loading.
Switch to a frugal approach: import only the SVG icons actually
needed from Material, treated as components so they are tree-shaken
and customizable (color, etc.). Icons do not change often, so the DX
remains good. The UI kit also supports custom icons not available in
the font, so this approach lets us define them and works like a
charm.
Removal of Remixicon is planned in a follow-up PR.
It save user loading almost 1Mo of assets for few icons.
posthog-js was bundled in the main chunk. Loading it dynamically saves
200ko on the initial load and around 4s of loading time on slow 4G
connections.
PostHog, and analytics in general, should not block the initial
rendering. The risk is that some feature-flagged features may not be
instantaneously available to the user, but for most users on
high-speed connections this will be imperceptible.
Other places in the code rely on a direct import from posthog, I only
verified the delay import of posthog for the home page, and did
not for the /room route. I'm pretty sure there is room for
improvements on other parts of the app.
The Valtio actions on the store were initially defined inside a hook,
which was a bad idea: it does not follow Valtio's recommendation of
defining actions at the module level, and the function definitions
were re-created every time the hook re-rendered, which happened on
every state update.
These action has nothing related to React.
Additionally, the snapshot should be used more directly so Valtio can
understand and optimize which parts of the proxy are of interest to
the snapshot being made.
Use the Python port of Google's phone number library on the backend
instead of the JS one on the client. Saves 140Kb of unnecessary JS
and avoids a complex dynamic import that would have been required to
optimize loading.
The transformation is static: the phone number lives in the Django
settings, so the backend can format it once and pass the result to
the client. This avoids every client loading the 140Kb library and
re-computing the same information.
Moreover, within a single client, the transformation was previously
re-computed several times during a webapp lifecycle.
Avoid re-rendering the whole home page when interacting with the later
meeting creation flow.
Maintaining the modal open/close state at the parent level was causing
the whole home page to re-render on every menu open/close state
change.
livekit-client is a 400ko package that was wrongly bundled in the main
index.js chunk.
Every page was loading this enormeous vendor package only necessary
to join and participate to a room.
It made sense to set the LiveKit log level at app init, however the
performance tradeoff forces us to move it as close as possible to
where it is actually needed.
The library is rarely used, so load it dynamically. It is only 50ko,
which might not have been worth the effort, but these 50ko were
bundled in the main chunk and are not needed on the homepage nor when
launching a room.
The static placeholder is good enough to be acceptable. On very slow
connections, the 50ko might take a second to load, after which the
text is updated with the right content.
Also improves renders across the components touched, especially the
idle modal with the countdown.
Introduce a component that renders its children only if the logged-in
user is considered Admin or User. This helps avoid mounting the hooks
and logic of components that previously early-returned when the user
was not admin or owner, but were still re-rendering whenever their
internal logic updated.
One of the best ways to reduce the size of the initially loaded chunk
(index.js, around 2Mo before optimization) is to lazy load routes and
features so their JS gets isolated in dedicated chunks and is loaded
only when needed.
For example, most of the JS under the legal terms page is never
consulted by users. Likewise, when loading the home page, there is no
need for all the feature-related JS required to display a
videoconference.
After this lazy-loading optimization, the initial chunk is now around
1.2Mo, a significant improvement.
While attempting to lazy-load routes, Vite/Rollup warnings revealed
that the Dialog and Form primitives were causing circular imports
through their use of the barrel file.
Easy to address: when using primitive elements inside those
primitives, rely on direct imports instead of the barrel file.
Auth-related code is used across the codebase and imported by many
different features. The barrel file, while convenient for importing
utilities, was harming code splitting by pulling unrelated modules
into shared chunks.
Verified by running `npm run build:debug` before and after: chunking
and code splitting are better without the barrel.
Install tooling to visualize the Rollup bundle output. It helps
understand precisely what composes our bundle and what is included in
each chunk, which is a great help when trying to enhance code
splitting.
You can see its output running npm run build:debug.
Note: the library is not compatible with Node 20, which is the build
image we use in Docker to build the frontend, so it will raise a
warning in CI. This does not matter much, as it is a dev-only
dependency used locally without Docker.
Mark JS imports as type-only when applicable so they are stripped at
build time and ignored during chunk splitting, ensuring we take full
advantage of Rollup's code-splitting optimizations.
Following the ProConnect documentation leads to inlining almost 40ko of
SVG assets in the JS bundle. Moreover, each SVG asset (around 20ko) is
not optimized in size.
As the ProConnect button is loaded in the main index.js chunk at app
launch, these few Ko are critical.
Optimize index.js by 40ko and preload the hovered variant to avoid any
blink on hover.
Could be further optimized by handling the button background with CSS,
but leaving that as an improvement for later.
Inspired by commit eb23aef in suitenumerique/docs. The same base
route path is used everywhere, which helps when the backend sits
behind an ingress serving it with a regex like `api/*`.
Ensure the meet-backend createsuperuser and migrate jobs run only
once and do not rebuild the image by default. When the backend
updates, resources should focus on updating the backend pod itself.
Align enabled/disabled mature features across the dev environments.
Also reorganize backend env variables with a clearer scope and add
comments that help understand the responsibility of each setting and
why it exists.
Maintaining the two dev stacks is a nightmare. Start factorizing them
to make sure they stay updated easily, and to highlight differences
between the two at a glance.
Document the changes introduced in #128333, which allows passing
configuration to the external API when creating a room.
Warning: this documentation was generated in a rush using an LLM and
may contain minor errors.
Plan to factorize these YAML files into a common and shared
structure.
The securityContext and podSecurityContext were harming hot reloading
on the dev stack. The file permission system prevented source updates
because the user running the image was not root.
Allow passing configuration and access level when creating a room
through the external API.
Also add a few guardrails:
* control whether public rooms are accepted on the external API
* set the default access level when creating a new room
Ensure the new room configuration and access level are returned by
the serializer when listing rooms.
Surface connection state changes (reconnecting, disconnected,
etc.) directly in the PiP window so the user stays informed
without needing to switch back to the main window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Replace the hard-coded tooltip offset with a semantic CSS token,
making it possible to override the spacing per context (e.g.
tighter spacing in the PiP window where vertical room is scarce).
Co-authored-by: Cyril <c.gromoff@gmail.com>
react-aria computes overlay positions against the main window's
dimensions, which produces incorrect placement when the overlay
is rendered into a PiP document. Tooltips were the most visible
symptom, but the issue affects overlays in general.
Introduce a context that lets our primitives know when they're
rendering across documents. When set, the primitives use the
host document's window for positioning instead of falling back
to react-aria's default.
Original work by @ovgod. No cleaner approach seems feasible in
the short term, react-aria doesn't expose a clean way to
override the positioning target, so this works around it at our
primitive layer.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP window can be resized to a wide range of dimensions, so
the control bar needs to adapt. Introduce a collapsible mechanism
that hides or condenses controls as available width shrinks,
keeping the UX usable at small sizes.
Original works by @ovgdd
Co-authored-by: Cyril <c.gromoff@gmail.com>
The toolbar's onFocus handler called focusManager.focusFirst() whenever
focus arrived from outside the toolbar, which included clicks on
reaction buttons. This caused focus (and the scroll viewport) to snap
back to the first button on every click.
Use :focus-visible to distinguish keyboard focus from pointer focus.
Only redirect to the first button when focus arrives via keyboard,
leaving click-induced focus untouched.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Render the notification region from the main window into the PiP
document via a portal, rather than duplicating it. This keeps a
single listener, a single store, and a single component instance —
so notifications stay consistent across both windows without extra
synchronization.
Simplest approach I could find. Good enough as a first pass;
worth revisiting if the requirements grow.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The core component of the PiP layout. Adapted from a simplified version
of @ovgodd's work in #890 — see that PR for context on the original design.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Adding a menu turned out to be tricky. Our usual menu primitive
is built on react-aria, which positions overlays based on the
`window` object. Since the JS runs in the main window, the
computed position refers to the main window's coordinates, and
the menu renders in the wrong place inside the PiP document.
After investigating, we couldn't find a clean way to make
react-aria target the PiP window's `window`/`document` without
significant rework. Agreed with @ovgodd to duplicate the menu in
the PiP window as a pragmatic workaround until a better approach
emerges.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP control bar shares state with the main window's control
bar — most importantly, the reaction toolbar and the mic/camera
toggles, which are fully stateful.
Sharing state works naturally here because the PiP content is
rendered through a portal into the same React tree, so the
controls in both windows read from and write to the same stores.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The main window computes a JS offset to align the reaction toolbar
with the (off-center) reaction toggle. PiP and mobile don't need
this — centering the toolbar is enough. Add `adjustedCentering` to
opt out.
Not a great seam: the component shouldn't know about its host.
A cleaner fix would move the alignment concern to the parent.
Leaving for now to unblock PiP.
Co-authored-by: Cyril <c.gromoff@gmail.com>
When the PiP window is active, replace the corresponding content
in the main window with a lightweight placeholder. This avoids
rendering the camera feed (and other expensive media) twice when
the user is only watching the PiP window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Add a hook that manages the logic needed to open a document
picture-in-picture window, stores a ref to the window in a global
store, and—once ready—mounts a portal to duplicate content into
the PiP document's container.
Mounting the PiP content via a portal lets us share the same React
tree as the main app, and therefore share application state. This
comes with some trade-offs, particularly around components that
rely on the DOM hierarchy: react-aria popovers and overlays may
not behave correctly across documents.
The logic is kept to a minimum here. This first commit does nothing
more than open a window containing a loading spinner. The topic is
new to us, so plenty is likely to be refined in follow-ups.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Refine keyboard behavior so left and right arrow keys no longer
control toolbar navigation, allowing users to directly tab into
individual reaction buttons for faster and more intuitive access.
Remove the reaction toolbar from accessibility tree to avoid
unnecessary focus and announcements, keeping assistive technology
focused on the actual reaction controls.
This ensures the UX centers on reaction actions rather than
auxiliary navigation elements.
Also, not related to this topic, I've reworked the scroll viewport styles.
Implement the missing disabled state styling for the
primaryDarkText button variant to ensure consistent UI feedback
when the button is not interactive.
Allow reaction interactions on mobile.
Adding the toolbar toggle currently impacts the responsive layout
of the control bar on very small screens. This will be improved in
a future PR with an auto-collapsing mobile control bar.
Replace the previous hardcoded offset approach with a dedicated
hook that dynamically centers the reaction toolbar relative to the
reaction toggle.
This improves layout accuracy and removes reliance on fixed values.
Note: ResizeObserver is used for positioning updates and may not
be supported in older browsers. Additional testing will be
performed before production release.
Replace direct DOM queries using hardcoded IDs with proper constants.
Add horizontal navigation support using left/right arrow controls
to scroll through available reactions on smaller screens.
This work is inspired by Cyril's implementation.
Rename strip-related components into a more generic container
abstraction.
Extract the buttons container into a dedicated component file and
update related naming to improve readability and maintainability.
Ensure the participant requesting a mute action is still present
in the room before processing the request.
This mitigates scenarios where a previously issued token could be
reused after the meeting has ended.
Current token lifetime is intentionally long-lived and will be
refactored in the future to better align with LiveKit session
constraints. In the meantime, add this extra validation step to
reduce the attack surface.
Fix unreachable code when notifying participants that they were
muted.
Prevent unnecessary function re-creations when props remain
unchanged.
Also guard against missing tokens by logging an error and
returning early when the token is undefined.
Extend the existing live synchronization mechanism beyond room
configuration to also include lobby access level changes.
This ensures that all owners and admins sharing a room maintain a
consistent and up-to-date view of room state in the frontend,
including configuration and access control updates.
Listen to room metadata change events and synchronize the React
Query cache with the latest room data fetched from the API.
This ensures clients react to live configuration updates, such as
showing or hiding mute controls when `everyone_can_mute` changes.
Update room serialization to include room configuration for all
users fetching the API response, not only room owners.
This behavior was inherited from the original upstream project.
At the moment, exposing this configuration does not appear to
introduce meaningful security concerns or provide attackers with
additional capabilities.
The decision will continue to be reviewed from a security
perspective, but sharing the configuration improves frontend
consistency and synchronization.
Simplify source serialization and validation logic while improving
type safety around room configuration handling.
Introduce a dedicated TypeScript type matching the backend
Pydantic model more precisely.
Also harmonize track source casing between frontend and backend to
remove redundant conversion logic and resolve#1282.
Introduce synchronization of room configuration changes across
active participants.
When a room configuration is updated through a PUT operation, the
backend now performs an additional LiveKit API call to notify room
participants through a room metadata update event.
This ensures admins and owners quickly see up-to-date settings in
their administration panel. It also prepares the frontend for
automatic updates of unprivileged participants room’s data without
refetching it from the API.
An event-driven design was chosen instead of storing the full room
configuration in LiveKit metadata. While embedding the state
directly in metadata would provide immediate synchronization, it
would also require initializing and maintaining configuration
state during room creation or webhook handling, increasing the
risk of operational failures and regressions.
Instead, the backend emits lightweight synchronization events and
active clients update their React Query cache, which remains the
single source of truth for room configuration data.
Introduce a new room setting controlling whether all participants,
including non-privileged users, can mute others.
Update API validation accordingly and add the frontend controls
allowing administrators to toggle the option and persist the
configuration through the API.
Allow non-privileged users to mute others when the
everyone_can_mute configuration is unset or true.
This setting is not yet customizable by room owners and will be
introduced in a future update.
Pass the LiveKit token when calling the mute-participant endpoint
to authenticate the request.
This enables non-authenticated participants to mute others through
the API while preserving proper authorization checks.
Enable any participant to mute others when the room configuration
allows it. This is enabled by default for all meetings unless
explicitly disabled by an administrator.
Privileged users retain the ability to mute any participant
regardless of the room configuration.
Fix two issues. 1: Missmatch between commands in dev and production in
Dockerfile, leading to unexpected behaviors. 2: Naming of
multi-user-transcriber -> multi-user-transcriber-dev for coherence.
Rollback the mail package upgrade after identifying multiple
breaking changes introduced in v5 that were not fully accounted
for.
Local testing initially missed the issue because the mail Docker
image had not been rebuilt automatically, causing broken emails to
go unnoticed.
Speaker-to-participant assignment relie on WhisperX word timings, but
incorrect word durations in the output can lead to inaccurate overlap
scoring and wrong user attribution. Add a custom heuristic to trim
overly long word durations before computing assignments.
Added a hyperlink to the "Open" text in step 1 of the recording
notification email instructions. Previously, "Open" was plain text
and users could only access their recording via the button below.
Now the text itself is a clickable link, improving accessibility
for email clients that may not render the button properly.
Updated MJML source template and all 4 locale files (en, fr, de, nl).
When duration is not reported in the files metadata,
we directly infer the duration from the audio packets.
This prevents errors on webm files.
Very simple audio & video test files have been added
that cover relevant usecases to prevent regressions.
Update dependencies to the latest minor versions fixed that
by re-resolving the fields.
This is needed for packaging as many distribution retrieve
node modules into the npm cache and then tries to install
node modules into the project without any internet connection.
Since there is no resolved/integrity field, it fails to
get packages from the cache.
Introduce a new user assignment mechanism to for more friendly output
than the current (SPEAKER_0, SPEAKER_1, ...). Use the VAD metadata to
compare speech intervals with those returned by WhisperX. User with the
highest overlap score above a defined threshold is assigned to each segment.
This method allows for multi-speaker scenarios for a single account.
Fix compatibility issues with the DINUM frontend image, which
overrides the default `font-sans` value.
Simplify the implementation by having the JavaScript layer only
toggle well-scoped CSS classes responsible for accessibility font
overrides. This makes the behavior more predictable and restoring
default styles straightforward.
Also clarify the intent of the hook by making its accessibility
purpose explicit and moving its usage to the App component, where
it better fits the application lifecycle.
The tasks endpoint used non-timezone-aware date and time values and split
them into separate variables, which is unconventional. Refactor the
implementation to use timezone-aware datetime objects and align transcription
formatting with the user-declared timezone. Update the source of truth for
recording start time to FileInfo.started_at for improved precision. Adjust
the task signature in preparation for upcoming user assignment work, which
will require `started_at`, `ended_at`, and `metadata_filename`.
Expose RECORDING_ENCODING_* settings to override the default LiveKit
Egress preset (H264_720P_30). When RECORDING_ENCODING_ENABLED is True,
the provided width/height/framerate/bitrate/keyframe values are passed
as advanced EncodingOptions. Lowering framerate and bitrate reduces
recording file size and egress worker CPU load.
Disabled by default, preserving current behaviour.
Udate the helm chart to support multiple transcribe worker in
the summary service.
This is useful when using multiple WhisperX instances to have one deployment
for each endpoint. This enables some kind of horizontal scaling (we still
keep one call per WhisperX endpoint but can have multiple WhisperX endpoints)
Room identifiers are created with `Math.random()`, which is predictable
and not suitable for security-sensitive identifiers. Predictable
room IDs increase the risk of room enumeration and unauthorized
access attempts, especially when IDs are part of join URLs.
Affected files: generateRoomId.ts
Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
Wrap Recording and RecordingAccess creation in a single transaction so a
partial failure does not leave orphan rows, and return 409 instead of 500
when a recording is already in progress for the room.
When the worker fails to start, transition the Recording to
FAILED_TO_START so the unique partial constraint on (room, status) no
longer blocks future recording attempts on the same room.
Fixes#1126 - Inconsistent role terminology in localization files.
Standardize on 'host' as the primary role term across en, de, and nl
locales, replacing mixed usage of 'administrator', 'organizer', 'admin',
'Organisator:in', 'Organisierende', and 'organisator'.
Room.configuration accepted arbitrary JSON without validation, allowing unsafe
or malformed payloads to be stored and creating a security risk. Define a
Pydantic schema to enforce structure and constraints, and add validation
at the serializer level to reject invalid inputs.
Updating connectionObserverSnapshot triggered page re-renders,
causing participants to reconnect due to a race condition.
Read directly from the underlying store instead of using the
snapshot to avoid unnecessary re-renders.
Ensure screen recording is properly disabled when the feature
flag is set to false.
Previously, no feature flag was enforced, allowing unintended
access.
Also update the blocked-access message to be more generic and not
specific to public sector users.
Integrate the add-ons authentication backend into the externalviewset.
This allows third-party integrations (e.g. calendar add-ins) to
obtain a JWT for a user and use it to call the API (e.g. create a
room) via a Bearer authorization header.
Introduce a configuration flag to enable or disable the
application token exchange (service account) mechanism.
This allows activating alternative authentication backends
without requiring full application token configuration.
Required to support the upcoming add-ons authentication backend.
The external viewset expected a plural "rooms:x" scope instead of
the incorrect singular "room:x".
Update it to accept the proper scope and emit tokens accordingly.
Provide the minimal components required to support an Outlook
add-in: user authentication, JWT retrieval, and API calls to
generate meeting links.
This implementation is an early alpha: developer experience is
limited, documentation is incomplete, and the solution is not
white-labeled.
It's too early to consider these parts ready to ship into
production.
As a result, it is currently only available within the DINUM
frontend image.
Add a new authentication backend for add-ons, with a core service
managing session state in cache and exposing three API endpoints.
Microsoft Add-ins recommend launching authentication from the
side panel via a dialog, then returning the JWT to the parent
context using postMessage. However, due to Django and SSO security
constraints (window.opener not preserved), this approach is not
viable.
Implement a three-step authentication flow:
- `/init`: create a session, return a short-lived transit token
and CSRF token. Store session ID in a secure HttpOnly cookie.
- `/poll`: allow the add-on to poll until authentication is
complete, then consume the session and clear the cookie.
- `/exchange`: exchange the transit token for a JWT, which is
later retrieved via the `/poll` endpoint.
The add-on opens an authentication dialog, stores the transit
token in sessionStorage, and performs the exchange after login.
This approach works within iframe constraints and provides a
sufficiently secure v0 despite known limitations.
A 1–7 scale is not commonly used in software products. Feedback
from both users and the support team suggests reducing the number
of options to simplify usage and analysis.
Adopting a 1–5 scale improves usability and makes responses
easier to interpret and process.
The scale has to be odd.
Allow controlling when the metadata collector agent is started,
enabling users to try the feature and disable it if needed.
Introduce a user-level feature flag to toggle the agent for the
initial release.
Introduce MetadataCollector and MetadataCollectorService classes to
centralize the collection and storage of user connections, VAD events,
and chat messages. This creates a structured foundation for future speaker
assignment logic based on voice activity detection. Add tests for this new
feature.
Capture selected ICE candidates for both subscriber and publisher
peer connections.
This enables correlation between survey feedback and connectivity
setup, helping identify problematic network configurations.
Bump the base image to Alpine 3.23 to resolve most vulnerabilities
reported by Cyberwatch and Trivy.
Remaining issues require manual updates:
- musl / musl-utils: upgrade to 1.2.5-r11 (CVE-2026-40200)
- zlib: upgrade to 1.3.2-r0 (CVE-2026-22184)
Run `uv lock --upgrade` to update transitive dependencies and
resolve the vulnerability in Pillow.
Upgrade Pillow from 12.1.1 to 12.2.0 to address the FITS GZIP
decompression bomb issue.
Replace costly PostHog surveys with basic surveys, which better
fit our headless usage and avoid short data retention limits.
Enhance emitted events with additional metadata, including a
unique session ID and room ID, to correlate survey responses with
specific sessions.
This lays the groundwork for further enrichment with participant
connection data.
Automatically copy required Kubernetes secrets when using the
Tilt development stack, as `make bootstrap` is not documented
as a prerequisite.
Feedback from Arnaud Robin
Tests were incorrectly mocking Twirp errors using HTTP status
codes instead of the meaningful error codes returned by the
LiveKit SDK.
Update mocks to reflect actual SDK behavior.
Replace passing the LiveKit JWT in the request body with the
Authorization header, following standard authentication practices.
Extend the LiveKit authentication backend usage across additional
endpoints.
This also raises questions about how clients should securely
retrieve LiveKit tokens, to be addressed later.
Avoid mapping all Twirp errors to generic 500 responses.
Explicitly handle the case where a participant is no longer in
the room, as this may indicate suspicious behavior or a client
state issue.
Improve error discrimination to provide more accurate responses.
Introduce toggle-hand and rename endpoints in RoomViewSet,
secured with LiveKit token authentication.
Remove direct permission for clients to update their own metadata
via LiveKit tokens to prevent spoofing (e.g. faking admin status).
Proxy participant metadata updates through the backend to enforce
proper validation and authorization.
Signed-off-by: lebaudantoine <lebaud.antoine131@gmail.com>
The multi-user transcriber service was not integrated into Docker Compose,
limiting its usability in local and development environments.
Add the necessary configuration to enable running and orchestrating the
multi-user transcriber via Compose.
This commit aim at adding a securityContext for
pod and container in Deployment and Job,
it include livekit pods as well
It adds 2 values :
- podSecurityContext : for pods
- securityContext : for containers
Please note that `celeryBackend` Deployment does
not have any values defined in `values.meet.yaml` at the moment.
Updated taskV2 API contract to be closer to the target gateway contract.
GET operations return the same things as the webhook payload.
Also store the summary on S3 to be iso with transcript.
Add multitenancy support to Summary sub-app. The V1 routes / tasks
behave like before, with the default tenant being "meet".
V2 routes / tasks support being called frm any tenant, and don't have
meet related logic.
V2 tasks are created in separate queues to avoid mix / match,i
Resolve warnings raised in jwt/api_jwt.py:365 by ensuring test
keys meet the required security length.
Align test configuration with expected cryptographic standards.
Mark the more options area as an explicit navigation
region for screen readers.
Update each sidepanel toggle to use `aria-expanded` to indicate
whether the panel is open, improving accessibility feedback.
Also, avoid render for screen reader the number of participants
as it's already added the the aria label
Declare a dedicated ARIA region for call controls
to improve accessibility.
Extract this region into a reusable component for better
consistency and maintainability.
Add an aria-label to clearly describe the purpose of the toolbar.
Avoid redundant wording in reaction button labels to improve
clarity for screen reader users.
Inspired by proprietary solutions.
Replace the dialog-based reaction UI with a toolbar integrated
directly into the DOM.
Allow it to remain open and support proper keyboard interaction,
improving accessibility and user experience.
Move hardcoded values used for layout alignment and animations
between the videoconference and side panel into shared constants.
This improves readability and makes future adjustments easier to
maintain.
Remove unnecessary wrapper divs to reduce layout complexity.
Explicitly name components to better reflect their
responsibilities, including RoomContentArea which handles the
video track viewport.
Group layout-related components under a single feature directory
to improve structure and readability.
This is a first step toward cleaning up the project and clarifying
the organization of the video call layout.
Use a single store, hook, and portal system to handle both local
and remote emoji reactions.
Improve code quality and reduce duplication through better
factorization of shared logic.
Reduce duplication by introducing YAML anchors for configurations
shared across multiple services.
Most settings were nearly identical across the three summary
services, making them easier to maintain and update.
The dev values are no longer in use and have not been used for over a year.
We primarily rely on the dev-keycloak values, and occasionally
the dev-dinum ones for testing on the Dinum-labeled frontend.
As a result, the unused dev values should be removed to reduce clutter
and simplify maintenance.
Serialization hardening introduced a breaking change between the
frontend and backend. Adjust the Pydantic model to restore
compatibility.
Reinstate support for can_subscribe_metric, which is passed by
default from the frontend.
Email addresses are PII and should not appear in technical or
error logs.
Sanitize logging to avoid exposing sensitive user data when
invitation sending fails.
Prevent invited participants from seeing each other's email
addresses when sending room invitations.
Ensure invitations are sent with proper isolation to avoid
mass PII disclosure.
This mitigates risks of email harvesting, spam, and phishing
through the platform.
After migrating to uv-build only the module matching the project name was
included in sdist/wheel packages. Without a src layout additional modules
need to be tracked manually to ship them in built packages.
Make SESSION_ENGINE configurable through environment variable,
following the same values.Value() pattern already used for
SESSION_COOKIE_AGE. This enables OIDC backchannel-logout by
allowing users to set SESSION_ENGINE to db backend.
Closes#1037
Update the link label to use explicit text "Download your recording"
instead of generic "following this link."
This ensures blind users understand the purpose of the link
and the behavior of opening a new window.
Update the logo alternative text to include the brand name instead
of a generic "logo image" description.
Use a more descriptive value such as "Logo LaSuite Meet"
to better convey the content to screen reader users.
It closes#1092
When uploading an image, depending on the available network
there might be a bit of wait while the image is being uploaded.
In this commit we add a preview (grayscale + spinner) to have
UI feedback that the upload is in progress.
The "Transcribe" and "Record" buttons had unclear and misleading
descriptions, both using the verb "record," which caused confusion,
especially for screen reader users.
Update descriptions to clearly reflect each action:
- Transcribe: generate a written transcript of the conversation
- Record: save the meeting as a video
This improves accessibility (RGAA 11.9) and reduces the risk of
users triggering the wrong action.
Closes#1173
The aria-label only announced the presence of a sidepanel without
including its title.
Append the sidepanel title to improve accessibility and context
for screen readers.
Closes#1176.
Add a custom virtual background feature.
If the backend supports uploading files, backgrounds are stored
in the backend for the user.
Otherwise, only one background image can be selected.
The getImageData call was using PROCESSING_WIDTH for both dimensions
instead of PROCESSING_WIDTH and PROCESSING_HEIGHT. This caused the
source image data to be 256x256 instead of the expected 256x144, leading
to buffer overflow when writing to the segmentation mask and potential
visual artifacts in Firefox background effects.
Sanitize error handling to prevent leaking internal details when
invalid or malicious requests are sent to the API.
Return generic error responses to reduce the risk of information
disclosure during probing attempts.
The dot before (?P<extension>...) was not escaped and matched any
character instead of a literal period.
Escape it to align with MEDIA_STORAGE_URL_PATTERN, which correctly
uses \. for the file extension separator.
Update the response message to explicitly state that certain
notifications are ignored on purpose, avoiding confusion
during debugging or log inspection.
With the introduction of background file uploads, a misconfigured
MinIO webhook could trigger the storage hook endpoint for unrelated
files.
While the dev setup now filters events via the MinIO lifecycle
configuration, add a safeguard at the application level.
Enforce a stricter filepath regex when parsing storage hook events
and ignore files outside the recording output directory.
Return a clean 200 response to acknowledge the webhook while
avoiding unnecessary processing.
Avoid duplicating patterns that match the same logical unit by
sharing common parts between the two regexes.
This logical factorization improves maintainability and ensures
consistent parsing behavior across S3 event handlers.
Initially I avoided coupling the recording path regex with the
setting that provides the Django static URL. However the new
file viewset already relies on it, and Drive does as well.
For consistency, update the recording regex to use the same
STATIC_URL-based approach.
While the coupling may not feel ideal, having two different
regex strategies for similar file paths would be worse.
Combine the regex used in Drive with the one from the recording
feature and centralize them in the enums module.
Although the module name suggests only enums, it also hosts
shared constants used across the codebase.
Add FILE_UPLOAD_ENABLED setting (default to False, to avoid
a breaking change). Also adds a max_count_by_user sub setting
to restrict the number of uploaded files per user.
InitialDelaySeconds was set to 30s which caused the tilt stack
startup to be very slow. In this commit I split the initial delay
and the period arg.
5s seems to be relevant given the nature of the app.
Authenticate the application secret before checking whether the
application is inactive.
This avoids revealing the inactive status of an application when
an incorrect secret is provided, preventing an authentication
state oracle and client_id enumeration.
Currently the inactive status is revealed before verifying the
secret, creating an authentication state oracle.
Introduce a failing test to capture the issue before
applying the fix.
With the introduction of file background uploads, only trigger the
webhook for files related to recordings.
Avoid firing the "recording saved" event for other file uploads,
preventing unnecessary queries and false triggers.
Add minimal admin support to allow administrators to mark an
application account as inactive if it becomes rogue or compromised.
This capability was missing when the application concept was
initially introduced in the backend.
Most models in the project use `is_active` rather than `active`.
The Application model was not aligned with this convention.
Rename the field and add a migration to standardize the naming.
Update related tests accordingly.
This change should not introduce breaking changes for external
applications.
Remove the user-provided filename while preserving the extension,
and switch to the format `uuid.extension`.
This is more natural than the previous `uuid/.extension` format
and avoids confusion.
Update related tests accordingly.
Migrate main meet app to use UV for dependancy management.
Also optimized the backend image build sequence for faster rebuilds
when dependencies don't change.
Also removed compiled django translations files are they are done in the build
process now.
Changes inspired by drive repo.
When support manually marks a recording as "failed to stop," enable
users to download the recording from the frontend.
This ensures users can recover their recordings even if the
automatic stop process fails.
Making the status read-only improved security and enforced least
privilege, but in production some recordings could not be properly
stopped, leaving them in an active state.
Introduce an admin action to allow support staff to mark recordings
as "failed to stop" or update their status when necessary.
Throttle the request-entry endpoint for authenticated users also
to guard against accidental hammering from buggy clients.
Authenticated users are throttled via RequestEntryUserRateThrottle.
Anonymous users are throttled using the lobby participant cookie
through RequestEntryAnonRateThrottle.
use the lobby participant cookie ID as the throttle cache key
rather than the client IP address.
This avoids penalising multiple users behind the same NAT or proxy
and aligns throttling with how LobbyService identifies participants.
This bug was spotted in production where users from ministries behind
NAT was blocked by throttling while using visio.
If no cookie is present yet, skip throttling for the request. The
cookie will be set on the first response and throttling will apply
from subsequent requests.
This throttle is intended to protect against accidental hammering
from buggy clients, not as a security control against DoS attacks.
This is not a security measure, we should use a WAF.
Enhancements to the German localization include:
- Refine phrases to sound more natural
- Ensure consistency in words usage
- Adapt phrases to be inclusive and gender-neutral
- Standardize to the "du" form of address, aligning with modern
practices and other open-source projects like GitLab and the Android
Open Source Project
The wrong port was configured, causing CORS issues
when uploading files directly to the MinIO bucket.
Port 9001 is reserved for the MinIO console;
use the correct API port instead.
* Add a simple celery-backend deployment to helm chart, which
uses the same docker image as the backend, and runs a single
worker.
* Update dev keycloack values accordingly
* Update Tiltfile to properly set service dependencies
* Update minio deployment to increase the default max body size limit
(relevant to uploading files)
Improve celery configuration from env variables and set
meet backend related tasks to run in the `meet-backend` queue
to ease sharing a single redis instance for multiple celery.
Add media_files_svc and ingress_media_files to helm chart
for serving medias related to the new files.
This setup is more straightforward than merging the
existing recording related media ones.
Also updated values to enable file upload by default in dev.
Mostly done by @lebaudantoine.
For the coming features we will need to store files on the meet side.
(for instance user backgrounds).
This commits adds a new Model to manage files, and the associated
serializers & viewsets. All are tested.
This work was heavily inspired by the work done by our friends at
https://github.com/suitenumerique/drive
It build on the same architecture design (upload directly to S3 but
download goes through our proxy), but model is much much simplier
(no folders, no file sharing, etc.).
Reused the logic developed by the team working on drive.
This is usefull for our own upload file backend (that will
come in later commits).
Dockefile was updated to add a required system dependency.
We might want to put this shared logic in a lib.
Recent styling changes introduced an overflow, causing the network
indicator to be pushed outside of the participant tile.
Remove width: 100% and add a minimal gap to prevent metadata
elements from being too close to each other.
Refactor styles to leverage PandaCSS inline capabilities for
better clarity and consistency.
Remove an unnecessary div wrapper that was causing a layout shift.
Create a separate Ingress resource to isolate traffic targeting the
webhook-livekit endpoint and allow applying specific NGINX
annotations to this route.
Use an exact path match to take precedence over the default /api
regex rule defined in the base Ingress.
No similar change is made for the S3 webhook endpoint, as this
dependency will be removed from the project.
Remove incorrect reference to ProConnect (DINUM SSO) from content
literals, where it should not be mentioned by default in the
white labeled version.
It closes#1075
The regex was being recreated on every function call, causing
unnecessary performance overhead.
Hoist the RegExp to a module-level constant to reuse the compiled
pattern.
The items array was defined inline, creating a new reference on
every render.
Hoist the array to a module-level constant or memoize it with
useMemo to prevent unnecessary re-renders.
The empty object literal created a new reference every render,
potentially triggering unnecessary re-renders.
Hoist an EMPTY_PROPS constant to the module level and reuse it
instead of allocating a new object.
Replace sequential await inside the loop with Promise.all, since
each enterRoom call is independent.
This prevents unnecessary delays when multiple participants are
waiting (e.g. 10 participants previously resulted in ~10x longer
execution time).
Improve validation of parameters accepted when starting a
recording to prevent unsupported or unexpected values.
Language validation will be further tightened to only accept
languages supported by the transcribe microservice.
Add extensive API validation tests to cover these scenarios.
Refactor the summary service to better separate concerns, making components
easier to isolate and test. Unify logging logic to ensure consistent
behavior and reduce duplication across the service layer. These changes
set up the codebase for granular testing.
Use settings directly in migrations to avoid noop
migrations. This might have undisered side effects
if we change the config over time 'invalid' data will be
in the database.
It's a simple quick fix.
Keeping some migrations that are no useless to avoid changing
too much the migration history for users.
Similar to https://github.com/suitenumerique/people/commit/
469014ac415b25be0ceed08b31a87d2d40d743cd
These fields previously triggered a suspicious operation exception
when passed to the API.
Make the list configurable so the serializer behavior can be
adjusted without requiring a new release.
During the bug bounty, attempts were made to pass unexpected hidden
fields to manipulate room behavior and join as a ghost.
Treat these parameters as suspicious. They are not sent by the
frontend, so their presence likely indicates tampering.
Explicitly allow the parameters but emit warning logs to help detect
and investigate suspicious activity.
Super useful for validation when handling unstructured dictionaries.
Follow qbey's recommendation and align with the
suitenumerique/conversation project approach to improve schema
validation and data integrity.
The Trivy GitHub repository was wiped over the weekend, raising
suspicions of a potential supply chain attack.
Temporarily disable the scan until the situation is clarified.
Add automated security review on new pull requests to strengthen
early detection of potential vulnerabilities.
Leverage Claude to help identify security issues and highlight
areas requiring special attention.
The previously pinned version (July release) did not support
passing the aria-disabled prop to React Aria Button.
A more recent release (August) introduced this capability.
Upgrade is required to make Cyril's proposal work.
Under some shells echo doesn't work as expected with the special formatting.
Using printf when creating the variables make it work and should be more robust.
Transcription and summarization results were always generated
using a French text structure (e.g. "Réunion du..."), regardless
of user preference or meeting language. Introduced basic localization
support to adapt generated string languages.
German was missing from the frontend/backend language list in the
sync hook, causing user preference updates to be ignored.
Add the language to ensure preference changes are properly applied.
Update default Renovate configuration to open PRs on
the first day of each month instead of weekly.
Security updates remain handled immediately by Dependabot, while
Renovate manages regular dependency updates to keep the project
up to date with third-party packages.
Refactor external API authentication classes to inherit from a
common base authentication backend.
Prepare the introduction of a new authentication class responsible
for verifying tokens provided to calendar integrations.
Move token decoding responsibility to the new token service so it
can both generate and validate tokens.
Encapsulate external exceptions and expose a clear interface by
defining custom Python exceptions raised during token validation.
Taken from #897.
Encapsulate token generation logic for authenticating to the
external API in a well-scoped service.
This service can later be reused in other parts of the codebase,
especially for providing tokens required by calendar integrations.
Commit was cherry picked from #897
Work adapted from PR #859 and partially extracted to ship as a
smaller, focused PR.
This allows users to view the full list of available shortcuts.
An editor to customize these shortcuts may be introduced later.
Pip was removed before copying the builder stage output, which caused
it to be reinstalled unintentionally. Adjust the order to align with
the backend image behavior.
Upgrade OpenSSL and related dependencies to address CVE-2025-15467
in meet-agents.
This vulnerability was blocking the image signature workflow, as it
is classified as a critical dependency.
Reduce surface area and keep the runtime image minimal.
Alpine 3.22 provides ffmpeg v6 as the latest version.
Alpine 3.23 does not include ffmpeg v7, so upgrade directly to v8.
Install pip temporarily for build steps, then remove it from the
production image.
Use the same user as in production to facilitate local testing with
the production image.
Assign group 127 to the docker user to mirror CI and match production
practices, even though the rationale for this group mapping is unclear.
Update all GitHub Actions to their latest major versions for improved
performance, security patches, and Node.js runtime compatibility.
Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
Configure the external application API across different Kubernetes setups
to enable seamless usage without repeated configuration
when iterating on endpoints.
Expose a Windows application web link requested by a partner who wraps Visio
inside a containerized Chrome application due to security concerns and limited
trust in video codecs.
This commit introduces a proof of concept implementation.
We plan to iterate on this approach and likely generalize it under a more
neutral lasuite meet naming in future revisions.
Add support for additional shortcuts to broaden accessibility and
integration capabilities. Some of these are required to ensure full
functionality with the RENATER SIP media gateway, allowing shortcut
mapping to DTMF signals. Others improve usability for keyboard-only
users; a lightweight helper will be introduced to surface available
shortcuts and make them easier to discover and use.
Add support for Shift and Alt modifiers when building shortcuts,
expanding the range of possible combinations and allowing more expressive
and flexible shortcut definitions.
Centralize shortcuts into a single source of truth, making them easier to
discover and manage, and laying the groundwork for future override support
and the ability to revert to default definitions if needed.
Shortcuts are now retrieved by identifier, while leaving each component
responsible for declaring when a shortcut should be enabled and which
handler should be called;
Token generation already verifies that the application is active, but this
guarantee was not enforced when the token was used. This change adds a
runtime check to ensure the client_id claim matches an existing and active
application when evaluating permissions.
This also introduces an emergency revocation mechanism, allowing all previously
issued tokens for a given application to be invalidated if the application is
disabled.
Use a mixin, introduced by @lunika in the shared
backend library to monitor throttling behavior.
The mixin tracks when throttling limits are reached, sending errors to Sentry
to trigger alerts when configured. This helps detect misconfigurations,
fine-tune throttling settings, and identify suspicious operations.
This enables safely increasing API throttling limits while ensuring stability,
providing confidence that higher limits won’t break the system.
Extract throttling classes into a dedicated Python module, following the
structure of suitenumerique/docs.
This is a preparatory refactor to ease upcoming changes to the throttling
implementation. No functional behavior change is introduced in this commit.
If a viewset action is not implemented, the permission layer no longer returns
a 403. Instead, it lets DRF handle the request and return the appropriate 405
Method Not Allowed response, ensuring cleaner and more standard API error
handling.
Enhance scope manipulation by normalizing and sanitizing
scope values before processing.
Scopes are now converted to lowercase to ensure consistent behavior,
deduplicated while preserving their original order, and handled in a
deterministic way aligned with the intended authorization model.
Reinforce the test suite around the external API viewset to better
prevent regressions, permission leaks, and unexpected failures.
Adds additional scenarios covering permission enforcement, edge cases,
and error handling to ensure the external API behavior remains stable
and secure as it evolves.
The previous replace usage was too broad and could remove multiple
occurrences, which was not the original intention.
Replace the replace call with removeprefix, which more accurately
matches the expected behavior by only removing the prefix when present
at the start of the string.
Apply strict permission validation on the external API room endpoint to
enforce the principle of least privilege. Unlike the default API (which allows
unauthenticated room retrieval and filters access in the serializer), the
external API now only exposes rooms to users with explicit permissions.
This change fixes a security issue. Slug-based room retrieval, as supported
by the default API, is not introduced here but could be added later if needed.
Retrieving rooms by UUID is retained, as guessing a UUID is significantly harder
than a slug.
A dedicated permission class was created to avoid coupling permissions between
the default and external APIs. The external API enforces stricter access rules.
Access policies may be revisited based on user and integrator feedback. The
external API currently has no production usage.
Add a failing test demonstrating that a user can retrieve a room they
do not have access to when the room UUID is known.
This highlights an improper object-level permission verification in the
external API. While exploitation requires obtaining the target room
UUID, this still represents a security issue (BOLA / IDOR class
vulnerability) and must be fixed.
The test documents the expected behavior and will pass once proper
access filtering or permission checks are enforced.
SCREEN_RECORDING_BASE_URL was renamed to RECORDING_DOWNLOAD_BASE_URL.
The new variable supersedes the old one, which is temporarily kept for backward
compatibility. This test failure was missed because the local common file was
out of sync with common.dist.
Add the new variable with a default value of None to ensure a smooth
deprecation path when the old variable is removed.
This update fixes several SQL injection vulnerabilities, including issues in
RasterField band index handling and crafted column aliases (notably in
QuerySet.order_by()), as reported in CVE-2026-1207, CVE-2026-1287, and
CVE-2026-1312.
Replace the basic select component that loaded thousands of options into the
DOM with a smarter component supporting dynamic loading and search.
With large user bases, linking users to recording access caused massive option
lists to render, severely impacting performance. This change dramatically
improves page loading speed.
These values should not be updated from the admin interface. Allowing changes
to a recording’s associated room could lead to data leaks (e.g., notifications
being resent to the wrong users after a malicious modification).
Also remove the room select field, which rendered a dropdown with ~150k options,
flooding the DOM and severely degrading page performance.
Use prefetch_related for the room–user access relationship to avoid N+1
queries. select_related cannot be used here since this is a many-to-many
relation. This significantly improves performance.
Use select_related on the room foreign key to avoid N+1 queries. This makes
Django perform a join between tables instead of triggering additional queries
per row, reducing complexity from O(n²) patterns to O(n) and significantly
improving performance.
This was a mistake: the filter was never used in production and caused
performance issues. It generated a list of unique room slugs, bloating the DOM
with thousands of values and slowing down view rendering. Remove this
regression.
This endpoint only exposes a custom action for token generation and does not
rely on serializers or querysets. Using ViewSet is more appropriate here, as
it provides routing without enforcing standard CRUD patterns or requiring a
serializer_class.
This removes unnecessary constraints and avoids warnings related to missing
serializer configuration, while better reflecting the actual responsibility of
this view.
I noticed this bug from Sentry issue 241308
Without this configuration, LiveKit does not notify the backend when a recording
starts, leaving it stuck in a “starting recording” state.
Thanks to @leobouloc for spotting the issue.
An XSS vulnerability was identified by an open-source contributor. While the
impact was limited, only a room owner could inject the content and then view the
recording page, it is important to address, especially before introducing
multi-owner support.
A new vulnerability (CVE-2026-0994) was reported and is not yet fixed.
It affects protobuf libraries used by the livekit-api Python package.
A fix is in progress upstream, but the related PR has not yet been merged or
released. Since a release is required tonight, the Trivy scan step is
temporarily disabled to allow the build to proceed. This should be re-enabled
once a patched version is available.
https://github.com/protocolbuffers/protobuf/pull/25239
Offer a way to redirect unauthenticated users to an external home page when they
visit the app, allowing a more marketing-focused entry point with a clearer
value proposition.
In many self-hosted deployments, the default unauthenticated home page is not
accessible or already redirects elsewhere. To ensure resilience, the client
briefly checks that the target page is reachable and falls back to the default
page if not.
Switching from Deepgram to our custom Kyutai implementation introduced changes
in how segment data is returned by the LiveKit agent, so the segment start time
is now treated as optional.
Fix a minor issue in the external API where users were matched using
case-sensitive email comparison, while authentication treats emails as
case-insensitive. This caused inconsistencies that are now resolved.
Spotted by T. Lemeur from Centrale.
The latest `setuptools` version pulls in a `jaraco.context` version that
triggers a Trivy scan failure. `jaraco.context` has a path traversal
vulnerability.
This fix is inspired by suitenumerique/people, specifically Marie’s PR #1010.
Icon fonts were loading just in time, which is good for performance, but caused
a visible blink where fallback text appeared before the font loaded. I followed
the documentation introduced in PR 963 of the fontsource repository.
This introduces preloading for critical fonts, slightly increases initial load
time, and defines custom @font-face rules to control font-display and avoid
font swapping. This approach only works with Vite-based frameworks,
as noted in the documentation.
See the advanced installation section for material-symbols-outlined on
fontsource.org, and apply the same approach for Material Icons.
I manually built the preload headers based on a comment from issue #83.
This works well with Vite, which replaces the font URLs at build time.
Encapsulate icon and symbol rendering in a dedicated component that applies
aria-hidden and disables translation attributes.
This prevents browsers from translating icon names and breaking the UI, and
ensures screen readers do not announce decorative icons.
This is a first draft and can be extended with additional variants later.
Refactor the duplicateIdentity boolean URL parameter into an extensible string
reason parameter, making it easier to customize the disconnection message
shown to users.
Avoid passing this value via URL parameters, which are easy to manipulate.
Instead, use Wouter’s built-in navigation state to pass data across pages.
This was initially missed because navigateTo is a wrapper around Wouter’s
official navigation function, and its arguments were easy to overlook. This is
now fixed.
This prepares the ground for supporting additional disconnection reasons in
upcoming commits.
Hide non-essential icons and refine the labels to emphasize that one option
applies a stronger blur than the other. This should provide clearer cues for
screen reader users.
While this makes it slightly less explicit that clicking an already selected
option will disable the active effect, it improves accessibility by avoiding
automatic focus movement from the previously active option to a separate “none”
option. That focus shift could be misleading or hard to follow
for screen reader users.
Open to feedback on this decision.
Update the virtual background effects tooltip and ARIA label with more
descriptive and concise wording based on Sophie’s feedback. This helps all
users, especially those using assistive technologies, by improving how
each virtual background is vocalized.
Allow configuring whether a VAD model runs before calling an external ASR API.
Running VAD can save API calls (and costs) when no audible sound is detected,
but comes with the trade-off of additional computational overhead.
Subtitles were still visible when leaving and rejoining a meeting, even though
the backend API call to start them was not triggered again.
Introduce a hook that closes the subtitles layout on unmount, ensuring users
must explicitly click the button to restart subtitles when they rejoin a room.
Enhance the terms of service by adding detailed sections on
service availability, security, support management,
and monitoring of service use. This update aims to provide clearer
guidelines and responsibilities for users and the DINUM administration.
CVE-2025-13601 has yet no fix. I don't want to migrate the base image
in this pull request, as it could introduce regression.
I'll open an issue to fix this CVE later on. The summary service isn't
exposed on internet, and the agent isn't used in production.
Closes#685: add a Trivy scan to the CI build steps for Meet Summary
and Meet Agents to ensure no vulnerabilities are present before pushing images
to the registry.
Based on a request from our European partners, introduce new languages for the
transcription feature. Dutch and German are now supported, which is a great
addition.
It closes#837.
WhisperX is expected to support both languages.
Simplify a broken conditional check that allowed users without
the required permissions to see the control menu. The `NoAccessView`
is now shown to any user who is neither an admin nor the meeting owner.
After upgrading Python to 3.13, not all development environments were
updated accordingly. This fixes the incorrect volume mount path
introduced by that upgrade.
Fix MinIO client configuration: I was incorrectly passing a full URL instead of
an endpoint, which caused errors in staging. Local development values did not
reflect the staging setup and were also out of sync with the backend.
Fix an unexpected behavior where filtering LiveKit webhook events sometimes
failed because the room name was not reliably extracted from the webhook data,
causing notifications to be ignored.
Configure the same filtering logic locally to avoid missing this kind of issue
in the future.
Pin egress to the production version, which uses a more recent release than the
default chart value (1.9.0).
Using the default could have led to issues; hopefully this change avoids them.
Instead of relying on the egress_started event—which fires when egress is
starting, not actually started—I now rely on egress_updated for more accurate
status updates. This is especially important for the active status, which
triggers after egress has truly joined the room. Using this avoids prematurely
stopping client-side listening to room.isRecording updates. A further
refactoring may remove reliance on room updates entirely.
The goal is to minimize handling metadata in the mediator class. egress_starting
is still used for simplicity, but egress_started could be considered in the
future.
Note: if the API to start egress hasn’t responded yet, the webhook may fail to
find the recording because it currently matches by worker ID. This is unstable.
A better approach would be to pass the database ID in the egress metadata and
recover the recording from it in the webhook.
Worked on a large PR (#827) and chose to consolidate all new features and
refactorings in the changelog at the end of the work instead of updating it per
commit. Not ideal—acknowledge this is bad practice.
Link recording statuses to the `isRecording` attribute from the room on the
client side.
After the refactor, the frontend relied only on recording statuses computed by
the backend. However, when egress is started and the backend is notified, the
recording is not actually active yet. It takes some time for the egress to join
the room and begin recording.
Enrich the frontend by combining backend statuses with the room recording state
to more accurately reflect when recording is truly active. This avoids missing
the first few seconds of audio at the beginning of a recording.
Only display transcription settings to room admins or owners. Showing these
controls to users without the required privileges would be misleading, since
they cannot actually configure or apply the settings.
Link the transcription document to its related recording by adding a short
header explaining that users can download the audio file via a dedicated link.
This was a highly requested feature, as many users need to keep their audio
files.
As part of a small refactor, remove the argument length check in the metadata
analytics class. The hardcoded argument count made code evolution harder and was
easy to forget updating. Argument unwrapping remains fragile and should be
redesigned later to be more robust.
The backend is responsible for generating the download link to ensure
consistency and reliability.
I tried adding a divider, but the Markdown-to-Yjs conversion is very lossy and
almost never handles it correctly. Only about one out of ten conversions works
as expected.
Specify distinct icons in the recording state toast for each mode to provide
clearer visual feedback on what is actually happening. Remove the pulse CSS
animation, as it did not improve visual clarity and accessibility.
Inspired by @ericboucher’s proposal, allow non-admin or non-owner participants
to request the start of a transcription or a recording.
All participants are notified of the request, but only the admin can actually
open the menu and start the recording.
This is a first simple and naive implementation and will be improved later.
Prefer opening the relevant recording menu for admins instead of offering a
direct quick action to start recording. With more options now tied to recording,
keeping the responsibility for starting it encapsulated within the side panel
felt cleaner.
This comes with some UX trade-offs, but it’s worth trying.
I also simplified the notification mechanism by disabling the action button for
the same duration as the notification, preventing duplicate triggers. This is
not perfect, since hovering the notification pauses its display, but it avoids
most accidental re-triggers.
Rework the visual hierarchy of the “no access” view to align it with other
presentation modes and ensure the title order is clear and understandable for
users.
Refactor literals in the recording status hook and introduce a new status.
Align the login prompt style with the newly introduced warning message, and
guide users by clearly indicating that the two modes are mutually exclusive.
Users are prompted to stop the other mode before starting a new one.
This situation should happen less often now that checkboxes allow users to start
transcription and recording together. Hopefully, the UX is clear enough.
The growing number of props passed to the controls buttons may become an issue
and will likely require refactoring later.
Mutualize and factorize the recording API error modal in a single place, and
extract all recording mutations into a dedicated hook exposing both start and
stop actions.
This hook is responsible for interacting with the API error dialog when needed.
Previously, this logic was duplicated across each side panel; centralizing it
clarifies responsibilities and reduces duplication.
This component is now extensible and way easier to understand.
Previously, the recording state toast was implicitly acting as a provider,
making its core responsibility unclear for developers. Its role is not to
inject all recording-related elements into the videoconference DOM, but to
expose a clean recording state toast reflecting the current recording status.
This commit also fixes the limit-reached modal that was no longer appearing
after the refactor, ensures the modal is always rendered,
and removes unused React ARIA labels.
In the original code, the limit reached dialog was wrongly rendered
only when the recording state toast was null.
It was a bug in the implementation. Fix it.
Following the previous commit, refactor the frontend to rely on room metadata to
track which recording is running and update the interface accordingly. This
implementation is not fully functional yet.
The limit-reached dialog triggering mechanism is currently broken and will be
fixed in upcoming commits. I also simplified the interface lifecycle, but some
edge cases are not yet handled—for example, transcription controls should be
disabled when a screen recording is started. This will be improved soon.
Controls were extracted into a reusable component using early returns. This
makes the logic easier to read, but slightly increases the overall complexity of
the recording side panel component.
Relying on literals to manage recording statuses is quite poor, feel free to
enhance this part.
Previously, this was handled manually by the client, sending notifications to
other participants and keeping the recording state only in memory. There was no
shared or persisted state, so leaving and rejoining a meeting lost this
information. Delegating this responsibility solely to the client was a poor
choice.
The backend now owns this responsibility and relies on LiveKit webhooks to keep
room metadata in sync with the egress lifecycle.
This also reveals that the room.isRecording attribute does not update as fast
as the egress stop event, which is unexpected and should be investigated
further.
This will make state management working when several room’s owner will be in
the same meeting, which is expected to arrive any time soon.
Now that screen recording and transcription share the same UI presentation,
extract the row logic into a reusable component to avoid code duplication and
improve code maintainability.
Centralize the logic to compute, internationalize, and present the maximum
recording duration in a human-readable way, reducing duplication across the
codebase.
Refactor the screen recording side panel to align with the transcription UX,
ensuring a more consistent and homogeneous user experience.
This commit also introduces a checkbox allowing users to request transcription
of the screen recording, which is one of the most requested features.
The side panel will be enriched with more information soon, especially once
Fichier is integrated for storing recordings, so the destination can be made
explicit.
More recording settings (layout, quality, etc.) will be introduced in upcoming
commits.
A lot of duplication existed, so I started factorizing components
now that a proper user experience is clearer.
Without over-abstracting, the first step introduces a reusable
“no access” view with configurable message and image.
This is just the beginning: props passing is still not ideal, but
it’s sufficient to merge and significantly reduce duplication.
Add a key feature allowing users to choose the language
of their transcription via a setting.
The default value is set to French, the most commonly used
language across our user base.
Users can still select English or “Automatic,” which re-enables automatic
language detection if no default is configured on the microservice.
Not all self-hosted instances will configure this setting, so a default text is
shown when the destination is unknown.
This is important to let users quickly click the link and understand which
platform is used to handle the transcription documents.
Video files are heavy recording files, sometimes several hours long.
Previously, recordings were naively submitted to the Whisper API without
chunking, resulting in very large requests that could take a long time
to process. Video files are much larger than audio-only files, which
could cause performance issues during upload.
Introduce an extra step to extract the audio component from MP4 files,
producing a lighter audio-only file (to be confirmed). No re-encoding
is done, just a minimal FFmpeg extraction based on community guidance,
since I’m not an FFmpeg expert.
This feature is experimental and may introduce regressions, especially
if audio quality or sampling is impacted, which could reduce Whisper’s
accuracy. Early tests with the ASR model worked, but it has not been
tested on long recordings (e.g., 3-hour meetings),
which some users have.
Screen recording are MP4 files containing video)
The current approach is suboptimal: the microservice will later be updated to
extract audio paths from video, which can be heavy to send to the Whisper
service.
This implementation is straightforward, but the notification service is now
handling many responsibilities through conditional logic. A refactor with a
more configurable approach (mapping attributes to processing steps via
settings) would be cleaner and easier to maintain.
For now, this works; further improvements can come later.
I follow the KISS principle, and try to make this new feature implemented
with the lesser impact on the codebase. This isn’t perfect.
The previous code lacked proper encapsulation, resulting in an overly complex
worker. While the initial naive approach was great for bootstrapping the
feature, the refactor introduces more maturity with dedicated service classes
that have clear, single responsibilities.
During the extraction to services, several minor issues were fixed:
1) Properly closing the MinIO response.
2) Enhanced validation of object filenames and extensions to ensure
correct file handling.
3) Introduced a context manager to automatically clean up temporary
local files, removing reliance on developers.
4) Slightly improved logging and naming for clarity.
5) Dynamic temporary file extension handling when it was previously
always an hardcoded .ogg file, even when it was not the case.
Pass recording options’ language to the summary service, allowing users to
personalize the recording language.
This is important because automatic language detection often fails, causing
empty transcriptions or 5xx errors from the Whisper API. Users then do not
receive their transcriptions, which leads to frustration. For most of our
userbase, meetings are in French, and automatic detection is unreliable.
Support for language parameterization in the Whisper API has existed for some
time; only the frontend and backend integration were missing.
I did not force French as the default, since a minority of users hold English or
other European meetings. A proper settings tab to configure this value will be
introduced later.
Major user feature request: allow starting recording and transcription
simultaneously. Inspired by Google Meet UX, add a subtle checkbox letting users
start a recording alongside transcription.
The backend support for this feature is not yet implemented and will come in
upcoming commits, I can only pass the options to the API. The update of the
notification service will be handled later.
We’re half way with a functional feature.
This is not enabled by default because screen recording is resource-intensive. I
prefer users opt in rather than making it their default choice until feature
usage and performance stabilize.
Using a JSON field allows iterating on recording data without running a new
migration each time additional options or metadata need to be tracked.
This comes with trade-offs, notably weaker data validation and less clarity on
which data can be stored alongside a recording.
In the long run, this JSON field can be refactored into dedicated columns once
the feature and data model have stabilized.
Inspired by proprietary solutions, add clearer details on how transcription
works and what users can expect from the feature. This new presentation is much
simpler to read, parse, and understand than the previous large block of text
that users were not reading at all.
Using icons helps users quickly understand where the transcription is sent, how
they are notified, and which meeting language is used.
Some information is currently hardcoded and will be parameterized in upcoming
commits. This work is ongoing.
Explicitly explain that transcription is reserved for public servants. Remove
the temporary beta form: the feature is now available to all public servants,
with restrictions based on domain. Make white-labeling rules explicit and
clarify who to contact for access.
The beta form created frustration, with users registering and never hearing
back from the team.
Improve guidance when a user may be the meeting host but is not logged in, and
therefore cannot activate recording. Add a clear hint and a quick action to log
in. This decision is based on frequent support requests where users could not
understand why recording was unavailable while they were simply not logged in.
Initially, I thought presenting the recording feature as a beta would clearly
signal that it was still under construction and being improved. In practice, it
sent a negative signal to users, reduced trust, and still generated many
questions for the support team.
Without clearly explaining why the feature was in beta or what was coming next,
the label only added confusion. I chose to simplify the interface and remove the
beta indication altogether.
Follow Robin’s suggestion on the meeting tool layout presentation. The result
does not yet exactly match the Figma design, and I took some freedom to stay
closer to a Google Meet–like layout.
In the initial approach, it was hard to understand that the full option was
clickable. Adding a light background improves discoverability and usability.
Robin chose to adopt Material Design icons, inspired by NVasse’s commit on
Fichier. This sets up the required CSS to easily use Material Icons throughout
the application.
Eventually, all icons in the app will be replaced with Material ones. For now,
the setup is only used in the recording UI refactor.
Simplify wording and presentation of the recording feature heading,
using a more concise and familiar product-style language inspired by
well-known proprietary solutions.
Many public servants use PCs with unusual screen resolutions. The screen
height is often quite small, which caused responsiveness issues on the
vertical axis.
When opening the side panel, they could not see the button to start the
recording. I improved the vertical responsiveness to address this issue and
reduce support requests such as “I cannot see the button”.
Users typically do not think about scrolling inside the side panel, so the
layout now better fits constrained screen heights.
Eliminate the perception of being 'under development,'
which can undermine trust with potential users.
Focus on creating a more confident and reassuring experience.
The previous attempt to make the Deepgram configuration extensible
introduced unnecessary complexity for a very limited use case and
made it harder to add new STT backends.
Refactor to a deliberately simple and explicit design with minimal
cognitive overhead. Configuration is now fully driven by environment
variables and provides enough flexibility for ops to select and
parameterize the STT backend.
Until a Pull Request is merged with our changes on livekit-agent
to support Kyutai API, we will use a custom and hacky python
library made from Arnaud's researches and published on an
unofficial pypi project page.
Everything is quite "draft" but it allows us to deploy and test
in real situation the work from Arnaud.
Some system dependencies were unexpectedly missing, causing the
LiveKit agent framework to fail at runtime.
Install the required dependencies based on runtime error logs.
This fixes Docker image failures in the remote (staging) environment.
Implement Langfuse tracing integration for LLM service calls to capture
prompts, responses, latency, token usage, and errors, enabling
comprehensive monitoring and debugging of AI model interactions
for performance analysis and cost optimization.
Replace plain string fields with Pydantic SecretStr class for all
sensitive configuration values in FastAPI settings to prevent accidental
exposure in logs, error messages, or debugging output, following
security best practices for credential handling.
Install Langfuse observability client in summary service
to enable LLM tracing, monitoring, and debugging capabilities
for AI-powered summarization workflows,
improving visibility into model performance and behavior.
Add OIDC_USER_SUB_FIELD_IMMUTABLE setting to our config and enforce
it in the user viewset. Previously relied on implicit Django
LaSuite defaults.
Makes the sub mutability constraint explicit and ensures it's enforced
at the application level, critical for provisional users where sub is
assigned on first login.
Update the sub field documentation to explicitly reflect its optional nature.
Originally intended to be mandatory, sub became optional due to a code issue.
This change acknowledges and formalizes that behavior as intentional.
The optional sub enables external API integrations to provision users with
only an email address. Full identity (sub) is assigned on first login,
allowing third-party platforms to create users before they authenticate.
Allow external platforms using the public API to create provisional users
with email-only identification when the user doesn't yet exist in our
system. This removes a key friction point blocking third-party integrations
from fully provisioning access on behalf of new users.
Provisional users are created with email as the primary identifier. Full
identity reconciliation (sub assignment) occurs on first login, ensuring
reliable user identification is eventually established.
While email-only user creation is not ideal from an identity perspective,
it provides a pragmatic path to unlock integrations and accelerate adoption
through external platforms that are increasingly driving our videoconference
tool's growth.
Reorder CHANGELOG section headings to follow standard Keep a Changelog format
(Added, Changed, Deprecated, Removed, Fixed, Security) for consistent structure
that users expect when reviewing release notes.
Implement automated CI validation ensuring pull request authors
update CHANGELOG with their changes, preventing undocumented
changes from merging and maintaining accurate release
documentation for users and maintainers.
Consolidate scattered transcript formatting functions into single
cohesive class encapsulating all transcript processing logic
for better maintainability and clearer separation of concerns.
Add transcript cleaning step to remove spurious recognition artifacts
like randomly predicted "Vap'n'Roll Thierry" phrases that appear
without corresponding audio, improving transcript quality
by filtering model hallucinations.
When declaring scopes with our OIDC provider, they require us to prefix
each scope with our application name. This is to prevent reserving generic
scopes like rooms:list for only our app, as they manage a large federation.
I’m proposing a workaround where, if a resource server prefix is detected in
the scope, it’s stripped out. This solution is simple and sufficient
in my opinion.
Since the scopes are defined in the database, I don’t want to update
them directly. Additionally, each self-hosted instance may have a different
application name, so the prefix should be configurable via a Django setting.
While creating a meeting on mobile, the dialog was opening,
and when its width exceeds the mobile width, users are unable
to close the dialog.
Prevent the dialog opening on mobile as a hot fix.
Adjust z-index values to restore button interactivity broken by previous
z-index changes in commit 53e68b7, ensuring fullscreen warning dismiss
controls remain accessible to users instead of being blocked
by overlay layering.
Manually update libexpat to 1.6.53-r0 in Alpine 3.21.3 base image
to address CVE-2025-64720, CVE-2025-65018,
CVE-2025-66293 high-severity vulnerability until newer Alpine base image
becomes available, ensuring Trivy security scans pass.
Create initial resource server integration documentation based on existing
service account documentation structure to help developers understand
authentication flow and implementation requirements for external services
consuming Meet's protected resources.
Upgrade django-lasuite to v0.0.19 to benefit from the latest resource server
authentication backend. Thanks @qbey for your work. For my needs, @qbey
refactored the class in #46 on django-lasuite.
Integrate ResourceServerAuthentication in the relevant viewset. The integration
is straightforward since most heavy lifting was done in the external-api viewset
when introducing the service account.
Slightly modify the existing service account authentication backend to defer to
ResourceServerAuthentication if a token is not recognized.
Override user provisioning behavior in ResourceServerBackend: now, a user is
automatically created if missing, based on the 'sub' claim (email is not yet
present in the introspection response). Note: shared/common implementation
currently only retrieves users, failing if the user does not exist.
Define Docker network enabling external service providers to share Keycloak
instance with local development stack, supporting OIDC authentication flow
where services obtain tokens from shared Keycloak then pass to Meet
for introspection and validation.
Prepares Meet infrastructure for multi-service authentication architecture
though external service provider Docker Compose integration changes remain
in separate repository.
A manually constructed authentication URL didn’t match the actual endpoint
address, causing the Django backend to issue a 301 redirect to the correct URL.
This wasn’t a problem for regular users at first, but once a client integrating
through a virtual browser came on board, it became significant. The 301 redirect
was disrupting the virtual browser’s cookie/cache system, which in turn broke
the authentication flow.
This change aims to resolve the issue, although it’s not yet certain that
it will fully address their problem.
- add admin action to retry a recording notification to external services
- log more Celery tasks' parameters
- add multilingual support for real-time subtitles
- update backend dependencies
Mark unnecessary decorative icons as aria-hidden following feedback
from @cyberbaloo to eliminate redundant screen reader announcements
that create noisy and annoying experience for users relying on
assistive technologies.
Regenerate backend translation files to include missing translations for newly
added translatable strings in recent code changes, ensuring complete
internationalization coverage across all supported languages.
django-lasuite 0.1.16 changed the user update mechanism from .update()
to .save(), which triggers Django's constraint validation. This causes
an additional SELECT query to verify 'sub' field uniqueness on every
user update, despite 'sub' being immutable in our auth flow.
This commit update the test to make them pass again.
Restrict pylint version to 3.x in renovate configuration because
pylint-django 2.6.1 requires pylint<4, preventing automatic upgrades
to pylint 4.x that would create unresolvable dependency conflicts
until pylint-django releases compatible version.
Update brotli compression library to version 1.2.0 addressing
CVE-2025-6176 security vulnerability to maintain secure
compression functionality and pass security scans.
Update Django from previous version to 5.2.8 addressing CVE-2025-64459
and CVE-2025-64458 security vulnerabilities to maintain secure
application infrastructure and pass security audits.
Introduce new variables for the docker registry where to push docker images on forks:
- DOCKER_CONTAINER_REGISTRY_HOSTNAME for the docker registry hostname, with default value "docker.io"
- DOCKER_CONTAINER_REGISTRY_NAMESPACE for the docker registry namespace, with default value "lasuite"
We will introduce in the next commits the compose set-up that also
require examples values/config files. Thus, re-organize the kube ones
to dedicated folder, to make the files organisation extensible.
This release adds support for injecting custom Kubernetes
resources through the extraManifests parameter.
New features:
- Add extraManifests support for deploying custom resources
- Support multiple input formats (list, map, raw YAML strings)
- Enable Helm template variables in injected manifests
Add ability to inject custom Kubernetes manifests through the
values.yaml file. This allows users to deploy additional
resources (Deployments, Services, ConfigMaps, etc.) without
modifying the chart templates.
The template supports multiple input formats: list of objects,
map of named objects, and raw YAML strings, providing maximum
flexibility for users.
- Create templates/extra-objects.yaml with flexible rendering
- Add extraManifests parameter in values.yaml with documentation
- Support Helm template variables in injected manifests
- Handle list, map, and string YAML formats automatically
Add dynamic configuration for Deepgram STT via environment variables,
enabling multilingual real-time subtitles with automatic language
detection.
Changes:
- Add DEEPGRAM_STT_* environment variables pattern for configuration
- Implement _build_deepgram_stt_kwargs() to dynamically build STT
parameters from environment variables
- Add whitelist of supported parameters (model, language) for LiveKit
Deepgram plugin
- Log warnings for unsupported parameters (diarize, smart_format, etc)
- Set default configuration: model=nova-3, language=multi
- Document supported parameters in Helm values.yaml
Configuration:
- DEEPGRAM_STT_MODEL: Deepgram model (default: nova-3)
- DEEPGRAM_STT_LANGUAGE: Language or 'multi' for automatic detection
of 10 languages (en, es, fr, de, hi, ru, pt, ja, it, nl)
Note: Advanced features like diarization and smart_format are not
supported by the LiveKit Deepgram plugin in streaming mode.
Add detailed logging for owner ID, recording metadata, and
processing context in transcription tasks to improve debugging
capabilities.
It was especially important to get the created document id,
so when having trouble with the docs API, I could share
with them the newly created documents being impacted.
Replace narrow HTTPError handling with broad RequestException
catch to prevent crashes from network failures (ConnectionError),
timeouts (30s exceeded), SSL/TLS errors, and other request failures
that previously caused unhandled exceptions.
Ensures consistent False return and proper logging for all network-related
failures instead of crashing application when summary service
communication encounters infrastructure issues beyond HTTP errors.
While helping users, it was such a pain to determine quickly which recording
was indeed a transcription or a video recording.
Added the column to help me, and support team.
The recording / transcription is the most unstable part of the project.
Enable administrators to manually retrigger external service notifications
from Django admin for failed or missed notification scenarios,
providing operational control over notification delivery.
Remove 1000 z-index from screenshare warning that was
causing conflicts with reaction menu and reaction displays,
retaining only necessary layering to hide participant
metadata underneath.
Persist user preference choices across sessions using localStorage
following notification store pattern, eliminating need to reconfigure
disabled features on every meeting join and respecting user's
long-term preference decisions.
Adopt unified switch component pattern for notification preferences to
enable future addition of descriptive text per notification type,
improving consistency and providing clearer explanation capability
for notification behaviors.
Allow users to opt-out of idle participant disconnection despite
default enforcement, trusting power users who modify this setting
won't forget to disconnect, though accepting risk they may block
maintenance configuration updates.
Add margin between switch description text and toggle button to
improve visual breathing room and prevent text from appearing
cramped against interactive control element.
Introduce pop-in alerting participants of automatic 2-minute idle
disconnect to enable LiveKit node configuration updates during
maintenance windows, preventing forgotten tabs from blocking
overnight production updates following patterns
from proprietary videoconference solutions.
Introduce new narrow-width alert dialog variant to improve
readability of short messages by preventing excessively
long line lengths that occur when brief alerts use
standard dialog widths.
Expose idle disconnect timeout as configurable parameter accepting None value
to disable feature entirely, providing emergency killswitch for buggy behavior
without redeployment, following other frontend configuration patterns.
Restore correct task_args ordering in metadata manager after commit f0939b6f
added sender argument to Celery signals for transcription task scoping,
unexpectedly shifting positional arguments and breaking metadata creation.
Issue went undetected due to missing staging analytics deployment, silently
losing production observability on microservice without blocking transcription
job execution, highlighting need for staging analytics activation.
Posthog-cli version wasn't pinned.
Please check issue #39846, which describe our issue, starting
0.5.0, the cli needs an API token and a Project ID.
Pin to the last stable version we used 0.4.8, and wait a bit
they already released a 0.5.1 that mitigate some of the breaking
change.
I would wait the 0.5.x to be stable and battle tested by other
developpers before switching.
Also as I consider switching the Error tracking to sentry.
Fixes "Invalid LiveKit token" errors caused by field mismatch between
token generation and authentication lookup.
Previously:
- generate_token() used user.sub as token identity
- LiveKitTokenAuthentication tried to retrieve user via user.id field
- This failed when sub was not a UUID (e.g., from LemonLDAP OIDC provider)
Now:
- generate_token() continues using user.sub (canonical OIDC identifier)
- LiveKitTokenAuthentication correctly looks up by sub field
- Both sides now consistently use the same field
This ensures compatibility with all RFC 7519-compliant OIDC providers,
regardless of their sub claim format.
Without explicit commands in values.yaml,
celeryTranscribe and celerySummarize pods
were using the Dockerfile's default CMD (uvicorn),
which started the REST API instead of Celery workers.
This fix adds default commands to values.yaml for both services,
ensuring they run as Celery workers processing their respective
queues (transcribe-queue and summarize-queue).
Add ability to use response_format in call function in order to
have better result with albert-large model
Use reponse_format for next steps and plan generation
This chart exposes an external API from the backend pod.
Currently, it does not include conditional addition of the external API route.
This functionality will be added later.
Add configurable room name regex filtering to exclude Tchap events from shared
LiveKit server webhooks, preventing backend spam from unrelated application
events while maintaining UUID-based room processing for visio.
Those unrelated application events are spamming the sentry.
Acknowledges this is a pragmatic solution trading proper namespace
prefixing for immediate spam reduction with minimal refactoring impact
leaving prefix-based approach for future improvement.
Restrict metadata manager signal triggers to transcription-specific Celery
tasks to prevent exceptions when new summary worker executes tasks
not designed for metadata operations, reducing false-positive Sentry errors.
Make WhisperX language detection configurable through FastAPI settings
to handle empty audio start scenarios where automatic detection fails and
incorrectly defaults to English despite 99% French usage.
Quick fix acknowledging long-term solution should allow dynamic
per-recording language selection configured by users through web
interface rather than global server settings.
Correct accidentally swapped keyboard shortcuts between video and
microphone toggle controls introduced during device component
refactoring, restoring expected shortcut behavior reported by users.
Instead of relying on make commands to set-up the minio webhook,
use a compose service, as we did for the createbucket one.
Aligned with the dev stack, and run by default when starting
for the first time the stack.
Introduce ENABLE_EXTERNAL_API setting (defaults to False) to allow
administrators to disable external API endpoints, preventing unintended
exposure for self-hosted instances where such endpoints aren't
needed or desired.
Document the external API using a simple Swagger file that can be opened
in any Swagger editor.
The content was mostly generated with the help of an LLM and has been human-
reviewed. Corrections or enhancements to the documentation are welcome.
Currently, my professional email address is included as a contact. A support
email will be added later once available. The documentation will also be
expanded as additional endpoints are added.
From a security perspective, the list endpoint should be limited to return only
rooms created by the external application. Currently, there is a risk of
exposing public rooms through this endpoint.
I will address this in upcoming commits by updating the room model to track
the source of generation. This will also provide useful information
for analytics.
The API viewset was largely copied and adapted. The serializer was heavily
restricted to return a response more appropriate for external applications,
providing ready-to-use information for their users
(for example, a clickable link).
I plan to extend the room information further, potentially aligning it with the
Google Meet API format. This first draft serves as a solid foundation.
Although scopes for delete and update exist, these methods have not yet been
implemented in the viewset. They will be added in future commits.
Enforce the principle of least privilege by granting viewset permissions only
based on the scopes included in the token.
JWTs should never be issued without controlling which actions the application
is allowed to perform.
The first and minimal scope is to allow creating a room link. Additional actions
on the viewset will only be considered after this baseline scope is in place.
This endpoint does not strictly follow the OAuth2 Machine-to-Machine
specification, as we introduce the concept of user delegation (instead of
using the term impersonation).
Typically, OAuth2 M2M is used only to authenticate a machine in server-to-server
exchanges. In our case, we require external applications to act on behalf of a
user in order to assign room ownership and access.
Since these external applications are not integrated with our authorization
server, a workaround was necessary. We treat the delegated user’s email as a
form of scope and issue a JWT to the application if it is authorized to request
it.
Using the term scope for an email may be confusing, but it remains consistent
with OAuth2 vocabulary and allows for future extension, such as supporting a
proper M2M process without any user delegation.
It is important not to confuse the scope in the request body with the scope in
the generated JWT. The request scope refers to the delegated email, while the
JWT scope defines what actions the external application can perform on our
viewset, matching Django’s viewset method naming.
The viewset currently contains a significant amount of logic. I did not find
a clean way to split it without reducing maintainability, but this can be
reconsidered in the future.
Error messages are intentionally vague to avoid exposing sensitive
information to attackers.
Prepare for the introduction of new endpoints reserved for external
applications. Configure the required router and update the Helm chart to ensure
that the Kubernetes ingress properly routes traffic to these new endpoints.
It is important to support independent versioning of both APIs.
Base route’s name aligns with PR #195 on lasuite/drive, opened by @lunika
We need to integrate with external applications. Objective: enable them to
securely generate room links with proper ownership attribution.
Proposed solution: Following the OAuth2 Machine-to-Machine specification,
we expose an endpoint allowing external applications to exchange a client_id
and client_secret pair for a JWT. This JWT is valid only within a well-scoped,
isolated external API, served through a dedicated viewset.
This commit introduces a model to persist application records in the database.
The main challenge lies in generating a secure client_secret and ensuring
it is properly stored.
The restframework-apikey dependency was discarded, as its approach diverges
significantly from OAuth2. Instead, inspiration was taken from oauthlib and
django-oauth-toolkit. However, their implementations proved either too heavy or
not entirely suitable for the intended use case. To avoid pulling in large
dependencies for minimal utility, the necessary components were selectively
copied, adapted, and improved.
A generic SecretField was introduced, designed for reuse and potentially
suitable for upstream contribution to Django.
Secrets are exposed only once at object creation time in the Django admin.
Once the object is saved, the secret is immediately hashed, ensuring it can
never be retrieved again.
One limitation remains: enforcing client_id and client_secret as read-only
during edits. At object creation, marking them read-only excluded them from
the Django form, which unintentionally regenerated new values.
This area requires further refinement.
The design prioritizes configurability while adhering to the principle of least
privilege. By default, new applications are created without any assigned scopes,
preventing them from performing actions on the API until explicitly configured.
If no domain is specified, domain delegation is not applied, allowing tokens
to be issued for any email domain.
Add detailed documentation on signaling server configuration
and associated environment variables to help administrators properly
configure WebRTC connection establishment.
Add documentation noting subtitle functionality is currently under
active development to set appropriate expectations for administrators
and prevent deployment assumptions about feature maturity.
Add comprehensive telephony documentation explaining system requirements
and component interactions to help administrators understand infrastructure
needs.
Add comprehensive recording documentation explaining system requirements
and component interactions to help administrators understand infrastructure
needs and troubleshoot recording functionality.
Expand authentication documentation to clarify supported authentication
mechanisms and their configuration nuances, helping administrators
understand different authentication flows and choose appropriate methods
for their deployment security requirements.
Add initial theming documentation covering both runtime customization and
build-time configuration methods to help self-hosters adapt the
application's visual identity to their organizational branding needs.
Improve installation instructions to prepare for comprehensive Docker
Compose documentation launch, clarifying setup steps and addressing
common deployment questions to reduce onboarding friction.
Enhance README by incorporating content from LaSuite Docs, adding
comprehensive list of other LaSuite Meet instances, and refining
presentation details to improve project discoverability and onboarding.
Delete deprecated internal release process documentation that no longer
applies to current deployment practices, eliminating confusion from
obsolete workflow references.
Sadly, we used user db id as the posthog distinct id
of identified user, and not the sub.
Before this commit, we were only passing sub to the
summary microservice.
Add the owner's id. Please note we introduce a different
naming behavir, by prefixing the id with "owner". We didn't
for the sub and the email.
We cannot align sub and email with this new naming approach,
because external contributors have already started building
their own microservice.
Manually update libexpat to 2.7.2-r0 in Alpine 3.21.3 base image
to address CVE-2025-59375 high-severity vulnerability until newer
Alpine base image becomes available, ensuring Trivy security scans pass.
Add additional room event tracking to PostHog analytics to better
understand and diagnose disconnection error patterns. Enhanced
telemetry will provide insights for improving connection stability.
Remove incorrect whitespace in queue names that prevented Celery
workers from listening to proper queues. Workers were attempting to
connect to non-existent queues, breaking task distribution.
Ensure transcribe jobs are properly assigned to their specific queue
instead of using default queue. This prevents job routing issues and
ensures proper task distribution across workers.
Implement automated MinIO webhook configuration using Kubernetes job
to enable recording feature functionality. This eliminates manual
setup requirements and ensures consistent webhook configuration
across deployments.
Restore certificate mounting for MinIO webhook communication to
backend after migrating away from unmaintained Bitnami chart.
Mount certificate in proper volume to enable secure bucket-to-backend
webhook delivery.
Add Celery summarize and transcribe worker configuration to Helm
charts for summary microservice. Create new deployment resources
and increment chart version to support distributed task processing.
Introduce FastAPI settings configuration option to completely disable
the summary feature. This improves developer experience by allowing
developers to skip summary-related setup when not needed for their
workflow.
Add watch configuration to Docker Compose file enabling compose watch
mode for Docker Compose 2.22+. This enhances developer experience on
Visio by providing automatic file synchronization and hot reloading
during development on the celery workers.
The recording feature and call to the summary service wasn't working
in the docker compose stack. It was a pain for new developper joining
the project to understand every piece of the stack.
Resolve storage webhook trigger issues by configuring proper environment
variables, settings, and MinIO setup to enhance developer experience
and eliminate manual configuration requirements.
Add new Makefile command to configure MinIO webhook via CLI since
webhook configuration cannot be declared as code. Update summary
microservice to reflect secure access false setting for MinIO bucket
consistency with Tilt stack configuration.
Implement summarization functionality that processes completed meeting
transcripts to generate concise summaries.
First draft base on a simple recursive agentic scenario.
Observability and evaluation will be added in the next PRs.
Name the Celery queue used by transcription worker to prepare for
dedicated summarization queue separation, enabling faster transcript
delivery while isolating new agentic logic in separate worker processes.
Rename incorrectly named OpenAI configuration settings since
they're used to instantiate WhisperX client which is not OpenAI
compatible, preventing confusion about actual service dependencies.
Include PostHog analytics configuration example in the summary
environment file with default disabled state. This provides developers
with clear setup guidance while maintaining privacy-first defaults.
Consolidate summary service into main development stack to centralize
development environment management and simplify service orchestration
with shared infrastructure like MinIO storage.
Adjust permission modal dimensions to properly fit mobile viewports
and prevent poor responsive user experience. Ensures modal content
remains accessible and readable across different screen sizes.
Resolve issue where users with disabled track preferences in local
storage wouldn't receive permission prompts in subsequent sessions,
causing app deadlock. Toggle tracks when permissions are disabled to
re-trigger permission requests.
This is a hotfix addressing critical user feedback. Permission handling
requires further testing and improvements based on gathered user
reports since release.
Resolve regression where non-admin/anonymous users couldn't mute
their microphone from participant list after mute permissions refactoring.
Replace API call with local track mute for better performance and
proper permission handling.
Add accessibility label to screenshare control button to ensure screen
readers can properly announce the button's function to users with
visual impairments.
Hide audio output selector component for Safari browsers due to lack
of native support for audio output device selection APIs. This
prevents user confusion and improves browser compatibility.
Revert recent changes to dynacast and adaptive streaming functionality
to isolate potential causes of regression issues. Changes will be
reintroduced in future commits with improved error handling and
thorough investigation of root causes.
Restore proper controlbar layout and spacing on mobile screens that broke
during recent audio control component refactoring, ensuring consistent
user interface across all device sizes.
Temporarily roll back LiveKit client SDK version to investigate and
resolve production stability problems that emerged after recent upgrade,
enabling system restoration while root cause analysis is performed.
Update development environment LiveKit server from previous version
to 1.9.0 for latest features and bug fixes.
Ensures development environment stays current
with LiveKit production version.
Powered by [LiveKit](https://livekit.io/), La Suite Meet offers Zoom-level performance with high-quality video and audio. No installation required—simply join calls directly from your browser. Check out LiveKit's impressive optimizations in their [blog post](https://blog.livekit.io/livekit-one-dot-zero/).
> [!TIP]
> New here? Start by introducing yourself in our Matrix channel:
> We’re happy to discuss ideas, answer questions, and help to deploy LaSuite Meet.
### Features
- Optimized for stability in large meetings (+100 p.)
- Support for multiple screen sharing streams
- Non-persistent, secure chat
- End-to-end encryption (coming soon)
- Meeting recording
- Meeting transcription (currently in beta)
- Meeting transcription & Summary (currently in beta)
- Telephony integration
- Secure participation with robust authentication and access control
- Customizable frontend style
- LiveKit Advances features including :
- speaker detection
- simulcast
@@ -49,11 +61,15 @@ La Suite Meet is fully self-hostable and released under the MIT License, ensurin
We’re continuously adding new features to enhance your experience, with the latest updates coming soon!
### 🚀 Major roll out to all French public servants
On the 29th of January 2026, Prime Minister Sébastien Lecornu, announced the full deployment of Visio—the French government’s dedicated Meet platform—to all public servants. ([Source in English](https://www.nytimes.com/2026/01/29/world/europe/france-zoom-alternative-visio.html))
## Table of Contents
- [Get started](#get-started)
- [Docs](#docs)
- [Self-host](#self-host)
- [Contributing](#contributing)
- [Philosophy](#philosophy)
- [Open source](#open-source)
@@ -61,25 +77,75 @@ We’re continuously adding new features to enhance your experience, with the la
## Get started
### La Suite Meet Cloud (Recommended)
Sign up for La Suite Meet Cloud, designed for french public servants. Hosted on SecNumCloud-compliant providers and accessible via government SSO, [ProConnect](https://www.proconnect.gouv.fr/). The easiest way to try our product. Reach out if your entity isn't connected yet to our sso.
### Open-source deployment (Advanced)
Deploy La Suite Meet on your own infrastructure using [our self-hosting guide](https://github.com/suitenumerique/meet/blob/main/docs/installation.md). Our open-source deployment is optimized for Kubernetes, and we're working on supporting additional deployment options. Keycloak integration and any SSO are supported. We offer customer support for open-source setups—just reach out for assistance.
## Docs
We're currently working on both technical and user documentation for La Suite Meet. In the meantime, many of the essential aspects are already well covered by the [LiveKit documentation](https://docs.livekit.io/home/) and their [self-hosting guide](https://docs.livekit.io/home/self-hosting/deployment/). Stay tuned for more updates!
## Contributing
## Self-host
We <3 contributions of any kind, big and small:
### La Suite Meet is easy to install on your own servers
- Vote on features or get early access to beta functionality in our [roadmap](https://github.com/orgs/suitenumerique/projects/3/views/2)
- Open a PR (see our instructions on [developing La Suite Meet locally](https://github.com/suitenumerique/meet/blob/main/docs/developping_locally.md))
- Submit a [feature request](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=enhancement&template=Feature_request.md) or [bug report](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md)
We use Kubernetes for our [production instance](https://visio.numerique.gouv.fr/) but also support Docker Compose. The community contributed a couple other methods (Nix, YunoHost etc.) check out the [docs](/docs/installation/README.md) to get detailed instructions and examples.
**Questions?** Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
> [!NOTE]
> Some advanced features (ex: recording, transcription) lack detailed documentation. We're working hard to provide comprehensive guides soon.
#### Known instances
We hope to see many more, here is an incomplete list of public La Suite Meet instances. Feel free to make a PR to add ones that are not listed below🙏
| [visio.numerique.gouv.fr](https://visio.numerique.gouv.fr/) | DINUM | French public agents working for the central administration and the extended public sphere. ProConnect is required to login in or sign up |
| [visio.suite.anct.gouv.fr](https://visio.suite.anct.gouv.fr/) | ANCT | French public agents working for the territorial administration and the extended public sphere. ProConnect is required to login in or sign up |
| [visio.lasuite.coop](https://visio.lasuite.coop/) | lasuite.coop | Free and open demo to all. Content and accounts are reset after one month |
| [mosacloud.cloud](https://mosa.cloud/) | mosa.cloud | Demo instance of mosa.cloud, a dutch company providing services around La Suite apps. |
| [Clever Cloud](https://www.clever.cloud/product/visio/) | clever cloud | Openvisio is a sovereign video conferencing solution based on LaSuite Meet offered by [Clever Cloud](https://www.clever.cloud/). |
| [Email.eu](https://email.eu/) | Email.eu | Sovereign business workspace. |
# Contributing
We <3 contributions of all kinds **big or small** and we’re genuinely glad you’re here. 🌱
### Start by saying hi
**The best first contribution is simply to come say hi.**
Before opening a PR, especially a larger one, or one written with the help of AI, we encourage you to reach out to a maintainer on our [Matrix channel](https://matrix.to/#/#meet-official:matrix.org) (@antoine.lebaud:matrix.org).
Getting in touch early helps us align on goals, avoid duplicated or wasted effort, and build a community that stays active, welcoming, and fun to be part of. There are no silly questions here: whether you’ve shipped hundreds of PRs or you’re just getting started, you’re welcome.
### AI contributions
AI-assisted contributions are welcome. But code is never the end goal. What matters most is building relationships, sharing knowledge, and growing a sustainable community over time.
If your contribution has been heavily generated with AI, please be transparent about it. This helps maintainers review it with the right context and respects the time they invest in the project.
Using AI does not transfer ownership of the contribution: you should still fully understand the code, the problem it solves, and the reasoning behind the approach you propose. In short, even if AI helped write it, the why should still be yours.
### Contributions beyond code
**Not technical? We need you too.**
Open source is much more than code. Writing documentation, improving onboarding, translating content, answering questions, reporting bugs, or simply helping others feel welcome all make a huge difference.
### Ways to contribute
When you’re ready, here are a few ways to get involved:
* 👋 **Say hello** and share your ideas with the community and maintainers on our [Matrix channel](https://matrix.to/#/#meet-official:matrix.org)
* 🛠️ **Open a PR** by following our guide to [develop La Suite Meet locally](https://github.com/suitenumerique/meet/blob/main/docs/developping_locally.md)
* 💡 **Suggest an idea** by opening a [feature request](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=enhancement&template=Feature_request.md)
* 🐛 **Report a bug** by opening a [bug report](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md)
Thank you for helping build something open, useful, and human. 💙
### Community call
We host a community call on the first Friday of every month to share updates, discuss ideas, and connect with contributors.
Whether you’re actively contributing or just curious about the project, you’re welcome to join. More details are shared on the [Matrix channel](https://matrix.to/#/#meet-official:matrix.org).
@@ -15,3 +15,15 @@ the following command inside your docker container:
(Note : in your development environment, you can `make migrate`.)
## [Unreleased]
## v1.23.0
As part of the 1.23.0 release, the legacy `api/v1` implementation has been removed from the _experimental_ Summary service and Meet has been migrated to the new `api/v2`.
**To avoid a breaking change, the Meet backend continues to use the Summary service's v1-compatible API format by default (`SUMMARY_SERVICE_VERSION` setting defaults to `1`).**
If you are deploying both Meet and Summary from this repository, you must configure the Meet backend to use the v2 API by setting the following environment variable `SUMMARY_SERVICE_VERSION=2`.
If you are upgrading only the Meet deployment while keeping an older Summary v1 compatible deployment, no action is required, as the v1-compatible API remains the default.
Note that we plan on removing the legacy `v1` summary compatibility in a future major version. If you have your own implementation for the summary service, we recommend updating its API contract and setting `SUMMARY_SERVICE_VERSION=2`.
@@ -142,4 +142,4 @@ Once the Kubernetes cluster is ready, start the application stack locally:
$ make start-tilt-keycloak
```
Monitor Tilt’s progress at [http://localhost:10350/](http://localhost:10350/). After Tilt actions finish, you can access the app at [https://visio.127.0.0.1.nip.io/](https://visio.127.0.0.1.nip.io/).
Monitor Tilt’s progress at [http://localhost:10350/](http://localhost:10350/). After Tilt actions finish, you can access the app at [https://meet.127.0.0.1.nip.io/](https://meet.127.0.0.1.nip.io/).
LiveKit offers Universal Egress, designed to provide universal exports of LiveKit sessions or tracks to a file or stream data.
It is kept in a separate system to keep the load off the [Single Forwarding Unit (SFU)](https://docs.livekit.io/reference/internals/livekit-sfu/) and avoid impacting real-time audio or video performance/quality.
## Getting started
### Prerequisite
1.**Verify Services**: Ensure the LiveKit server and Egress service are both up and running.
2.**Install CLI**: Confirm that the LiveKit CLI utility is installed on your system.
3.**Set Permissions**: Since the Egress service does not run as the root user, you need to grant write permissions to all users for the output directory. Update the permissions of the `docker/livekit/out` folder before starting the docker-compose stack:
```bash
$ chmod o+w ./docker/livekit/out
```
### Make a recording
LiveKit provides examples for creating Egress requests, which you can find [here](https://github.com/livekit/livekit-cli/tree/main/cmd/livekit-cli/examples). One of these examples has been added to the repository under `docker/livekit/egress-example`.
Follow these steps to start an Egress request:
1.**Create a Room**: Create a room either through the frontend or using the `livekit-cli` command.
2.**Retrieve Room Name**: Get the room's name (e.g., the UUID4 in the URL from the frontend).
3.**Update Configuration**: Edit the `docker/livekit/egress-example/room-composite-file.json` file with your room's name.
4.**Start Egress Request**: Initiate a new Egress request.
> We provide those instructions as an example, for production environments, you should follow the [official documentation](https://www.keycloak.org/documentation).
The following variables need to be updated with your own values, others can be left as is:
```env
POSTGRES_PASSWORD=<generate postgres password>
KC_HOSTNAME=https://id.yourdomain.tld # Change with your own URL
KC_BOOTSTRAP_ADMIN_PASSWORD=<generate your password>
```
### Step 3: Expose keycloak instance on https
> [!NOTE]
> You can skip this section if you already have your own setup.
To access your Keycloak instance on the public network, it needs to be exposed on a domain with SSL termination. You can use our [example with nginx proxy and Let's Encrypt companion](../nginx-proxy/README.md) for automated creation/renewal of certificates using [acme.sh](http://acme.sh).
If following our example, uncomment the environment and network sections in compose file and update it with your values.
```yaml
version:'3'
services:
keycloak:
...
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=id.yourdomain.tld # used by nginx proxy
# - VIRTUAL_PORT=8080 # used by nginx proxy
# - LETSENCRYPT_HOST=id.yourdomain.tld # used by lets encrypt to generate TLS certificate
...
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
# Uncomment if using our nginx proxy example
#networks:
# proxy-tier:
# external: true
```
### Step 4: Start the service
```bash
`docker compose up -d`
```
Your keycloak instance is now available on https://id.yourdomain.tld
> [!CAUTION]
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image. You can find available versions on [Keycloak registry](https://quay.io/repository/keycloak/keycloak?tab=tags).
## Creating an OIDC Client for Meet Application
### Step 1: Create a New Realm
1. Log in to the Keycloak administration console.
2. Navigate to the realm tab and click on the "Create realm" button.
3. Enter the name of the realm - `meet`.
4. Click "Create".
### Step 2: Create a New Client
1. Navigate to the "Clients" tab.
2. Click on the "Create client" button.
3. Enter the client ID - e.g. `meet`.
4. Enable "Client authentication" option.
6. Set the "Valid redirect URIs" to the URL of your meet application suffixed with `/*` - e.g., "https://meet.example.com/*".
1. Set the "Web Origins" to the URL of your meet application - e.g. `https://meet.example.com`.
1. Click "Save".
### Step 3: Get Client Credentials
1. Go to the "Credentials" tab.
2. Copy the client ID (`meet` in this example) and the client secret.
> We provide those instructions as an example, for extended development or production environments, you should follow the [official documentation](https://github.com/nginx-proxy/acme-companion/tree/main/docs).
Nginx-proxy sets up a container running nginx and docker-gen. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
Acme-companion is a lightweight companion container for nginx-proxy. It handles the automated creation, renewal and use of SSL certificates for proxied Docker containers through the ACME protocol.
### Step 2: Edit `DEFAULT_EMAIL` in the compose file.
Albeit optional, it is recommended to provide a valid default email address through the `DEFAULT_EMAIL` environment variable, so that Let's Encrypt can warn you about expiring certificates and allow you to recover your account.
### Step 3: Create docker network
Containers need share the same network for auto-discovery.
```bash
docker network create proxy-tier
```
### Step 4: Start service
```bash
docker compose up -d
```
## Usage
Once both nginx-proxy and acme-companion containers are up and running, start any container you want proxied with environment variables `VIRTUAL_HOST` and `LETSENCRYPT_HOST` both set to the domain(s) your proxied container is going to use.
La Suite Meet supports **OIDC authentication** using the Authorization Code Flow.
Authentication relies on [django-lasuite](https://github.com/suitenumerique/django-lasuite) for OIDC integration, token validation, and user management.
| OIDC_CREATE_USER | Automatically create a local user if none exists | `true` |
| **Security & Verification** | | |
| OIDC_VERIFY_SSL | Verify SSL certificates when contacting the OIDC provider | `true` |
| OIDC_USE_NONCE | Use `nonce` to prevent replay attacks | `true` |
| OIDC_STORE_ID_TOKEN | Store the ID token returned by the OIDC provider (useful for backend validation) | `true` |
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | Fallback to identifying users by email if `sub` claim does not match. Enable only if emails are unique. | `false` |
| **Endpoints** | | |
| OIDC_OP_JWKS_ENDPOINT | URL to retrieve JSON Web Key Sets (for token verification) | — |
| OIDC_OP_AUTHORIZATION_ENDPOINT | URL for authorization requests | — |
| OIDC_OP_TOKEN_ENDPOINT | URL to exchange authorization code for tokens | — |
| OIDC_OP_USER_ENDPOINT | URL to fetch user information | — |
| OIDC_OP_USER_ENDPOINT_FORMAT | Format of user endpoint response. Options: `AUTO` (detect automatically), `JWT`, or `JSON` | `AUTO` |
| OIDC_OP_LOGOUT_ENDPOINT | URL for logout requests | — |
| **User Info Mapping** | | |
| OIDC_USERINFO_FULLNAME_FIELDS | List of OIDC claims used to build user’s full name | `["given_name", "usual_name"]` |
| OIDC_USERINFO_SHORTNAME_FIELD | OIDC claim used for the user’s short name | `given_name` |
| OIDC_USERINFO_ESSENTIAL_CLAIMS | List of essential claims required from the provider | `[]` |
| **Redirects & Scopes** | | |
| OIDC_REDIRECT_REQUIRE_HTTPS | Require HTTPS for OIDC redirect URIs (**recommended in production**) | `false` |
| OIDC_PKCE_CODE_VERIFIER_SIZE | Length of the random string used as PKCE code verifier (43–128 characters) | `64` |
| **Other** | | |
| FRONTEND_IS_SILENT_LOGIN_ENABLED | Silent login allows La Suite Meet to authenticate users automatically without showing a login prompt, providing a seamless experience when an active session already exists with the OIDC provider. It works by replaying the authentication request with prompt=none: if the user has a valid session, login succeeds silently; otherwise, it fails gracefully and redirects the user to the initial page. Silent login is optional and enabled by default in standard deployments. The app retries silent login after any 401 response, with at least a 30-second interval between attempts (not configurable via environment variables). Controlled by the backend parameter. /!\ Your OIDC provider must support `prompt=none`. | `false` |
## Sessions
* After login, users receive a **Django session cookie** to maintain authentication across requests.
These features are currently under active development and are not yet ready for official documentation. Comprehensive documentation will be provided as soon as possible.
An initial integration with OpenExchange is already available and will be documented shortly.
La Suite Meet offers a room recording feature that is currently in beta, with ongoing improvements planned.
The feature allows users to record their room sessions. When a recording is complete, the room owner receives a notification with a link to download the recorded file. Recordings are automatically deleted after `RECORDING_EXPIRATION_DAYS`.
It uses LiveKit Egress to record room sessions. For reference, see the [LiveKit Egress repository](https://github.com/livekit/egress) and the [official documentation](https://docs.livekit.io/home/egress/overview/).
**Current Limitations**:
* Users cannot record and transcribe simultaneously. ([Issue #527](https://github.com/suitenumerique/meet/issues/527)
is on our backlog)
* Recording layout cannot be configured from the frontend. By default, the egress captures the active speaker and any shared screens. (not yet planned)
* Shareable links with an embedded video player are not yet supported. (not yet planned)
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
## Special requirements
To use the room recording feature, the following components are required:
- A running [LiveKit Egress](https://github.com/livekit/egress) server capable of handling room composite recordings.
- A S3-compatible object storage that supports webhook events to notify the backend when recordings are uploaded.
- An email service to notify room owners when a recording is available for download.
- Webhook events configured between LiveKit Server and the backend.
> [!CAUTION]
> Minio supports lifecycle events; other providers may not work out of the box. There is currently a dependency on Minio, which is planned to be refactored in the future.
> [!NOTE]
> Celery isn’t in use for these async tasks yet. It’s something we’d like to add, but it’s not planned at this stage.
## How It Works
```mermaid
sequenceDiagram
participant User
participant Frontend as Frontend (React)
participant Backend as Django Backend
participant LiveKit as LiveKit API
participant Egress as LiveKit Egress
participant Storage as Object Storage
participant Room as LiveKit Room
participant Email as Email Service
User->>Frontend: Click start recording button
Frontend->>Backend: POST /api/v1.0/rooms/{id}/start-recording/
Backend->>LiveKit: Create egress request
LiveKit->>Egress: Start room composite egress
Egress->>Room: Join room as recording participant
Note over Egress,Room: Egress joins room to capture audio/video
LiveKit-->>Backend: Return egress_id
Backend->>Backend: Update Recording with worker_id
Backend-->>Frontend: HTTP 201 - Recording started
Frontend->>Frontend: Update recording status
Frontend->>Frontend: Notify other participants
Note over Frontend: Via LiveKit data channel
Note over Egress,Room: Recording in progress...
User->>Frontend: Click stop recording button
Frontend->>Backend: POST /api/v1.0/rooms/{id}/stop-recording/
Backend->>LiveKit: Stop egress request
LiveKit->>Egress: Stop recording
Egress->>Storage: Upload recorded file
Storage->>Backend: Storage event notification
Backend->>Backend: Update Recording status to SAVED
Backend->>Email: Send notification to room owner
Backend-->>Frontend: HTTP 200 - Recording stopped
Frontend->>Frontend: Update UI and notify participants
Email->>User: Send email with recording link
User->>Frontend: Navigate to /recording/{id} to download file
| **RECORDING_ENABLE** | Boolean | `False` | Enable or disable the room recording feature. |
| **RECORDING_OUTPUT_FOLDER** | String | `"recordings"` | Folder/prefix where recordings are stored in the object storage. |
| **RECORDING_WORKER_CLASSES** | Dict | `{ "screen_recording": "core.recording.worker.services.VideoCompositeEgressService", "transcript": "core.recording.worker.services.AudioCompositeEgressService" }` | Maps recording types to their worker service classes. |
| **RECORDING_EVENT_PARSER_CLASS** | String | `"core.recording.event.parsers.MinioParser"` | Class responsible for parsing storage events and updating the backend. |
| **RECORDING_STORAGE_EVENT_ENABLE** | Boolean | `False` | Enable handling of storage events (must configure webhook in storage). If `False`, fallback to LiveKit egress complete webhook. |
| **RECORDING_STORAGE_EVENT_TOKEN** | Secret/File | `None` | Token used to authenticate storage webhook requests, if `RECORDING_ENABLE_STORAGE_EVENT_AUTH` is enabled. |
| **RECORDING_EXPIRATION_DAYS** | Integer | `None` | Number of days before recordings expire. Should match bucket lifecycle policy. Set to `None` for no expiration. |
| **RECORDING_MAX_DURATION** | Integer | `None` | Maximum duration of a recording in milliseconds. Must be synced with the LiveKit Egress configuration. Set to None for unlimited duration. When the maximum duration is reached, the recording is automatically stopped and saved, and the user is prompted in the frontend with an alert message. |
| **RECORDING_ENCODING_ENABLED** | Boolean | `False` | When `False`, LiveKit Egress uses its built-in `H264_720P_30` preset. When `True`, the `RECORDING_ENCODING_*` values below are sent to LiveKit as advanced `EncodingOptions`. See [Tuning recording encoding](#tuning-recording-encoding). |
| **RECORDING_ENCODING_WIDTH** | Integer | `1280` | Recording video width in pixels. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_HEIGHT** | Integer | `720` | Recording video height in pixels. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_FRAMERATE** | Integer | `30` | Recording video framerate (fps). Directly impacts egress worker CPU (roughly linear). Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_VIDEO_BITRATE_KBPS** | Integer | `3000` | H.264 MAIN video bitrate in kbps. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_AUDIO_BITRATE_KBPS** | Integer | `128` | AAC audio bitrate in kbps. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
| **RECORDING_ENCODING_KEY_FRAME_INTERVAL_S** | Float | `4.0` | Keyframe interval in seconds. Drives seek granularity in the recorded MP4 (a player can only seek to keyframe boundaries). Larger values give the encoder slightly more bits for non-keyframe content at a fixed bitrate. `4.0` is a standard VOD value. Only applied when `RECORDING_ENCODING_ENABLED` is `True`. |
### Manual Storage Webhook
Storage events must be configured manually; the Kubernetes chart does not do this automatically.
1. Configure your S3 bucket to send file creation events to the backend webhook.
2. Enable events and token in settings:
```python
RECORDING_STORAGE_EVENT_ENABLE=True
RECORDING_ENABLE_STORAGE_EVENT_AUTH=True
RECORDING_STORAGE_EVENT_TOKEN=<token>
```
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
## LiveKit Egress
La Suite Meet uses LiveKit Egress to record room sessions. For reference, see the [LiveKit Egress repository](https://github.com/livekit/egress) and the [official documentation](https://docs.livekit.io/home/egress/overview/).
Currently, only `RoomCompositeEgress` is supported. This mode combines all video and audio tracks from the room into a single recording.
To monitor egress workers and inspect recording status, it is recommended to install `livekit-cli`. For example, you can list active egress sessions using the following command:
This allows you to verify which recordings are in progress, troubleshoot egress issues, and confirm that recordings are being processed correctly.
## Tuning recording encoding
By default, LiveKit Egress records with the built-in `H264_720P_30` preset: 1280×720 at 30 fps, 3000 kbps H.264 MAIN video and 128 kbps AAC audio. For a one-hour meeting this produces a file of roughly **1.4 GB**, which is often heavier than necessary for talking-head content and screen sharing.
The `RECORDING_ENCODING_*` settings let operators override this preset without modifying the source. Values are passed straight through LiveKit's `EncodingOptions.advanced` to the GStreamer pipeline (`x264enc` for video, `faac` for audio), so there are no hidden conversions — what you set is what the encoder receives.
The H.264 profile is fixed to MAIN and the x264 `speed-preset` to `veryfast` by LiveKit (real-time constraint) — lowering the framerate is therefore the main lever to save CPU, while lowering the bitrate is the main lever to shrink the output file.
### Reference profiles
Rough 30-minute file-size estimates assume video + audio bitrate multiplied by duration. Actual sizes vary with content (static talking heads compress better than heavy screen motion). Egress CPU figures are indicative, measured on a single Ryzen laptop core saturated by the default preset (= 100 %); scaling is roughly linear with `framerate × bitrate` but the absolute numbers depend on the host hardware.
| Profile | Resolution | FPS | Video (kbps) | Audio (kbps) | Keyframe (s) | ~ size / 30 min | Egress CPU (vs. default) | Suitable for |
★ Recommended starting point for typical LaSuite Meet usage.
Environment variables for the **Low CPU / small file** profile:
```bash
RECORDING_ENCODING_ENABLED=True
RECORDING_ENCODING_WIDTH=1280
RECORDING_ENCODING_HEIGHT=720
RECORDING_ENCODING_FRAMERATE=15
RECORDING_ENCODING_VIDEO_BITRATE_KBPS=600
RECORDING_ENCODING_AUDIO_BITRATE_KBPS=64
RECORDING_ENCODING_KEY_FRAME_INTERVAL_S=4.0
```
### Caveats
- **Screen-share readability — think bits/frame, not bitrate**: at 720p, text legibility starts to break down below ~40 kbits/frame (= `bitrate ÷ framerate`). The recommended preset (600 kbps × 15 fps) sits at exactly that threshold, comfortable for talking heads with occasional slide sharing. The same 600 kbps at 30 fps would only deliver 20 kbits/frame and visibly blur dense slides — which is why **lowering framerate is a more screen-share-friendly lever than lowering bitrate**. For deck-heavy or IDE-share meetings, prefer the **Slide-heavy** profile (900 kbps × 15 fps ≈ 60 kbits/frame).
- **Motion handling**: the `veryfast` x264 preset is set by LiveKit and cannot be overridden here. Low-bitrate settings will therefore show more artefacts on fast motion than an offline re-encode with a slower preset would. This is the other reason FPS reduction is the safer tuning lever for meeting recordings.
- **Audio**: AAC at 64 kbps stereo is transparent for voice but starts to compress music noticeably. Keep 128 kbps if you expect music playback in meetings.
- **Codec choice**: H.264 MAIN is hardcoded on purpose. Switching to HEVC or VP9 would increase egress CPU cost 2×–5×, defeating the goal of this tuning.
Signaling is essential for LiveKit’s real-time communication. It enables peers to discover each other, exchange session descriptions, and negotiate network paths for audio and video streams.
## How Signaling Works
LiveKit signaling relies on a WebSocket connection between the client and the LiveKit API server. This WebSocket is required for all signaling messages, including session descriptions, ICE candidates, and connection state updates.
We do not cover internal signaling behavior. For full reference, see the [LiveKit client protocol](https://docs.livekit.io/reference/internals/client-protocol/).
> [!IMPORTANT]
> The WebSocket is the backbone of LiveKit signaling. All signaling messages rely on it, and without it, ICE candidate exchange and peer connection setup cannot occur. If the WebSocket connection is lost, the client automatically attempts to resume the RTC session once connectivity is restored.
| `LIVEKIT_FORCE_WSS_PROTOCOL` | Boolean | `True` | Forces the WebSocket URL to use `wss://`. Required for legacy browsers (Firefox <124, Chrome <125, Edge <125) where HTTPS URLs in `WebSocket()` may fail. |
| `LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND` | Boolean | `True` | Workaround for Firefox clients behind proxies that fail to establish WebSocket connections. Pre-establishes a dummy connection to “prime” the WebSocket. |
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
This feature is currently under development and not yet ready for production use. Documentation and detailed instructions will be provided once the feature is stable and officially released.
This feature is currently under development and not yet ready for production use. Documentation and detailed instructions will be provided once the feature is stable and officially released.
Enable participants to join a video conference via phone, allowing them to participate in the room even when their internet connection is poor or unavailable.
**Current Limitations**:
* Supports only a single SIP trunk provider per instance.
* A participant joining over the phone cannot enter the room until the first WebRTC participant has connected.
## Special requirements
To use the telephony feature, the following components are required:
* A running [LiveKit SIP server](https://github.com/livekit/sip) ([documentation](https://docs.livekit.io/home/self-hosting/sip-server/)) to handle SIP participants and connect them to room sessions.
* A SIP trunk to route incoming and outgoing phone calls.
* Webhook events configured between the LiveKit server and the backend.
| ROOM_TELEPHONY_ENABLED | Boolean | False | Enable or disable telephony (phone call) support for rooms. |
| ROOM_TELEPHONY_PIN_LENGTH | Positive Integer | 10 | Length of the PIN code participants must enter to join a call. |
| ROOM_TELEPHONY_PIN_MAX_RETRIES | Positive Integer | 5 | Maximum number of attempts a participant can make when entering the PIN. |
| ROOM_TELEPHONY_PHONE_NUMBER | String | None | The phone number associated with the room for incoming calls. Required to route calls via the telephony system. |
| ROOM_TELEPHONY_DEFAULT_COUNTRY | String | "US" | Default country code for phone numbers, used for parsing and formatting phone numbers. |
### SIP Trunk Authentication
You may need to configure authentication between LiveKit SIP and your SIP trunk provider to enable participants to join via phone.
Please refer to [the official documentation](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk/).
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
### Language Customization for Audio Prompts
You may need to configure the default LiveKit voice to match your locale. By default, all LiveKit audio instructions are in English.
To customize the prompts, mount the appropriate audio files as a volume in your deployment. The audio resources are available here: [LiveKit SIP audio files](https://github.com/livekit/sip/tree/main/res).
## Documentation
For detailed information on integrating and configuring SIP with LiveKit, refer to the official LiveKit SIP documentation: [LiveKit SIP Documentation](https://docs.livekit.io/sip/). This guide covers SIP server setup, trunk configuration, dispatch rules, etc.
La Suite Meet provides a room transcription capability, currently available in beta. This feature is under active development, with ongoing enhancements planned.
The transcription feature enables users to record room sessions. Upon completion of a recording, the room owner receives a notification containing a link to LaSuite Docs, where the transcribed meeting content can be accessed.
> [!NOTE]
> Audio recordings are automatically deleted after the configured `RECORDING_EXPIRATION_DAYS` period.
For configuration and setup details of the recording functionality, refer to the [Recording feature documentation](https://github.com/suitenumerique/meet/blob/main/docs/features/recording.md).
This page only describes the supplementary tools required for audio processing.
Example of a transcript :
```
**SPEAKER_00**: Hello everyone!
**SPEAKER_01**: Yes, it works.
```
### Current Limitations
* Participant identification is not yet implemented; participants are labeled generically (e.g., `PARTICIPANT_1`).
* Transcription backend relies on [WhisperX](https://github.com/m-bain/whisperX), which does not provide an OpenAI-compatible API.
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
## Special requirements
To enable the transcription feature, the following components must be in place:
* Recording feature components: All dependencies and configurations required for the [recording feature](https://github.com/suitenumerique/meet/blob/main/docs/features/recording.md).
* LaSuite Docs instance: A running [LaSuite Docs](https://github.com/suitenumerique/docs) capable of handling requests to the `/create-for-owner` endpoint.
* WhisperX API: A running WhisperX service. An open-source implementation combining WhisperX and FastAPI is available [here](https://github.com/suitenumerique/meet-whisperx).
* Deployment of the [summary service](https://hub.docker.com/r/lasuite/meet-summary), a Celery worker, and a Redis instance.
## How It Works
```mermaid
sequenceDiagram
participant Backend as Backend API
participant Summary as Summary Service
participant Celery as Celery Workers (transcribe-queue)
participant MinIO as MinIO (Object Storage)
participant STT as WhisperX API
participant Docs as LaSuite Docs
Backend->>Summary: POST /api/v1/tasks/ (bearer token, payload)
Note right of Backend: Payload contains 7 params: owner_id, filename, email, sub, room, recording_date, recording_time
| app_name | String | `"app"` | Name of the application/service. |
| app_api_v1_str | String | `"/api/v1"` | Base path for the API endpoints. |
| app_api_token | Secret | — | API token for authenticating requests. |
| recording_max_duration | Integer | `None` | Maximum duration of audio recordings in milliseconds. Set to `None` for unlimited. Audio recordings longer than the configured limit will be ignored and not processed. |
If you want to install La Suite Meet you've come to the right place.
Here are a bunch of resources to help you install the project.
## Kubernetes
La Suite Meet maintainers use only the Kubernetes deployment method in production, so advanced support is available exclusively for this setup. Please follow the instructions provided [here](/docs/installation/kubernetes.md).
## Docker Compose
We understand that not everyone has a Kubernetes cluster available, please follow the instructions provided [here](/docs/installation/compose.md) to set up a docker compose instance.
We also provide [Docker images](https://hub.docker.com/u/lasuite?page=1&search=meet) that can be deployed using Compose.
## Scalingo
La Suite Meet can be deployed on Scalingo PaaS using the Suite Numérique buildpack. See the [Scalingo deployment guide](./scalingo.md) for detailed instructions.
## Other ways to install La Suite Meet
Community members have contributed alternative ways to install La Suite Meet 🙏. While maintainers may not provide direct support, we help keep these instructions up to date, and you can reach out to contributors or the community for assistance.
Here is the list of other methods in alphabetical order:
We provide a sample configuration for running Meet using Docker Compose. Please note that this configuration is experimental, and the official way to deploy Meet in production is to use [k8s](../installation/kubernetes.md).
## Requirements
All services are required to run the minimalist instance of LaSuite Meet. Click the links for ready-to-use configuration examples:
Meet configuration is achieved through environment variables. We provide a [detailed description of all variables](../../src/helm/meet/README.md).
In this example, we assume the following services:
- OIDC provider on https://id.yourdomain.tld
- Livekit server on https://livekit.yourdomain.tld
- Meet server on https://meet.yourdomain.tld
**Set your own values in `.env`**
### OIDC
Authentication in Meet is managed through Open ID Connect protocol. A functional Identity Provider implementing this protocol is required.
For guidance, refer to our [Keycloak deployment example](../examples/compose/keycloak/README.md).
If using Keycloak as your Identity Provider, in `env.d/common` set `OIDC_RP_CLIENT_ID` and `OIDC_RP_CLIENT_SECRET` variables with those of the OIDC client created for Meet. By default we have set `meet` as the realm name, if you have named your realm differently, update the value `REALM_NAME` in `.env`
For others OIDC providers, update the variables in `env.d/common`.
### Postgresql
Meet uses PostgreSQL as its database. Although an external PostgreSQL can be used, our example provides a deployment method.
If you are using the example provided, you need to generate a secure key for `DB_PASSWORD` and set it in `env.d/postgresql`.
If you are using an external service or not using our default values, you should update the variables in `env.d/postgresql`
### Redis
Meet uses Redis for caching and inter-service communication. While an external Redis can be used, our example provides a deployment method.
If you are using an external service, you need to set `REDIS_URL` environment variable in `env.d/common`.
### Livekit
[LiveKit](https://github.com/livekit/livekit) server is used as the WebRTC SFU (Selective Forwarding Unit) allowing multi-user conferencing. For more information, head to [livekit documentation](https://docs.livekit.io/home/self-hosting/).
Generate a secure key for `LIVEKIT_API_SECRET` in `env.d/common`.
We provide a minimal recommended config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
To view other customization options, see [config-sample.yaml](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
> [!NOTE]
> In this example, we configured multiplexing on a single UDP port. For better performance, you can configure a range of UDP ports.
### Meet
The Meet backend is built on the Django Framework.
Generate a [secure key](https://docs.djangoproject.com/en/5.2/ref/settings/#secret-key.) for `DJANGO_SECRET_KEY` in `env.d/common`.
### Mail
The following environment variables are required in `env.d/common` for the mail service to send invitations :
```env
DJANGO_EMAIL_HOST=<smtp host>
DJANGO_EMAIL_HOST_USER=<smtp user>
DJANGO_EMAIL_HOST_PASSWORD=<smtp password>
DJANGO_EMAIL_PORT=<smtp port>
DJANGO_EMAIL_FROM=<your email address>
#DJANGO_EMAIL_USE_TLS=true # A flag to enable or disable TLS for email sending.
#DJANGO_EMAIL_USE_SSL=true # A flag to enable or disable SSL for email sending.
DJANGO_EMAIL_BRAND_NAME=<brand name used in email templates> # e.g. "La Suite Numérique"
DJANGO_EMAIL_LOGO_IMG=<logo image to use in email templates.> # e.g. "https://meet.yourdomain.tld/assets/logo-suite-numerique.png"
```
## Step 3: Configure your firewall
If you are using a firewall as it is usually recommended in a production environment you will need to allow the webservice traffic on ports 80 and 443 but also to allow UDP traffic for the WebRTC service.
The following ports will need to be opened:
- 80/tcp - for TLS issuance
- 443/tcp - for listening on HTTPS and TURN/TLS packets
- 7881/tcp - WebRTC ICE over TCP
- 7882/udp - for WebRTC multiplexing over UDP
If you are using ufw, enter the following:
```
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 443/udp
ufw allow 7881/tcp
ufw allow 7882/udp
ufw enable
```
## Step 4: Reverse proxy and SSL/TLS
> [!WARNING]
> In a production environment, configure SSL/TLS termination to run your instance on https.
If you have your own certificates and proxy setup, you can skip this part.
You can follow our [nginx proxy example](../examples/compose/nginx-proxy/README.md) with automatic generation and renewal of certificate with Let's Encrypt.
You will need to uncomment the environment and network sections in compose file and update it with your values.
```yaml
frontend:
...
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=${MEET_HOST} # used by nginx proxy
# - VIRTUAL_PORT=8083 # used by nginx proxy
# - LETSENCRYPT_HOST=${MEET_HOST} # used by lets encrypt to generate TLS certificate
...
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
...
# environment:
# - VIRTUAL_HOST=${LIVEKIT_HOST} # used by nginx proxy
# - VIRTUAL_PORT=7880 # used by nginx proxy
# - LETSENCRYPT_HOST=${LIVEKIT_HOST} # used by lets encrypt to generate TLS certificate
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
#networks:
# proxy-tier:
# external: true
```
#### Caddy Reverse Proxy
Expose the Frontend port to the host
```yaml
frontend:
…
ports:
- "8086:8086"
```
## Step 5: Start Meet
You are ready to start your Meet application !
```bash
docker compose up -d
```
> [!NOTE]
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image.
## Step 6: Run the database migration and create Django admin user
```bash
docker compose run --rm backend python manage.py migrate
@@ -122,11 +122,11 @@ If you haven't run the script **bin/start-kind.sh**, you'll need to manually cre
$ kubectl create namespace meet
```
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/ directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/<name>values.yaml" from based on the path it is being executed.
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/helm directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/helm/<name>values.yaml" from based on the path it is being executed.
LaSuite Meet uses a postgresql db as backend so if you have a provider, obtain the necessary information to use it. If you do not have, you can install a postgresql testing environment as follows:
These are the environmental options available on meet backend.
@@ -277,11 +340,11 @@ These are the environmental options available on meet backend.
| FRONTEND_CUSTOM_CSS_URL | URL of an additional CSS file to load in the frontend app. If set, a `<link>` tag with this URL as href is added to the `<head>` of the frontend app | |
| FRONTEND_ANALYTICS | Analytics information | {} |
| FRONTEND_SUPPORT | Crisp frontend support configuration, also you can pass help articles, with `help_article_transcript`, `help_article_recording`, `help_article_more_tools` | {} |
| FRONTEND_TRANSCRIPT | Frontend transcription configuration, you can pass a beta form, with `form_beta_users` | {} |
| FRONTEND_MANIFEST_LINK | Link to the "Learn more" button on the homepage | {} |
This guide explains how to deploy La Suite Meet on [Scalingo](https://scalingo.com/) using the [Suite Numérique buildpack](https://github.com/suitenumerique/buildpack).
## Overview
Scalingo is a Platform-as-a-Service (PaaS) that simplifies application deployment. This setup uses a custom buildpack to handle both the frontend (Vite) and backend (Django) builds, serving them through Nginx.
## Prerequisites
- A Scalingo account
- Scalingo CLI installed (optional but recommended)
- A PostgreSQL database addon
- A Redis addon (for caching and sessions)
## Step 1: Create Your App
Create a new app on Scalingo using `scalingo` cli or using the [Scalingo dashboard](https://dashboard.scalingo.com/).
## Step 2: Provision Addons
Add the required PostgreSQL and Redis services.
This will set the following environment variables automatically:
Whenever we are cooking a new release (e.g. `4.18.1`) we should follow a standard procedure described below:
1. Create a new branch named: `release/4.18.1`.
2. Bump the release number for backend project, frontend projects, and Helm files:
- for backend, update the version number by hand in `pyproject.toml`,
- for each frontend projects (`src/frontend`, `src/mail`), run `npm version 4.18.1` in their directory. This will update both their `package.json` and `package-lock.json` for you,
- for Helm, update Docker image tag in files located at `src/helm/env.d` for both `preprod` and `production` environments:
```yaml
image:
repository: lasuite/meet-backend
pullPolicy: Always
tag: "v4.18.1" # Replace with your new version number, without forgetting the "v" prefix
...
frontend:
image:
repository: lasuite/meet-frontend
pullPolicy: Always
tag: "v4.18.1" # Replace with your new version number, without forgetting the "v" prefix
```
The new images don't exist _yet_: they will be created automatically later in the process.
3. ~~Update the project's `Changelog` following the [keepachangelog](https://keepachangelog.com/en/0.3.0/) recommendations~~ _we don't keep a changelog yet for now as the project is still in its infancy. Soon™!_
4. Commit your changes with the following format: the 🔖 release emoji, the type of release (patch/minor/patch) and the release version:
```text
🔖(minor) bump release to 4.18.0
```
5. Open a pull request, wait for an approval from your peers and merge it.
6. Checkout and pull changes from the `main` branch to ensure you have the latest updates.
7. Tag and push your commit:
```bash
git tag v4.18.1 && git push origin --tags
```
Doing this triggers the CI and tells it to build the new Docker image versions that you targeted earlier in the Helm files.
8. Ensure the new [backend](https://hub.docker.com/r/lasuite/meet-frontend/tags) and [frontend](https://hub.docker.com/r/lasuite/meet-frontend/tags) image tags are on Docker Hub.
9. The release is now done!
# Deploying
> [!TIP]
> The `staging` platform is deployed automatically with every update of the `main` branch.
Making a new release doesn't publish it automatically in production.
Deployment is done by ArgoCD. ArgoCD checks for the `production` tag and automatically deploys the production platform with the targeted commit.
To publish, we mark the commit we want with the `production` tag. ArgoCD is then notified that the tag has changed. It then deploys the Docker image tags specified in the Helm files of the targeted commit.
- **Runtime Theming**. You can load a custom CSS file to apply any CSS you want. You can change all design-system tokens through CSS variables: colors, fonts, spacing multipliers, and more.
- **Build-time Theming**. Some additional things, like the app name appearing in the browser tab, can be customized through environment variables that are applied at build-time.
## Runtime Theming
### How to Use
To use this feature, simply set the `FRONTEND_CUSTOM_CSS_URL` environment variable (of the **backend** service) to the URL of your custom CSS file. For example:
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CUSTOM_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
Setting this variable makes the app load your CSS at runtime, adding a `<link>` to `<head>` so you can override CSS variables and customize the frontend without rebuilding.
This feature lets you customize the app’s look with any CSS, giving full flexibility and allowing changes to take effect instantly at runtime without touching the code.
### Example Use Case
Let's say you want to change the font of our application to a custom font. You can create a custom CSS file with the following contents:
Then, set the `FRONTEND_CUSTOM_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
> [!IMPORTANT]
> You can override any CSS token—semantic or palette. See [panda.config.ts](../src/frontend/panda.config.ts) for all defined semantic tokens.
> The app does **not provide separate light/dark themes**: outside a meeting it defaults to light, and in a room it switches to dark.
### Key Semantic Tokens
These control the main visual aspects of the interface:
The footer cannot be customized yet. This is a work in progress, and we welcome contributions — feel free to open a pull request if you’d like to help add this feature.
You can enable the official French government footer by setting the environment variable `FRONTEND_USE_FRENCH_GOV_FOOTER` to true. This option is disabled (false) by default.
"auth":"Ihre Sitzung ist abgelaufen. Bitte versuchen Sie es erneut.",
"retry":"Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"details":"Fehler: {{message}}"
}
},
"meeting_message":{
"join":"An der {{app_name}}-Besprechung teilnehmen",
"phone_only":"Oder per Telefon teilnehmen (nur Audio)",
"phone_fr":"(FR) {{phone}}",
"pin_code":"Code {{pin}}"
},
"footer":{
"feedback":"Teilen Sie uns Ihr Feedback mit"
}
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.