mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-17 11:12:33 +02:00
preliminary support for astra desktop
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.astradesktopdiscovery'
|
||||
version = '0.1.0'
|
||||
|
||||
android {
|
||||
namespace "expo.modules.astradesktopdiscovery"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
</manifest>
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package expo.modules.astradesktopdiscovery
|
||||
|
||||
import android.content.Context
|
||||
import android.net.nsd.NsdManager
|
||||
import android.net.nsd.NsdServiceInfo
|
||||
import android.os.Build
|
||||
import expo.modules.kotlin.exception.Exceptions
|
||||
import expo.modules.kotlin.functions.Coroutine
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.Inet4Address
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class AstraDesktopDiscoveryModule : Module() {
|
||||
private val serviceType = "_astra-remote._tcp."
|
||||
private var discoveryListener: NsdManager.DiscoveryListener? = null
|
||||
private val cached = ConcurrentHashMap<String, Map<String, Any?>>()
|
||||
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("AstraDesktopDiscovery")
|
||||
|
||||
Events("onDesktopRemoteFound", "onDesktopRemoteLost")
|
||||
|
||||
AsyncFunction("start").Coroutine<Unit> {
|
||||
withContext(Dispatchers.Main) { startDiscovery() }
|
||||
}
|
||||
|
||||
AsyncFunction("stop").Coroutine<Unit> {
|
||||
withContext(Dispatchers.Main) { stopDiscovery() }
|
||||
}
|
||||
|
||||
Function("getCached") {
|
||||
cached.values.toList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun requireContext(): Context =
|
||||
appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
||||
|
||||
private fun nsdManager(): NsdManager =
|
||||
requireContext().getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||
|
||||
private fun startDiscovery() {
|
||||
if (discoveryListener != null) return
|
||||
|
||||
val listener = object : NsdManager.DiscoveryListener {
|
||||
override fun onDiscoveryStarted(regType: String) = Unit
|
||||
override fun onDiscoveryStopped(serviceType: String) = Unit
|
||||
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
||||
stopDiscovery()
|
||||
}
|
||||
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
|
||||
stopDiscovery()
|
||||
}
|
||||
override fun onServiceFound(serviceInfo: NsdServiceInfo) {
|
||||
if (serviceInfo.serviceType != serviceType) return
|
||||
resolve(serviceInfo)
|
||||
}
|
||||
override fun onServiceLost(serviceInfo: NsdServiceInfo) {
|
||||
cached.remove(serviceInfo.serviceName)
|
||||
sendEvent("onDesktopRemoteLost", mapOf("name" to serviceInfo.serviceName))
|
||||
}
|
||||
}
|
||||
|
||||
discoveryListener = listener
|
||||
nsdManager().discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
|
||||
}
|
||||
|
||||
private fun stopDiscovery() {
|
||||
val listener = discoveryListener ?: return
|
||||
discoveryListener = null
|
||||
try {
|
||||
nsdManager().stopServiceDiscovery(listener)
|
||||
} catch (_: Throwable) {
|
||||
// Android can throw if discovery already stopped. Treat stop as idempotent.
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolve(serviceInfo: NsdServiceInfo) {
|
||||
try {
|
||||
nsdManager().resolveService(serviceInfo, object : NsdManager.ResolveListener {
|
||||
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) = Unit
|
||||
|
||||
override fun onServiceResolved(resolved: NsdServiceInfo) {
|
||||
val host = resolved.host
|
||||
val address = host?.hostAddress ?: return
|
||||
if (host !is Inet4Address) return
|
||||
val port = resolved.port
|
||||
if (port <= 0) return
|
||||
|
||||
val endpointUuid = txt(resolved, "endpoint_uuid")
|
||||
val desktopName = txt(resolved, "name")
|
||||
val protocolVersion = txt(resolved, "protocol_version")?.toIntOrNull() ?: 1
|
||||
val payload = mapOf(
|
||||
"endpointUuid" to endpointUuid,
|
||||
"desktopName" to desktopName,
|
||||
"protocolVersion" to protocolVersion,
|
||||
"name" to (desktopName ?: resolved.serviceName),
|
||||
"baseUrl" to "http://$address:$port",
|
||||
"address" to address,
|
||||
"port" to port,
|
||||
"lastSeenAt" to System.currentTimeMillis(),
|
||||
)
|
||||
cached[resolved.serviceName] = payload
|
||||
sendEvent("onDesktopRemoteFound", payload)
|
||||
}
|
||||
})
|
||||
} catch (_: Throwable) {
|
||||
// Resolve races are normal while browsing; ignore and wait for the next mDNS packet.
|
||||
}
|
||||
}
|
||||
|
||||
private fun txt(serviceInfo: NsdServiceInfo, key: String): String? {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null
|
||||
val value = serviceInfo.attributes[key] ?: return null
|
||||
return value.toString(Charsets.UTF_8).trim().ifEmpty { null }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user