forked from Shiloh/remnantchat
feat(connection-test): Display tracker connection status (#128)
* feat(ConnectionTest): track tracker state * feat(ConnectionTest): show tracker searching state * chore(deps): use github:jeremyckahn/trystero#feature/get-tracker-connections * feat(connection-test): hide network indicator in non-room routes * feat(connection-test): show peer searching status * feat(connection-test): hide peer searching UI when not in a room
This commit is contained in:
parent
2c29674a48
commit
291ed0c2b9
2853
package-lock.json
generated
2853
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@
|
||||
"sdp": "^3.2.0",
|
||||
"secure-file-transfer": "^0.0.7",
|
||||
"streamsaver": "^2.0.6",
|
||||
"trystero": "^0.12.1",
|
||||
"trystero": "github:jeremyckahn/trystero#feature/get-tracker-connections",
|
||||
"typeface-public-sans": "^1.1.13",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.7.4",
|
||||
|
1
src/__mocks__/trystero/torrent.ts
Normal file
1
src/__mocks__/trystero/torrent.ts
Normal file
@ -0,0 +1 @@
|
||||
export const getTrackers = () => ({})
|
@ -1,5 +1,8 @@
|
||||
import { Tooltip, Typography } from '@mui/material'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Circle from '@mui/icons-material/FiberManualRecord'
|
||||
import { Box } from '@mui/system'
|
||||
|
||||
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'
|
||||
|
||||
@ -7,8 +10,21 @@ interface ConnectionTestResultsProps {
|
||||
connectionTestResults: IConnectionTestResults
|
||||
}
|
||||
export const ConnectionTestResults = ({
|
||||
connectionTestResults: { hasHost, hasRelay },
|
||||
connectionTestResults: { hasHost, hasRelay, hasTracker },
|
||||
}: ConnectionTestResultsProps) => {
|
||||
if (!hasTracker) {
|
||||
return (
|
||||
<Typography variant="subtitle2">
|
||||
<Box
|
||||
sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}
|
||||
>
|
||||
<CircularProgress size={16} sx={{ mr: 1.5 }} />
|
||||
<span>Searching for servers...</span>
|
||||
</Box>
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
if (hasHost && hasRelay) {
|
||||
return (
|
||||
<Tooltip title="Connections can be established with all peers that also have a full network connection.">
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import MuiDrawer from '@mui/material/Drawer'
|
||||
import List from '@mui/material/List'
|
||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||
@ -8,11 +9,14 @@ import IconButton from '@mui/material/IconButton'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
import VolumeUp from '@mui/icons-material/VolumeUp'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import Box from '@mui/material/Box'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
|
||||
import { PeerListHeader } from 'components/Shell/PeerListHeader'
|
||||
import { Username } from 'components/Username/Username'
|
||||
import { AudioState, Peer } from 'models/chat'
|
||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
||||
import { routes } from 'config/routes'
|
||||
|
||||
import { PeerListItem } from './PeerListItem'
|
||||
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'
|
||||
@ -22,6 +26,7 @@ export const peerListWidth = 300
|
||||
|
||||
export interface PeerListProps extends PropsWithChildren {
|
||||
userId: string
|
||||
roomId: string | undefined
|
||||
isPeerListOpen: boolean
|
||||
onPeerListClose: () => void
|
||||
peerList: Peer[]
|
||||
@ -33,6 +38,7 @@ export interface PeerListProps extends PropsWithChildren {
|
||||
|
||||
export const PeerList = ({
|
||||
userId,
|
||||
roomId,
|
||||
isPeerListOpen,
|
||||
onPeerListClose,
|
||||
peerList,
|
||||
@ -64,9 +70,24 @@ export const PeerList = ({
|
||||
<ChevronRightIcon />
|
||||
</IconButton>
|
||||
<ListItem>
|
||||
<Routes>
|
||||
{/*
|
||||
This stub route is needed to silence spurious warnings in the tests.
|
||||
*/}
|
||||
<Route path={routes.ROOT} element={<></>}></Route>
|
||||
|
||||
{[routes.PUBLIC_ROOM, routes.PRIVATE_ROOM].map(route => (
|
||||
<Route
|
||||
key={route}
|
||||
path={route}
|
||||
element={
|
||||
<ConnectionTestResults
|
||||
connectionTestResults={connectionTestResults}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Routes>
|
||||
</ListItem>
|
||||
</PeerListHeader>
|
||||
<Divider />
|
||||
@ -90,6 +111,24 @@ export const PeerList = ({
|
||||
peerAudios={peerAudios}
|
||||
/>
|
||||
))}
|
||||
{peerList.length === 0 &&
|
||||
typeof roomId === 'string' &&
|
||||
connectionTestResults.hasTracker &&
|
||||
connectionTestResults.hasHost ? (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
m: 2,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={16} sx={{ mr: 1.5 }} />
|
||||
<span>Searching for peers...</span>
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
</List>
|
||||
</MuiDrawer>
|
||||
)
|
||||
|
@ -325,6 +325,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
</RouteContent>
|
||||
<PeerList
|
||||
userId={userPeerId}
|
||||
roomId={roomId}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
onPeerListClose={handlePeerListClick}
|
||||
peerList={peerList}
|
||||
|
@ -9,16 +9,19 @@ import {
|
||||
export interface ConnectionTestResults {
|
||||
hasHost: boolean
|
||||
hasRelay: boolean
|
||||
hasTracker: boolean
|
||||
}
|
||||
|
||||
const pollInterval = 20 * 1000
|
||||
const rtcPollInterval = 20 * 1000
|
||||
const trackerPollInterval = 5 * 1000
|
||||
|
||||
export const useConnectionTest = () => {
|
||||
const [hasHost, setHasHost] = useState(false)
|
||||
const [hasRelay, setHasRelay] = useState(false)
|
||||
const [hasTracker, setHasTracker] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const checkConnection = async () => {
|
||||
const checkRtcConnection = async () => {
|
||||
const connectionTest = new ConnectionTest()
|
||||
|
||||
const handleHasHostChanged = ((event: ConnectionTestEvent) => {
|
||||
@ -58,10 +61,10 @@ export const useConnectionTest = () => {
|
||||
ConnectionTestEvents.HAS_RELAY_CHANGED,
|
||||
handleHasRelayChanged
|
||||
)
|
||||
}, pollInterval)
|
||||
}, rtcPollInterval)
|
||||
|
||||
try {
|
||||
await connectionTest.runRtcPeerConnectionTest()
|
||||
await connectionTest.initRtcPeerConnectionTest()
|
||||
} catch (e) {
|
||||
setHasHost(false)
|
||||
setHasRelay(false)
|
||||
@ -73,14 +76,21 @@ export const useConnectionTest = () => {
|
||||
|
||||
;(async () => {
|
||||
while (true) {
|
||||
const connectionTest = await checkConnection()
|
||||
await sleep(pollInterval)
|
||||
connectionTest.destroy()
|
||||
const connectionTest = await checkRtcConnection()
|
||||
await sleep(rtcPollInterval)
|
||||
connectionTest.destroyRtcPeerConnectionTest()
|
||||
}
|
||||
})()
|
||||
;(async () => {
|
||||
while (true) {
|
||||
const connectionTest = new ConnectionTest()
|
||||
setHasTracker(connectionTest.testTrackerConnection())
|
||||
await sleep(trackerPollInterval)
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
return {
|
||||
connectionTestResults: { hasHost, hasRelay },
|
||||
connectionTestResults: { hasHost, hasRelay, hasTracker },
|
||||
}
|
||||
}
|
||||
|
@ -62,5 +62,5 @@ export const ShellContext = createContext<ShellContextProps>({
|
||||
setPeerAudios: () => {},
|
||||
customUsername: '',
|
||||
setCustomUsername: () => {},
|
||||
connectionTestResults: { hasHost: false, hasRelay: false },
|
||||
connectionTestResults: { hasHost: false, hasRelay: false, hasTracker: false },
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getTrackers } from 'trystero/torrent'
|
||||
import { rtcConfig } from 'config/rtcConfig'
|
||||
import { parseCandidate } from 'sdp'
|
||||
|
||||
@ -12,6 +13,7 @@ export type ConnectionTestEvent = CustomEvent<ConnectionTest>
|
||||
const checkExperationTime = 10 * 1000
|
||||
|
||||
export class ConnectionTest extends EventTarget {
|
||||
hasTracker = false
|
||||
hasHost = false
|
||||
hasRelay = false
|
||||
hasPeerReflexive = false
|
||||
@ -19,7 +21,7 @@ export class ConnectionTest extends EventTarget {
|
||||
|
||||
rtcPeerConnection?: RTCPeerConnection
|
||||
|
||||
async runRtcPeerConnectionTest() {
|
||||
async initRtcPeerConnectionTest() {
|
||||
if (typeof RTCPeerConnection === 'undefined') return
|
||||
|
||||
const { iceServers } = rtcConfig
|
||||
@ -91,9 +93,25 @@ export class ConnectionTest extends EventTarget {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
destroyRtcPeerConnectionTest() {
|
||||
this.rtcPeerConnection?.close()
|
||||
}
|
||||
|
||||
testTrackerConnection() {
|
||||
const trackers = getTrackers()
|
||||
|
||||
const readyStates = Object.values(trackers).map(
|
||||
({ readyState }) => readyState
|
||||
)
|
||||
|
||||
const areAnyTrackersConnected = readyStates.some(
|
||||
readyState => readyState === WebSocket.OPEN
|
||||
)
|
||||
|
||||
this.hasTracker = areAnyTrackersConnected
|
||||
|
||||
return this.hasTracker
|
||||
}
|
||||
}
|
||||
|
||||
export const connectionTest = new ConnectionTest()
|
||||
|
Loading…
Reference in New Issue
Block a user