mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-14 20:53:26 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd0112a6f8 | |||
| b7abd0ae6e | |||
| 40e4f17c65 |
@@ -21,6 +21,9 @@ and this project adheres to
|
|||||||
- 🐛(frontend) implement hysteresis band for the control bar layout
|
- 🐛(frontend) implement hysteresis band for the control bar layout
|
||||||
- 🐛(frontend) fix toolbar ResizeObserver loop and alignment drift
|
- 🐛(frontend) fix toolbar ResizeObserver loop and alignment drift
|
||||||
- 🐛(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) 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
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ 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'
|
||||||
@@ -137,6 +138,7 @@ export const captureMediaEvent = async (
|
|||||||
| 'media-device-success'
|
| 'media-device-success'
|
||||||
| 'device-not-found'
|
| 'device-not-found'
|
||||||
| 'permissions-denied'
|
| 'permissions-denied'
|
||||||
|
| 'screen-share-permission-denied'
|
||||||
| 'silent-mic-detected'
|
| 'silent-mic-detected'
|
||||||
| 'silent-mic-analyser-unavailable'
|
| 'silent-mic-analyser-unavailable'
|
||||||
| 'silent-mic-recovered'
|
| 'silent-mic-recovered'
|
||||||
|
|||||||
@@ -225,9 +225,10 @@ 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 () => {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { A, Button, Dialog, P } from '@/primitives'
|
import { A, Button, Dialog, P } from '@/primitives'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
|
import { getOS, type OS } from '@/utils/os'
|
||||||
|
|
||||||
|
const SCREEN_CAPTURE_SETTINGS_LINKS: Partial<Record<OS, string>> = {
|
||||||
|
macos:
|
||||||
|
'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture',
|
||||||
|
windows: 'ms-settings:privacy-graphicscaptureprogrammatic',
|
||||||
|
}
|
||||||
|
|
||||||
// todo - refactor it into a generic system
|
// todo - refactor it into a generic system
|
||||||
export const ScreenShareErrorModal = ({
|
export const ScreenShareErrorModal = ({
|
||||||
@@ -11,7 +18,8 @@ export const ScreenShareErrorModal = ({
|
|||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'error.screenShare' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'error.screenShare' })
|
||||||
const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1
|
const os = getOS()
|
||||||
|
const settingsHref = SCREEN_CAPTURE_SETTINGS_LINKS[os]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -26,15 +34,16 @@ export const ScreenShareErrorModal = ({
|
|||||||
<>
|
<>
|
||||||
<P>
|
<P>
|
||||||
{t('message')}{' '}
|
{t('message')}{' '}
|
||||||
{isMac && (
|
{settingsHref && (
|
||||||
<>
|
<>
|
||||||
{t('macInstructions')}{' '}
|
{t('settingsInstructions')}{' '}
|
||||||
<A
|
<A
|
||||||
href="x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
|
href={settingsHref}
|
||||||
|
target="_blank"
|
||||||
color="primary"
|
color="primary"
|
||||||
aria-label={t('macSystemPreferences') + '-' + t('newTab')}
|
aria-label={t(`settingsLabel.${os}`) + '-' + t('newTab')}
|
||||||
>
|
>
|
||||||
{t('macSystemPreferences')}
|
{t(`settingsLabel.${os}`)}
|
||||||
</A>
|
</A>
|
||||||
.{' '}
|
.{' '}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -50,15 +50,16 @@ 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 || !kind) return
|
if (!failure) 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: kind ?? 'unknown',
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import { SidePanel } from '../components/SidePanel'
|
|||||||
import { RecordingProvider } from '@/features/recording'
|
import { RecordingProvider } from '@/features/recording'
|
||||||
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
||||||
import { ConnectionObserver } from '../components/ConnectionObserver'
|
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 { MediaStateObserver } from '../components/MediaStateObserver'
|
||||||
import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer'
|
import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer'
|
||||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||||
@@ -36,6 +38,20 @@ export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElemen
|
|||||||
SettingsComponent?: React.ComponentType
|
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.
|
* 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
|
* 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 [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<RoomMetadataSynchronizer />
|
<RoomMetadataSynchronizer />
|
||||||
@@ -92,21 +136,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
|||||||
<StageLayout />
|
<StageLayout />
|
||||||
)}
|
)}
|
||||||
</RoomContentArea>
|
</RoomContentArea>
|
||||||
<ControlBar
|
<ControlBar onDeviceError={handleDeviceError} />
|
||||||
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)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SidePanel />
|
<SidePanel />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -214,8 +214,11 @@
|
|||||||
"title": "Bildschirmfreigabe nicht möglich",
|
"title": "Bildschirmfreigabe nicht möglich",
|
||||||
"ariaLabel": "Bildschirmfreigabe nicht möglich",
|
"ariaLabel": "Bildschirmfreigabe nicht möglich",
|
||||||
"message": "Deinem Browser fehlt möglicherweise die Berechtigung, den Bildschirm deines Geräts abzugreifen.",
|
"message": "Deinem Browser fehlt möglicherweise die Berechtigung, den Bildschirm deines Geräts abzugreifen.",
|
||||||
"macInstructions": "Gehe zu den",
|
"settingsInstructions": "Gehe zu den",
|
||||||
"macSystemPreferences": "Systemeinstellungen",
|
"settingsLabel": {
|
||||||
|
"macos": "Systemeinstellungen",
|
||||||
|
"windows": "Windows-Datenschutzeinstellungen"
|
||||||
|
},
|
||||||
"helpLinkText": "Weitere Informationen findest du unter",
|
"helpLinkText": "Weitere Informationen findest du unter",
|
||||||
"helpLinkLabel": "Bildschirmfreigabeproblem",
|
"helpLinkLabel": "Bildschirmfreigabeproblem",
|
||||||
"closeButton": "Schließen",
|
"closeButton": "Schließen",
|
||||||
|
|||||||
@@ -214,8 +214,11 @@
|
|||||||
"title": "Unable to share your screen",
|
"title": "Unable to share your screen",
|
||||||
"ariaLabel": "Unable to share your screen",
|
"ariaLabel": "Unable to share your screen",
|
||||||
"message": "Your browser may not be allowed to record the screen on your computer.",
|
"message": "Your browser may not be allowed to record the screen on your computer.",
|
||||||
"macInstructions": "Go to your",
|
"settingsInstructions": "Go to your",
|
||||||
"macSystemPreferences": "System Preferences",
|
"settingsLabel": {
|
||||||
|
"macos": "System Preferences",
|
||||||
|
"windows": "Windows privacy settings"
|
||||||
|
},
|
||||||
"helpLinkText": "To learn more, see",
|
"helpLinkText": "To learn more, see",
|
||||||
"helpLinkLabel": "Presentation issue",
|
"helpLinkLabel": "Presentation issue",
|
||||||
"closeButton": "Dismiss",
|
"closeButton": "Dismiss",
|
||||||
|
|||||||
@@ -214,8 +214,11 @@
|
|||||||
"title": "Impossible de partager votre écran",
|
"title": "Impossible de partager votre écran",
|
||||||
"ariaLabel": "Impossible de partager votre écran",
|
"ariaLabel": "Impossible de partager votre écran",
|
||||||
"message": "Il se peut que votre navigateur ne soit pas autorisé à enregistrer l'écran sur votre ordinateur.",
|
"message": "Il se peut que votre navigateur ne soit pas autorisé à enregistrer l'écran sur votre ordinateur.",
|
||||||
"macInstructions": "Accèdez à vos",
|
"settingsInstructions": "Accédez à vos",
|
||||||
"macSystemPreferences": "Préférences système",
|
"settingsLabel": {
|
||||||
|
"macos": "Préférences système",
|
||||||
|
"windows": "paramètres de confidentialité Windows"
|
||||||
|
},
|
||||||
"helpLinkText": "Pour en savoir plus, consulter",
|
"helpLinkText": "Pour en savoir plus, consulter",
|
||||||
"helpLinkLabel": "Problème de présentation",
|
"helpLinkLabel": "Problème de présentation",
|
||||||
"closeButton": "Ignorer",
|
"closeButton": "Ignorer",
|
||||||
|
|||||||
@@ -214,8 +214,11 @@
|
|||||||
"title": "Kan uw scherm niet delen",
|
"title": "Kan uw scherm niet delen",
|
||||||
"ariaLabel": "Kan uw scherm niet delen",
|
"ariaLabel": "Kan uw scherm niet delen",
|
||||||
"message": "Het is mogelijk dat uw browser geen toestemming heeft om het scherm op uw computer op te nemen.",
|
"message": "Het is mogelijk dat uw browser geen toestemming heeft om het scherm op uw computer op te nemen.",
|
||||||
"macInstructions": "Ga naar uw",
|
"settingsInstructions": "Ga naar uw",
|
||||||
"macSystemPreferences": "Systeemvoorkeuren",
|
"settingsLabel": {
|
||||||
|
"macos": "Systeemvoorkeuren",
|
||||||
|
"windows": "Windows-privacyinstellingen"
|
||||||
|
},
|
||||||
"helpLinkText": "Meer informatie, zie",
|
"helpLinkText": "Meer informatie, zie",
|
||||||
"helpLinkLabel": "Presentatieprobleem",
|
"helpLinkLabel": "Presentatieprobleem",
|
||||||
"closeButton": "Negeren",
|
"closeButton": "Negeren",
|
||||||
|
|||||||
Reference in New Issue
Block a user