import 'dart:io'; import 'package:cronet_http/cronet_http.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'cookie_jar.dart'; import 'http_util.dart'; /// Shared HTTP stack for every G4F provider. /// /// Python g4f uses `curl_cffi(impersonate="chrome")` plus a session cookie /// jar. On Android the closest TLS equivalent is Cronet; cookies are kept /// in [g4fCookies] because Cronet does not expose a Dart cookie store. http.Client createG4fClient() { if (!kIsWeb && Platform.isAndroid) { final engine = CronetEngine.build( cacheMode: CacheMode.memory, cacheMaxSize: 4 * 1024 * 1024, userAgent: kDesktopUserAgent, enableHttp2: true, enableBrotli: true, enableQuic: true, ); return CronetClient.fromCronetEngine(engine); } return http.Client(); } final G4fCookieJar g4fCookies = G4fCookieJar(); final http.Client g4fHttp = CookieClient(createG4fClient(), g4fCookies); class CookieClient extends http.BaseClient { CookieClient(this._inner, this.jar); final http.Client _inner; final G4fCookieJar jar; @override Future send(http.BaseRequest request) async { final fromJar = jar.headerFor(request.url); if (fromJar.isNotEmpty) { final existing = request.headers['cookie']; if (existing == null || existing.isEmpty) { request.headers['cookie'] = fromJar; } else { request.headers['cookie'] = '$existing; $fromJar'; } } final response = await _inner.send(request); jar.absorb(request.url, response.headers); return response; } @override void close() => _inner.close(); } final Set _warmedOrigins = {}; /// GET the provider origin once so Cloudflare/session cookies land in [g4fCookies]. Future warmupOrigin(String url) async { if (url.isEmpty) return; final uri = Uri.tryParse(url); if (uri == null || !uri.hasScheme || uri.host.isEmpty) return; final origin = uri.origin; if (_warmedOrigins.contains(origin)) return; _warmedOrigins.add(origin); try { await g4fHttp .get( uri, headers: browserHeaders( desktop: true, accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', referer: '$origin/', ), ) .timeout(const Duration(seconds: 12)); } catch (_) {} }