import 'dart:convert';
import 'dart:html' as html;
import 'package:http/http.dart' as http;
/// Client HTTP authentifié pour les routes `/api/*` protégées.
///
/// POURQUOI CE FICHIER EXISTE
/// Les routes `/api/epg`, `/api/recordings` et `/api/season-passes` sont
/// montées derrière `authMiddleware` côté serveur. Or plusieurs écrans les
/// appelaient avec `package:http` nu, qui n'envoie ni en-tête `Authorization`
/// ni cookie : sur le web, `BrowserClient` a `withCredentials = false`, donc
/// le cookie `session` reste à quai. Toutes ces requêtes repartaient en 401 —
/// guide TV vide, liste d'enregistrements vide, season passes vides.
///
/// Ce wrapper injecte le même jeton que `ApiClient` et `XtreamService`
/// (`localStorage['auth_token']`), de sorte qu'il n'existe qu'une seule
/// source de vérité pour la session.
class AuthedHttp {
AuthedHttp._();
/// Jeton de session posé par `ApiClient.setToken` à la connexion.
static String? get _token {
final t = html.window.localStorage['auth_token'];
return (t != null && t.isNotEmpty) ? t : null;
}
/// IPTV active, posée de façon synchrone par l'écran de sélection et le
/// tableau de bord (voir [usePlaylist]).
///
/// POURQUOI : `localStorage['selected_playlist']` n'est écrit que par un
/// écouteur Riverpod enregistré tardivement ; après un changement d'IPTV
/// il restait périmé, le serveur retombait sur la playlist par défaut et
/// les deux IPTV partageaient la même règle Auto NBA et les mêmes
/// enregistrements.
static String? _currentPlaylistId;
/// Déclare l'IPTV active et la persiste immédiatement.
static void usePlaylist(String id, {Map? json}) {
if (id.isEmpty) return;
_currentPlaylistId = id;
if (json != null) {
try {
html.window.localStorage['selected_playlist'] = jsonEncode(json);
} catch (_) {}
}
}
/// IPTV envoyée au serveur : celle déclarée, sinon la dernière persistée.
static String? get selectedPlaylistId {
if (_currentPlaylistId != null) return _currentPlaylistId;
try {
final stored = html.window.localStorage['selected_playlist'];
if (stored == null) return null;
final id = (jsonDecode(stored) as Map)['id'];
return (id is String && id.isNotEmpty) ? id : null;
} catch (_) {
return null;
}
}
static Map _headers([Map? extra]) {
final token = _token;
final playlistId = selectedPlaylistId;
return {
if (playlistId != null) 'X-Playlist-Id': playlistId,
if (extra != null) ...extra,
if (token != null) 'Authorization': 'Bearer $token',
};
}
static Future get(Uri url, {Map? headers}) =>
http.get(url, headers: _headers(headers));
static Future post(
Uri url, {
Map? headers,
Object? body,
}) =>
http.post(url, headers: _headers(headers), body: body);
static Future put(
Uri url, {
Map? headers,
Object? body,
}) =>
http.put(url, headers: _headers(headers), body: body);
static Future delete(
Uri url, {
Map? headers,
Object? body,
}) =>
http.delete(url, headers: _headers(headers), body: body);
}