From 96c8bb5db1a2e0ced999a366e3cf28f9895ec39f Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Tue, 24 Mar 2026 21:51:35 +0100
Subject: [PATCH] feat: Bonjour auto-discovery + VPN IP field in connection flow
---
lib/services/mqtt_service.dart | 95 ++++++++++++++++++++++++++++++++++++-----------
1 files changed, 73 insertions(+), 22 deletions(-)
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index 87eafc9..200ba3c 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -2,6 +2,7 @@
import 'dart:convert';
import 'dart:io';
+import 'package:bonsoir/bonsoir.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart' as pp;
import 'package:mqtt_client/mqtt_client.dart';
@@ -102,29 +103,50 @@
}
final clientId = await _getClientId();
- final hosts = _getHosts();
- _mqttLog('MQTT: hosts=${hosts.join(", ")} port=${config.port}');
- for (final host in hosts) {
+ // Connection order: local → Bonjour → VPN → remote
+ final attempts = <MapEntry<String, int>>[]; // host → timeout ms
+ if (config.localHost != null && config.localHost!.isNotEmpty) {
+ attempts.add(MapEntry(config.localHost!, 2500));
+ }
+ // Bonjour placeholder — inserted dynamically below
+ if (config.vpnHost != null && config.vpnHost!.isNotEmpty) {
+ attempts.add(MapEntry(config.vpnHost!, 3000));
+ }
+ if (config.host.isNotEmpty) {
+ attempts.add(MapEntry(config.host, 5000));
+ }
+ _mqttLog('MQTT: attempts=${attempts.map((e) => e.key).join(", ")} port=${config.port}');
+
+ // Try configured local host first
+ for (final attempt in attempts) {
if (_intentionalClose) return;
-
- _mqttLog('MQTT: trying $host:${config.port}');
+ _mqttLog('MQTT: trying ${attempt.key}:${config.port}');
try {
- final connected = await _tryConnect(
- host,
- clientId,
- timeout: host == hosts.first && hosts.length > 1 ? 2500 : 5000,
- );
- _mqttLog('MQTT: $host result=$connected');
- if (connected) return;
+ if (await _tryConnect(attempt.key, clientId, timeout: attempt.value)) return;
} catch (e) {
- _mqttLog('MQTT: $host error=$e');
- continue;
+ _mqttLog('MQTT: ${attempt.key} error=$e');
+ }
+
+ // After local host fails, try Bonjour discovery before VPN/remote
+ if (attempt.key == config.localHost && !_intentionalClose) {
+ _mqttLog('MQTT: trying Bonjour discovery...');
+ final bonjourHost = await _discoverViaMdns();
+ if (bonjourHost != null && !_intentionalClose) {
+ _mqttLog('MQTT: Bonjour found $bonjourHost');
+ try {
+ if (await _tryConnect(bonjourHost, clientId, timeout: 3000)) return;
+ } catch (e) {
+ _mqttLog('MQTT: Bonjour host $bonjourHost error=$e');
+ }
+ } else {
+ _mqttLog('MQTT: Bonjour discovery returned nothing');
+ }
}
}
// All hosts failed — retry after delay
- _mqttLog('MQTT: all hosts failed, retrying in 5s');
+ _mqttLog('MQTT: all attempts failed, retrying in 5s');
_setStatus(ConnectionStatus.reconnecting);
Future.delayed(const Duration(seconds: 5), () {
if (!_intentionalClose && _status != ConnectionStatus.connected) {
@@ -133,14 +155,43 @@
});
}
- /// Returns [localHost, remoteHost] for dual-connect attempts.
- List<String> _getHosts() {
- if (config.localHost != null &&
- config.localHost!.isNotEmpty &&
- config.localHost != config.host) {
- return [config.localHost!, config.host];
+ /// Discover AIBroker on local network via Bonjour/mDNS.
+ /// Returns the IP address or null if not found within timeout.
+ Future<String?> _discoverViaMdns({Duration timeout = const Duration(seconds: 3)}) async {
+ try {
+ final discovery = BonsoirDiscovery(type: '_mqtt._tcp');
+ await discovery.initialize();
+
+ final completer = Completer<String?>();
+ StreamSubscription? sub;
+
+ sub = discovery.eventStream?.listen((event) {
+ switch (event) {
+ case BonsoirDiscoveryServiceResolvedEvent():
+ final ip = event.service.host;
+ _mqttLog('MQTT: Bonjour resolved: ${event.service.name} at $ip:${event.service.port}');
+ if (ip != null && ip.isNotEmpty && !completer.isCompleted) {
+ completer.complete(ip);
+ }
+ case BonsoirDiscoveryServiceFoundEvent():
+ _mqttLog('MQTT: Bonjour found: ${event.service.name}');
+ default:
+ break;
+ }
+ });
+
+ await discovery.start();
+
+ final ip = await completer.future.timeout(timeout, onTimeout: () => null);
+
+ await sub?.cancel();
+ await discovery.stop();
+
+ return ip;
+ } catch (e) {
+ _mqttLog('MQTT: Bonjour discovery error: $e');
+ return null;
}
- return [config.host];
}
Future<bool> _tryConnect(String host, String clientId, {int timeout = 5000}) async {
--
Gitblit v1.3.1