Files
999/lib/g4f/http_client.dart
Alma Alexyz fbf04d90db feat: embed a Flutter G4F client with Cronet, cookie jar, and origin WebView
Port the Oracle off the Python g4f container onto on-device providers: Chromium HTTP via Cronet, a shared cookie session, IterList retry, hosted OpenAI-compat aggregators, and same-origin WebView fallbacks instead of CORS fetch.
2026-09-01 22:05:38 +05:00

84 lines
2.4 KiB
Dart

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<http.StreamedResponse> 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<String> _warmedOrigins = {};
/// GET the provider origin once so Cloudflare/session cookies land in [g4fCookies].
Future<void> 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 (_) {}
}