mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-15 05:03:34 +00:00
🐛(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.
This commit is contained in:
committed by
aleb_the_flash
parent
77c5329f8a
commit
40e4f17c65
@@ -21,6 +21,7 @@ and this project adheres to
|
||||
- 🐛(frontend) implement hysteresis band for the control bar layout
|
||||
- 🐛(frontend) fix toolbar ResizeObserver loop and alignment drift
|
||||
- 🐛(analytics) filter benign ResizeObserver loop error in Sentry/PostHog
|
||||
- 🐛(frontend) stop reporting screen-share denials as errors
|
||||
|
||||
## [1.26.0] - 2026-08-12
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export const captureMediaEvent = async (
|
||||
| 'media-device-success'
|
||||
| 'device-not-found'
|
||||
| 'permissions-denied'
|
||||
| 'screen-share-permission-denied'
|
||||
| 'silent-mic-detected'
|
||||
| 'silent-mic-analyser-unavailable'
|
||||
| 'silent-mic-recovered'
|
||||
|
||||
@@ -11,7 +11,9 @@ import { SidePanel } from '../components/SidePanel'
|
||||
import { RecordingProvider } from '@/features/recording'
|
||||
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
||||
import { ConnectionObserver } from '../components/ConnectionObserver'
|
||||
import { reportError } from '@/features/analytics/telemetry'
|
||||
import { captureMediaEvent, reportError } from '@/features/analytics/telemetry'
|
||||
import { getOS } from '@/utils/os'
|
||||
import { isFireFox } from '@/utils/livekit'
|
||||
import { MediaStateObserver } from '../components/MediaStateObserver'
|
||||
import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer'
|
||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||
@@ -36,6 +38,20 @@ export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElemen
|
||||
SettingsComponent?: React.ComponentType
|
||||
}
|
||||
|
||||
const getScreenSharePermissionDeniedScope = (
|
||||
error: Error
|
||||
): 'system' | 'user' | 'browser' | null => {
|
||||
if (error.name === 'NotAllowedError') {
|
||||
if (/by system/i.test(error.message)) return 'system'
|
||||
if (/by user/i.test(error.message)) return 'user'
|
||||
return 'browser'
|
||||
}
|
||||
if (error.name === 'NotFoundError' && isFireFox() && getOS() === 'macos') {
|
||||
return 'system'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The `VideoConference` ready-made component is your drop-in solution for a classic video conferencing application.
|
||||
* It provides functionality such as focusing on one participant, grid view with pagination to handle large numbers
|
||||
@@ -61,6 +77,34 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
|
||||
const [isShareErrorVisible, setIsShareErrorVisible] = useState(false)
|
||||
|
||||
const handleDeviceError = ({
|
||||
source,
|
||||
error,
|
||||
}: {
|
||||
source: Track.Source
|
||||
error: Error
|
||||
}) => {
|
||||
if (source === Track.Source.ScreenShare) {
|
||||
const scope = getScreenSharePermissionDeniedScope(error)
|
||||
if (scope) {
|
||||
if (scope === 'system') setIsShareErrorVisible(true)
|
||||
void captureMediaEvent('screen-share-permission-denied', {
|
||||
at: 'ControlBar.onDeviceError',
|
||||
source,
|
||||
error_name: error.name,
|
||||
error_message: error.message,
|
||||
denied_scope: scope,
|
||||
os: getOS(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
reportError('device_switch_failure', error, {
|
||||
at: 'ControlBar.onDeviceError',
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RoomMetadataSynchronizer />
|
||||
@@ -92,21 +136,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
<StageLayout />
|
||||
)}
|
||||
</RoomContentArea>
|
||||
<ControlBar
|
||||
onDeviceError={(e) => {
|
||||
reportError('device_switch_failure', e.error, {
|
||||
at: 'ControlBar.onDeviceError',
|
||||
source: e.source,
|
||||
})
|
||||
if (
|
||||
e.source == Track.Source.ScreenShare &&
|
||||
e.error.toString() ==
|
||||
'NotAllowedError: Permission denied by system'
|
||||
) {
|
||||
setIsShareErrorVisible(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ControlBar onDeviceError={handleDeviceError} />
|
||||
<SidePanel />
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user