mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-29 19:57:14 +00:00
wip connection tester updated
This commit is contained in:
@@ -42,6 +42,23 @@ export type IceCandidateReport = {
|
|||||||
working: IceCandidatePair[]
|
working: IceCandidatePair[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the selected pair does not carry media over plain UDP: the browser
|
||||||
|
* fell back to a TURN relay over TCP or TLS, or even the direct route is not
|
||||||
|
* UDP. Media still flows, but quality usually degrades under load.
|
||||||
|
*
|
||||||
|
* Accepts the loosely typed `data` stored on the step result; anything that is
|
||||||
|
* not an IceCandidateReport simply yields false.
|
||||||
|
*/
|
||||||
|
export const isSuboptimalRoute = (data: unknown): boolean => {
|
||||||
|
const selected = (data as IceCandidateReport | null | undefined)?.selected
|
||||||
|
if (!selected) return false
|
||||||
|
const transport = selected.local.relayProtocol ?? selected.local.protocol
|
||||||
|
// An unreported transport is not evidence of a bad route.
|
||||||
|
if (!transport) return false
|
||||||
|
return transport.toLowerCase() !== 'udp'
|
||||||
|
}
|
||||||
|
|
||||||
const PROBE_WIDTH = 320
|
const PROBE_WIDTH = 320
|
||||||
const PROBE_HEIGHT = 180
|
const PROBE_HEIGHT = 180
|
||||||
const PROBE_FPS = 15
|
const PROBE_FPS = 15
|
||||||
|
|||||||
@@ -2,18 +2,33 @@ import type { ReactNode } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ProgressBar } from 'react-aria-components'
|
import { ProgressBar } from 'react-aria-components'
|
||||||
import { css, cx } from '@/styled-system/css'
|
import { css, cx } from '@/styled-system/css'
|
||||||
|
import { A } from '@/primitives'
|
||||||
import type { ConnectionTestStats } from '../types'
|
import type { ConnectionTestStats } from '../types'
|
||||||
import { statusSquareClass } from './stepAppearance'
|
import { statusSquareClass } from './stepAppearance'
|
||||||
|
|
||||||
type SummaryState = 'idle' | 'running' | 'passed' | 'partial' | 'failed'
|
/**
|
||||||
|
* Network prerequisites (protocols, ports, TURN fallbacks), written for the
|
||||||
|
* reader's IT department rather than for the end user.
|
||||||
|
*/
|
||||||
|
const NETWORK_REQUIREMENTS_DOC_URL =
|
||||||
|
'https://docs.numerique.gouv.fr/docs/f2baa1b9-f29e-4d58-959d-65d4376fc6b8/'
|
||||||
|
|
||||||
/** Only a failure earns a colour: everything else stays near-black. */
|
type SummaryState =
|
||||||
|
| 'idle'
|
||||||
|
| 'running'
|
||||||
|
| 'passed'
|
||||||
|
| 'partial'
|
||||||
|
| 'failed'
|
||||||
|
| 'warning'
|
||||||
|
|
||||||
|
/** Only a failure or a degraded route earns a colour: everything else stays near-black. */
|
||||||
const stateColorClass: Record<SummaryState, string> = {
|
const stateColorClass: Record<SummaryState, string> = {
|
||||||
idle: css({ color: 'greyscale.1000' }),
|
idle: css({ color: 'greyscale.1000' }),
|
||||||
running: css({ color: 'greyscale.1000' }),
|
running: css({ color: 'greyscale.1000' }),
|
||||||
passed: css({ color: 'greyscale.1000' }),
|
passed: css({ color: 'greyscale.1000' }),
|
||||||
partial: css({ color: 'greyscale.1000' }),
|
partial: css({ color: 'greyscale.1000' }),
|
||||||
failed: css({ color: 'danger.600' }),
|
failed: css({ color: 'danger.600' }),
|
||||||
|
warning: css({ color: 'warning' }),
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardClass = css({
|
const cardClass = css({
|
||||||
@@ -176,23 +191,31 @@ const Counter = ({
|
|||||||
export const ConnectionTestSummary = ({
|
export const ConnectionTestSummary = ({
|
||||||
stats,
|
stats,
|
||||||
isRunning,
|
isRunning,
|
||||||
|
routeWarning = false,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
stats: ConnectionTestStats
|
stats: ConnectionTestStats
|
||||||
isRunning: boolean
|
isRunning: boolean
|
||||||
|
/** The selected ICE route is usable but not UDP (e.g. TURN over TCP/TLS). */
|
||||||
|
routeWarning?: boolean
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('connectionTest')
|
const { t } = useTranslation('connectionTest')
|
||||||
|
|
||||||
|
// A hard failure still outranks the route warning; the warning outranks
|
||||||
|
// 'partial' because a measured degraded route matters more than skipped
|
||||||
|
// camera or microphone checks.
|
||||||
const state: SummaryState = isRunning
|
const state: SummaryState = isRunning
|
||||||
? 'running'
|
? 'running'
|
||||||
: !stats.hasStarted
|
: !stats.hasStarted
|
||||||
? 'idle'
|
? 'idle'
|
||||||
: stats.failed > 0
|
: stats.failed > 0
|
||||||
? 'failed'
|
? 'failed'
|
||||||
: stats.skipped > 0
|
: routeWarning
|
||||||
? 'partial'
|
? 'warning'
|
||||||
: 'passed'
|
: stats.skipped > 0
|
||||||
|
? 'partial'
|
||||||
|
: 'passed'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={cardClass}>
|
<section className={cardClass}>
|
||||||
@@ -206,7 +229,22 @@ export const ConnectionTestSummary = ({
|
|||||||
: t(`summary.${state}`)}
|
: t(`summary.${state}`)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className={hintClass}>{t(`summary.${state}Hint`)}</p>
|
<p className={hintClass}>
|
||||||
|
{t(`summary.${state}Hint`)}
|
||||||
|
{state === 'warning' && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<A
|
||||||
|
href={NETWORK_REQUIREMENTS_DOC_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{t('summary.warningDocLink')}
|
||||||
|
</A>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{stats.hasStarted && (
|
{stats.hasStarted && (
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ const fromCheckInfo = (info: CheckInfo): Partial<ConnectionTestStepResult> => ({
|
|||||||
status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed',
|
status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed',
|
||||||
summary: info.description,
|
summary: info.description,
|
||||||
logs: info.logs,
|
logs: info.logs,
|
||||||
|
// Only SelectedCandidateCheck sets `data` (the ICE candidate report); for
|
||||||
|
// the stock LiveKit checks it is undefined and this is a no-op.
|
||||||
|
data: info.data as Record<string, unknown> | undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const groupDevicesByKind = (devices: MediaDeviceInfo[]) => {
|
const groupDevicesByKind = (devices: MediaDeviceInfo[]) => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { css } from '@/styled-system/css'
|
|||||||
import { Center, VStack } from '@/styled-system/jsx'
|
import { Center, VStack } from '@/styled-system/jsx'
|
||||||
import { Permissions } from '@/features/rooms/components/Permissions'
|
import { Permissions } from '@/features/rooms/components/Permissions'
|
||||||
import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner'
|
import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner'
|
||||||
|
import { isSuboptimalRoute } from '../checks/selectedCandidate'
|
||||||
import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow'
|
import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow'
|
||||||
import { ConnectionTestSummary } from '../components/ConnectionTestSummary'
|
import { ConnectionTestSummary } from '../components/ConnectionTestSummary'
|
||||||
import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types'
|
import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types'
|
||||||
@@ -70,6 +71,13 @@ const ConnectionTest = () => {
|
|||||||
() => new Map(steps.map((step) => [step.id, step] as const)),
|
() => new Map(steps.map((step) => [step.id, step] as const)),
|
||||||
[steps]
|
[steps]
|
||||||
)
|
)
|
||||||
|
// The summary headline downgrades to a warning when media flows but over a
|
||||||
|
// fallback route: TURN over TCP/TLS, or anything else that is not UDP.
|
||||||
|
const routeWarning = useMemo(() => {
|
||||||
|
const step = stepsById.get('selectedCandidate')
|
||||||
|
return step?.status === 'success' && isSuboptimalRoute(step.data)
|
||||||
|
}, [stepsById])
|
||||||
|
|
||||||
const isPublishVideoRunning =
|
const isPublishVideoRunning =
|
||||||
stepsById.get('publishVideo')?.status === 'running'
|
stepsById.get('publishVideo')?.status === 'running'
|
||||||
|
|
||||||
@@ -99,7 +107,11 @@ const ConnectionTest = () => {
|
|||||||
<CenteredContent withBackButton>
|
<CenteredContent withBackButton>
|
||||||
<Center>
|
<Center>
|
||||||
<VStack gap="1.5rem" maxWidth="40rem" width="100%">
|
<VStack gap="1.5rem" maxWidth="40rem" width="100%">
|
||||||
<ConnectionTestSummary stats={stats} isRunning={isRunning}>
|
<ConnectionTestSummary
|
||||||
|
stats={stats}
|
||||||
|
isRunning={isRunning}
|
||||||
|
routeWarning={routeWarning}
|
||||||
|
>
|
||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
// A disabled "run" button while the test runs is dead weight:
|
// A disabled "run" button while the test runs is dead weight:
|
||||||
// cancelling is the only thing left to do.
|
// cancelling is the only thing left to do.
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
"passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.",
|
"passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.",
|
||||||
"partial": "Teilweiser Test",
|
"partial": "Teilweiser Test",
|
||||||
"partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.",
|
"partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.",
|
||||||
|
"warning": "Verbindung nicht optimal",
|
||||||
|
"warningHint": "Achtung: Sie befinden sich nicht in optimalen Bedingungen für die Nutzung des Tools. Ihre Medien werden nicht über UDP übertragen (Rückgriff auf ein TURN-Relay über TCP oder TLS), was die Audio- und Videoqualität beeinträchtigen kann.",
|
||||||
|
"warningDocLink": "Technische Dokumentation für Ihre IT-Abteilung",
|
||||||
"failed_one": "{{count}} Prüfung fehlgeschlagen",
|
"failed_one": "{{count}} Prüfung fehlgeschlagen",
|
||||||
"failed_other": "{{count}} Prüfungen fehlgeschlagen",
|
"failed_other": "{{count}} Prüfungen fehlgeschlagen",
|
||||||
"failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung."
|
"failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung."
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
"passedHint": "Your browser, your devices and your network are ready for a meeting.",
|
"passedHint": "Your browser, your devices and your network are ready for a meeting.",
|
||||||
"partial": "Partially tested",
|
"partial": "Partially tested",
|
||||||
"partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.",
|
"partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.",
|
||||||
|
"warning": "Suboptimal connection",
|
||||||
|
"warningHint": "Warning: you are not in optimal conditions to use the tool. Your media is not carried over UDP (it falls back to a TURN relay over TCP or TLS), which can degrade audio and video quality.",
|
||||||
|
"warningDocLink": "Technical documentation for your IT department",
|
||||||
"failed_one": "{{count}} check failed",
|
"failed_one": "{{count}} check failed",
|
||||||
"failed_other": "{{count}} checks failed",
|
"failed_other": "{{count}} checks failed",
|
||||||
"failedHint": "Open the failed checks below for details, then send the report to your IT department."
|
"failedHint": "Open the failed checks below for details, then send the report to your IT department."
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
"passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.",
|
"passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.",
|
||||||
"partial": "Test partiel",
|
"partial": "Test partiel",
|
||||||
"partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.",
|
"partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.",
|
||||||
|
"warning": "Connexion non optimale",
|
||||||
|
"warningHint": "Attention : vous n'êtes pas dans les conditions optimales pour accéder à l'outil. Vos flux ne transitent pas en UDP (repli sur un relais TURN en TCP ou TLS), ce qui peut dégrader la qualité audio et vidéo.",
|
||||||
|
"warningDocLink": "Documentation technique à destination de votre service informatique",
|
||||||
"failed_one": "{{count}} vérification en échec",
|
"failed_one": "{{count}} vérification en échec",
|
||||||
"failed_other": "{{count}} vérifications en échec",
|
"failed_other": "{{count}} vérifications en échec",
|
||||||
"failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique."
|
"failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique."
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
"passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.",
|
"passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.",
|
||||||
"partial": "Gedeeltelijke test",
|
"partial": "Gedeeltelijke test",
|
||||||
"partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.",
|
"partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.",
|
||||||
|
"warning": "Verbinding niet optimaal",
|
||||||
|
"warningHint": "Let op: je bevindt je niet in optimale omstandigheden om de tool te gebruiken. Je media loopt niet via UDP (terugval op een TURN-relay via TCP of TLS), wat de audio- en videokwaliteit kan verminderen.",
|
||||||
|
"warningDocLink": "Technische documentatie voor je IT-afdeling",
|
||||||
"failed_one": "{{count}} controle mislukt",
|
"failed_one": "{{count}} controle mislukt",
|
||||||
"failed_other": "{{count}} controles mislukt",
|
"failed_other": "{{count}} controles mislukt",
|
||||||
"failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling."
|
"failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling."
|
||||||
|
|||||||
Reference in New Issue
Block a user