mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
* tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
448 lines
11 KiB
Dart
448 lines
11 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
import 'package:cw_core/utils/proxy_logger/abstract.dart';
|
|
import 'package:cw_core/utils/proxy_socket/abstract.dart';
|
|
import 'package:cw_core/utils/tor/abstract.dart';
|
|
import 'package:cw_core/utils/tor/android.dart';
|
|
import 'package:cw_core/utils/tor/disabled.dart';
|
|
import 'package:http/http.dart';
|
|
import 'package:socks5_proxy/socks_client.dart';
|
|
import 'package:http/io_client.dart' as ioc;
|
|
|
|
class ProxyWrapper {
|
|
static final ProxyWrapper _proxyWrapper = ProxyWrapper._internal();
|
|
static ProxyLogger? logger;
|
|
|
|
factory ProxyWrapper() {
|
|
return _proxyWrapper;
|
|
}
|
|
|
|
ProxyWrapper._internal();
|
|
Future<ProxySocket> getSocksSocket(bool sslEnabled, String host, int port, {Duration? connectionTimeout}) async {
|
|
logger?.log(
|
|
uri: Uri(
|
|
scheme: sslEnabled ? "https" : "http",
|
|
host: host,
|
|
port: port,
|
|
),
|
|
method: RequestMethod.newProxySocket,
|
|
body: Uint8List(0),
|
|
response: null,
|
|
network: requestNetwork(),
|
|
error: null
|
|
);
|
|
return ProxySocket.connect(sslEnabled, ProxyAddress(host: host, port: port), connectionTimeout: connectionTimeout);
|
|
}
|
|
|
|
RequestNetwork requestNetwork() {
|
|
return CakeTor.instance.started ? RequestNetwork.tor : RequestNetwork.clearnet;
|
|
}
|
|
|
|
ioc.IOClient getHttpIOClient({int? portOverride, bool internal = false}) {
|
|
if (!internal) {
|
|
logger?.log(
|
|
uri: null,
|
|
method: RequestMethod.newHttpIOClient,
|
|
body: Uint8List(0),
|
|
response: null,
|
|
network: requestNetwork(),
|
|
error: null,
|
|
);
|
|
}
|
|
// ignore: deprecated_member_use_from_same_package
|
|
final httpClient = ProxyWrapper().getHttpClient(portOverride: portOverride, internal: true);
|
|
return ioc.IOClient(httpClient);
|
|
}
|
|
|
|
int getPort() => CakeTor.instance.port;
|
|
|
|
@Deprecated('Use ProxyWrapper().get/post/put methods instead, and provide proper clearnet and onion uri.')
|
|
HttpClient getHttpClient({int? portOverride, bool internal = false}) {
|
|
if (!internal) {
|
|
logger?.log(
|
|
uri: null,
|
|
method: RequestMethod.newProxySocket,
|
|
body: Uint8List(0),
|
|
response: null,
|
|
network: requestNetwork(),
|
|
error: null
|
|
);
|
|
}
|
|
if (CakeTor.instance.started) {
|
|
// Assign connection factory.
|
|
final client = HttpClient();
|
|
SocksTCPClient.assignToHttpClient(client, [
|
|
ProxySettings(
|
|
InternetAddress.loopbackIPv4,
|
|
CakeTor.instance.port,
|
|
password: null,
|
|
),
|
|
]);
|
|
return client;
|
|
} else {
|
|
return HttpClient();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Future<Response> _make({
|
|
required RequestMethod method,
|
|
required ioc.IOClient client,
|
|
required Uri uri,
|
|
required Map<String, String>? headers,
|
|
String? body,
|
|
}) async {
|
|
Object? error;
|
|
Response? resp;
|
|
try {
|
|
switch (method) {
|
|
case RequestMethod.get:
|
|
resp = await client. get(
|
|
uri,
|
|
headers: headers,
|
|
);
|
|
break;
|
|
case RequestMethod.delete:
|
|
resp = await client.delete(
|
|
uri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
break;
|
|
case RequestMethod.post:
|
|
resp = await client.post(
|
|
uri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
break;
|
|
case RequestMethod.put:
|
|
resp = await client.put(
|
|
uri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
break;
|
|
case RequestMethod.newHttpClient:
|
|
case RequestMethod.newHttpIOClient:
|
|
case RequestMethod.newProxySocket:
|
|
throw UnimplementedError();
|
|
}
|
|
return resp;
|
|
} catch (e) {
|
|
error = e;
|
|
rethrow;
|
|
} finally {
|
|
logger?.log(
|
|
uri: uri,
|
|
method: RequestMethod.get,
|
|
body: utf8.encode(body ?? ''),
|
|
response: resp,
|
|
network: requestNetwork(),
|
|
error: error?.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<Response> get({
|
|
Map<String, String>? headers,
|
|
int? portOverride,
|
|
Uri? clearnetUri,
|
|
Uri? onionUri,
|
|
}) async {
|
|
ioc.IOClient? torClient;
|
|
bool torEnabled = CakeTor.instance.started;
|
|
|
|
if (CakeTor.instance.started) {
|
|
torEnabled = true;
|
|
} else {
|
|
torEnabled = false;
|
|
}
|
|
|
|
// if tor is enabled, try to connect to the onion url first:
|
|
if (torEnabled) {
|
|
try {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
|
|
if (onionUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.get,
|
|
client: torClient,
|
|
uri: onionUri,
|
|
headers: headers,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.get,
|
|
client: torClient,
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return HttpOverrides.runZoned(
|
|
() async {
|
|
return await _make(
|
|
method: RequestMethod.get,
|
|
client: ioc.IOClient(),
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
);
|
|
},
|
|
);
|
|
} catch (_) {
|
|
// we weren't able to get a response:
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception("Unable to connect to server");
|
|
}
|
|
|
|
|
|
Future<Response> post({
|
|
Map<String, String>? headers,
|
|
int? portOverride,
|
|
Uri? clearnetUri,
|
|
Uri? onionUri,
|
|
String? body,
|
|
bool allowMitmMoneroBypassSSLCheck = false,
|
|
}) async {
|
|
HttpClient? torHttpClient;
|
|
HttpClient cleatnetHttpClient = HttpClient();
|
|
if (allowMitmMoneroBypassSSLCheck) {
|
|
cleatnetHttpClient.badCertificateCallback =
|
|
((X509Certificate cert, String host, int port) => true);
|
|
}
|
|
|
|
ioc.IOClient clearnetClient = ioc.IOClient(cleatnetHttpClient);
|
|
|
|
|
|
bool torEnabled = CakeTor.instance.started;
|
|
|
|
if (torEnabled) {
|
|
try {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
torHttpClient = await getHttpClient(portOverride: portOverride);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
if (allowMitmMoneroBypassSSLCheck) {
|
|
torHttpClient.badCertificateCallback =
|
|
((X509Certificate cert, String host, int port) => true);
|
|
}
|
|
if (onionUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.post,
|
|
client: ioc.IOClient(torHttpClient),
|
|
uri: onionUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.post,
|
|
client: ioc.IOClient(torHttpClient),
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return HttpOverrides.runZoned(
|
|
() async {
|
|
return await _make(
|
|
method: RequestMethod.post,
|
|
client: clearnetClient,
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
},
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception("Unable to connect to server");
|
|
}
|
|
|
|
Future<Response> put({
|
|
Map<String, String>? headers,
|
|
int? portOverride,
|
|
Uri? clearnetUri,
|
|
Uri? onionUri,
|
|
String? body,
|
|
}) async {
|
|
ioc.IOClient? torClient;
|
|
bool torEnabled = CakeTor.instance.started;
|
|
|
|
if (torEnabled) {
|
|
try {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
|
|
} catch (_) {}
|
|
|
|
if (onionUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.put,
|
|
client: torClient!,
|
|
uri: onionUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.put,
|
|
client: torClient!,
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return HttpOverrides.runZoned(
|
|
() async {
|
|
return await _make(
|
|
method: RequestMethod.put,
|
|
client: ioc.IOClient(),
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
body: body,
|
|
);
|
|
},
|
|
);
|
|
} catch (_) {
|
|
// we weren't able to get a response:
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception("Unable to connect to server");
|
|
}
|
|
|
|
Future<Response> delete({
|
|
Map<String, String>? headers,
|
|
int? portOverride,
|
|
Uri? clearnetUri,
|
|
Uri? onionUri,
|
|
}) async {
|
|
ioc.IOClient? torClient;
|
|
bool torEnabled = CakeTor.instance.started;
|
|
|
|
if (CakeTor.instance.started) {
|
|
torEnabled = true;
|
|
} else {
|
|
torEnabled = false;
|
|
}
|
|
|
|
// if tor is enabled, try to connect to the onion url first:
|
|
if (torEnabled) {
|
|
try {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
|
|
if (onionUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.delete,
|
|
client: torClient,
|
|
uri: onionUri,
|
|
headers: headers,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return await _make(
|
|
method: RequestMethod.delete,
|
|
client: torClient,
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clearnetUri != null) {
|
|
try {
|
|
return HttpOverrides.runZoned(
|
|
() async {
|
|
return await _make(
|
|
method: RequestMethod.delete,
|
|
client: ioc.IOClient(),
|
|
uri: clearnetUri,
|
|
headers: headers,
|
|
);
|
|
},
|
|
);
|
|
} catch (_) {
|
|
// we weren't able to get a response:
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
throw Exception("Unable to connect to server");
|
|
}
|
|
}
|
|
|
|
|
|
class CakeTor {
|
|
static final CakeTorInstance instance = CakeTorInstance.getInstance();
|
|
}
|