fbf04d90db
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.
84 lines
2.4 KiB
Dart
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 (_) {}
|
|
}
|