// lib/remote/auth_client.dart import 'dart:convert'; import 'package:http/http.dart' as http; /// Gestisce autenticazione remota e caching del Bearer token. /// - [baseUrl]: URL base del server (con o senza '/') /// - [email]/[password]: credenziali /// - [loginPath]: path dell'endpoint di login (default 'auth/login') /// - [timeout]: timeout per le richieste (default 20s) class RemoteAuth { final Uri base; final String email; final String password; final String loginPath; final Duration timeout; String? _token; RemoteAuth({ required String baseUrl, required this.email, required this.password, this.loginPath = 'auth/login', this.timeout = const Duration(seconds: 20), }) : base = Uri.parse(baseUrl.endsWith('/') ? baseUrl : '$baseUrl/'); Uri get _loginUri => base.resolve(loginPath); /// Esegue il login e memorizza il token. /// Lancia eccezione con messaggio chiaro in caso di errore HTTP, rete o JSON. Future login() async { final uri = _loginUri; final headers = {'Content-Type': 'application/json'}; final bodyStr = json.encode({'email': email, 'password': password}); http.Response res; try { res = await http .post(uri, headers: headers, body: bodyStr) .timeout(timeout); } catch (e) { throw Exception('Login fallito: errore di rete verso $uri: $e'); } // Follow esplicito per redirect POST moderni (307/308) mantenendo metodo e body if ({307, 308}.contains(res.statusCode) && res.headers['location'] != null) { final redirectUri = uri.resolve(res.headers['location']!); try { res = await http .post(redirectUri, headers: headers, body: bodyStr) .timeout(timeout); } catch (e) { throw Exception('Login fallito: errore di rete verso $redirectUri: $e'); } } if (res.statusCode != 200) { final snippet = utf8.decode(res.bodyBytes.take(200).toList()); throw Exception( 'Login fallito: HTTP ${res.statusCode} ${res.reasonPhrase} – $snippet', ); } // Parsing JSON robusto Map map; try { map = json.decode(utf8.decode(res.bodyBytes)) as Map; } catch (_) { throw Exception('Login fallito: risposta non è un JSON valido'); } // Supporto sia 'token' sia 'access_token' final token = (map['token'] ?? map['access_token']) as String?; if (token == null || token.isEmpty) { throw Exception('Login fallito: token assente nella risposta'); } _token = token; return token; } /// Ritorna gli header con Bearer; se non hai token, esegue login. Future> authHeaders() async { _token ??= await login(); return {'Authorization': 'Bearer $_token'}; } /// Forza il rinnovo del token (es. dopo 401) e ritorna i nuovi header. Future> refreshAndHeaders() async { _token = null; return await authHeaders(); } /// Accesso in sola lettura al token corrente (può essere null). String? get token => _token; }