Files
xtremflow/lib/core/api/authed_http.dart
T
MichaelandClaude Opus 5.5 4f978755fd Multi-IPTV : chaque fonction utilise la playlist sélectionnée
Le serveur ignorait l'IPTV choisie : direct, VOD, guide, enregistrements,
season passes et Auto NBA passaient tous par la playlist la plus récente
(ou celle du premier utilisateur pour la capture FFmpeg), et les
enregistrements s'affichaient sous toutes les playlists.

- get_playlist respecte ?playlist=<id> (prioritaire) puis l'en-tête
  X-Playlist-Id, avec contrôle du propriétaire (admin : toutes).
- Sessions FFmpeg HLS/VOD séparées par IPTV (même stream_id possible) ;
  le m3u8 propage ?playlist= aux segments, redirections et bascule
  .ts → HLS du lecteur web aussi.
- tv_recordings, season_passes, auto_rules : colonne playlist_id
  (migrations v4-v6) ; lignes existantes rattachées à la playlist la
  plus récente de leur propriétaire ; URL de capture avec ?playlist= ;
  listes filtrées par IPTV ; une règle NBA par IPTV.
- Proxy /api/xtream : cache d'allowlist par playlist, paramètre retiré
  avant l'appel au panneau ; CORS autorise X-Playlist-Id.
- Client : AuthedHttp envoie X-Playlist-Id ; XtreamService ajoute
  ?playlist= aux flux, à l'EPG et aux appels xtream-api (cache distinct).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:53:45 +02:00

77 lines
2.6 KiB
Dart

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 sélectionnée (persistée par `playlistPersistenceProvider`).
///
/// Envoyée à chaque appel : sans elle le serveur prenait la playlist la
/// plus récente, et guide, enregistrements et Auto NBA visaient la
/// mauvaise IPTV quand plusieurs étaient configurées.
static String? get selectedPlaylistId {
try {
final stored = html.window.localStorage['selected_playlist'];
if (stored == null) return null;
final id = (jsonDecode(stored) as Map<String, dynamic>)['id'];
return (id is String && id.isNotEmpty) ? id : null;
} catch (_) {
return null;
}
}
static Map<String, String> _headers([Map<String, String>? 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<http.Response> get(Uri url, {Map<String, String>? headers}) =>
http.get(url, headers: _headers(headers));
static Future<http.Response> post(
Uri url, {
Map<String, String>? headers,
Object? body,
}) =>
http.post(url, headers: _headers(headers), body: body);
static Future<http.Response> put(
Uri url, {
Map<String, String>? headers,
Object? body,
}) =>
http.put(url, headers: _headers(headers), body: body);
static Future<http.Response> delete(
Uri url, {
Map<String, String>? headers,
Object? body,
}) =>
http.delete(url, headers: _headers(headers), body: body);
}