설정 A 분야 2: 네트워크 · DNS (NetworkSettings) - 키 4 · dnsConfig → network.config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
428fe99a82
commit
1db0bda829
|
|
@ -45,13 +45,13 @@ void main() {
|
||||||
final answered = <String>[];
|
final answered = <String>[];
|
||||||
final logs = <String>[];
|
final logs = <String>[];
|
||||||
final g = NetGuard.instance
|
final g = NetGuard.instance
|
||||||
..config = (() => c.settings.dnsConfig)
|
..config = (() => c.settings.network.config)
|
||||||
..log = logs.add;
|
..log = logs.add;
|
||||||
g.resolver.onAnswer = (h, s) => answered.add('$h@$s');
|
g.resolver.onAnswer = (h, s) => answered.add('$h@$s');
|
||||||
// 원인 가르기: JJ_CEF_NO_PROXY=1 이면 프록시 없이 (Chrome 엔진만)
|
// 원인 가르기: JJ_CEF_NO_PROXY=1 이면 프록시 없이 (Chrome 엔진만)
|
||||||
final noProxy = Platform.environment['JJ_CEF_NO_PROXY'] == '1';
|
final noProxy = Platform.environment['JJ_CEF_NO_PROXY'] == '1';
|
||||||
if (noProxy) c.settings.dnsMode = 'system';
|
if (noProxy) c.settings.network.mode = 'system';
|
||||||
expect(c.settings.dnsMode, noProxy ? 'system' : 'custom');
|
expect(c.settings.network.mode, noProxy ? 'system' : 'custom');
|
||||||
await tester.runAsync(g.ensure);
|
await tester.runAsync(g.ensure);
|
||||||
HttpOverrides.global = JjHttpOverrides();
|
HttpOverrides.global = JjHttpOverrides();
|
||||||
Future<void> wait(int ms) async {
|
Future<void> wait(int ms) async {
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ void main() {
|
||||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
c.settings
|
c.settings
|
||||||
..webViewDataDir = p.join(data, 'webview')
|
..webViewDataDir = p.join(data, 'webview')
|
||||||
..dnsMode = 'custom'
|
..network.mode = 'custom'
|
||||||
..dnsPrimary = '192.0.2.1'
|
..network.primary = '192.0.2.1'
|
||||||
..dnsSecondary = '';
|
..network.secondary = '';
|
||||||
final logs = <String>[];
|
final logs = <String>[];
|
||||||
final g = NetGuard.instance
|
final g = NetGuard.instance
|
||||||
..config = (() => c.settings.dnsConfig)
|
..config = (() => c.settings.network.config)
|
||||||
..log = logs.add;
|
..log = logs.add;
|
||||||
await tester.runAsync(g.ensure);
|
await tester.runAsync(g.ensure);
|
||||||
Future<void> wait(int ms) async {
|
Future<void> wait(int ms) async {
|
||||||
|
|
@ -69,6 +69,6 @@ void main() {
|
||||||
|
|
||||||
await tester.pumpWidget(const SizedBox());
|
await tester.pumpWidget(const SizedBox());
|
||||||
await wait(1000);
|
await wait(1000);
|
||||||
g.config = () => c.settings.dnsConfig;
|
g.config = () => c.settings.network.config;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,15 +66,15 @@ void main() {
|
||||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
c.settings
|
c.settings
|
||||||
..downloadRoot = dir.path
|
..downloadRoot = dir.path
|
||||||
..dnsMode = forced ? 'custom' : 'system'
|
..network.mode = forced ? 'custom' : 'system'
|
||||||
..dnsPrimary = '8.8.8.8'
|
..network.primary = '8.8.8.8'
|
||||||
..dnsSecondary = '8.8.4.4';
|
..network.secondary = '8.8.4.4';
|
||||||
final g = NetGuard.instance
|
final g = NetGuard.instance
|
||||||
..config = (() => c.settings.dnsConfig)
|
..config = (() => c.settings.network.config)
|
||||||
..log = log;
|
..log = log;
|
||||||
await tester.runAsync(g.ensure);
|
await tester.runAsync(g.ensure);
|
||||||
addTearDown(() async {
|
addTearDown(() async {
|
||||||
c.settings.dnsMode = 'system';
|
c.settings.network.mode = 'system';
|
||||||
await g.ensure();
|
await g.ensure();
|
||||||
});
|
});
|
||||||
final d = DownloadManager(backends: [Aria2Backend(aria2c: aria2c)], settings: () => c.settings);
|
final d = DownloadManager(backends: [Aria2Backend(aria2c: aria2c)], settings: () => c.settings);
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ void main() {
|
||||||
final logs = <String>[];
|
final logs = <String>[];
|
||||||
final answered = <String>[];
|
final answered = <String>[];
|
||||||
final g = NetGuard.instance
|
final g = NetGuard.instance
|
||||||
..config = (() => c.settings.dnsConfig)
|
..config = (() => c.settings.network.config)
|
||||||
..log = logs.add;
|
..log = logs.add;
|
||||||
g.resolver.onAnswer = (h, s) => answered.add('$h@$s');
|
g.resolver.onAnswer = (h, s) => answered.add('$h@$s');
|
||||||
TorService.instance.log = logs.add;
|
TorService.instance.log = logs.add;
|
||||||
expect(c.settings.dnsMode, 'custom');
|
expect(c.settings.network.mode, 'custom');
|
||||||
expect(c.settings.dnsPrimary, '8.8.8.8');
|
expect(c.settings.network.primary, '8.8.8.8');
|
||||||
await tester.runAsync(g.ensure);
|
await tester.runAsync(g.ensure);
|
||||||
HttpOverrides.global = JjHttpOverrides();
|
HttpOverrides.global = JjHttpOverrides();
|
||||||
Future<void> wait(int ms) async {
|
Future<void> wait(int ms) async {
|
||||||
|
|
@ -97,8 +97,8 @@ void main() {
|
||||||
|
|
||||||
// 3. 닿지 않는 DNS (TEST-NET) → 기기 DNS 로 바꾸지 않고 실패 + 알림
|
// 3. 닿지 않는 DNS (TEST-NET) → 기기 DNS 로 바꾸지 않고 실패 + 알림
|
||||||
c.settings
|
c.settings
|
||||||
..dnsPrimary = '192.0.2.1'
|
..network.primary = '192.0.2.1'
|
||||||
..dnsSecondary = '';
|
..network.secondary = '';
|
||||||
g.resolver.clearCache();
|
g.resolver.clearCache();
|
||||||
final err = await tester.runAsync(() async {
|
final err = await tester.runAsync(() async {
|
||||||
final cl = HttpClient();
|
final cl = HttpClient();
|
||||||
|
|
@ -117,8 +117,8 @@ void main() {
|
||||||
expect(err, contains('502'));
|
expect(err, contains('502'));
|
||||||
expect(g.failure.value, isNotNull);
|
expect(g.failure.value, isNotNull);
|
||||||
c.settings
|
c.settings
|
||||||
..dnsPrimary = '8.8.8.8'
|
..network.primary = '8.8.8.8'
|
||||||
..dnsSecondary = '8.8.4.4';
|
..network.secondary = '8.8.4.4';
|
||||||
g.resolver.clearCache();
|
g.resolver.clearCache();
|
||||||
|
|
||||||
// 4. Tor 컴포넌트 받기 (공식 번들 · SHA-256) → Tor 화면 → check.torproject.org
|
// 4. Tor 컴포넌트 받기 (공식 번들 · SHA-256) → Tor 화면 → check.torproject.org
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import 'components.dart';
|
import 'components.dart';
|
||||||
import '../core/reader_sources.dart' show defaultImageExtensions;
|
import '../core/reader_sources.dart' show defaultImageExtensions;
|
||||||
import '../core/dns_resolver.dart';
|
|
||||||
import '../core/download_detect.dart';
|
import '../core/download_detect.dart';
|
||||||
import '../core/encode_options.dart';
|
import '../core/encode_options.dart';
|
||||||
import '../core/playlist.dart';
|
import '../core/playlist.dart';
|
||||||
|
|
@ -17,11 +16,13 @@ import '../services/app_shell.dart' show appIconOf;
|
||||||
import '../core/remote_idle.dart';
|
import '../core/remote_idle.dart';
|
||||||
import 'settings/records.dart';
|
import 'settings/records.dart';
|
||||||
import 'settings/settings_group.dart';
|
import 'settings/settings_group.dart';
|
||||||
|
import 'settings/network_settings.dart';
|
||||||
import 'settings/explorer_settings.dart';
|
import 'settings/explorer_settings.dart';
|
||||||
|
|
||||||
export 'settings/records.dart';
|
export 'settings/records.dart';
|
||||||
export 'settings/settings_group.dart';
|
export 'settings/settings_group.dart';
|
||||||
export 'settings/settings_store.dart';
|
export 'settings/settings_store.dart';
|
||||||
|
export 'settings/network_settings.dart';
|
||||||
export 'settings/explorer_settings.dart';
|
export 'settings/explorer_settings.dart';
|
||||||
|
|
||||||
/// 환경 설정 (settings.json 에 저장)
|
/// 환경 설정 (settings.json 에 저장)
|
||||||
|
|
@ -157,13 +158,7 @@ class AppSettings {
|
||||||
bool webJavaScript = true;
|
bool webJavaScript = true;
|
||||||
|
|
||||||
// ── 네트워크 · DNS (사용자 요청 10/10: "DNS 주소를 강제로 내가 설정한 주소로", 기본 8.8.8.8 · 보조 8.8.4.4 - 관리자 판단) ──
|
// ── 네트워크 · DNS (사용자 요청 10/10: "DNS 주소를 강제로 내가 설정한 주소로", 기본 8.8.8.8 · 보조 8.8.4.4 - 관리자 판단) ──
|
||||||
/// 'system' 기기 DNS (끄기) · 'custom' 직접 지정 (기본) · 'doh' DNS over HTTPS
|
|
||||||
String dnsMode = DnsConfig.defaultMode;
|
|
||||||
String dnsPrimary = DnsConfig.defaultPrimary;
|
|
||||||
String dnsSecondary = DnsConfig.defaultSecondary;
|
|
||||||
String dnsDohUrl = DnsConfig.defaultDohUrl;
|
|
||||||
|
|
||||||
DnsConfig get dnsConfig => DnsConfig(mode: dnsMode, primary: dnsPrimary, secondary: dnsSecondary, dohUrl: dnsDohUrl);
|
|
||||||
|
|
||||||
// ── Tor 브라우저 (사용자 요청 10/10) ──
|
// ── Tor 브라우저 (사용자 요청 10/10) ──
|
||||||
/// Tor 화면의 쿠키 · 기록 · 캐시를 닫은 뒤에도 남기기 (기본: 닫으면 지움)
|
/// Tor 화면의 쿠키 · 기록 · 캐시를 닫은 뒤에도 남기기 (기본: 닫으면 지움)
|
||||||
|
|
@ -507,8 +502,11 @@ class AppSettings {
|
||||||
/// 파일 탐색기 (X-plore 참고)
|
/// 파일 탐색기 (X-plore 참고)
|
||||||
final explorer = ExplorerSettings();
|
final explorer = ExplorerSettings();
|
||||||
|
|
||||||
|
/// 네트워크 · DNS (사용자 요청 10/10)
|
||||||
|
final network = NetworkSettings();
|
||||||
|
|
||||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||||
List<SettingsGroup> get groups => [explorer];
|
List<SettingsGroup> get groups => [explorer, network];
|
||||||
|
|
||||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||||
|
|
@ -570,10 +568,6 @@ class AppSettings {
|
||||||
'externalBrowser': externalBrowser,
|
'externalBrowser': externalBrowser,
|
||||||
'webTranslate': webTranslate,
|
'webTranslate': webTranslate,
|
||||||
'webJavaScript': webJavaScript,
|
'webJavaScript': webJavaScript,
|
||||||
'dnsMode': dnsMode,
|
|
||||||
'dnsPrimary': dnsPrimary,
|
|
||||||
'dnsSecondary': dnsSecondary,
|
|
||||||
'dnsDohUrl': dnsDohUrl,
|
|
||||||
'torKeepData': torKeepData,
|
'torKeepData': torKeepData,
|
||||||
'torIdleMinutes': torIdleMinutes,
|
'torIdleMinutes': torIdleMinutes,
|
||||||
'torHomeUrl': torHomeUrl,
|
'torHomeUrl': torHomeUrl,
|
||||||
|
|
@ -770,19 +764,6 @@ class AppSettings {
|
||||||
..externalBrowser = j['externalBrowser'] as String? ?? 'system'
|
..externalBrowser = j['externalBrowser'] as String? ?? 'system'
|
||||||
..webTranslate = j['webTranslate'] as bool? ?? false
|
..webTranslate = j['webTranslate'] as bool? ?? false
|
||||||
..webJavaScript = j['webJavaScript'] as bool? ?? true
|
..webJavaScript = j['webJavaScript'] as bool? ?? true
|
||||||
// 값이 없거나 틀리면 처음 값 (사용자가 정한 기본: 직접 지정 8.8.8.8 · 8.8.4.4). 보조는 비워 둘 수 있다
|
|
||||||
..dnsMode = DnsConfig.modes.contains(j['dnsMode']) ? j['dnsMode'] as String : DnsConfig.defaultMode
|
|
||||||
..dnsPrimary = j['dnsPrimary'] is String && parseDnsServer(j['dnsPrimary'] as String) != null
|
|
||||||
? (j['dnsPrimary'] as String).trim()
|
|
||||||
: DnsConfig.defaultPrimary
|
|
||||||
..dnsSecondary = j['dnsSecondary'] is String
|
|
||||||
? (parseDnsServer(j['dnsSecondary'] as String) != null || (j['dnsSecondary'] as String).trim().isEmpty
|
|
||||||
? (j['dnsSecondary'] as String).trim()
|
|
||||||
: DnsConfig.defaultSecondary)
|
|
||||||
: DnsConfig.defaultSecondary
|
|
||||||
..dnsDohUrl = j['dnsDohUrl'] is String && isValidDohUrl(j['dnsDohUrl'] as String)
|
|
||||||
? (j['dnsDohUrl'] as String).trim()
|
|
||||||
: DnsConfig.defaultDohUrl
|
|
||||||
..torKeepData = j['torKeepData'] == true
|
..torKeepData = j['torKeepData'] == true
|
||||||
..torIdleMinutes = j['torIdleMinutes'] is int && (j['torIdleMinutes'] as int) >= 0 && (j['torIdleMinutes'] as int) <= 60
|
..torIdleMinutes = j['torIdleMinutes'] is int && (j['torIdleMinutes'] as int) >= 0 && (j['torIdleMinutes'] as int) <= 60
|
||||||
? j['torIdleMinutes'] as int
|
? j['torIdleMinutes'] as int
|
||||||
|
|
@ -1027,7 +1008,7 @@ class AppSettings {
|
||||||
if (j.isNotEmpty && !j.containsKey('swipeNav') && s.swipeNav) 'swipeNav',
|
if (j.isNotEmpty && !j.containsKey('swipeNav') && s.swipeNav) 'swipeNav',
|
||||||
if (j.isNotEmpty && !j.containsKey('runInBackground') && s.runInBackground && Platform.isAndroid) 'background',
|
if (j.isNotEmpty && !j.containsKey('runInBackground') && s.runInBackground && Platform.isAndroid) 'background',
|
||||||
// DNS 강제 (사용자 요청 10/10, 처음 값 직접 지정 8.8.8.8 · 8.8.4.4) 가 없던 판에서 올라오면 이름을 묻는 서버가 바뀐다 - 193 과 같이 한 번 알림
|
// DNS 강제 (사용자 요청 10/10, 처음 값 직접 지정 8.8.8.8 · 8.8.4.4) 가 없던 판에서 올라오면 이름을 묻는 서버가 바뀐다 - 193 과 같이 한 번 알림
|
||||||
if (j.isNotEmpty && !j.containsKey('dnsMode') && s.dnsMode == 'custom') 'dnsForced',
|
if (j.isNotEmpty && !j.containsKey('dnsMode') && s.network.mode == 'custom') 'dnsForced',
|
||||||
// 193 · 229: 예전 판을 쓰던 사람 (설정 파일이 있는데 이 항목이 없음) 에게 복사 · 이동이 받는 쪽의 더 새 파일을 건너뛰게 바뀐 것을 한 번
|
// 193 · 229: 예전 판을 쓰던 사람 (설정 파일이 있는데 이 항목이 없음) 에게 복사 · 이동이 받는 쪽의 더 새 파일을 건너뛰게 바뀐 것을 한 번
|
||||||
if (j.isNotEmpty && !j.containsKey('copySkipNewer')) 'copySkipNewer',
|
if (j.isNotEmpty && !j.containsKey('copySkipNewer')) 'copySkipNewer',
|
||||||
// 193: robocopy 를 쓰던 사람 (Windows) 에게 링크 · junction 을 따라가지 않게 (/XJ) 바뀐 것을 한 번
|
// 193: robocopy 를 쓰던 사람 (Windows) 에게 링크 · junction 을 따라가지 않게 (/XJ) 바뀐 것을 한 번
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
import '../../core/dns_resolver.dart';
|
||||||
|
import 'settings_group.dart';
|
||||||
|
|
||||||
|
/// 약속 (설정 A): 환경 설정 > 네트워크 · DNS (사용자 요청 10/10) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||||
|
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||||
|
class NetworkSettings implements SettingsGroup {
|
||||||
|
/// 'system' 기기 DNS (끄기) · 'custom' 직접 지정 (기본) · 'doh' DNS over HTTPS
|
||||||
|
String mode = DnsConfig.defaultMode;
|
||||||
|
|
||||||
|
String primary = DnsConfig.defaultPrimary;
|
||||||
|
|
||||||
|
String secondary = DnsConfig.defaultSecondary;
|
||||||
|
|
||||||
|
String dohUrl = DnsConfig.defaultDohUrl;
|
||||||
|
|
||||||
|
DnsConfig get config => DnsConfig(mode: mode, primary: primary, secondary: secondary, dohUrl: dohUrl);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||||
|
j['dnsMode'] = mode;
|
||||||
|
j['dnsPrimary'] = primary;
|
||||||
|
j['dnsSecondary'] = secondary;
|
||||||
|
j['dnsDohUrl'] = dohUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void read(Map<String, dynamic> j) {
|
||||||
|
// 값이 없거나 틀리면 처음 값 (사용자가 정한 기본: 직접 지정 8.8.8.8 · 8.8.4.4). 보조는 비워 둘 수 있다
|
||||||
|
mode = DnsConfig.modes.contains(j['dnsMode']) ? j['dnsMode'] as String : DnsConfig.defaultMode;
|
||||||
|
primary = j['dnsPrimary'] is String && parseDnsServer(j['dnsPrimary'] as String) != null
|
||||||
|
? (j['dnsPrimary'] as String).trim()
|
||||||
|
: DnsConfig.defaultPrimary;
|
||||||
|
secondary = j['dnsSecondary'] is String
|
||||||
|
? (parseDnsServer(j['dnsSecondary'] as String) != null || (j['dnsSecondary'] as String).trim().isEmpty
|
||||||
|
? (j['dnsSecondary'] as String).trim()
|
||||||
|
: DnsConfig.defaultSecondary)
|
||||||
|
: DnsConfig.defaultSecondary;
|
||||||
|
dohUrl = j['dnsDohUrl'] is String && isValidDohUrl(j['dnsDohUrl'] as String)
|
||||||
|
? (j['dnsDohUrl'] as String).trim()
|
||||||
|
: DnsConfig.defaultDohUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -312,7 +312,7 @@ Future<void> main(List<String> args) async {
|
||||||
downloads.refresh();
|
downloads.refresh();
|
||||||
};
|
};
|
||||||
downloads.useSystemDns = () async {
|
downloads.useSystemDns = () async {
|
||||||
await controller.updateSettings((x) => x.dnsMode = 'system');
|
await controller.updateSettings((x) => x.network.mode = 'system');
|
||||||
downloads.refresh();
|
downloads.refresh();
|
||||||
};
|
};
|
||||||
// COM 준비 상태 지킴이: 풀려 있으면 (앱 안 브라우저가 "CoInitialize 가 호출되지 않았습니다" 로 안 뜨는 원인)
|
// COM 준비 상태 지킴이: 풀려 있으면 (앱 안 브라우저가 "CoInitialize 가 호출되지 않았습니다" 로 안 뜨는 원인)
|
||||||
|
|
@ -468,7 +468,7 @@ Future<void> openFromOtherApp(
|
||||||
/// [second] (재생만 하는 새 창) 은 설정을 읽기만 한다
|
/// [second] (재생만 하는 새 창) 은 설정을 읽기만 한다
|
||||||
Future<void> setupNetwork(AppController c, String dataDir, {bool second = false}) async {
|
Future<void> setupNetwork(AppController c, String dataDir, {bool second = false}) async {
|
||||||
final g = NetGuard.instance
|
final g = NetGuard.instance
|
||||||
..config = (() => c.settings.dnsConfig)
|
..config = (() => c.settings.network.config)
|
||||||
..log = c.note;
|
..log = c.note;
|
||||||
try {
|
try {
|
||||||
await g.ensure();
|
await g.ensure();
|
||||||
|
|
@ -498,17 +498,17 @@ Future<void> setupNetwork(AppController c, String dataDir, {bool second = false}
|
||||||
onHide: () => unawaited(WebProxy.setForeground(false)),
|
onHide: () => unawaited(WebProxy.setForeground(false)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var last = c.settings.dnsConfig.key;
|
var last = c.settings.network.config.key;
|
||||||
var lastPause = c.settings.webPauseOnLeave;
|
var lastPause = c.settings.webPauseOnLeave;
|
||||||
c.addListener(() async {
|
c.addListener(() async {
|
||||||
if (Platform.isAndroid && c.settings.webPauseOnLeave != lastPause) {
|
if (Platform.isAndroid && c.settings.webPauseOnLeave != lastPause) {
|
||||||
lastPause = c.settings.webPauseOnLeave;
|
lastPause = c.settings.webPauseOnLeave;
|
||||||
await WebProxy.apply();
|
await WebProxy.apply();
|
||||||
}
|
}
|
||||||
final now = c.settings.dnsConfig.key;
|
final now = c.settings.network.config.key;
|
||||||
if (now == last) return;
|
if (now == last) return;
|
||||||
last = now;
|
last = now;
|
||||||
c.note(trf('DNS 설정: {0}', [c.settings.dnsMode == 'system' ? tr('시스템 DNS') : c.settings.dnsConfig.label]));
|
c.note(trf('DNS 설정: {0}', [c.settings.network.mode == 'system' ? tr('시스템 DNS') : c.settings.network.config.label]));
|
||||||
await g.ensure();
|
await g.ensure();
|
||||||
if (Platform.isAndroid) await WebProxy.apply();
|
if (Platform.isAndroid) await WebProxy.apply();
|
||||||
});
|
});
|
||||||
|
|
@ -528,7 +528,7 @@ void watchDnsFailure(GlobalKey<ScaffoldMessengerState> messengerKey, AppControll
|
||||||
// 사용자에게는 무엇이 안 되고 무엇을 할지만 (예외 원문은 작업 기록에 - NetGuard.reportError).
|
// 사용자에게는 무엇이 안 되고 무엇을 할지만 (예외 원문은 작업 기록에 - NetGuard.reportError).
|
||||||
// 상태가 풀리거나 같은 안내를 보이는 화면이 열리면 스스로 닫힌다 (248)
|
// 상태가 풀리거나 같은 안내를 보이는 화면이 열리면 스스로 닫힌다 (248)
|
||||||
content: DnsFailureNotice(failure: f),
|
content: DnsFailureNotice(failure: f),
|
||||||
action: (tr('시스템 DNS 로 바꾸기'), () => c.updateSettings((x) => x.dnsMode = 'system')),
|
action: (tr('시스템 DNS 로 바꾸기'), () => c.updateSettings((x) => x.network.mode = 'system')),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -632,7 +632,7 @@ Future<void> runAndroid(String dataDir) async {
|
||||||
downloads.refresh();
|
downloads.refresh();
|
||||||
};
|
};
|
||||||
downloads.useSystemDns = () async {
|
downloads.useSystemDns = () async {
|
||||||
await controller.updateSettings((x) => x.dnsMode = 'system');
|
await controller.updateSettings((x) => x.network.mode = 'system');
|
||||||
downloads.refresh();
|
downloads.refresh();
|
||||||
};
|
};
|
||||||
downloads.addToEditList = controller.addDownloaded;
|
downloads.addToEditList = controller.addDownloaded;
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
Wrap(spacing: 8, runSpacing: 8, alignment: WrapAlignment.center, children: [
|
Wrap(spacing: 8, runSpacing: 8, alignment: WrapAlignment.center, children: [
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await widget.c.updateSettings((x) => x.dnsMode = 'system');
|
await widget.c.updateSettings((x) => x.network.mode = 'system');
|
||||||
// 새 DNS 설정이 프록시에 걸린 뒤 다시 읽는다
|
// 새 DNS 설정이 프록시에 걸린 뒤 다시 읽는다
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
if (mounted) retry();
|
if (mounted) retry();
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,7 @@ class _DownloadRow extends StatelessWidget {
|
||||||
child: Text(tr('자세히')),
|
child: Text(tr('자세히')),
|
||||||
),
|
),
|
||||||
// 263: 아직 DNS 강제면 [시스템 DNS 로 바꾸기] (바꾼 뒤 [다시 시도] 는 사용자가 - 몰래 다시 받지 않음)
|
// 263: 아직 DNS 강제면 [시스템 DNS 로 바꾸기] (바꾼 뒤 [다시 시도] 는 사용자가 - 몰래 다시 받지 않음)
|
||||||
if (t.extra['dnsServer'] != null && d.useSystemDns != null && d.settings().dnsMode != 'system')
|
if (t.extra['dnsServer'] != null && d.useSystemDns != null && d.settings().network.mode != 'system')
|
||||||
TextButton(style: _small, onPressed: () => d.useSystemDns!(), child: Text(tr('시스템 DNS 로 바꾸기'))),
|
TextButton(style: _small, onPressed: () => d.useSystemDns!(), child: Text(tr('시스템 DNS 로 바꾸기'))),
|
||||||
TextButton(style: _small, onPressed: () => d.resume([t]), child: Text(tr('다시 시도'))),
|
TextButton(style: _small, onPressed: () => d.resume([t]), child: Text(tr('다시 시도'))),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class DnsFailureLine extends StatelessWidget {
|
||||||
TextButton(
|
TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact, padding: const EdgeInsets.symmetric(horizontal: 8), minimumSize: const Size(0, 36)),
|
visualDensity: VisualDensity.compact, padding: const EdgeInsets.symmetric(horizontal: 8), minimumSize: const Size(0, 36)),
|
||||||
onPressed: () => c.updateSettings((x) => x.dnsMode = 'system'),
|
onPressed: () => c.updateSettings((x) => x.network.mode = 'system'),
|
||||||
child: Text(tr('시스템 DNS 로 바꾸기'), style: const TextStyle(fontSize: 13)),
|
child: Text(tr('시스템 DNS 로 바꾸기'), style: const TextStyle(fontSize: 13)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
@ -59,7 +59,7 @@ class DnsFailureLine extends StatelessWidget {
|
||||||
const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orange),
|
const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orange),
|
||||||
Text(text, style: const TextStyle(fontSize: 12, color: Colors.orange)),
|
Text(text, style: const TextStyle(fontSize: 12, color: Colors.orange)),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => c.updateSettings((x) => x.dnsMode = 'system'),
|
onPressed: () => c.updateSettings((x) => x.network.mode = 'system'),
|
||||||
child: Text(tr('시스템 DNS 로 바꾸기')),
|
child: Text(tr('시스템 DNS 로 바꾸기')),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
@ -149,11 +149,11 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
_testing = true;
|
_testing = true;
|
||||||
_test = null;
|
_test = null;
|
||||||
});
|
});
|
||||||
final r = DnsResolver(config: () => c.settings.dnsConfig);
|
final r = DnsResolver(config: () => c.settings.network.config);
|
||||||
String? server;
|
String? server;
|
||||||
r.onAnswer = (_, s) => server = s;
|
r.onAnswer = (_, s) => server = s;
|
||||||
try {
|
try {
|
||||||
final a = c.settings.dnsMode == 'system'
|
final a = c.settings.network.mode == 'system'
|
||||||
? await InternetAddress.lookup('www.google.com').then((v) {
|
? await InternetAddress.lookup('www.google.com').then((v) {
|
||||||
server = tr('기기 DNS');
|
server = tr('기기 DNS');
|
||||||
return v;
|
return v;
|
||||||
|
|
@ -188,34 +188,34 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
subtitle: Text(tr('주소 이름 (예: youtube.com) 을 찾을 때 물어볼 서버. 직접 지정하면 기기의 DNS 대신 이 서버만 씁니다. '
|
subtitle: Text(tr('주소 이름 (예: youtube.com) 을 찾을 때 물어볼 서버. 직접 지정하면 기기의 DNS 대신 이 서버만 씁니다. '
|
||||||
'서버에 닿지 않으면 기기 DNS 로 몰래 바꾸지 않고 알립니다.')),
|
'서버에 닿지 않으면 기기 DNS 로 몰래 바꾸지 않고 알립니다.')),
|
||||||
trailing: DropdownButton<String>(
|
trailing: DropdownButton<String>(
|
||||||
value: s.dnsMode,
|
value: s.network.mode,
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem(value: 'system', child: Text(tr('시스템 DNS (끄기)'))),
|
DropdownMenuItem(value: 'system', child: Text(tr('시스템 DNS (끄기)'))),
|
||||||
DropdownMenuItem(value: 'custom', child: Text(tr('직접 지정 (기본)'))),
|
DropdownMenuItem(value: 'custom', child: Text(tr('직접 지정 (기본)'))),
|
||||||
DropdownMenuItem(value: 'doh', child: Text(tr('DNS over HTTPS'))),
|
DropdownMenuItem(value: 'doh', child: Text(tr('DNS over HTTPS'))),
|
||||||
],
|
],
|
||||||
onChanged: (v) => _save((x) => x.dnsMode = v!),
|
onChanged: (v) => _save((x) => x.network.mode = v!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (s.dnsMode != 'system')
|
if (s.network.mode != 'system')
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||||
child: Wrap(spacing: 12, runSpacing: 12, children: [
|
child: Wrap(spacing: 12, runSpacing: 12, children: [
|
||||||
if (s.dnsMode == 'doh')
|
if (s.network.mode == 'doh')
|
||||||
_field(tr('DoH 주소'), s.dnsDohUrl, _dohError, hint: DnsConfig.defaultDohUrl, (v) {
|
_field(tr('DoH 주소'), s.network.dohUrl, _dohError, hint: DnsConfig.defaultDohUrl, (v) {
|
||||||
final ok = isValidDohUrl(v);
|
final ok = isValidDohUrl(v);
|
||||||
setState(() => _dohError = ok ? null : tr('https:// 로 시작하는 주소를 넣으세요'));
|
setState(() => _dohError = ok ? null : tr('https:// 로 시작하는 주소를 넣으세요'));
|
||||||
if (ok) _save((x) => x.dnsDohUrl = v.trim());
|
if (ok) _save((x) => x.network.dohUrl = v.trim());
|
||||||
}),
|
}),
|
||||||
_field(s.dnsMode == 'doh' ? tr('DoH 서버 이름을 찾을 서버') : tr('기본 DNS'), s.dnsPrimary, _primaryError, hint: DnsConfig.defaultPrimary, (v) {
|
_field(s.network.mode == 'doh' ? tr('DoH 서버 이름을 찾을 서버') : tr('기본 DNS'), s.network.primary, _primaryError, hint: DnsConfig.defaultPrimary, (v) {
|
||||||
final ok = parseDnsServer(v) != null;
|
final ok = parseDnsServer(v) != null;
|
||||||
setState(() => _primaryError = ok ? null : tr('IPv4 · IPv6 주소를 넣으세요 (예: 8.8.8.8)'));
|
setState(() => _primaryError = ok ? null : tr('IPv4 · IPv6 주소를 넣으세요 (예: 8.8.8.8)'));
|
||||||
if (ok) _save((x) => x.dnsPrimary = v.trim());
|
if (ok) _save((x) => x.network.primary = v.trim());
|
||||||
}),
|
}),
|
||||||
_field(tr('보조 DNS (비워 둘 수 있음)'), s.dnsSecondary, _secondaryError, hint: DnsConfig.defaultSecondary, (v) {
|
_field(tr('보조 DNS (비워 둘 수 있음)'), s.network.secondary, _secondaryError, hint: DnsConfig.defaultSecondary, (v) {
|
||||||
final ok = v.trim().isEmpty || parseDnsServer(v) != null;
|
final ok = v.trim().isEmpty || parseDnsServer(v) != null;
|
||||||
setState(() => _secondaryError = ok ? null : tr('IPv4 · IPv6 주소를 넣으세요 (예: 8.8.4.4)'));
|
setState(() => _secondaryError = ok ? null : tr('IPv4 · IPv6 주소를 넣으세요 (예: 8.8.4.4)'));
|
||||||
if (ok) _save((x) => x.dnsSecondary = v.trim());
|
if (ok) _save((x) => x.network.secondary = v.trim());
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ void main() {
|
||||||
|
|
||||||
testWidgets('멈춘 마그넷 줄: "트래커 · 피어에 닿지 않습니다" · 이유 후보 · [다시 시도] (지금 것을 지우고 새로 넣음)', (tester) async {
|
testWidgets('멈춘 마그넷 줄: "트래커 · 피어에 닿지 않습니다" · 이유 후보 · [다시 시도] (지금 것을 지우고 새로 넣음)', (tester) async {
|
||||||
final b = _Backend();
|
final b = _Backend();
|
||||||
final s = AppSettings()..dnsMode = 'custom';
|
final s = AppSettings()..network.mode = 'custom';
|
||||||
final d = DownloadManager(backends: [b], settings: () => s);
|
final d = DownloadManager(backends: [b], settings: () => s);
|
||||||
final t = d.add('magnet:?xt=urn:btih:7acf8fb590b2060dd9c3146ef770169d593433b0')!;
|
final t = d.add('magnet:?xt=urn:btih:7acf8fb590b2060dd9c3146ef770169d593433b0')!;
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ void main() {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
||||||
await tester.pump(const Duration(milliseconds: 400));
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
expect(c.settings.dnsMode, 'system');
|
expect(c.settings.network.mode, 'system');
|
||||||
expect(find.text(msg), findsNothing);
|
expect(find.text(msg), findsNothing);
|
||||||
expect(nav.reloads, 2);
|
expect(nav.reloads, 2);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ void main() {
|
||||||
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (01:35부터)'), findsOneWidget);
|
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (01:35부터)'), findsOneWidget);
|
||||||
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(c.settings.dnsMode, 'system');
|
expect(c.settings.network.mode, 'system');
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('위쪽 이동 버튼: 설치한 화면만 · 설정을 바꾸면 바로 · 하나뿐이면 그것만', (tester) async {
|
testWidgets('위쪽 이동 버튼: 설치한 화면만 · 설정을 바꾸면 바로 · 하나뿐이면 그것만', (tester) async {
|
||||||
|
|
|
||||||
|
|
@ -464,15 +464,15 @@ void main() {
|
||||||
|
|
||||||
test('설정: 처음은 직접 지정 8.8.8.8 · 8.8.4.4, 틀린 값은 처음 값, 사용자가 고른 값은 그대로', () {
|
test('설정: 처음은 직접 지정 8.8.8.8 · 8.8.4.4, 틀린 값은 처음 값, 사용자가 고른 값은 그대로', () {
|
||||||
final s = AppSettings();
|
final s = AppSettings();
|
||||||
expect((s.dnsMode, s.dnsPrimary, s.dnsSecondary, s.dnsDohUrl), ('custom', '8.8.8.8', '8.8.4.4', 'https://dns.google/dns-query'));
|
expect((s.network.mode, s.network.primary, s.network.secondary, s.network.dohUrl), ('custom', '8.8.8.8', '8.8.4.4', 'https://dns.google/dns-query'));
|
||||||
expect(AppSettings.fromJson({}).dnsMode, 'custom');
|
expect(AppSettings.fromJson({}).network.mode, 'custom');
|
||||||
final bad = AppSettings.fromJson({'dnsMode': 'x', 'dnsPrimary': 'dns.google', 'dnsSecondary': '1.2.3', 'dnsDohUrl': 'ftp://x'});
|
final bad = AppSettings.fromJson({'dnsMode': 'x', 'dnsPrimary': 'dns.google', 'dnsSecondary': '1.2.3', 'dnsDohUrl': 'ftp://x'});
|
||||||
expect((bad.dnsMode, bad.dnsPrimary, bad.dnsSecondary, bad.dnsDohUrl), ('custom', '8.8.8.8', '8.8.4.4', 'https://dns.google/dns-query'));
|
expect((bad.network.mode, bad.network.primary, bad.network.secondary, bad.network.dohUrl), ('custom', '8.8.8.8', '8.8.4.4', 'https://dns.google/dns-query'));
|
||||||
final mine = AppSettings.fromJson(
|
final mine = AppSettings.fromJson(
|
||||||
AppSettings().toJson()..addAll({'dnsMode': 'system', 'dnsPrimary': '1.1.1.1', 'dnsSecondary': '', 'dnsDohUrl': 'https://cloudflare-dns.com/dns-query'}));
|
AppSettings().toJson()..addAll({'dnsMode': 'system', 'dnsPrimary': '1.1.1.1', 'dnsSecondary': '', 'dnsDohUrl': 'https://cloudflare-dns.com/dns-query'}));
|
||||||
expect((mine.dnsMode, mine.dnsPrimary, mine.dnsSecondary, mine.dnsDohUrl), ('system', '1.1.1.1', '', 'https://cloudflare-dns.com/dns-query'));
|
expect((mine.network.mode, mine.network.primary, mine.network.secondary, mine.network.dohUrl), ('system', '1.1.1.1', '', 'https://cloudflare-dns.com/dns-query'));
|
||||||
final back = AppSettings.fromJson(mine.toJson());
|
final back = AppSettings.fromJson(mine.toJson());
|
||||||
expect(back.dnsConfig.key, mine.dnsConfig.key);
|
expect(back.network.config.key, mine.network.config.key);
|
||||||
// Tor 설정 처음 값
|
// Tor 설정 처음 값
|
||||||
expect((s.torKeepData, s.torHomeUrl, s.torJavaScript, s.torNoticeSeen), (false, 'https://check.torproject.org/', true, false));
|
expect((s.torKeepData, s.torHomeUrl, s.torJavaScript, s.torNoticeSeen), (false, 'https://check.torproject.org/', true, false));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ void main() {
|
||||||
final b = _Backend(tunnel);
|
final b = _Backend(tunnel);
|
||||||
final d = DownloadManager(backends: [b], settings: () => s, readClipboard: () async => null);
|
final d = DownloadManager(backends: [b], settings: () => s, readClipboard: () async => null);
|
||||||
d.useSystemDns = () async {
|
d.useSystemDns = () async {
|
||||||
s.dnsMode = 'system';
|
s.network.mode = 'system';
|
||||||
d.refresh();
|
d.refresh();
|
||||||
};
|
};
|
||||||
final t = d.addPage('https://cdn.example.com/page')!..title = '영상';
|
final t = d.addPage('https://cdn.example.com/page')!..title = '영상';
|
||||||
|
|
@ -149,7 +149,7 @@ void main() {
|
||||||
|
|
||||||
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(s.dnsMode, 'system');
|
expect(s.network.mode, 'system');
|
||||||
expect(find.text('시스템 DNS 로 바꾸기'), findsNothing);
|
expect(find.text('시스템 DNS 로 바꾸기'), findsNothing);
|
||||||
expect(b.starts, 1, reason: '몰래 다시 받지 않는다');
|
expect(b.starts, 1, reason: '몰래 다시 받지 않는다');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -428,10 +428,10 @@ void main() {
|
||||||
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime(2026, 10, 12, 9, 5));
|
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime(2026, 10, 12, 9, 5));
|
||||||
await t.pump();
|
await t.pump();
|
||||||
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (09:05부터)'), findsOneWidget);
|
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (09:05부터)'), findsOneWidget);
|
||||||
expect(c.settings.dnsMode, 'custom');
|
expect(c.settings.network.mode, 'custom');
|
||||||
await t.tap(find.text('시스템 DNS 로 바꾸기'));
|
await t.tap(find.text('시스템 DNS 로 바꾸기'));
|
||||||
await t.pump();
|
await t.pump();
|
||||||
expect(c.settings.dnsMode, 'system');
|
expect(c.settings.network.mode, 'system');
|
||||||
g.failure.value = null;
|
g.failure.value = null;
|
||||||
await t.pump();
|
await t.pump();
|
||||||
expect(find.textContaining('에 닿지 않음'), findsNothing);
|
expect(find.textContaining('에 닿지 않음'), findsNothing);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue