Files
FlClash/lib/common/request.dart
T

204 lines
6.2 KiB
Dart
Raw Normal View History

2025-06-12 10:47:02 +08:00
import 'dart:async';
2024-12-03 21:47:12 +08:00
import 'dart:convert';
import 'dart:io';
2024-04-30 23:38:49 +08:00
2024-06-03 18:02:05 +08:00
import 'package:dio/dio.dart';
2025-03-05 16:08:50 +08:00
import 'package:dio/io.dart';
2024-07-02 08:08:31 +08:00
import 'package:fl_clash/common/common.dart';
2025-12-16 11:23:09 +08:00
import 'package:fl_clash/enum/enum.dart';
2024-09-08 21:21:21 +08:00
import 'package:fl_clash/models/models.dart';
2024-06-03 18:02:05 +08:00
import 'package:fl_clash/state.dart';
2024-08-26 20:44:30 +08:00
import 'package:flutter/cupertino.dart';
2026-02-02 10:14:13 +08:00
import 'package:flutter/foundation.dart';
2024-04-30 23:38:49 +08:00
class Request {
2025-07-31 17:09:18 +08:00
late final Dio dio;
2025-03-05 16:08:50 +08:00
late final Dio _clashDio;
2024-08-26 20:44:30 +08:00
String? userAgent;
2024-06-03 18:02:05 +08:00
Request() {
2025-07-31 17:09:18 +08:00
dio = Dio(BaseOptions(headers: {'User-Agent': browserUa}));
2025-03-05 16:08:50 +08:00
_clashDio = Dio();
2025-07-31 17:09:18 +08:00
_clashDio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
final client = HttpClient();
client.findProxy = (Uri uri) {
2026-02-02 10:14:13 +08:00
client.userAgent = globalState.ua;
2025-07-31 17:09:18 +08:00
return FlClashHttpOverrides.handleFindProxy(uri);
};
return client;
},
);
2024-06-03 18:02:05 +08:00
}
2025-10-14 15:13:52 +08:00
Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
2025-12-16 11:23:09 +08:00
try {
return await _clashDio.get<Uint8List>(
url,
options: Options(responseType: ResponseType.bytes),
);
} catch (e) {
commonPrint.log('getFileResponseForUrl error ${e.toString()}');
if (e is DioException) {
if (e.type == DioExceptionType.unknown) {
2026-02-02 10:14:13 +08:00
throw currentAppLocalizations.unknownNetworkError;
2025-12-16 11:23:09 +08:00
} else if (e.type == DioExceptionType.badResponse) {
2026-02-02 10:14:13 +08:00
throw currentAppLocalizations.networkException;
2025-12-16 11:23:09 +08:00
}
rethrow;
}
2026-02-02 10:14:13 +08:00
throw currentAppLocalizations.unknownNetworkError;
2025-12-16 11:23:09 +08:00
}
2024-06-03 18:02:05 +08:00
}
2025-10-14 15:13:52 +08:00
Future<Response<String>> getTextResponseForUrl(String url) async {
final response = await _clashDio.get<String>(
2025-05-02 02:24:12 +08:00
url,
2025-07-31 17:09:18 +08:00
options: Options(responseType: ResponseType.plain),
2025-05-02 02:24:12 +08:00
);
return response;
}
2024-08-26 20:44:30 +08:00
Future<MemoryImage?> getImage(String url) async {
if (url.isEmpty) return null;
2025-07-31 17:09:18 +08:00
final response = await dio.get<Uint8List>(
2024-08-26 20:44:30 +08:00
url,
2025-07-31 17:09:18 +08:00
options: Options(responseType: ResponseType.bytes),
2024-08-26 20:44:30 +08:00
);
final data = response.data;
if (data == null) return null;
return MemoryImage(data);
}
2024-06-03 18:02:05 +08:00
Future<Map<String, dynamic>?> checkForUpdate() async {
2025-12-16 11:23:09 +08:00
try {
final response = await dio.get(
'https://api.github.com/repos/$repository/releases/latest',
options: Options(responseType: ResponseType.json),
);
if (response.statusCode != 200) return null;
final data = response.data as Map<String, dynamic>;
final remoteVersion = data['tag_name'];
final version = globalState.packageInfo.version;
final hasUpdate =
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return null;
return data;
} catch (e) {
commonPrint.log('checkForUpdate failed', logLevel: LogLevel.warning);
return null;
}
2024-04-30 23:38:49 +08:00
}
2024-06-05 16:19:23 +08:00
2025-02-09 18:39:38 +08:00
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
2025-07-31 17:09:18 +08:00
'https://ipwho.is': IpInfo.fromIpWhoIsJson,
'https://api.myip.com': IpInfo.fromMyIpJson,
'https://ipapi.co/json': IpInfo.fromIpApiCoJson,
'https://ident.me/json': IpInfo.fromIdentMeJson,
'http://ip-api.com/json': IpInfo.fromIpAPIJson,
'https://api.ip.sb/geoip': IpInfo.fromIpSbJson,
'https://ipinfo.io/json': IpInfo.fromIpInfoIoJson,
2025-02-09 18:39:38 +08:00
};
2024-06-05 16:19:23 +08:00
2025-06-12 10:47:02 +08:00
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
var failureCount = 0;
2025-12-16 11:23:09 +08:00
final token = cancelToken ?? CancelToken();
2025-06-12 10:47:02 +08:00
final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer();
2026-02-02 10:14:13 +08:00
void handleFailRes() {
2025-06-07 01:48:34 +08:00
if (!completer.isCompleted && failureCount == _ipInfoSources.length) {
completer.complete(Result.success(null));
}
}
2025-07-31 17:09:18 +08:00
final future = dio
.get<Map<String, dynamic>>(
2026-05-27 15:16:51 +08:00
source.key,
cancelToken: token,
options: Options(responseType: ResponseType.json),
)
2025-07-31 17:09:18 +08:00
.timeout(const Duration(seconds: 10));
future
.then((res) {
2026-05-27 15:16:51 +08:00
if (res.statusCode == HttpStatus.ok && res.data != null) {
completer.complete(Result.success(source.value(res.data!)));
return;
}
commonPrint.log('checkIp data empty', logLevel: LogLevel.info);
failureCount++;
handleFailRes();
})
2025-07-31 17:09:18 +08:00
.catchError((e) {
2026-05-27 15:16:51 +08:00
failureCount++;
if (e is DioException && e.type == DioExceptionType.cancel) {
completer.complete(Result.error('cancelled'));
return;
}
commonPrint.log('checkIp error $e', logLevel: LogLevel.warning);
handleFailRes();
});
2025-06-12 10:47:02 +08:00
return completer.future;
});
final res = await Future.any(futures);
2025-12-16 11:23:09 +08:00
token.cancel();
2025-06-12 10:47:02 +08:00
return res;
2024-06-05 16:19:23 +08:00
}
2024-12-03 21:47:12 +08:00
Future<bool> pingHelper() async {
2026-02-02 10:14:13 +08:00
if (kDebugMode) return true;
2024-12-03 21:47:12 +08:00
try {
2025-07-31 17:09:18 +08:00
final response = await dio
2024-12-03 21:47:12 +08:00
.get(
2026-05-27 15:16:51 +08:00
'http://$localhost:$helperPort/ping',
options: Options(responseType: ResponseType.plain),
)
2025-07-31 17:09:18 +08:00
.timeout(const Duration(milliseconds: 2000));
2024-12-03 21:47:12 +08:00
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == globalState.coreSHA256;
2024-12-03 21:47:12 +08:00
} catch (_) {
return false;
}
}
Future<bool> startCoreByHelper(String arg) async {
try {
2025-07-31 17:09:18 +08:00
final response = await dio
2024-12-03 21:47:12 +08:00
.post(
2026-05-27 15:16:51 +08:00
'http://$localhost:$helperPort/start',
data: json.encode({'path': appPath.corePath, 'arg': arg}),
options: Options(responseType: ResponseType.plain),
)
2025-07-31 17:09:18 +08:00
.timeout(const Duration(milliseconds: 2000));
2024-12-03 21:47:12 +08:00
if (response.statusCode != HttpStatus.ok) {
return false;
}
final data = response.data as String;
return data.isEmpty;
} catch (_) {
return false;
}
}
Future<bool> stopCoreByHelper() async {
try {
2025-07-31 17:09:18 +08:00
final response = await dio
2024-12-03 21:47:12 +08:00
.post(
2026-05-27 15:16:51 +08:00
'http://$localhost:$helperPort/stop',
options: Options(responseType: ResponseType.plain),
)
2025-07-31 17:09:18 +08:00
.timeout(const Duration(milliseconds: 2000));
2024-12-03 21:47:12 +08:00
if (response.statusCode != HttpStatus.ok) {
return false;
}
final data = response.data as String;
return data.isEmpty;
} catch (_) {
return false;
}
}
2024-04-30 23:38:49 +08:00
}
2024-06-03 18:02:05 +08:00
2024-06-07 17:22:55 +08:00
final request = Request();