mirror of
https://github.com/localsend/localsend.git
synced 2026-06-23 04:10:07 +00:00
feat: migrate away from dio
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:common/api_route_builder.dart';
|
||||
@@ -7,7 +6,6 @@ import 'package:common/constants.dart';
|
||||
import 'package:common/isolate.dart';
|
||||
import 'package:common/model/dto/file_dto.dart';
|
||||
import 'package:common/model/dto/multicast_dto.dart';
|
||||
import 'package:common/util/dio.dart';
|
||||
import 'package:common/util/logger.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -61,6 +59,8 @@ Future<RefenaContainer> preInit(List<String> args) async {
|
||||
initLogger(args.contains('-v') || args.contains('--verbose') ? Level.ALL : Level.INFO);
|
||||
MapperContainer.globals.use(const FileDtoMapper());
|
||||
|
||||
await Rhttp.init();
|
||||
|
||||
final dynamicColors = await getDynamicColors();
|
||||
|
||||
final persistenceService = await PersistenceService.initialize(
|
||||
@@ -78,20 +78,20 @@ Future<RefenaContainer> preInit(List<String> args) async {
|
||||
// Check if this app is already open and let it "show up".
|
||||
// If this is the case, then exit the current instance.
|
||||
|
||||
final dio = createDio(const Duration(milliseconds: 100), persistenceService.getSecurityContext());
|
||||
final client = createRhttpClient(const Duration(milliseconds: 100), persistenceService.getSecurityContext());
|
||||
|
||||
try {
|
||||
await dio.post(
|
||||
await client.post(
|
||||
ApiRoute.show.targetRaw(
|
||||
'127.0.0.1',
|
||||
persistenceService.getPort(),
|
||||
persistenceService.isHttps(),
|
||||
peerProtocolVersion,
|
||||
),
|
||||
queryParameters: {
|
||||
query: {
|
||||
'token': persistenceService.getShowToken(),
|
||||
},
|
||||
data: jsonEncode({
|
||||
body: HttpBody.json({
|
||||
'args': args,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'package:common/util/dio.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:localsend_app/provider/logging/http_logs_provider.dart';
|
||||
import 'package:localsend_app/provider/security_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
class DioCollection {
|
||||
final Dio discovery;
|
||||
final Dio longLiving;
|
||||
|
||||
DioCollection({
|
||||
required this.discovery,
|
||||
required this.longLiving,
|
||||
});
|
||||
}
|
||||
|
||||
/// Provides a dio having a specific timeout.
|
||||
/// Changes must be made in common/lib/src/isolate/child/dio_provider.dart also
|
||||
final dioProvider = ViewProvider((ref) {
|
||||
final securityContext = ref.watch(securityProvider);
|
||||
final discoveryTimeout = ref.watch(settingsProvider.select((state) => state.discoveryTimeout));
|
||||
return DioCollection(
|
||||
discovery: createDio(Duration(milliseconds: discoveryTimeout), securityContext),
|
||||
longLiving: createDio(
|
||||
const Duration(days: 30),
|
||||
securityContext,
|
||||
interceptor: LogInterceptor(
|
||||
requestHeader: false,
|
||||
requestBody: true,
|
||||
request: false,
|
||||
responseHeader: false,
|
||||
responseBody: true,
|
||||
error: true,
|
||||
logPrint: (log) => ref.notifier(httpLogsProvider).addLog(log.toString()),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:localsend_app/provider/logging/http_logs_provider.dart';
|
||||
import 'package:localsend_app/provider/security_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/util/rhttp.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:rhttp/rhttp.dart';
|
||||
|
||||
class HttpClientCollection {
|
||||
final RhttpClient discovery;
|
||||
final RhttpClient longLiving;
|
||||
|
||||
HttpClientCollection({
|
||||
required this.discovery,
|
||||
required this.longLiving,
|
||||
});
|
||||
}
|
||||
|
||||
/// Provides a dio having a specific timeout.
|
||||
/// Changes must be made in common/lib/src/isolate/child/dio_provider.dart also
|
||||
final httpProvider = ViewProvider((ref) {
|
||||
final securityContext = ref.watch(securityProvider);
|
||||
final discoveryTimeout = ref.watch(settingsProvider.select((state) => state.discoveryTimeout));
|
||||
return HttpClientCollection(
|
||||
discovery: createRhttpClient(Duration(milliseconds: discoveryTimeout), securityContext),
|
||||
longLiving: createRhttpClient(
|
||||
const Duration(days: 30),
|
||||
securityContext,
|
||||
interceptor: SimpleInterceptor(
|
||||
beforeRequest: (request) async {
|
||||
ref.notifier(httpLogsProvider).addLog('HTTP Request: ${request.method} ${request.url}');
|
||||
return Interceptor.next();
|
||||
},
|
||||
afterResponse: (response) async {
|
||||
final body = switch (response) {
|
||||
HttpTextResponse() => response.body,
|
||||
HttpBytesResponse() => '<${response.body.length} bytes>',
|
||||
HttpStreamResponse() => '<stream>',
|
||||
};
|
||||
|
||||
ref
|
||||
.notifier(httpLogsProvider)
|
||||
.addLog('HTTP Response: ${response.request.method} ${response.request.url} returned ${response.statusCode} with body: $body');
|
||||
return Interceptor.next();
|
||||
},
|
||||
onError: (exception) async {
|
||||
ref.notifier(httpLogsProvider).addLog(exception.toString());
|
||||
return Interceptor.next();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -14,7 +14,6 @@ import 'package:common/model/file_status.dart';
|
||||
import 'package:common/model/file_type.dart';
|
||||
import 'package:common/model/session_status.dart';
|
||||
import 'package:common/util/sleep.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/model/cross_file.dart';
|
||||
import 'package:localsend_app/model/send_mode.dart';
|
||||
@@ -24,13 +23,14 @@ import 'package:localsend_app/pages/home_page.dart';
|
||||
import 'package:localsend_app/pages/progress_page.dart';
|
||||
import 'package:localsend_app/pages/send_page.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/provider/dio_provider.dart';
|
||||
import 'package:localsend_app/provider/http_provider.dart';
|
||||
import 'package:localsend_app/provider/progress_provider.dart';
|
||||
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/widget/dialogs/pin_dialog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:rhttp/rhttp.dart';
|
||||
import 'package:routerino/routerino.dart';
|
||||
import 'package:uri_content/uri_content.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@@ -62,7 +62,7 @@ class SendNotifier extends Notifier<Map<String, SendSessionState>> {
|
||||
required List<CrossFile> files,
|
||||
required bool background,
|
||||
}) async {
|
||||
final requestDio = ref.read(dioProvider).longLiving;
|
||||
final client = ref.read(httpProvider).longLiving;
|
||||
final cancelToken = CancelToken();
|
||||
final sessionId = _uuid.v4();
|
||||
|
||||
@@ -140,22 +140,23 @@ class SendNotifier extends Notifier<Map<String, SendSessionState>> {
|
||||
);
|
||||
}
|
||||
|
||||
Response? response;
|
||||
HttpTextResponse? response;
|
||||
bool invalidPin;
|
||||
bool pinFirstAttempt = true;
|
||||
String? pin;
|
||||
do {
|
||||
invalidPin = false;
|
||||
try {
|
||||
response = await requestDio.post(
|
||||
ApiRoute.prepareUpload.target(target, query: {
|
||||
response = await client.post(
|
||||
ApiRoute.prepareUpload.target(target),
|
||||
query: {
|
||||
if (pin != null) 'pin': pin,
|
||||
}),
|
||||
data: jsonEncode(requestDto), // jsonEncode for better logging
|
||||
},
|
||||
body: HttpBody.json(requestDto.toJson()),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
switch (e.response?.statusCode) {
|
||||
} on RhttpStatusCodeException catch (e) {
|
||||
switch (e.statusCode) {
|
||||
case 401:
|
||||
invalidPin = true;
|
||||
|
||||
@@ -234,7 +235,7 @@ class SendNotifier extends Notifier<Map<String, SendSessionState>> {
|
||||
|
||||
final Map<String, String> fileMap;
|
||||
if (target.version == '1.0') {
|
||||
fileMap = (response.data as Map).cast<String, String>();
|
||||
fileMap = (response.bodyToJson as Map).cast<String, String>();
|
||||
} else {
|
||||
if (response.statusCode == 204) {
|
||||
// Nothing selected
|
||||
@@ -242,7 +243,7 @@ class SendNotifier extends Notifier<Map<String, SendSessionState>> {
|
||||
fileMap = {};
|
||||
} else {
|
||||
try {
|
||||
final responseDto = PrepareUploadResponseDto.fromJson(response.data);
|
||||
final responseDto = PrepareUploadResponseDto.fromJson(response.bodyToJson);
|
||||
fileMap = responseDto.files;
|
||||
state = state.updateSession(
|
||||
sessionId: sessionId,
|
||||
@@ -508,7 +509,7 @@ class SendNotifier extends Notifier<Map<String, SendSessionState>> {
|
||||
// notify the receiver
|
||||
try {
|
||||
ref
|
||||
.read(dioProvider)
|
||||
.read(httpProvider)
|
||||
.discovery
|
||||
// ignore: discarded_futures
|
||||
.post(ApiRoute.cancel.target(sessionState.target, query: remoteSessionId != null ? {'sessionId': remoteSessionId} : null));
|
||||
@@ -607,17 +608,27 @@ extension on SendSessionState {
|
||||
extension on Object {
|
||||
String get humanErrorMessage {
|
||||
final e = this;
|
||||
if (e is DioException && e.response != null) {
|
||||
final body = e.response!.data;
|
||||
String message;
|
||||
try {
|
||||
message = (body as Map)['message'];
|
||||
} catch (_) {
|
||||
message = body;
|
||||
}
|
||||
return '[${e.response!.statusCode}] $message';
|
||||
final (statusCode, message) = switch (this) {
|
||||
RhttpStatusCodeException(:final statusCode, :final body) => (statusCode, _parseErrorMessage(body)),
|
||||
_ => (null, e.toString()),
|
||||
};
|
||||
|
||||
if (statusCode != null && message != null) {
|
||||
return '[$statusCode] $message';
|
||||
}
|
||||
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
String? _parseErrorMessage(Object? body) {
|
||||
if (body is! String) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return (jsonDecode(body) as Map)['message'];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import 'package:localsend_app/pages/progress_page.dart';
|
||||
import 'package:localsend_app/pages/receive_page.dart';
|
||||
import 'package:localsend_app/pages/receive_page_controller.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/provider/dio_provider.dart';
|
||||
import 'package:localsend_app/provider/favorites_provider.dart';
|
||||
import 'package:localsend_app/provider/http_provider.dart';
|
||||
import 'package:localsend_app/provider/logging/discovery_logs_provider.dart';
|
||||
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
|
||||
import 'package:localsend_app/provider/network/send_provider.dart';
|
||||
@@ -742,7 +742,7 @@ class ReceiveController {
|
||||
// notify sender
|
||||
try {
|
||||
// ignore: unawaited_futures
|
||||
server.ref.read(dioProvider).discovery.post(ApiRoute.cancel.target(session.sender, query: {'sessionId': session.sessionId}));
|
||||
server.ref.read(httpProvider).discovery.post(ApiRoute.cancel.target(session.sender, query: {'sessionId': session.sessionId}));
|
||||
} catch (e) {
|
||||
_logger.warning('Failed to notify sender', e);
|
||||
}
|
||||
|
||||
+19
-14
@@ -8,20 +8,7 @@ class RhttpWrapper implements CustomHttpClient {
|
||||
RhttpWrapper._(this._client);
|
||||
|
||||
factory RhttpWrapper.create(Duration timeout, StoredSecurityContext securityContext) {
|
||||
final client = RhttpClient.createSync(
|
||||
settings: ClientSettings(
|
||||
timeoutSettings: TimeoutSettings(
|
||||
timeout: timeout,
|
||||
),
|
||||
tlsSettings: TlsSettings(
|
||||
verifyCertificates: false,
|
||||
clientCertificate: ClientCertificate(
|
||||
certificate: securityContext.certificate,
|
||||
privateKey: securityContext.privateKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final client = createRhttpClient(timeout, securityContext);
|
||||
return RhttpWrapper._(client);
|
||||
}
|
||||
|
||||
@@ -79,3 +66,21 @@ class RhttpWrapper implements CustomHttpClient {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RhttpClient createRhttpClient(Duration timeout, StoredSecurityContext securityContext, {Interceptor? interceptor}) {
|
||||
return RhttpClient.createSync(
|
||||
settings: ClientSettings(
|
||||
timeoutSettings: TimeoutSettings(
|
||||
timeout: timeout,
|
||||
),
|
||||
tlsSettings: TlsSettings(
|
||||
verifyCertificates: false,
|
||||
clientCertificate: ClientCertificate(
|
||||
certificate: securityContext.certificate,
|
||||
privateKey: securityContext.privateKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
interceptors: interceptor != null ? [interceptor] : [],
|
||||
);
|
||||
}
|
||||
|
||||
+2
-18
@@ -349,22 +349,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1319,10 +1303,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rhttp
|
||||
sha256: "8dc4e1b50cbbd4422a3da93accfd40ea96227fb7d2264d6856754122aedfb194"
|
||||
sha256: "92fb57dea6338370efe1e4e2101e8b521f91f15bc60ef6908469b4392dd9803a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.0"
|
||||
version: "0.9.1"
|
||||
routerino:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
+1
-2
@@ -19,7 +19,6 @@ dependencies:
|
||||
desktop_drop: 0.5.0
|
||||
device_apps: 2.2.0
|
||||
device_info_plus: 10.1.2
|
||||
dio: 5.7.0
|
||||
dynamic_color: 1.7.0
|
||||
file_picker: 8.1.2
|
||||
file_selector: 1.0.3
|
||||
@@ -51,7 +50,7 @@ dependencies:
|
||||
pretty_qr_code: 3.3.0
|
||||
refena_flutter: 2.1.1
|
||||
refena_inspector_client: 2.0.0
|
||||
rhttp: 0.9.0
|
||||
rhttp: 0.9.1
|
||||
routerino: 0.8.0
|
||||
saf_stream: 0.7.5
|
||||
screen_retriever: 0.1.9
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:common/model/device.dart';
|
||||
import 'package:common/model/dto/info_dto.dart';
|
||||
import 'package:common/src/isolate/child/http_provider.dart';
|
||||
import 'package:common/src/isolate/child/sync_provider.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena/refena.dart';
|
||||
|
||||
@@ -37,9 +36,6 @@ class HttpTargetDiscoveryService {
|
||||
});
|
||||
final dto = InfoDtoMapper.deserialize(response);
|
||||
return dto.toDevice(ip, port, https);
|
||||
} on DioException catch (e) {
|
||||
onError?.call(url, e);
|
||||
return null;
|
||||
} catch (e) {
|
||||
onError?.call(url, e);
|
||||
return null;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:common/model/stored_security_context.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
|
||||
/// It always trust the self signed certificate as all requests happen in a local network.
|
||||
/// The user only needs to trust the local IP address.
|
||||
/// Thanks to TCP (HTTP uses TCP), IP spoofing is nearly impossible.
|
||||
Dio createDio(Duration timeout, StoredSecurityContext securityContext, {Interceptor? interceptor}) {
|
||||
final dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: timeout,
|
||||
sendTimeout: timeout,
|
||||
),
|
||||
);
|
||||
|
||||
// Allow any self signed certificate
|
||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient(
|
||||
context: SecurityContext()
|
||||
..usePrivateKeyBytes(securityContext.privateKey.codeUnits)
|
||||
..useCertificateChainBytes(securityContext.certificate.codeUnits),
|
||||
);
|
||||
client.badCertificateCallback = (cert, host, port) => true;
|
||||
return client;
|
||||
},
|
||||
);
|
||||
|
||||
// Add logging
|
||||
if (interceptor != null) {
|
||||
dio.interceptors.add(interceptor);
|
||||
}
|
||||
|
||||
return dio;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ environment:
|
||||
dependencies:
|
||||
collection: ^1.17.2 # allow newer versions, so it can compile with newer Flutter versions
|
||||
dart_mappable: ^4.2.0
|
||||
dio: 5.7.0
|
||||
logging: ^1.2.0
|
||||
meta: ^1.9.1
|
||||
mime: ^1.0.4
|
||||
|
||||
Reference in New Issue
Block a user