Files
orca/mobile/src/diagnostics/host-reachability.test.ts
Jinwoo HongandOrca 73f1bbfc94 mobile: recover from wedged Tailscale tunnels and say 'check Tailscale' when it's the likely culprit (#7980)
A wedged Tailscale tunnel (known iOS failure mode) produces no AppState
or network-type transition, so no revival nudge ever fires and the
reconnect loop parked permanently at its give-up cap — users had to
toggle Tailscale off/on just to force a transition (#7824).

- rpc-client: past the give-up cap, drop to a 90s trickle dial instead
  of parking so the session self-heals once the tunnel recovers.
- host screen: nudge the shared client on focus so opening the host
  retries immediately instead of waiting out a backoff/trickle timer.
- connection-health: warning/unreachable verdicts on 100.64/10 or
  *.ts.net endpoints now carry a 'check Tailscale' hint, shown on the
  home host list and the in-session status line after ~3 failed
  attempts.
- troubleshoot: 'Cannot reach <tailnet-ip>' now says to check
  Tailscale, adds a dedicated Tailscale section, and stops telling
  Tailscale users to disable their VPN (that advice killed their only
  route to the host); sections extracted to
  troubleshoot-common-issues.tsx to stay under the max-lines cap.

Co-authored-by: Orca <help@stably.ai>
2026-07-09 15:56:25 -07:00

47 lines
1.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { testHostReachability, unreachableHostDetail } from './host-reachability'
describe('unreachableHostDetail', () => {
it('points at Tailscale for tailnet CGNAT endpoints', () => {
expect(unreachableHostDetail('ws://100.65.9.106:6768')).toBe(
'Cannot reach 100.65.9.106:6768 — check Tailscale'
)
})
it('points at Tailscale for MagicDNS endpoints', () => {
expect(unreachableHostDetail('ws://my-desktop.tailnet-1234.ts.net:6768')).toBe(
'Cannot reach my-desktop.tailnet-1234.ts.net:6768 — check Tailscale'
)
})
it('stays generic for LAN endpoints', () => {
expect(unreachableHostDetail('ws://192.168.1.50:6768')).toBe('Cannot reach 192.168.1.50:6768')
})
it('does not treat non-CGNAT 100.x addresses as Tailscale', () => {
expect(unreachableHostDetail('ws://100.20.1.5:6768')).toBe('Cannot reach 100.20.1.5:6768')
})
})
describe('testHostReachability', () => {
it('returns false without leaving timers when WebSocket rejects a malformed endpoint', async () => {
vi.useFakeTimers()
vi.stubGlobal(
'WebSocket',
class {
constructor() {
throw new TypeError('Invalid URL')
}
}
)
try {
await expect(testHostReachability('not-a-url')).resolves.toBe(false)
expect(vi.getTimerCount()).toBe(0)
} finally {
vi.unstubAllGlobals()
vi.useRealTimers()
}
})
})