90 lines
3.4 KiB
Dart
90 lines
3.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
|
|
import '../l10n/tr.dart';
|
|
import 'net_guard.dart';
|
|
import 'tor_service.dart';
|
|
|
|
/// Android 웹 뷰 프록시 (androidx.webkit ProxyController - 앱의 모든 웹 뷰에 한꺼번에 걸린다).
|
|
/// - Tor 화면이 열려 있으면: 모든 웹 뷰가 Tor 다리로 (관리자 결정 10/10 - 일반 브라우저에는 안내 띠)
|
|
/// - 아니면: DNS 강제면 DNS 프록시로, 아니면 프록시 없음
|
|
/// 바꿀 때는 끝날 때까지 기다린다 (그 전에 페이지를 읽지 않게). Windows 는 웹 뷰마다 따로라 여기서 하지 않는다.
|
|
class WebProxy {
|
|
WebProxy._();
|
|
|
|
static const _ch = MethodChannel('jj_mkvmaker/android');
|
|
|
|
/// Tor 화면이 열려 있는지 (일반 브라우저의 안내 띠도 이것을 본다)
|
|
static final torOpen = ValueNotifier<bool>(false);
|
|
|
|
/// 지금 걸린 프록시 ('' = 없음)
|
|
static String _applied = '';
|
|
static String get applied => _applied;
|
|
static Future<void> _chain = Future.value();
|
|
|
|
/// 이 기기 웹 뷰가 Tor 화면에 필요한 기능을 갖췄는지: 프록시 걸기 · 따로 프로필 · 페이지보다 먼저 스크립트 (WebRTC 끄기).
|
|
/// 없는 것의 이름 목록 (모두 있으면 빈 목록)
|
|
static Future<List<String>> torMissingFeatures() async {
|
|
if (!Platform.isAndroid) return const [];
|
|
try {
|
|
final m = await _ch.invokeMapMethod<String, bool>('torWebSupport') ?? const {};
|
|
return [
|
|
if (m['proxyOverride'] != true) 'PROXY_OVERRIDE',
|
|
if (m['multiProfile'] != true) 'MULTI_PROFILE',
|
|
if (m['documentStartScript'] != true) 'DOCUMENT_START_SCRIPT',
|
|
];
|
|
} catch (e) {
|
|
return ['$e'];
|
|
}
|
|
}
|
|
|
|
/// 지금 상태에 맞게 다시 건다 (차례로 - 겹치지 않게). 끝나면 걸린 프록시
|
|
static Future<String> apply() {
|
|
if (!Platform.isAndroid) return Future.value('');
|
|
final f = _chain.then((_) => _apply());
|
|
_chain = f.then((_) {}, onError: (_) {});
|
|
return f;
|
|
}
|
|
|
|
static Future<String> _apply() async {
|
|
String want = '';
|
|
if (torOpen.value) {
|
|
want = 'http://127.0.0.1:${await TorService.instance.bridgePort()}';
|
|
} else if (NetGuard.instance.forcing) {
|
|
want = NetGuard.instance.browserProxy ?? '';
|
|
}
|
|
if (want == _applied) return _applied;
|
|
final pc = ProxyController.instance();
|
|
if (want.isEmpty) {
|
|
await pc.clearProxyOverride();
|
|
} else {
|
|
await pc.setProxyOverride(
|
|
settings: ProxySettings(
|
|
proxyRules: [ProxyRule(url: want)],
|
|
// Tor 화면: localhost 까지 Tor 로 (이 기기 · 집 안을 들여다보지 못하게). DNS 강제: 기본 (이 기기 주소는 직접)
|
|
removeImplicitRules: torOpen.value,
|
|
));
|
|
}
|
|
_applied = want;
|
|
debugPrint(trf('웹 뷰 프록시: {0}', [want.isEmpty ? tr('없음') : want]));
|
|
return want;
|
|
}
|
|
|
|
/// Tor 화면 전용 프로필 지우기 (쿠키 · 저장소 · 캐시). 웹 뷰가 없어진 뒤에 부른다
|
|
static Future<bool> clearTorProfile() async {
|
|
if (!Platform.isAndroid) return true;
|
|
try {
|
|
return await _ch.invokeMethod<bool>('torProfileClear', {'name': torProfileName}) ?? false;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Tor 화면 웹 뷰의 프로필 이름 (androidx.webkit Profile)
|
|
static const torProfileName = 'jj_tor';
|
|
}
|