feat(connection-test): display failed tracker connections (#129)

This commit is contained in:
Jeremy Kahn 2023-07-14 17:21:25 -05:00 committed by GitHub
parent 291ed0c2b9
commit f67dbb60d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 71 additions and 14 deletions

View File

@ -3,6 +3,9 @@ import Tooltip from '@mui/material/Tooltip'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import Circle from '@mui/icons-material/FiberManualRecord' import Circle from '@mui/icons-material/FiberManualRecord'
import { Box } from '@mui/system' import { Box } from '@mui/system'
import ReportIcon from '@mui/icons-material/Report'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest' import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'
@ -10,9 +13,22 @@ interface ConnectionTestResultsProps {
connectionTestResults: IConnectionTestResults connectionTestResults: IConnectionTestResults
} }
export const ConnectionTestResults = ({ export const ConnectionTestResults = ({
connectionTestResults: { hasHost, hasRelay, hasTracker }, connectionTestResults: { hasHost, hasRelay, trackerConnection },
}: ConnectionTestResultsProps) => { }: ConnectionTestResultsProps) => {
if (!hasTracker) { if (trackerConnection === TrackerConnection.FAILED) {
return (
<Typography variant="subtitle2">
<Box
sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}
>
<ReportIcon color="error" sx={{ mr: 1 }} />
<span>Server connection failed</span>
</Box>
</Typography>
)
}
if (trackerConnection !== TrackerConnection.CONNECTED) {
return ( return (
<Typography variant="subtitle2"> <Typography variant="subtitle2">
<Box <Box

View File

@ -16,6 +16,7 @@ import { PeerListHeader } from 'components/Shell/PeerListHeader'
import { Username } from 'components/Username/Username' import { Username } from 'components/Username/Username'
import { AudioState, Peer } from 'models/chat' import { AudioState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom' import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
import { routes } from 'config/routes' import { routes } from 'config/routes'
import { PeerListItem } from './PeerListItem' import { PeerListItem } from './PeerListItem'
@ -113,7 +114,8 @@ export const PeerList = ({
))} ))}
{peerList.length === 0 && {peerList.length === 0 &&
typeof roomId === 'string' && typeof roomId === 'string' &&
connectionTestResults.hasTracker && connectionTestResults.trackerConnection ===
TrackerConnection.CONNECTED &&
connectionTestResults.hasHost ? ( connectionTestResults.hasHost ? (
<> <>
<Box <Box

View File

@ -4,12 +4,13 @@ import {
ConnectionTest, ConnectionTest,
ConnectionTestEvent, ConnectionTestEvent,
ConnectionTestEvents, ConnectionTestEvents,
TrackerConnection,
} from 'services/ConnectionTest/ConnectionTest' } from 'services/ConnectionTest/ConnectionTest'
export interface ConnectionTestResults { export interface ConnectionTestResults {
hasHost: boolean hasHost: boolean
hasRelay: boolean hasRelay: boolean
hasTracker: boolean trackerConnection: TrackerConnection
} }
const rtcPollInterval = 20 * 1000 const rtcPollInterval = 20 * 1000
@ -18,7 +19,9 @@ const trackerPollInterval = 5 * 1000
export const useConnectionTest = () => { export const useConnectionTest = () => {
const [hasHost, setHasHost] = useState(false) const [hasHost, setHasHost] = useState(false)
const [hasRelay, setHasRelay] = useState(false) const [hasRelay, setHasRelay] = useState(false)
const [hasTracker, setHasTracker] = useState(false) const [trackerConnection, setTrackerConnection] = useState(
TrackerConnection.SEARCHING
)
useEffect(() => { useEffect(() => {
const checkRtcConnection = async () => { const checkRtcConnection = async () => {
@ -83,14 +86,22 @@ export const useConnectionTest = () => {
})() })()
;(async () => { ;(async () => {
while (true) { while (true) {
try {
const connectionTest = new ConnectionTest() const connectionTest = new ConnectionTest()
setHasTracker(connectionTest.testTrackerConnection()) const trackerConnectionTestResult =
connectionTest.testTrackerConnection()
setTrackerConnection(trackerConnectionTestResult)
} catch (e) {
setTrackerConnection(TrackerConnection.FAILED)
}
await sleep(trackerPollInterval) await sleep(trackerPollInterval)
} }
})() })()
}, []) }, [])
return { return {
connectionTestResults: { hasHost, hasRelay, hasTracker }, connectionTestResults: { hasHost, hasRelay, trackerConnection },
} }
} }

View File

@ -4,6 +4,7 @@ import { AlertOptions } from 'models/shell'
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom' import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import { ConnectionTestResults } from 'components/Shell/useConnectionTest' import { ConnectionTestResults } from 'components/Shell/useConnectionTest'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
interface ShellContextProps { interface ShellContextProps {
tabHasFocus: boolean tabHasFocus: boolean
@ -62,5 +63,9 @@ export const ShellContext = createContext<ShellContextProps>({
setPeerAudios: () => {}, setPeerAudios: () => {},
customUsername: '', customUsername: '',
setCustomUsername: () => {}, setCustomUsername: () => {},
connectionTestResults: { hasHost: false, hasRelay: false, hasTracker: false }, connectionTestResults: {
hasHost: false,
hasRelay: false,
trackerConnection: TrackerConnection.SEARCHING,
},
}) })

View File

@ -8,12 +8,18 @@ export enum ConnectionTestEvents {
HAS_RELAY_CHANGED = 'HAS_RELAY_CHANGED', HAS_RELAY_CHANGED = 'HAS_RELAY_CHANGED',
} }
export enum TrackerConnection {
SEARCHING = 'SEARCHING',
CONNECTED = 'CONNECTED',
FAILED = 'FAILED',
}
export type ConnectionTestEvent = CustomEvent<ConnectionTest> export type ConnectionTestEvent = CustomEvent<ConnectionTest>
const checkExperationTime = 10 * 1000 const checkExperationTime = 10 * 1000
export class ConnectionTest extends EventTarget { export class ConnectionTest extends EventTarget {
hasTracker = false trackerConnection = TrackerConnection.SEARCHING
hasHost = false hasHost = false
hasRelay = false hasRelay = false
hasPeerReflexive = false hasPeerReflexive = false
@ -100,17 +106,34 @@ export class ConnectionTest extends EventTarget {
testTrackerConnection() { testTrackerConnection() {
const trackers = getTrackers() const trackers = getTrackers()
const readyStates = Object.values(trackers).map( const trackerSockets = Object.values(trackers)
({ readyState }) => readyState
if (trackerSockets.length === 0) {
// Trystero has not yet initialized tracker sockets
this.trackerConnection = TrackerConnection.SEARCHING
return this.trackerConnection
}
const readyStates = trackerSockets.map(({ readyState }) => readyState)
const haveAllTrackerConnectionsFailed = readyStates.every(
readyState => readyState === WebSocket.CLOSED
) )
if (haveAllTrackerConnectionsFailed) {
this.trackerConnection = TrackerConnection.FAILED
throw new Error('Could not connect to a WebTorrent tracker')
}
const areAnyTrackersConnected = readyStates.some( const areAnyTrackersConnected = readyStates.some(
readyState => readyState === WebSocket.OPEN readyState => readyState === WebSocket.OPEN
) )
this.hasTracker = areAnyTrackersConnected this.trackerConnection = areAnyTrackersConnected
? TrackerConnection.CONNECTED
: TrackerConnection.SEARCHING
return this.hasTracker return this.trackerConnection
} }
} }