feat(core+frontend): integrate mDNS-based network discovery and update UI
- **Network Discovery Service:** - Added platform-specific `DiscoveryModule` with JmDNS-based `JmDnsDiscoveryService` for JVM and no-op implementation for JS. - Implemented service and device discovery using mDNS to enable peer-to-peer synchronization within LAN. - Registered the module in Koin for dependency injection and integrated it with `networkModule`. - **Frontend Integration:** - Enhanced `BewerbViewModel` with intents and actions for starting, stopping, and refreshing network scans. - Introduced polling for discovered services during an active scan. - **UI Additions:** - Added a `NetworkDiscoveryPanel` in `TurnierBewerbeTab` to display discovered services and indicate scan state. - Updated action buttons to include toggle functionality for network scans.
This commit is contained in:
+4
-1
@@ -7,8 +7,10 @@ import io.ktor.client.plugins.logging.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import at.mocode.frontend.core.network.discovery.discoveryModule
|
||||
|
||||
/**
|
||||
* Schnittstelle zur Token-Bereitstellung – entkoppelt core-network von core-auth.
|
||||
@@ -20,7 +22,8 @@ interface TokenProvider { fun getAccessToken(): String? }
|
||||
* - "baseHttpClient": Roh-Client für Auth/Keycloak (kein Token-Header)
|
||||
* - "apiClient": Konfigurierter Client für das API-Gateway (Auth-Header, Retry, Timeout)
|
||||
*/
|
||||
val networkModule = module {
|
||||
val networkModule: Module = module {
|
||||
includes(discoveryModule)
|
||||
|
||||
// 1. Basis-Client (für Auth-Endpunkte, ohne Bearer-Token)
|
||||
single(named("baseHttpClient")) {
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
import org.koin.core.module.Module
|
||||
|
||||
/**
|
||||
* Erwartetes Koin-Modul für die Netzwerk-Discovery.
|
||||
* Plattform-spezifische Implementierungen (JVM mit JmDNS, JS/Wasm evtl. No-op)
|
||||
* müssen hier injiziert werden.
|
||||
*/
|
||||
expect val discoveryModule: Module
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
/**
|
||||
* Modell für einen entdeckten Dienst im lokalen Netzwerk.
|
||||
*/
|
||||
data class DiscoveredService(
|
||||
val name: String,
|
||||
val host: String,
|
||||
val port: Int,
|
||||
val metadata: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
/**
|
||||
* Interface für die mDNS-basierte Entdeckung von Meldestelle-Instanzen.
|
||||
* Erlaubt Offline-First Synchronisation im LAN.
|
||||
*/
|
||||
interface NetworkDiscoveryService {
|
||||
/**
|
||||
* Startet das Scannen nach verfügbaren Diensten im Netzwerk.
|
||||
*/
|
||||
fun startDiscovery()
|
||||
|
||||
/**
|
||||
* Stoppt den Scan-Vorgang.
|
||||
*/
|
||||
fun stopDiscovery()
|
||||
|
||||
/**
|
||||
* Registriert den eigenen Dienst, damit andere Instanzen ihn finden können.
|
||||
* @param port Der Port, auf dem der lokale WebSocket-Server lauscht.
|
||||
*/
|
||||
fun registerService(port: Int)
|
||||
|
||||
/**
|
||||
* Gibt die Liste der aktuell entdeckten Dienste zurück.
|
||||
*/
|
||||
fun getDiscoveredServices(): List<DiscoveredService>
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
* JS-spezifische Implementierung (vorerst No-op, da mDNS im Browser nicht nativ möglich).
|
||||
*/
|
||||
actual val discoveryModule: Module = module {
|
||||
single<NetworkDiscoveryService> { NoOpDiscoveryService() }
|
||||
}
|
||||
|
||||
class NoOpDiscoveryService : NetworkDiscoveryService {
|
||||
override fun startDiscovery() {}
|
||||
override fun stopDiscovery() {}
|
||||
override fun registerService(port: Int) {}
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> = emptyList()
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
* JVM-spezifische Implementierung des DiscoveryModules.
|
||||
*/
|
||||
actual val discoveryModule: Module = module {
|
||||
single<NetworkDiscoveryService> { JmDnsDiscoveryService() }
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package at.mocode.frontend.core.network.discovery
|
||||
|
||||
import javax.jmdns.JmDNS
|
||||
import javax.jmdns.ServiceEvent
|
||||
import javax.jmdns.ServiceInfo
|
||||
import javax.jmdns.ServiceListener
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* JVM-spezifische Implementierung der Netzwerk-Discovery mittels JmDNS.
|
||||
*/
|
||||
class JmDnsDiscoveryService : NetworkDiscoveryService {
|
||||
|
||||
private var jmdns: JmDNS? = null
|
||||
private val SERVICE_TYPE = "_meldestelle-biest._tcp.local."
|
||||
private val discoveredServicesMap = ConcurrentHashMap<String, DiscoveredService>()
|
||||
|
||||
override fun startDiscovery() {
|
||||
if (jmdns == null) {
|
||||
jmdns = JmDNS.create(InetAddress.getLocalHost())
|
||||
}
|
||||
|
||||
jmdns?.addServiceListener(SERVICE_TYPE, object : ServiceListener {
|
||||
override fun serviceAdded(event: ServiceEvent) {
|
||||
// Bei ServiceAdded fordern wir die Details an
|
||||
jmdns?.requestServiceInfo(event.type, event.name)
|
||||
}
|
||||
|
||||
override fun serviceRemoved(event: ServiceEvent) {
|
||||
discoveredServicesMap.remove(event.name)
|
||||
println("[Discovery] Service entfernt: ${event.name}")
|
||||
}
|
||||
|
||||
override fun serviceResolved(event: ServiceEvent) {
|
||||
val info = event.info
|
||||
val service = DiscoveredService(
|
||||
name = event.name,
|
||||
host = info.inetAddresses.firstOrNull()?.hostAddress ?: "unknown",
|
||||
port = info.port,
|
||||
metadata = info.propertyNames.asSequence().associateWith { info.getPropertyString(it) }
|
||||
)
|
||||
discoveredServicesMap[event.name] = service
|
||||
println("[Discovery] Service gefunden: ${service.name} @ ${service.host}:${service.port}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun stopDiscovery() {
|
||||
jmdns?.close()
|
||||
jmdns = null
|
||||
discoveredServicesMap.clear()
|
||||
}
|
||||
|
||||
override fun registerService(port: Int) {
|
||||
val serviceInfo = ServiceInfo.create(
|
||||
SERVICE_TYPE,
|
||||
"Meldestelle-${System.getProperty("user.name")}",
|
||||
port,
|
||||
"Offline-First Sync Node"
|
||||
)
|
||||
jmdns?.registerService(serviceInfo)
|
||||
println("[Discovery] Eigenen Dienst registriert auf Port $port")
|
||||
}
|
||||
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> {
|
||||
return discoveredServicesMap.values.toList()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user