1
+import 'dart:async';
2
+import 'package:cw_core/node.dart';
3
+import 'package:cw_core/wallet_type.dart';
4
+import 'package:cake_wallet/store/app_store.dart';
5
+import 'package:cake_wallet/store/settings_store.dart';
6
+import 'package:cw_core/utils/print_verbose.dart';
7
+import 'package:hive/hive.dart';
8
+
9
+class NodeSwitchingService {
10
+ NodeSwitchingService({
11
+ required this.appStore,
12
+ required this.settingsStore,
13
+ required this.nodeSource,
14
+ });
15
+
16
+ static const int _healthCheckIntervalSeconds = 30;
17
+
18
+ Timer? _healthCheckTimer;
19
+
20
+ bool _isSwitching = false;
21
+ bool get isSwitching => _isSwitching;
22
+
23
+ final AppStore appStore;
24
+ final SettingsStore settingsStore;
25
+ final Box<Node> nodeSource;
26
+
27
+ // Track used nodes to cycle through all trusted nodes
28
+ final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
29
+
30
+ void startHealthCheckTimer() {
31
+ _healthCheckTimer?.cancel();
32
+ _healthCheckTimer = Timer.periodic(
33
+ Duration(seconds: _healthCheckIntervalSeconds),
34
+ (_) => performHealthCheck(),
35
+ );
36
+
37
+ performHealthCheck();
38
+ }
39
+
40
+ void stopHealthCheckTimer() {
41
+ _healthCheckTimer?.cancel();
42
+ _healthCheckTimer = null;
43
+ }
44
+
45
+ Future<void> performHealthCheck() async {
46
+ if (appStore.wallet == null) return;
47
+
48
+ if (!settingsStore.enableAutomaticNodeSwitching) return;
49
+
50
+ if (_isSwitching) return;
51
+
52
+ try {
53
+ final isHealthy = await appStore.wallet!.checkNodeHealth();
54
+
55
+ if (!isHealthy) {
56
+ await _switchToNextTrustedNode();
57
+ }
58
+ } catch (e) {
59
+ printV('Error during health check: $e');
60
+ }
61
+ }
62
+
63
+ /// Switch to the next available trusted node
64
+ Future<void> _switchToNextTrustedNode() async {
65
+ _isSwitching = true;
66
+
67
+ try {
68
+ final walletType = appStore.wallet!.type;
69
+ final currentNode = settingsStore.getCurrentNode(walletType);
70
+
71
+ // Get all trusted nodes for this wallet type
72
+ final trustedNodes = nodeSource.values
73
+ .where((node) => node.type == walletType && node.isEnabledForAutoSwitching)
74
+ .toList();
75
+
76
+ if (trustedNodes.isEmpty) {
77
+ printV('No trusted nodes available for switching');
78
+ return;
79
+ }
80
+
81
+ // Initialize used nodes list for this wallet type if it does not exist
82
+ _usedNodeKeys.putIfAbsent(walletType, () => []);
83
+
84
+ // Add current node to used list if not already there
85
+ if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) {
86
+ _usedNodeKeys[walletType]!.add(currentNode.key);
87
+ }
88
+
89
+ // Get next unused trusted node from the list
90
+ Node? nextNode;
91
+ for (final node in trustedNodes) {
92
+ if (!_usedNodeKeys[walletType]!.contains(node.key)) {
93
+ nextNode = node;
94
+ break;
95
+ }
96
+ }
97
+
98
+ // If all trusted nodes have been used, reset the list and start over
99
+ if (nextNode == null) {
100
+ printV('All trusted nodes have been tried, resetting and starting over');
101
+ _usedNodeKeys[walletType]!.clear();
102
+ nextNode = trustedNodes.first;
103
+ }
104
+
105
+ // Add the next node to used list
106
+ _usedNodeKeys[walletType]!.add(nextNode.key);
107
+
108
+ printV('Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw}');
109
+ printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}');
110
+
111
+ // Update the current node in settings
112
+ settingsStore.nodes[walletType] = nextNode;
113
+
114
+ // Connect the wallet to the new node
115
+ await appStore.wallet!.connectToNode(node: nextNode);
116
+
117
+ await appStore.wallet!.startSync();
118
+
119
+ printV('Successfully switched to node: ${nextNode.uriRaw}');
120
+ } catch (e) {
121
+ printV('Error switching to next trusted node: $e');
122
+ } finally {
123
+ _isSwitching = false;
124
+ }
125
+ }
126
+}