fix(flutter): cover URLs send Bearer auth + audio handler diag logging
Two issues from on-device testing against prod HTTPS: 1. ServerImage was resolving cover URLs correctly (https://minstrel.fabledsword.com/api/albums/.../cover) but the server returned 401 because Image.network doesn't carry the session token automatically the way the browser sends cookies. Forwards the stored session token as an Authorization: Bearer header. No-op when no token is present. 2. The "Cleartext HTTP traffic to 127.0.0.1" audio error reproduced even after the previous defensive check landed, which means the URL handed to ExoPlayer has a valid scheme+host (just the wrong host). The check only catches scheme-less URLs, so it didn't fire. Added debugPrint logging at configure() and setQueueFromTracks() time to show the actual baseUrl + per-track resolved URL on the next reproduction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import '../models/track.dart';
|
||||
@@ -15,6 +16,8 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
void configure({required String baseUrl, required String? token}) {
|
||||
_baseUrl = baseUrl;
|
||||
_token = token;
|
||||
debugPrint('audio_handler.configure: baseUrl="$baseUrl" '
|
||||
'tokenPresent=${token != null && token.isNotEmpty}');
|
||||
}
|
||||
|
||||
Future<void> setQueueFromTracks(List<TrackRef> tracks, {int initialIndex = 0}) async {
|
||||
@@ -25,8 +28,12 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
}
|
||||
|
||||
final headers = _token == null ? null : {'Authorization': 'Bearer $_token'};
|
||||
debugPrint('audio_handler.setQueueFromTracks: '
|
||||
'_baseUrl="$_baseUrl" trackCount=${tracks.length}');
|
||||
final sources = tracks.map((t) {
|
||||
final url = _resolveStreamUrl(t);
|
||||
debugPrint('audio_handler: track.id=${t.id} '
|
||||
'track.streamUrl="${t.streamUrl}" → resolved="$url"');
|
||||
// Defensive: a scheme-less URL handed to ExoPlayer crashes with
|
||||
// a confusing "Cleartext HTTP traffic to 127.0.0.1 not permitted"
|
||||
// error because Android's URL parser falls back to localhost.
|
||||
|
||||
@@ -31,7 +31,15 @@ class ServerImage extends ConsumerWidget {
|
||||
final base = ref.watch(serverUrlProvider).value;
|
||||
final resolved = _resolve(base, url);
|
||||
if (resolved == null) return empty;
|
||||
return Image.network(resolved, fit: fit);
|
||||
// Cover endpoints are gated by RequireUser server-side. Image.network
|
||||
// doesn't carry cookies/Bearer tokens automatically the way the
|
||||
// browser's <img> tag does, so we forward the session token as a
|
||||
// header. Without this, the server returns 401 and the image fails.
|
||||
final token = ref.watch(sessionTokenProvider).value;
|
||||
final headers = (token != null && token.isNotEmpty)
|
||||
? {'Authorization': 'Bearer $token'}
|
||||
: null;
|
||||
return Image.network(resolved, fit: fit, headers: headers);
|
||||
}
|
||||
|
||||
static String? _resolve(String? baseUrl, String url) {
|
||||
|
||||
Reference in New Issue
Block a user