77 lines
2.5 KiB
Dart
77 lines
2.5 KiB
Dart
// lib/remote/remote_client.dart
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import 'remote_models.dart';
|
|
import 'auth_client.dart';
|
|
|
|
class RemoteJsonClient {
|
|
final Uri indexUri; // es. https://prova.patachina.it/photos/
|
|
final RemoteAuth? auth; // opzionale: se presente, aggiunge Bearer
|
|
|
|
RemoteJsonClient(
|
|
String baseUrl,
|
|
String indexPath, {
|
|
this.auth,
|
|
}) : indexUri = Uri.parse(baseUrl.endsWith('/') ? baseUrl : '$baseUrl/')
|
|
.resolve(indexPath);
|
|
|
|
Future<List<RemotePhotoItem>> fetchAll() async {
|
|
Map<String, String> headers = {};
|
|
if (auth != null) {
|
|
headers = await auth!.authHeaders();
|
|
}
|
|
|
|
// DEBUG: stampa la URL precisa
|
|
// ignore: avoid_print
|
|
print('[remote-client] GET $indexUri');
|
|
|
|
http.Response res;
|
|
try {
|
|
res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
} catch (e) {
|
|
throw Exception('Errore rete su $indexUri: $e');
|
|
}
|
|
|
|
// Retry 1 volta in caso di 401 (token scaduto/invalidato)
|
|
if (res.statusCode == 401 && auth != null) {
|
|
headers = await auth!.refreshAndHeaders();
|
|
res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
}
|
|
|
|
// Follow 301/302 mantenendo Authorization
|
|
if ((res.statusCode == 301 || res.statusCode == 302) && res.headers['location'] != null) {
|
|
final loc = res.headers['location']!;
|
|
final redirectUri = indexUri.resolve(loc);
|
|
res = await http.get(redirectUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
}
|
|
if (res.statusCode != 200) {
|
|
final snippet = utf8.decode(res.bodyBytes.take(200).toList());
|
|
throw Exception('HTTP ${res.statusCode} ${res.reasonPhrase} su $indexUri. Body: $snippet');
|
|
}
|
|
|
|
final body = utf8.decode(res.bodyBytes);
|
|
// DEBUG
|
|
print('DEBUG JSON: $body');
|
|
|
|
// Qui siamo espliciti: ci aspettiamo SEMPRE una lista top-level
|
|
final dynamic decoded = json.decode(body);
|
|
|
|
if (decoded is! List) {
|
|
throw Exception('JSON inatteso: atteso array top-level, ricevuto ${decoded.runtimeType}');
|
|
}
|
|
|
|
final List<dynamic> rawList = decoded;
|
|
|
|
// Costruiamo a mano la List<RemotePhotoItem>, tipizzata esplicitamente
|
|
final List<RemotePhotoItem> items = rawList.map<RemotePhotoItem>((e) {
|
|
if (e is! Map<String, dynamic>) {
|
|
throw Exception('Elemento JSON non è una mappa: ${e.runtimeType} -> $e');
|
|
}
|
|
return RemotePhotoItem.fromJson(e);
|
|
}).toList();
|
|
|
|
return items;
|
|
|
|
|
|
}
|
|
}
|