Matthias Nott
2026-04-01 058511cb668a1373059a6d6829cb1cbf3b9ef577
lib/services/mqtt_service.dart
....@@ -114,40 +114,51 @@
114114
115115 final clientId = await _getClientId();
116116
117
- // Probe all hosts in parallel to find which one responds, then connect to the winner
118
- final hosts = <String>{};
117
+ // Phase 1: Race configured hosts (fast — just TLS probe, ~1s each)
118
+ final hosts = <String>[];
119119 if (config.localHost != null && config.localHost!.isNotEmpty) hosts.add(config.localHost!);
120
- if (_lastDiscoveredHost != null) hosts.add(_lastDiscoveredHost!);
120
+ if (_lastDiscoveredHost != null && !hosts.contains(_lastDiscoveredHost)) hosts.add(_lastDiscoveredHost!);
121121 if (config.vpnHost != null && config.vpnHost!.isNotEmpty) hosts.add(config.vpnHost!);
122122 if (config.host.isNotEmpty) hosts.add(config.host);
123
- _mqttLog('MQTT: probing ${hosts.length} hosts in parallel: ${hosts.join(", ")}');
124
- onStatusDetail?.call('Probing ${hosts.length} hosts...');
123
+ _mqttLog('MQTT: racing ${hosts.length} configured hosts: ${hosts.join(", ")}');
124
+ onStatusDetail?.call('Connecting...');
125125
126
- // Probe all configured hosts in parallel — first to respond wins
126
+ // Race: first probe to succeed wins, don't wait for others
127127 String? winner;
128128 if (hosts.isNotEmpty) {
129
- final probes = hosts.map((h) => _probeHost(h, config.port)).toList();
130
- // Also start discovery in parallel
131
- if (_lastDiscoveredHost == null) {
132
- probes.add(() async {
133
- final discovered = await _discoverViaMdns();
134
- if (discovered != null) {
135
- _lastDiscoveredHost = discovered;
136
- return discovered;
129
+ final completer = Completer<String?>();
130
+ int pending = hosts.length;
131
+ for (final host in hosts) {
132
+ _probeHost(host, config.port).then((result) {
133
+ if (result != null && !completer.isCompleted) {
134
+ completer.complete(result);
135
+ } else {
136
+ pending--;
137
+ if (pending <= 0 && !completer.isCompleted) {
138
+ completer.complete(null); // All failed
139
+ }
137140 }
138
- return null;
139
- }());
141
+ });
140142 }
141
- final results = await Future.wait(probes);
142
- winner = results.firstWhere((r) => r != null, orElse: () => null);
143
- } else if (_lastDiscoveredHost == null) {
144
- // No configured hosts — try discovery only
145
- winner = await _discoverViaMdns();
146
- if (winner != null) _lastDiscoveredHost = winner;
143
+ winner = await completer.future.timeout(
144
+ const Duration(seconds: 3),
145
+ onTimeout: () => null,
146
+ );
147
+ }
148
+
149
+ // Phase 2: If configured hosts failed, try Bonjour/subnet discovery
150
+ if (winner == null && !_intentionalClose) {
151
+ _mqttLog('MQTT: configured hosts failed, trying discovery...');
152
+ onStatusDetail?.call('Scanning network...');
153
+ final discovered = await _discoverViaMdns();
154
+ if (discovered != null) {
155
+ _lastDiscoveredHost = discovered;
156
+ winner = discovered;
157
+ }
147158 }
148159
149160 if (winner != null && !_intentionalClose) {
150
- _mqttLog('MQTT: probe winner: $winner, connecting...');
161
+ _mqttLog('MQTT: winner: $winner, connecting...');
151162 onStatusDetail?.call('Connecting to $winner...');
152163 try {
153164 if (await _tryConnect(winner, clientId, timeout: 5000)) return;
....@@ -314,7 +325,7 @@
314325 final socket = await SecureSocket.connect(
315326 host,
316327 port,
317
- timeout: const Duration(seconds: 1),
328
+ timeout: const Duration(milliseconds: 500),
318329 onBadCertificate: (_) => true, // Accept self-signed cert during scan
319330 );
320331 await socket.close();