@inthepocket/react-native-service-discovery 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,6 +3,8 @@ package com.servicediscovery
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.net.nsd.NsdManager
|
|
5
5
|
import android.net.nsd.NsdServiceInfo
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
6
8
|
import android.util.Log
|
|
7
9
|
import com.facebook.react.bridge.Promise
|
|
8
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -12,6 +14,7 @@ import com.facebook.react.bridge.WritableNativeArray
|
|
|
12
14
|
import com.facebook.react.bridge.WritableNativeMap
|
|
13
15
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
14
16
|
import java.nio.charset.StandardCharsets
|
|
17
|
+
import java.util.LinkedList
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class ServiceDiscoveryModule internal constructor(context: ReactApplicationContext) :
|
|
@@ -24,6 +27,21 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
24
27
|
// Keep track of resolved services, because onServiceLost doesn't pass a resolved service
|
|
25
28
|
private val resolvedServices: MutableMap<String, NsdServiceInfo> = HashMap()
|
|
26
29
|
|
|
30
|
+
// Resolve queue to handle Android's single-resolve limitation
|
|
31
|
+
private data class ResolveRequest(val service: NsdServiceInfo, var retryCount: Int = 0)
|
|
32
|
+
private val resolveQueue: LinkedList<ResolveRequest> = LinkedList()
|
|
33
|
+
private var isResolving: Boolean = false
|
|
34
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
35
|
+
private val pendingServices: MutableSet<String> = HashSet()
|
|
36
|
+
|
|
37
|
+
companion object {
|
|
38
|
+
private const val SERVICE_FOUND = "serviceFound"
|
|
39
|
+
private const val SERVICE_LOST = "serviceLost"
|
|
40
|
+
private const val MAX_RETRY_COUNT = 3
|
|
41
|
+
private const val RETRY_DELAY_MS = 100L
|
|
42
|
+
const val NAME = "ServiceDiscovery"
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
override fun getName(): String {
|
|
28
46
|
return NAME
|
|
29
47
|
}
|
|
@@ -42,6 +60,86 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
42
60
|
override fun removeListeners(count: Double) {
|
|
43
61
|
}
|
|
44
62
|
|
|
63
|
+
private fun queueServiceForResolve(service: NsdServiceInfo) {
|
|
64
|
+
val serviceKey = "${service.serviceName}-${service.serviceType}"
|
|
65
|
+
synchronized(resolveQueue) {
|
|
66
|
+
// Skip if already pending or resolved
|
|
67
|
+
if (pendingServices.contains(serviceKey) || resolvedServices.containsKey(service.serviceName)) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
pendingServices.add(serviceKey)
|
|
71
|
+
resolveQueue.add(ResolveRequest(service))
|
|
72
|
+
Log.d(NAME, "Queued service for resolve: ${service.serviceName} (queue size: ${resolveQueue.size})")
|
|
73
|
+
}
|
|
74
|
+
processResolveQueue()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun processResolveQueue() {
|
|
78
|
+
synchronized(resolveQueue) {
|
|
79
|
+
if (isResolving || resolveQueue.isEmpty()) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
isResolving = true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
val request = synchronized(resolveQueue) { resolveQueue.poll() } ?: run {
|
|
86
|
+
synchronized(resolveQueue) { isResolving = false }
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Log.d(NAME, "Resolving service: ${request.service.serviceName} (attempt ${request.retryCount + 1})")
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
nsdManager.resolveService(request.service, object : NsdManager.ResolveListener {
|
|
94
|
+
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
|
|
95
|
+
try {
|
|
96
|
+
val serviceKey = "${serviceInfo.serviceName}-${serviceInfo.serviceType}"
|
|
97
|
+
synchronized(resolveQueue) {
|
|
98
|
+
pendingServices.remove(serviceKey)
|
|
99
|
+
}
|
|
100
|
+
resolvedServices[serviceInfo.serviceName] = serviceInfo
|
|
101
|
+
sendEvent(SERVICE_FOUND, serviceToMap(serviceInfo))
|
|
102
|
+
Log.d(NAME, "Resolved service: ${serviceInfo.serviceName}")
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
Log.e(NAME, "Error handling resolved service", e)
|
|
105
|
+
} finally {
|
|
106
|
+
synchronized(resolveQueue) { isResolving = false }
|
|
107
|
+
// Small delay before processing next to avoid overwhelming NsdManager
|
|
108
|
+
mainHandler.postDelayed({ processResolveQueue() }, 50)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
|
113
|
+
Log.e(NAME, "Resolve failed for ${serviceInfo.serviceName}: errorCode=$errorCode")
|
|
114
|
+
|
|
115
|
+
val serviceKey = "${serviceInfo.serviceName}-${serviceInfo.serviceType}"
|
|
116
|
+
|
|
117
|
+
// Retry on FAILURE_ALREADY_ACTIVE (3) or other transient errors
|
|
118
|
+
if (request.retryCount < MAX_RETRY_COUNT) {
|
|
119
|
+
request.retryCount++
|
|
120
|
+
synchronized(resolveQueue) {
|
|
121
|
+
resolveQueue.add(request) // Re-queue for retry
|
|
122
|
+
}
|
|
123
|
+
Log.d(NAME, "Re-queued ${serviceInfo.serviceName} for retry (attempt ${request.retryCount})")
|
|
124
|
+
} else {
|
|
125
|
+
synchronized(resolveQueue) {
|
|
126
|
+
pendingServices.remove(serviceKey)
|
|
127
|
+
}
|
|
128
|
+
Log.e(NAME, "Giving up on ${serviceInfo.serviceName} after ${MAX_RETRY_COUNT} retries")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
synchronized(resolveQueue) { isResolving = false }
|
|
132
|
+
// Longer delay after failure before retrying
|
|
133
|
+
mainHandler.postDelayed({ processResolveQueue() }, RETRY_DELAY_MS)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
} catch (e: Exception) {
|
|
137
|
+
Log.e(NAME, "Exception starting resolve", e)
|
|
138
|
+
synchronized(resolveQueue) { isResolving = false }
|
|
139
|
+
mainHandler.postDelayed({ processResolveQueue() }, RETRY_DELAY_MS)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
45
143
|
@ReactMethod
|
|
46
144
|
override fun startSearch(type: String, promise: Promise) {
|
|
47
145
|
try {
|
|
@@ -55,38 +153,22 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
55
153
|
val discoveryListener: NsdManager.DiscoveryListener =
|
|
56
154
|
object : NsdManager.DiscoveryListener {
|
|
57
155
|
override fun onDiscoveryStarted(regType: String) {
|
|
58
|
-
|
|
156
|
+
Log.d(NAME, "Discovery started for $regType")
|
|
59
157
|
}
|
|
60
158
|
|
|
61
159
|
override fun onDiscoveryStopped(regType: String) {
|
|
62
|
-
|
|
160
|
+
Log.d(NAME, "Discovery stopped for $regType")
|
|
63
161
|
}
|
|
64
162
|
|
|
65
163
|
override fun onServiceFound(service: NsdServiceInfo) {
|
|
66
164
|
try {
|
|
67
165
|
// Service discovery success
|
|
68
166
|
if (service.serviceType == serviceType) {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
|
|
72
|
-
try {
|
|
73
|
-
resolvedServices[serviceInfo.serviceName] = serviceInfo
|
|
74
|
-
sendEvent(SERVICE_FOUND, serviceToMap(serviceInfo))
|
|
75
|
-
} catch (e: Exception) {
|
|
76
|
-
Log.e(NAME, "Error resolving service", e)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
override fun onResolveFailed(
|
|
81
|
-
serviceInfo: NsdServiceInfo,
|
|
82
|
-
errorCode: Int
|
|
83
|
-
) {
|
|
84
|
-
Log.e(NAME, "Resolve failed: $errorCode")
|
|
85
|
-
}
|
|
86
|
-
})
|
|
167
|
+
// Queue the service for resolution instead of resolving immediately
|
|
168
|
+
queueServiceForResolve(service)
|
|
87
169
|
}
|
|
88
170
|
} catch (e: Exception) {
|
|
89
|
-
Log.e(NAME, "Error
|
|
171
|
+
Log.e(NAME, "Error queuing service for resolve", e)
|
|
90
172
|
}
|
|
91
173
|
}
|
|
92
174
|
|
|
@@ -101,24 +183,27 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
101
183
|
sendEvent(SERVICE_LOST, serviceToMap(serviceInfo))
|
|
102
184
|
}
|
|
103
185
|
} catch (e: Exception) {
|
|
104
|
-
Log.e(NAME, "Error
|
|
186
|
+
Log.e(NAME, "Error handling service lost", e)
|
|
105
187
|
}
|
|
106
188
|
}
|
|
107
189
|
|
|
108
190
|
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
191
|
+
Log.e(NAME, "Discovery start failed: $errorCode")
|
|
109
192
|
tryStopServiceDiscovery(this)
|
|
110
193
|
}
|
|
111
194
|
|
|
112
195
|
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
196
|
+
Log.e(NAME, "Discovery stop failed: $errorCode")
|
|
113
197
|
tryStopServiceDiscovery(this)
|
|
114
198
|
}
|
|
115
199
|
}
|
|
116
200
|
discoveryListeners[serviceType] = discoveryListener
|
|
201
|
+
|
|
117
202
|
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
|
|
118
203
|
promise.resolve(null)
|
|
119
204
|
return
|
|
120
205
|
} catch (e: Exception) {
|
|
121
|
-
Log.e(NAME, "Error
|
|
206
|
+
Log.e(NAME, "Error starting search", e)
|
|
122
207
|
promise.reject(e)
|
|
123
208
|
}
|
|
124
209
|
}
|
|
@@ -127,16 +212,23 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
127
212
|
override fun stopSearch(type: String, promise: Promise) {
|
|
128
213
|
try {
|
|
129
214
|
resolvedServices.clear()
|
|
215
|
+
synchronized(resolveQueue) {
|
|
216
|
+
resolveQueue.clear()
|
|
217
|
+
pendingServices.clear()
|
|
218
|
+
isResolving = false
|
|
219
|
+
}
|
|
220
|
+
|
|
130
221
|
val serviceType = getServiceType(type)
|
|
131
222
|
val discoveryListener = discoveryListeners.remove(serviceType)
|
|
132
223
|
if (discoveryListener != null) {
|
|
133
224
|
tryStopServiceDiscovery(discoveryListener)
|
|
134
225
|
}
|
|
226
|
+
|
|
227
|
+
promise.resolve(null)
|
|
135
228
|
} catch (e: Exception) {
|
|
136
|
-
Log.e(NAME, "Error
|
|
229
|
+
Log.e(NAME, "Error stopping search", e)
|
|
137
230
|
promise.reject(e)
|
|
138
231
|
}
|
|
139
|
-
|
|
140
232
|
}
|
|
141
233
|
|
|
142
234
|
private fun tryStopServiceDiscovery(listener: NsdManager.DiscoveryListener) {
|
|
@@ -178,14 +270,8 @@ class ServiceDiscoveryModule internal constructor(context: ReactApplicationConte
|
|
|
178
270
|
}
|
|
179
271
|
service.putMap("txt", txt)
|
|
180
272
|
} catch (e: Exception) {
|
|
181
|
-
Log.e(NAME, "Error
|
|
273
|
+
Log.e(NAME, "Error converting service to map", e)
|
|
182
274
|
}
|
|
183
275
|
return service
|
|
184
276
|
}
|
|
185
|
-
|
|
186
|
-
companion object {
|
|
187
|
-
private const val SERVICE_FOUND = "serviceFound"
|
|
188
|
-
private const val SERVICE_LOST = "serviceLost"
|
|
189
|
-
const val NAME = "ServiceDiscovery"
|
|
190
|
-
}
|
|
191
277
|
}
|