fix(desktop): relay host status, display, and server-side LAN candidate
- Probe relay hosts through a throwaway E2EE tunnel in the host switcher instead of an HTTP probe against the relay:// pseudo-URL, which always reported Unreachable - Show 'via OpenChamber Relay' for relay hosts in the switcher and the servers list instead of the raw relay:// pseudo-URL; hide the URL-centric edit action for relay hosts (saving it would drop the tunnel descriptor) - Pairing LAN candidate prefers the address the requesting client actually reached the server on; interface scanning could pick an unroutable virtual bridge (docker0), producing links whose LAN leg silently failed and forced devices onto the relay
This commit is contained in:
@@ -450,6 +450,13 @@ export function DesktopHostSwitcherDialog({
|
||||
const localClientToken = await getLocalClientToken();
|
||||
const results = await Promise.all(
|
||||
hosts.map(async (h) => {
|
||||
// Relay hosts have no HTTP address to probe — check reachability
|
||||
// through a throwaway E2EE tunnel instead.
|
||||
if (h.relay) {
|
||||
const startedAt = performance.now();
|
||||
const ok = await probeRelayHost(h.relay).catch(() => false);
|
||||
return [h.id, { status: ok ? ('ok' as const) : ('unreachable' as const), latencyMs: Math.round(performance.now() - startedAt) } satisfies HostStatus] as const;
|
||||
}
|
||||
const url = normalizeHostUrl(isElectronShell() ? getDesktopHostApiUrl(h) : h.url);
|
||||
if (!url) {
|
||||
return [h.id, { status: 'unreachable' as const, latencyMs: 0 } satisfies HostStatus] as const;
|
||||
@@ -898,7 +905,9 @@ export function DesktopHostSwitcherDialog({
|
||||
const displayLabel = host.id === LOCAL_HOST_ID
|
||||
? t('desktopHostSwitcher.instance.local')
|
||||
: redactSensitiveUrl(host.label);
|
||||
const displayUrl = redactSensitiveUrl(effectiveUrl);
|
||||
// Relay hosts have a relay:// pseudo-URL that means nothing to a
|
||||
// person — say how the connection works instead.
|
||||
const displayUrl = host.relay ? t('mobile.connect.relay.badge') : redactSensitiveUrl(effectiveUrl);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1459,16 +1459,23 @@ export const RemoteInstancesPage: React.FC = () => {
|
||||
<p className="typography-ui-label text-foreground truncate">{redactSensitiveUrl(host.label)}</p>
|
||||
{directDefaultHostId === host.id ? <span className="typography-micro text-muted-foreground">{t('desktopHostSwitcher.header.default')}</span> : null}
|
||||
</div>
|
||||
<p className="typography-micro text-muted-foreground font-mono truncate">{redactSensitiveUrl(host.apiUrl || host.url)}</p>
|
||||
<p className={cn('typography-micro text-muted-foreground truncate', !host.relay && 'font-mono')}>
|
||||
{host.relay ? t('mobile.connect.relay.badge') : redactSensitiveUrl(host.apiUrl || host.url)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<Button type="button" variant="ghost" size="xs" className="!font-normal" onClick={() => void setDefaultDirectHost(host.id)} disabled={directSaving || directDefaultHostId === host.id} aria-label={t('desktopHostSwitcher.actions.setAsDefaultAria')}>
|
||||
{directDefaultHostId === host.id ? <Icon name="star-fill" className="h-3.5 w-3.5" /> : <Icon name="star" className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="xs" className="!font-normal" onClick={() => beginEditDirectHost(host)} disabled={directSaving}>
|
||||
<Icon name="pencil" className="h-3.5 w-3.5" />
|
||||
{t('desktopHostSwitcher.actions.edit')}
|
||||
</Button>
|
||||
{/* The edit form is URL/token-centric; saving it would drop a
|
||||
relay host's tunnel descriptor. Relay hosts are re-imported
|
||||
via a fresh pairing link instead. */}
|
||||
{host.relay ? null : (
|
||||
<Button type="button" variant="ghost" size="xs" className="!font-normal" onClick={() => beginEditDirectHost(host)} disabled={directSaving}>
|
||||
<Icon name="pencil" className="h-3.5 w-3.5" />
|
||||
{t('desktopHostSwitcher.actions.edit')}
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" variant="ghost" size="xs" className="!font-normal" onClick={() => void handleRemoveDirectHost(host.id)} disabled={directSaving}>
|
||||
<Icon name="delete-bin" className="h-3.5 w-3.5" />
|
||||
{t('settings.common.actions.delete')}
|
||||
|
||||
Reference in New Issue
Block a user