diff --git a/lib/services/multi_server_manager.dart b/lib/services/multi_server_manager.dart index 9afd4282..4b79c75d 100644 --- a/lib/services/multi_server_manager.dart +++ b/lib/services/multi_server_manager.dart @@ -83,7 +83,7 @@ class MultiServerManager { final cachedEndpoint = storage.getServerEndpoint(serverId); // Find best working connection, passing cached endpoint for fast-path - final streamIterator = StreamIterator(server.findBestWorkingConnection(preferredUri: cachedEndpoint)); + final streamIterator = StreamIterator(server.findBestWorkingConnection(preferredUri: cachedEndpoint, clientIdentifier: clientIdentifier)); if (!await streamIterator.moveNext()) { throw Exception('No working connection found'); @@ -388,7 +388,7 @@ class MultiServerManager { try { appLogger.d('Starting connection optimization for ${server.name}', error: {'reason': reason}); - await for (final connection in server.findBestWorkingConnection(preferredUri: cachedEndpoint)) { + await for (final connection in server.findBestWorkingConnection(preferredUri: cachedEndpoint, clientIdentifier: _clientIdentifier)) { final newUrl = connection.uri; // Check if this is actually a better connection than current diff --git a/lib/services/plex_auth_service.dart b/lib/services/plex_auth_service.dart index ef715099..574d3151 100644 --- a/lib/services/plex_auth_service.dart +++ b/lib/services/plex_auth_service.dart @@ -392,7 +392,7 @@ class PlexServer { /// Priority: local > remote > relay, then HTTPS > HTTP, then lowest latency /// Tests both plex.direct URI and direct IP for each connection /// HTTPS connections are tested first, with HTTP as fallback - Stream findBestWorkingConnection({String? preferredUri}) async* { + Stream findBestWorkingConnection({String? preferredUri, String? clientIdentifier}) async* { if (connections.isEmpty) { appLogger.w('No connections available for server discovery'); return; @@ -438,6 +438,7 @@ class PlexServer { cachedCandidate.url, accessToken, timeout: preferredTimeout, + clientIdentifier: clientIdentifier, ); if (result.success) { @@ -457,7 +458,7 @@ class PlexServer { appLogger.d('Running connection race to find first working endpoint', error: {'candidateCount': totalCandidates}); for (final candidate in candidates) { - PlexClient.testConnectionWithLatency(candidate.url, accessToken, timeout: raceTimeout).then((result) { + PlexClient.testConnectionWithLatency(candidate.url, accessToken, timeout: raceTimeout, clientIdentifier: clientIdentifier).then((result) { completedTests++; if (!result.success) { @@ -502,7 +503,7 @@ class PlexServer { } // Attempt HTTPS upgrade on the Phase 1 winner before emitting - final upgradedFirstCandidate = await _upgradeCandidateToHttpsIfPossible(firstCandidate); + final upgradedFirstCandidate = await _upgradeCandidateToHttpsIfPossible(firstCandidate, clientIdentifier: clientIdentifier); final emitCandidate = upgradedFirstCandidate ?? firstCandidate; final firstConnection = _updateConnectionUrl(emitCandidate.connection, emitCandidate.url); @@ -524,7 +525,7 @@ class PlexServer { await Future.wait( candidates.map((candidate) async { - final result = await PlexClient.testConnectionWithAverageLatency(candidate.url, accessToken, attempts: 2); + final result = await PlexClient.testConnectionWithAverageLatency(candidate.url, accessToken, attempts: 2, clientIdentifier: clientIdentifier); if (result.success) { candidateResults[candidate] = result; @@ -548,7 +549,7 @@ class PlexServer { // Emit the best connection if it's different from the first one if (bestCandidate != null) { - final upgradedCandidate = await _upgradeCandidateToHttpsIfPossible(bestCandidate) ?? bestCandidate; + final upgradedCandidate = await _upgradeCandidateToHttpsIfPossible(bestCandidate, clientIdentifier: clientIdentifier) ?? bestCandidate; final bestConnection = _updateConnectionUrl(upgradedCandidate.connection, upgradedCandidate.url); if (bestConnection.uri != firstConnection.uri) { @@ -666,7 +667,7 @@ class PlexServer { return urls; } - Future<_ConnectionCandidate?> _upgradeCandidateToHttpsIfPossible(_ConnectionCandidate candidate) async { + Future<_ConnectionCandidate?> _upgradeCandidateToHttpsIfPossible(_ConnectionCandidate candidate, {String? clientIdentifier}) async { final currentUrl = candidate.url; if (currentUrl.startsWith('https://')) { return null; @@ -716,6 +717,7 @@ class PlexServer { httpsUrl, accessToken, timeout: ConnectionTimeouts.connectionRace, + clientIdentifier: clientIdentifier, ); if (!result.success) { diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 7aac83c8..e9ebd435 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -215,6 +215,8 @@ class PlexClient { String baseUrl, String token, { Duration timeout = const Duration(seconds: 5), + String? clientIdentifier, + String appName = 'Plezy', }) async { final stopwatch = Stopwatch()..start(); @@ -230,7 +232,14 @@ class PlexClient { ), ); - final response = await dio.get('/', options: Options(headers: {'X-Plex-Token': token})); + final headers = {'X-Plex-Token': token}; + if (clientIdentifier != null) { + headers['X-Plex-Client-Identifier'] = clientIdentifier; + headers['X-Plex-Product'] = appName; + headers['X-Plex-Device-Name'] = appName; + } + + final response = await dio.get('/', options: Options(headers: headers)); stopwatch.stop(); final success = response.statusCode == 200; @@ -266,11 +275,19 @@ class PlexClient { String token, { int attempts = 3, Duration timeout = const Duration(seconds: 5), + String? clientIdentifier, + String appName = 'Plezy', }) async { final results = []; for (int i = 0; i < attempts; i++) { - final result = await testConnectionWithLatency(baseUrl, token, timeout: timeout); + final result = await testConnectionWithLatency( + baseUrl, + token, + timeout: timeout, + clientIdentifier: clientIdentifier, + appName: appName, + ); // If any attempt fails, return failed result immediately if (!result.success) {