Compare commits

..

2 Commits

Author SHA1 Message Date
lebaudantoine 09aeec3285 🐛(frontend) generalize screen-share error modal beyond macOS
The screen-share error modal was tailored to macOS and did not work
correctly on other operating systems.

Make it OS-aware so it also handles Windows properly, showing the
right guidance for each platform.

Also open the OS settings link in a new tab, so the user is not
disconnected from the ongoing meeting when following it.
2026-08-14 10:59:34 +02:00
lebaudantoine 3f07885204 🐛(frontend) stop reporting screen-share denials as errors
Add a small helper that classifies a `getDisplayMedia` failure as a
user, browser, or OS permission denial, or returns null when it is
a genuine error.

Chromium reports denials with explicit, non-localized messages:

* "Permission denied by user" when the user cancels or dismisses
  the source picker.
* "Permission denied by system" when the OS blocks capture (e.g.
  the macOS Screen Recording privacy setting).
* Plain "Permission denied" for browser-level blocks (site
  settings, enterprise policy, permissions-policy).

Firefox and Safari use generic `NotAllowedError` messages, which
fall into the "browser" bucket.

Firefox additionally does not map macOS Screen Recording (TCC)
blocks to `NotAllowedError`: the OS silently returns no capturable
sources, so `getDisplayMedia` rejects with `NotFoundError` ("The
object can not be found here."). Same quirk as the mic/cam OS blocks
handled in `useWatchMediaDeviceErrors` via `isLikelySystemNotFound`.

Behavior on a denied screen-share permission:

* Denials are expected outcomes (picker cancelled by the user, OS
  privacy settings, enterprise policy…) and no longer surface as
  exceptions in error tracking; capture an analytics event instead.
* Only OS-level blocks get the modal, since it explains how to
  unblock them.
2026-08-14 10:59:11 +02:00
15 changed files with 24 additions and 30 deletions
-3
View File
@@ -8,8 +8,6 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
## [1.27.0] - 2026-08-14
### Changed ### Changed
- 🔥(frontend) drop unused vendored ConnectionObserver - 🔥(frontend) drop unused vendored ConnectionObserver
@@ -25,7 +23,6 @@ and this project adheres to
- 🐛(analytics) filter benign ResizeObserver loop error in Sentry/PostHog - 🐛(analytics) filter benign ResizeObserver loop error in Sentry/PostHog
- 🐛(frontend) stop reporting screen-share denials as errors - 🐛(frontend) stop reporting screen-share denials as errors
- 🐛(frontend) generalize screen-share error modal beyond macOS - 🐛(frontend) generalize screen-share error modal beyond macOS
- 📈(frontend) stop double-reporting media device failures
## [1.26.0] - 2026-08-12 ## [1.26.0] - 2026-08-12
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "agents" name = "agents"
version = "1.27.0" version = "1.26.0"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"livekit-agents==1.6.7", "livekit-agents==1.6.7",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "agents" name = "agents"
version = "1.27.0" version = "1.26.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "livekit-agents" }, { name = "livekit-agents" },
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project] [project]
name = "meet" name = "meet"
version = "1.27.0" version = "1.26.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]] [[package]]
name = "meet" name = "meet"
version = "1.27.0" version = "1.26.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "meet", "name": "meet",
"version": "1.27.0", "version": "1.26.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "meet", "name": "meet",
"version": "1.27.0", "version": "1.26.0",
"dependencies": { "dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6", "@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11", "@fontsource-variable/lexend": "5.2.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "meet", "name": "meet",
"private": true, "private": true,
"version": "1.27.0", "version": "1.26.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "panda codegen && vite", "dev": "panda codegen && vite",
@@ -23,7 +23,6 @@ export type LogCode =
| 'livekit_room_error' | 'livekit_room_error'
| 'device_switch_failure' | 'device_switch_failure'
| 'permission_poll_failure' | 'permission_poll_failure'
| 'media_devices_error_event'
// non-media families // non-media families
| 'participant_mute_api_failure' | 'participant_mute_api_failure'
| 'permissions_api_failure' | 'permissions_api_failure'
@@ -225,10 +225,9 @@ export const Conference = ({
backgroundColor: 'primaryDark.50 !important', backgroundColor: 'primaryDark.50 !important',
})} })}
onError={(e) => { onError={(e) => {
const failure = MediaDeviceFailure.getFailure(e)
if (failure && failure !== MediaDeviceFailure.Other) return
reportError('livekit_room_error', e, { reportError('livekit_room_error', e, {
path: 'connect_publish', path: 'connect_publish',
failure: MediaDeviceFailure.getFailure(e) ?? 'not-a-device-error',
}) })
}} }}
onConnected={async () => { onConnected={async () => {
@@ -50,16 +50,15 @@ export const useWatchMediaDeviceErrors = (): MediaDeviceAlert & {
useEffect(() => { useEffect(() => {
const onDeviceError = (error: Error, kind?: MediaDeviceKind) => { const onDeviceError = (error: Error, kind?: MediaDeviceKind) => {
const failure = MediaDeviceFailure.getFailure(error) const failure = MediaDeviceFailure.getFailure(error)
if (!failure) return if (!failure || !kind) return
if (failure != MediaDeviceFailure.Other) {
void captureMediaEvent('media-device-error', { void captureMediaEvent('media-device-error', {
log_code: 'media_devices_error_event', log_code: 'media_devices_error_event',
path: 'connect_publish', path: 'connect_publish',
failure, failure,
kind: kind ?? 'unknown', kind,
}) })
}
if (!kind) return
const permissionKind = PERMISSION_BY_DEVICE_KIND[kind] const permissionKind = PERMISSION_BY_DEVICE_KIND[kind]
switch (failure) { switch (failure) {
case MediaDeviceFailure.DeviceInUse: case MediaDeviceFailure.DeviceInUse:
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.27.0", "version": "1.26.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.27.0", "version": "1.26.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@html-to/text-cli": "0.6.0", "@html-to/text-cli": "0.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.27.0", "version": "1.26.0",
"description": "An util to generate html and text django's templates from mjml templates", "description": "An util to generate html and text django's templates from mjml templates",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.27.0", "version": "1.26.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sdk", "name": "sdk",
"version": "1.27.0", "version": "1.26.0",
"license": "ISC", "license": "ISC",
"workspaces": [ "workspaces": [
"./library", "./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.27.0", "version": "1.26.0",
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "summary" name = "summary"
version = "1.27.0" version = "1.26.0"
dependencies = [ dependencies = [
"fastapi[standard]>=0.105.0", "fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0", "uvicorn>=0.24.0",