wip connection tester updated

This commit is contained in:
lebaudantoine
2026-08-25 23:14:05 +02:00
parent 76a24d4787
commit f4c315bdbf
8 changed files with 89 additions and 7 deletions
@@ -42,6 +42,23 @@ export type IceCandidateReport = {
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_HEIGHT = 180
const PROBE_FPS = 15
@@ -2,18 +2,33 @@ import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { ProgressBar } from 'react-aria-components'
import { css, cx } from '@/styled-system/css'
import { A } from '@/primitives'
import type { ConnectionTestStats } from '../types'
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> = {
idle: css({ color: 'greyscale.1000' }),
running: css({ color: 'greyscale.1000' }),
passed: css({ color: 'greyscale.1000' }),
partial: css({ color: 'greyscale.1000' }),
failed: css({ color: 'danger.600' }),
warning: css({ color: 'warning' }),
}
const cardClass = css({
@@ -176,23 +191,31 @@ const Counter = ({
export const ConnectionTestSummary = ({
stats,
isRunning,
routeWarning = false,
children,
}: {
stats: ConnectionTestStats
isRunning: boolean
/** The selected ICE route is usable but not UDP (e.g. TURN over TCP/TLS). */
routeWarning?: boolean
children?: ReactNode
}) => {
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
? 'running'
: !stats.hasStarted
? 'idle'
: stats.failed > 0
? 'failed'
: stats.skipped > 0
? 'partial'
: 'passed'
: routeWarning
? 'warning'
: stats.skipped > 0
? 'partial'
: 'passed'
return (
<section className={cardClass}>
@@ -206,7 +229,22 @@ export const ConnectionTestSummary = ({
: t(`summary.${state}`)}
</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>
{stats.hasStarted && (
@@ -53,6 +53,9 @@ const fromCheckInfo = (info: CheckInfo): Partial<ConnectionTestStepResult> => ({
status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed',
summary: info.description,
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[]) => {
@@ -13,6 +13,7 @@ import { css } from '@/styled-system/css'
import { Center, VStack } from '@/styled-system/jsx'
import { Permissions } from '@/features/rooms/components/Permissions'
import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner'
import { isSuboptimalRoute } from '../checks/selectedCandidate'
import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow'
import { ConnectionTestSummary } from '../components/ConnectionTestSummary'
import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types'
@@ -70,6 +71,13 @@ const ConnectionTest = () => {
() => new Map(steps.map((step) => [step.id, step] as const)),
[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 =
stepsById.get('publishVideo')?.status === 'running'
@@ -99,7 +107,11 @@ const ConnectionTest = () => {
<CenteredContent withBackButton>
<Center>
<VStack gap="1.5rem" maxWidth="40rem" width="100%">
<ConnectionTestSummary stats={stats} isRunning={isRunning}>
<ConnectionTestSummary
stats={stats}
isRunning={isRunning}
routeWarning={routeWarning}
>
{isRunning ? (
// A disabled "run" button while the test runs is dead weight:
// 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.",
"partial": "Teilweiser Test",
"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_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."
@@ -46,6 +46,9 @@
"passedHint": "Your browser, your devices and your network are ready for a meeting.",
"partial": "Partially tested",
"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_other": "{{count}} checks failed",
"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.",
"partial": "Test partiel",
"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_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."
@@ -46,6 +46,9 @@
"passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.",
"partial": "Gedeeltelijke test",
"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_other": "{{count}} controles mislukt",
"failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling."