Files
mesh/client/src/components/ConnectionIndicator.tsx
T
Gilles Soulier 1d177e96a6 first
2026-01-05 13:20:54 +01:00

152 lines
4.1 KiB
TypeScript

// Created by: Claude
// Date: 2026-01-03
// Purpose: Indicateur de qualité de connexion WebRTC
// Refs: client/CLAUDE.md
import React, { useEffect, useState } from 'react'
import styles from './ConnectionIndicator.module.css'
export interface ConnectionIndicatorProps {
peerConnection?: RTCPeerConnection
peerId: string
username: string
}
type ConnectionQuality = 'excellent' | 'good' | 'poor' | 'disconnected'
const ConnectionIndicator: React.FC<ConnectionIndicatorProps> = ({
peerConnection,
peerId,
username,
}) => {
const [quality, setQuality] = useState<ConnectionQuality>('disconnected')
const [stats, setStats] = useState<{
rtt?: number // Round-trip time en ms
packetsLost?: number
jitter?: number // En ms
}>({})
useEffect(() => {
if (!peerConnection) {
setQuality('disconnected')
return
}
// Surveiller l'état de connexion
const handleConnectionStateChange = () => {
const state = peerConnection.connectionState
console.log(`[${username}] Connection state:`, state)
if (state === 'connected') {
setQuality('good')
} else if (state === 'connecting') {
setQuality('poor')
} else if (state === 'disconnected' || state === 'failed' || state === 'closed') {
setQuality('disconnected')
}
}
peerConnection.addEventListener('connectionstatechange', handleConnectionStateChange)
handleConnectionStateChange() // État initial
// Récupérer les stats toutes les 2 secondes
const statsInterval = setInterval(async () => {
if (peerConnection.connectionState !== 'connected') {
return
}
try {
const stats = await peerConnection.getStats()
let rtt: number | undefined
let packetsLost = 0
let jitter: number | undefined
stats.forEach((report) => {
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
rtt = report.currentRoundTripTime ? report.currentRoundTripTime * 1000 : undefined
}
if (report.type === 'inbound-rtp' && report.kind === 'video') {
packetsLost = report.packetsLost || 0
jitter = report.jitter ? report.jitter * 1000 : undefined
}
})
setStats({ rtt, packetsLost, jitter })
// Déterminer la qualité selon RTT
if (rtt !== undefined) {
if (rtt < 100) {
setQuality('excellent')
} else if (rtt < 200) {
setQuality('good')
} else {
setQuality('poor')
}
}
} catch (error) {
console.error('Error getting WebRTC stats:', error)
}
}, 2000)
return () => {
peerConnection.removeEventListener('connectionstatechange', handleConnectionStateChange)
clearInterval(statsInterval)
}
}, [peerConnection, username])
const getQualityIcon = () => {
switch (quality) {
case 'excellent':
return '📶'
case 'good':
return '📡'
case 'poor':
return '⚠️'
case 'disconnected':
return '❌'
}
}
const getQualityLabel = () => {
switch (quality) {
case 'excellent':
return 'Excellente'
case 'good':
return 'Bonne'
case 'poor':
return 'Faible'
case 'disconnected':
return 'Déconnecté'
}
}
return (
<div className={`${styles.indicator} ${styles[quality]}`} title={getTooltip()}>
<span className={styles.icon}>{getQualityIcon()}</span>
<span className={styles.label}>{getQualityLabel()}</span>
</div>
)
function getTooltip(): string {
if (quality === 'disconnected') {
return 'Pas de connexion'
}
const parts: string[] = []
if (stats.rtt !== undefined) {
parts.push(`RTT: ${stats.rtt.toFixed(0)}ms`)
}
if (stats.packetsLost !== undefined && stats.packetsLost > 0) {
parts.push(`Paquets perdus: ${stats.packetsLost}`)
}
if (stats.jitter !== undefined) {
parts.push(`Jitter: ${stats.jitter.toFixed(1)}ms`)
}
return parts.length > 0 ? parts.join(' | ') : getQualityLabel()
}
}
export default ConnectionIndicator