diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index dd9ba986..1589fb1f 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -97,6 +97,7 @@ class _AuthScreenState extends State { multiServerProvider: context.read(), librariesProvider: context.read(), syncService: context.read(), + clientIdentifier: _authService.clientIdentifier, ); if (!result.hasConnections) { 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 e6c51f48..2297c449 100644 --- a/lib/services/plex_auth_service.dart +++ b/lib/services/plex_auth_service.dart @@ -391,7 +391,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; @@ -437,6 +437,7 @@ class PlexServer { cachedCandidate.url, accessToken, timeout: preferredTimeout, + clientIdentifier: clientIdentifier, ); if (result.success) { @@ -456,7 +457,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) { @@ -501,7 +502,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); @@ -523,7 +524,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; @@ -547,7 +548,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) { @@ -665,7 +666,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; @@ -715,6 +716,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 6d3d4705..b22b3f32 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -216,6 +216,7 @@ class PlexClient { String baseUrl, String token, { Duration timeout = const Duration(seconds: 5), + String? clientIdentifier, }) async { final stopwatch = Stopwatch()..start(); @@ -231,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'] = 'Plezy'; + headers['X-Plex-Device-Name'] = 'Plezy'; + } + + final response = await dio.get('/', options: Options(headers: headers)); stopwatch.stop(); final success = response.statusCode == 200; @@ -267,11 +275,17 @@ class PlexClient { String token, { int attempts = 3, Duration timeout = const Duration(seconds: 5), + String? clientIdentifier, }) 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, + ); // If any attempt fails, return failed result immediately if (!result.success) {