mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-27 18:56:58 +00:00
wip connection tester updated
This commit is contained in:
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user