// nodes_provider.dart — 节点清单 + 当前选择 + 实测延迟 // // 从 GET /v1/nodes 拉取真实节点(含 host/port);拉取后后台对各节点做 TCP 握手 // 实测 per-client 延迟并回填。未登录 / 失败返回空列表(不再伪造演示节点)。 import 'dart:async'; import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import '../models/node.dart'; import '../services/api_config.dart'; import '../services/latency_probe.dart'; import 'auth_provider.dart'; // ── 节点列表 AsyncNotifier ──────────────────────────────────────── class NodesNotifier extends AsyncNotifier> { bool _disposed = false; @override Future> build() async { ref.onDispose(() => _disposed = true); final auth = ref.watch(authProvider); if (!auth.isLoggedIn) return const []; final list = await _fetchNodes(auth.accessToken!); unawaited(_measure(list)); return list; } Future refresh() async { final auth = ref.read(authProvider); if (!auth.isLoggedIn) { state = const AsyncData([]); return; } state = const AsyncLoading(); state = await AsyncValue.guard(() async { final list = await _fetchNodes(auth.accessToken!); unawaited(_measure(list)); return list; }); } /// 后台实测各节点延迟,完成后回填(notifier 未销毁才更新)。 Future _measure(List list) async { if (list.isEmpty) return; final pings = await probeAll( [for (final n in list) (uuid: n.uuid, host: n.host, port: n.port)], ); if (_disposed) return; final updated = [for (final n in list) n.copyWith(ping: pings[n.uuid] ?? n.ping)]; state = AsyncData(updated); } static Future> _fetchNodes(String accessToken) async { final uri = Uri.parse('$kApiBaseUrl/v1/nodes'); final resp = await http.get(uri, headers: { 'Authorization': 'Bearer $accessToken', }).timeout(const Duration(seconds: 10)); if (resp.statusCode != 200) return const []; final body = jsonDecode(resp.body) as Map; final rawList = body['nodes'] as List? ?? []; return rawList.map((e) { final m = e as Map; return Node( uuid: m['id'] as String? ?? '', code: m['region'] as String? ?? '??', nameZh: m['name_zh'] as String? ?? '', nameEn: m['name_en'] as String? ?? '', tier: m['tier'] as String? ?? 'free', host: m['host'] as String? ?? '', port: (m['port'] as num?)?.toInt() ?? 0, ping: 0, // 由 _measure 实测回填 ); }).toList(); } } final nodesProvider = AsyncNotifierProvider>(NodesNotifier.new); // ── 当前选中的节点 code;'AUTO' 表示智能选择(默认)───────────────── final selectedNodeCodeProvider = StateProvider((ref) => kSmartNodeCode); /// 是否处于智能选择。 final isSmartSelectProvider = Provider( (ref) => ref.watch(selectedNodeCodeProvider) == kSmartNodeCode, ); /// 实际生效的节点:列表为空时返回占位;智能选择取实测延迟最小者(未测得排后)。 final effectiveNodeProvider = Provider((ref) { final nodes = ref.watch(nodesProvider).valueOrNull ?? const []; if (nodes.isEmpty) return kPlaceholderNode; final code = ref.watch(selectedNodeCodeProvider); if (code == kSmartNodeCode) { return nodes.reduce((a, b) { final pa = a.ping > 0 ? a.ping : 1 << 30; final pb = b.ping > 0 ? b.ping : 1 << 30; return pa <= pb ? a : b; }); } return nodes.firstWhere((n) => n.code == code, orElse: () => nodes.first); });