fix: restrict failover to working endpoint network class

close #902
This commit is contained in:
edde746
2026-04-21 19:20:57 +02:00
parent e6c1923b0e
commit fd701e5d1f
+34 -3
View File
@@ -238,6 +238,9 @@ class PlexAuthService {
}
}
/// Classification of an endpoint URL by the server's published connection type.
enum PlexNetworkClass { local, remote, relay, unknown }
/// Helper class to track connection candidates during testing
class _ConnectionCandidate {
final PlexConnection connection;
@@ -591,7 +594,14 @@ class PlexServer {
return null;
}
List<_ConnectionCandidate> _buildPrioritizedCandidates({Set<String>? excludeUrls}) {
/// Classify [url] against the server's published connections. Returns
/// [PlexNetworkClass.unknown] when the URL doesn't match any known endpoint
/// (e.g. a manually-entered custom URL).
PlexNetworkClass networkClassForUrl(String url) {
return _candidateForUrl(url)?.connection.networkClass ?? PlexNetworkClass.unknown;
}
List<_ConnectionCandidate> _buildPrioritizedCandidates({Set<String>? excludeUrls, PlexNetworkClass? restrictTo}) {
final seen = <String>{};
if (excludeUrls != null) {
seen.addAll(excludeUrls);
@@ -641,19 +651,34 @@ class PlexServer {
}
}
return [...httpsLocal, ...httpsRemote, ...httpsRelay, ...httpLocal, ...httpRemote, ...httpRelay];
final all = [...httpsLocal, ...httpsRemote, ...httpsRelay, ...httpLocal, ...httpRemote, ...httpRelay];
// Failover reachability filter. After discovery, failover should stay within
// endpoint families plausible for the current session: when a remote or relay
// endpoint is what's working, LAN-only endpoints are unreachable from off-LAN
// clients and only pollute the retry chain (see GH #902). Reconnect /
// reoptimization widens the search again via _reoptimizeServer. Local is kept
// conservative — it may still be reachable over VPN/routed-LAN, but those
// setups re-widen on the next connectivity event.
if (restrictTo == PlexNetworkClass.remote || restrictTo == PlexNetworkClass.relay) {
final filtered = all.where((c) => c.connection.networkClass != PlexNetworkClass.local).toList();
if (filtered.isNotEmpty) return filtered;
}
return all;
}
List<String> prioritizedEndpointUrls({String? preferredFirst}) {
final urls = <String>[];
final exclude = <String>{};
PlexNetworkClass? restrictTo;
if (preferredFirst != null && preferredFirst.isNotEmpty) {
urls.add(preferredFirst);
exclude.add(preferredFirst);
restrictTo = networkClassForUrl(preferredFirst);
}
final candidates = _buildPrioritizedCandidates(excludeUrls: exclude);
final candidates = _buildPrioritizedCandidates(excludeUrls: exclude, restrictTo: restrictTo);
urls.addAll(candidates.map((candidate) => candidate.url));
return urls;
}
@@ -927,6 +952,12 @@ class PlexConnection {
return 'Remote';
}
PlexNetworkClass get networkClass {
if (relay) return PlexNetworkClass.relay;
if (local) return PlexNetworkClass.local;
return PlexNetworkClass.remote;
}
/// Create an HTTP fallback version of this HTTPS connection
/// This allows testing HTTP when HTTPS is unavailable (e.g., certificate issues)
PlexConnection toHttpFallback() {