react-native-zeroconf 0.16.1 → 0.17.1
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.
- package/NOTICE +0 -3
- package/README.md +3 -3
- package/android/build.gradle +0 -3
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java +1 -1
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/Zeroconf.java +12 -2
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfModule.java +129 -4
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfOptions.java +77 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/dnssd/DnssdImpl.java +654 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java +393 -39
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceInfoCallbacks.java +50 -13
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceTypesQuery.java +296 -0
- package/android/src/main/java/com/github/druk/dnssd/DNSSDEmbedded.java +1 -1
- package/android/src/main/jni/mdnsresponder/mDNSShared/dnssd_clientshim.c +59 -22
- package/dist/index.js +235 -48
- package/index.d.ts +105 -7
- package/ios/RNZeroconf/RNZeroconf.m +285 -22
- package/package.json +28 -4
- package/src/index.js +214 -44
- package/windows/ExperimentalFeatures.props +33 -0
- package/windows/NuGet.config +11 -0
- package/windows/RNZeroconf/RNZeroconf.cpp +228 -0
- package/windows/RNZeroconf/RNZeroconf.def +3 -0
- package/windows/RNZeroconf/RNZeroconf.h +70 -0
- package/windows/RNZeroconf/RNZeroconf.rc +0 -0
- package/windows/RNZeroconf/RNZeroconf.vcxproj +150 -0
- package/windows/RNZeroconf/RNZeroconf.vcxproj.filters +50 -0
- package/windows/RNZeroconf/ReactPackageProvider.cpp +20 -0
- package/windows/RNZeroconf/ReactPackageProvider.h +24 -0
- package/windows/RNZeroconf/ReactPackageProvider.idl +9 -0
- package/windows/RNZeroconf/ZeroconfCore.cpp +1095 -0
- package/windows/RNZeroconf/ZeroconfCore.h +167 -0
- package/windows/RNZeroconf/pch.cpp +1 -0
- package/windows/RNZeroconf/pch.h +30 -0
- package/windows/RNZeroconf/resource.h +5 -0
- package/windows/RNZeroconf/targetver.h +8 -0
- package/windows/RNZeroconf.sln +43 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java +0 -305
- package/android/src/main/java/com/github/druk/rx2dnssd/BonjourService.java +0 -400
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2BrowseListener.java +0 -55
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2Dnssd.java +0 -127
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdCommon.java +0 -280
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdEmbedded.java +0 -31
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2QueryListener.java +0 -70
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2RegisterListener.java +0 -47
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2ResolveListener.java +0 -51
|
@@ -2,6 +2,7 @@ package com.balthazargronon.RCTZeroconf.nsd;
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint;
|
|
4
4
|
import android.content.Context;
|
|
5
|
+
import android.net.Network;
|
|
5
6
|
import android.net.nsd.NsdManager;
|
|
6
7
|
import android.net.nsd.NsdServiceInfo;
|
|
7
8
|
import android.net.wifi.WifiManager;
|
|
@@ -12,6 +13,7 @@ import android.util.Log;
|
|
|
12
13
|
|
|
13
14
|
import com.balthazargronon.RCTZeroconf.Zeroconf;
|
|
14
15
|
import com.balthazargronon.RCTZeroconf.ZeroconfModule;
|
|
16
|
+
import com.balthazargronon.RCTZeroconf.ZeroconfOptions;
|
|
15
17
|
import com.facebook.react.bridge.Promise;
|
|
16
18
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
17
19
|
|
|
@@ -28,6 +30,8 @@ import java.util.ArrayList;
|
|
|
28
30
|
import java.util.Collections;
|
|
29
31
|
import java.util.Iterator;
|
|
30
32
|
import java.util.List;
|
|
33
|
+
import java.util.Set;
|
|
34
|
+
import java.util.HashSet;
|
|
31
35
|
import java.util.Locale;
|
|
32
36
|
import java.util.Map;
|
|
33
37
|
import java.util.concurrent.ConcurrentHashMap;
|
|
@@ -59,9 +63,16 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
59
63
|
|
|
60
64
|
private static class NsdScan {
|
|
61
65
|
final String scanId;
|
|
66
|
+
double resolveTimeoutSeconds = 5;
|
|
62
67
|
NsdManager.DiscoveryListener discoveryListener;
|
|
63
68
|
// Android 14+: services are followed with registerServiceInfoCallback, null before
|
|
64
69
|
@Nullable ServiceInfoCallbacks infoCallbacks;
|
|
70
|
+
// Service type scans query the network directly, NsdManager can't list types
|
|
71
|
+
@Nullable ServiceTypesQuery typesQuery;
|
|
72
|
+
// Types listed, and those answered by the network: the others are this app's own services,
|
|
73
|
+
// which the system mDNS stack doesn't answer back
|
|
74
|
+
final Set<String> listedTypes = new HashSet<>();
|
|
75
|
+
final Set<String> networkTypes = new HashSet<>();
|
|
65
76
|
|
|
66
77
|
NsdScan(String scanId) {
|
|
67
78
|
this.scanId = scanId;
|
|
@@ -69,12 +80,18 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
private static class PendingResolve {
|
|
72
|
-
final String scanId;
|
|
83
|
+
@Nullable final String scanId;
|
|
73
84
|
final NsdServiceInfo serviceInfo;
|
|
85
|
+
// resolveService(): settled instead of emitting events
|
|
86
|
+
@Nullable final Promise promise;
|
|
87
|
+
// Seconds before the TIMEOUT error, counted from the start of the resolve
|
|
88
|
+
final double timeoutSeconds;
|
|
74
89
|
|
|
75
|
-
PendingResolve(String scanId, NsdServiceInfo serviceInfo) {
|
|
90
|
+
PendingResolve(@Nullable String scanId, NsdServiceInfo serviceInfo, @Nullable Promise promise, double timeoutSeconds) {
|
|
76
91
|
this.scanId = scanId;
|
|
77
92
|
this.serviceInfo = serviceInfo;
|
|
93
|
+
this.promise = promise;
|
|
94
|
+
this.timeoutSeconds = timeoutSeconds;
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
|
|
@@ -85,16 +102,65 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
@Override
|
|
88
|
-
public void scan(final String scanId, String type, String protocol, String domain) {
|
|
105
|
+
public void scan(final String scanId, String type, String protocol, String domain, ZeroconfOptions options) {
|
|
89
106
|
final NsdManager nsdManager = getNsdManager();
|
|
90
107
|
|
|
91
108
|
this.stop(scanId);
|
|
92
|
-
acquireMulticastLock();
|
|
93
109
|
|
|
94
110
|
final NsdScan scan = new NsdScan(scanId);
|
|
111
|
+
scan.resolveTimeoutSeconds = options.resolveTimeoutSeconds;
|
|
112
|
+
final boolean typesOnly = ZeroconfModule.isServiceTypesScan(type, protocol);
|
|
113
|
+
|
|
114
|
+
Network network = null;
|
|
115
|
+
if (options.networkInterface != null && !typesOnly) {
|
|
116
|
+
String networkError = checkNetworkSupport(options.networkInterface);
|
|
117
|
+
network = networkError == null ? ZeroconfModule.findNetwork(reactApplicationContext, options.networkInterface) : null;
|
|
118
|
+
if (network == null) {
|
|
119
|
+
sendNetworkError(networkError, options.networkInterface, null, scanId);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
acquireMulticastLock();
|
|
124
|
+
|
|
125
|
+
if (typesOnly) {
|
|
126
|
+
scan.typesQuery = new ServiceTypesQuery(options.networkInterface, new ServiceTypesQuery.Listener() {
|
|
127
|
+
@Override
|
|
128
|
+
public void onTypeFound(String serviceType) {
|
|
129
|
+
synchronized (scan) {
|
|
130
|
+
scan.networkTypes.add(serviceType);
|
|
131
|
+
if (!scan.listedTypes.add(serviceType)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
sendScanEvent(ZeroconfModule.EVENT_FOUND, nameToMap(serviceType), scanId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Override
|
|
139
|
+
public void onTypeLost(String serviceType) {
|
|
140
|
+
synchronized (scan) {
|
|
141
|
+
scan.networkTypes.remove(serviceType);
|
|
142
|
+
if (localServiceTypes().contains(serviceType) || !scan.listedTypes.remove(serviceType)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
sendScanEvent(ZeroconfModule.EVENT_REMOVE, nameToMap(serviceType), scanId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Override
|
|
150
|
+
public void onError(String message) {
|
|
151
|
+
zeroconfModule.sendError(ZeroconfModule.ERROR_DOMAIN_LIBRARY, ZeroconfModule.ERROR_CODE_EXCEPTION, "Listing service types failed: " + message, null, scanId);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
mScans.put(scanId, scan);
|
|
155
|
+
scan.typesQuery.start();
|
|
156
|
+
sendScanEvent(ZeroconfModule.EVENT_START, new WritableNativeMap(), scanId);
|
|
157
|
+
syncLocalServiceTypes();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
95
160
|
|
|
96
161
|
if (Build.VERSION.SDK_INT >= 34) {
|
|
97
|
-
|
|
162
|
+
long timeoutMs = (long) (options.resolveTimeoutSeconds * 1000);
|
|
163
|
+
scan.infoCallbacks = new ServiceInfoCallbacks(nsdManager, mCallbackExecutor, mHandler, timeoutMs, new ServiceInfoCallbacks.Listener() {
|
|
98
164
|
@Override
|
|
99
165
|
public void onServiceUpdated(NsdServiceInfo serviceInfo) {
|
|
100
166
|
if (getHostAddresses(serviceInfo).isEmpty()) {
|
|
@@ -107,6 +173,11 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
107
173
|
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
108
174
|
zeroconfModule.sendError(ZeroconfModule.ERROR_DOMAIN_NSD, errorCode, "Resolving service " + serviceInfo.getServiceName() + " failed: " + ZeroconfModule.describeNsdError(errorCode), serviceInfo.getServiceName(), scanId);
|
|
109
175
|
}
|
|
176
|
+
|
|
177
|
+
@Override
|
|
178
|
+
public void onTimeout(NsdServiceInfo serviceInfo) {
|
|
179
|
+
sendResolveTimeout(serviceInfo.getServiceName(), scanId);
|
|
180
|
+
}
|
|
110
181
|
});
|
|
111
182
|
}
|
|
112
183
|
|
|
@@ -144,7 +215,7 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
144
215
|
if (scan.infoCallbacks != null) {
|
|
145
216
|
scan.infoCallbacks.register(serviceInfo);
|
|
146
217
|
} else {
|
|
147
|
-
enqueueResolve(
|
|
218
|
+
enqueueResolve(scan, serviceInfo);
|
|
148
219
|
}
|
|
149
220
|
}
|
|
150
221
|
|
|
@@ -159,8 +230,34 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
159
230
|
};
|
|
160
231
|
|
|
161
232
|
mScans.put(scanId, scan);
|
|
162
|
-
|
|
163
|
-
|
|
233
|
+
// "_ipp._tcp,_printer" browses the _printer subtype
|
|
234
|
+
String serviceType = options.subtype != null
|
|
235
|
+
? ZeroconfOptions.withSubtypes(String.format("_%s._%s", type, protocol), Collections.singletonList(options.subtype))
|
|
236
|
+
: String.format("_%s._%s.", type, protocol);
|
|
237
|
+
if (network != null && Build.VERSION.SDK_INT >= 33) {
|
|
238
|
+
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, network, mCallbackExecutor, scan.discoveryListener);
|
|
239
|
+
} else {
|
|
240
|
+
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, scan.discoveryListener);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Choosing the network of NSD requests needs Android 13, null when supported
|
|
245
|
+
@Nullable
|
|
246
|
+
private static String checkNetworkSupport(String networkInterface) {
|
|
247
|
+
return Build.VERSION.SDK_INT >= 33 ? null : ZeroconfModule.ERROR_CODE_UNSUPPORTED;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private void sendNetworkError(@Nullable String code, String networkInterface, @Nullable Promise promise, @Nullable String scanId) {
|
|
251
|
+
boolean unsupported = ZeroconfModule.ERROR_CODE_UNSUPPORTED.equals(code);
|
|
252
|
+
String errorCode = unsupported ? ZeroconfModule.ERROR_CODE_UNSUPPORTED : ZeroconfModule.ERROR_CODE_UNKNOWN_INTERFACE;
|
|
253
|
+
String message = unsupported
|
|
254
|
+
? "Choosing the network interface needs Android 13 or later with NSD, use DNSSD"
|
|
255
|
+
: "Unknown network interface " + networkInterface;
|
|
256
|
+
if (promise != null) {
|
|
257
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_LIBRARY, errorCode, message, null);
|
|
258
|
+
} else {
|
|
259
|
+
zeroconfModule.sendError(ZeroconfModule.ERROR_DOMAIN_LIBRARY, errorCode, message, null, scanId);
|
|
260
|
+
}
|
|
164
261
|
}
|
|
165
262
|
|
|
166
263
|
@Override
|
|
@@ -183,10 +280,17 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
183
280
|
scan.infoCallbacks.unregisterAll();
|
|
184
281
|
}
|
|
185
282
|
|
|
283
|
+
if (scan.typesQuery != null) {
|
|
284
|
+
scan.typesQuery.stop();
|
|
285
|
+
// No discovery listener reports it
|
|
286
|
+
sendScanEvent(ZeroconfModule.EVENT_STOP, new WritableNativeMap(), scanId);
|
|
287
|
+
}
|
|
288
|
+
|
|
186
289
|
synchronized (mResolveQueue) {
|
|
187
290
|
Iterator<PendingResolve> iterator = mResolveQueue.iterator();
|
|
188
291
|
while (iterator.hasNext()) {
|
|
189
|
-
|
|
292
|
+
// resolveService() entries have no scan id
|
|
293
|
+
if (scanId.equals(iterator.next().scanId)) {
|
|
190
294
|
iterator.remove();
|
|
191
295
|
}
|
|
192
296
|
}
|
|
@@ -222,6 +326,12 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
222
326
|
zeroconfModule.sendEvent(getReactApplicationContext(), eventName, body);
|
|
223
327
|
}
|
|
224
328
|
|
|
329
|
+
private static WritableMap nameToMap(String name) {
|
|
330
|
+
WritableMap service = new WritableNativeMap();
|
|
331
|
+
service.putString(ZeroconfModule.KEY_SERVICE_NAME, name);
|
|
332
|
+
return service;
|
|
333
|
+
}
|
|
334
|
+
|
|
225
335
|
private static WritableMap serviceNameToMap(NsdServiceInfo serviceInfo) {
|
|
226
336
|
WritableMap service = new WritableNativeMap();
|
|
227
337
|
service.putString(ZeroconfModule.KEY_SERVICE_NAME, serviceInfo.getServiceName());
|
|
@@ -229,22 +339,119 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
229
339
|
}
|
|
230
340
|
|
|
231
341
|
@Override
|
|
232
|
-
public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, Promise promise) {
|
|
233
|
-
|
|
342
|
+
public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, ZeroconfOptions options, Promise promise) {
|
|
343
|
+
Network network = null;
|
|
344
|
+
if (options.networkInterface != null) {
|
|
345
|
+
String networkError = checkNetworkSupport(options.networkInterface);
|
|
346
|
+
network = networkError == null ? ZeroconfModule.findNetwork(reactApplicationContext, options.networkInterface) : null;
|
|
347
|
+
if (network == null) {
|
|
348
|
+
sendNetworkError(networkError, options.networkInterface, promise, null);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// "_ipp._tcp,_printer,_color" registers the subtypes too
|
|
353
|
+
String serviceType = options.subtypes.isEmpty()
|
|
354
|
+
? String.format("_%s._%s.", type, protocol)
|
|
355
|
+
: ZeroconfOptions.withSubtypes(String.format("_%s._%s", type, protocol), options.subtypes);
|
|
356
|
+
ServiceRegistrationListener listener = new ServiceRegistrationListener(promise, serviceType, port, network);
|
|
357
|
+
getNsdManager().registerService(listener.buildServiceInfo(name, txt), NsdManager.PROTOCOL_DNS_SD, listener);
|
|
358
|
+
}
|
|
234
359
|
|
|
235
|
-
|
|
236
|
-
|
|
360
|
+
/**
|
|
361
|
+
* NsdManager can't update a registration: unregisters and registers again with the new TXT record,
|
|
362
|
+
* without emitting unpublished and published
|
|
363
|
+
*/
|
|
364
|
+
@Override
|
|
365
|
+
public void updateService(String serviceName, final ReadableArray txt, final Promise promise) {
|
|
366
|
+
final ServiceRegistrationListener previous = mPublishedServices.remove(serviceName);
|
|
367
|
+
if (previous == null) {
|
|
368
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_LIBRARY, ZeroconfModule.ERROR_CODE_NOT_PUBLISHED, "Service " + serviceName + " is not published", serviceName);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
final ServiceRegistrationListener next = new ServiceRegistrationListener(promise, previous.serviceType, previous.port, previous.network);
|
|
372
|
+
next.announceRegistered = false;
|
|
373
|
+
final NsdServiceInfo serviceInfo = next.buildServiceInfo(serviceName, txt);
|
|
374
|
+
previous.announceUnregistered = false;
|
|
375
|
+
previous.afterUnregistered = () -> getNsdManager().registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, next);
|
|
376
|
+
getNsdManager().unregisterService(previous);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@Override
|
|
380
|
+
public void resolveService(String name, String type, String protocol, String domain, ZeroconfOptions options, final Promise promise) {
|
|
381
|
+
final NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
|
237
382
|
serviceInfo.setServiceName(name);
|
|
238
|
-
serviceInfo.setServiceType(
|
|
239
|
-
|
|
383
|
+
serviceInfo.setServiceType(String.format("_%s._%s", type, protocol));
|
|
384
|
+
if (options.networkInterface != null) {
|
|
385
|
+
String networkError = checkNetworkSupport(options.networkInterface);
|
|
386
|
+
Network network = networkError == null ? ZeroconfModule.findNetwork(reactApplicationContext, options.networkInterface) : null;
|
|
387
|
+
if (network == null) {
|
|
388
|
+
sendNetworkError(networkError, options.networkInterface, promise, null);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (Build.VERSION.SDK_INT >= 33) {
|
|
392
|
+
serviceInfo.setNetwork(network);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
240
395
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
396
|
+
if (Build.VERSION.SDK_INT < 34) {
|
|
397
|
+
// One resolve at a time before Android 14, shares the queue of the scans
|
|
398
|
+
synchronized (mResolveQueue) {
|
|
399
|
+
mResolveQueue.add(new PendingResolve(null, serviceInfo, promise, options.timeoutSeconds));
|
|
400
|
+
}
|
|
401
|
+
resolveNext();
|
|
402
|
+
return;
|
|
244
403
|
}
|
|
245
404
|
|
|
246
|
-
|
|
247
|
-
|
|
405
|
+
final boolean[] settled = { false };
|
|
406
|
+
final NsdManager nsdManager = getNsdManager();
|
|
407
|
+
final NsdManager.ServiceInfoCallback[] callback = new NsdManager.ServiceInfoCallback[1];
|
|
408
|
+
final Runnable finish = () -> {
|
|
409
|
+
try {
|
|
410
|
+
nsdManager.unregisterServiceInfoCallback(callback[0]);
|
|
411
|
+
} catch (IllegalArgumentException e) {
|
|
412
|
+
// Already unregistered
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
callback[0] = new NsdManager.ServiceInfoCallback() {
|
|
416
|
+
@Override
|
|
417
|
+
public void onServiceInfoCallbackRegistrationFailed(int errorCode) {
|
|
418
|
+
synchronized (settled) {
|
|
419
|
+
if (settled[0]) return;
|
|
420
|
+
settled[0] = true;
|
|
421
|
+
}
|
|
422
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_NSD, errorCode, "Resolving service " + name + " failed: " + ZeroconfModule.describeNsdError(errorCode), name);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@Override
|
|
426
|
+
public void onServiceUpdated(NsdServiceInfo updated) {
|
|
427
|
+
if (getHostAddresses(updated).isEmpty()) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
synchronized (settled) {
|
|
431
|
+
if (settled[0]) return;
|
|
432
|
+
settled[0] = true;
|
|
433
|
+
}
|
|
434
|
+
promise.resolve(serviceInfoToMap(updated));
|
|
435
|
+
mHandler.post(finish);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@Override
|
|
439
|
+
public void onServiceLost() {
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
@Override
|
|
443
|
+
public void onServiceInfoCallbackUnregistered() {
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
nsdManager.registerServiceInfoCallback(serviceInfo, mCallbackExecutor, callback[0]);
|
|
447
|
+
mHandler.postDelayed(() -> {
|
|
448
|
+
synchronized (settled) {
|
|
449
|
+
if (settled[0]) return;
|
|
450
|
+
settled[0] = true;
|
|
451
|
+
}
|
|
452
|
+
finish.run();
|
|
453
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_LIBRARY, ZeroconfModule.ERROR_CODE_TIMEOUT, "Resolving service " + name + " failed: timed out", name);
|
|
454
|
+
}, (long) (options.timeoutSeconds * 1000));
|
|
248
455
|
}
|
|
249
456
|
|
|
250
457
|
@Override
|
|
@@ -262,6 +469,53 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
262
469
|
mPublishedServices.remove(serviceName);
|
|
263
470
|
serviceListener.unregisterPromise = promise;
|
|
264
471
|
nsdManager.unregisterService(serviceListener);
|
|
472
|
+
syncLocalServiceTypes();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// "_ipp._tcp,_printer" or "_http._tcp." -> "_ipp._tcp", "_http._tcp"
|
|
476
|
+
private static String baseServiceType(String serviceType) {
|
|
477
|
+
int comma = serviceType.indexOf(',');
|
|
478
|
+
String type = comma >= 0 ? serviceType.substring(0, comma) : serviceType;
|
|
479
|
+
return type.endsWith(".") ? type.substring(0, type.length() - 1) : type;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private Set<String> localServiceTypes() {
|
|
483
|
+
Set<String> types = new HashSet<>();
|
|
484
|
+
for (ServiceRegistrationListener listener : mPublishedServices.values()) {
|
|
485
|
+
types.add(baseServiceType(listener.serviceType));
|
|
486
|
+
}
|
|
487
|
+
return types;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Lists the types this app publishes in service type scans, and removes those it no longer publishes
|
|
491
|
+
private void syncLocalServiceTypes() {
|
|
492
|
+
Set<String> local = localServiceTypes();
|
|
493
|
+
for (NsdScan scan : mScans.values()) {
|
|
494
|
+
if (scan.typesQuery == null) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
List<String> found = new ArrayList<>();
|
|
498
|
+
List<String> removed = new ArrayList<>();
|
|
499
|
+
synchronized (scan) {
|
|
500
|
+
for (String type : local) {
|
|
501
|
+
if (scan.listedTypes.add(type)) {
|
|
502
|
+
found.add(type);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
for (String type : new ArrayList<>(scan.listedTypes)) {
|
|
506
|
+
if (!local.contains(type) && !scan.networkTypes.contains(type)) {
|
|
507
|
+
scan.listedTypes.remove(type);
|
|
508
|
+
removed.add(type);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
for (String type : found) {
|
|
513
|
+
sendScanEvent(ZeroconfModule.EVENT_FOUND, nameToMap(type), scan.scanId);
|
|
514
|
+
}
|
|
515
|
+
for (String type : removed) {
|
|
516
|
+
sendScanEvent(ZeroconfModule.EVENT_REMOVE, nameToMap(type), scan.scanId);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
265
519
|
}
|
|
266
520
|
|
|
267
521
|
@Override
|
|
@@ -282,13 +536,19 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
282
536
|
return reactApplicationContext;
|
|
283
537
|
}
|
|
284
538
|
|
|
285
|
-
private void enqueueResolve(
|
|
539
|
+
private void enqueueResolve(NsdScan scan, NsdServiceInfo serviceInfo) {
|
|
540
|
+
// A resolve can't be cancelled before Android 14, so there is no retry: waiting twice as long
|
|
541
|
+
// matches the total time of the other implementations, which retry once
|
|
286
542
|
synchronized (mResolveQueue) {
|
|
287
|
-
mResolveQueue.add(new PendingResolve(scanId, serviceInfo));
|
|
543
|
+
mResolveQueue.add(new PendingResolve(scan.scanId, serviceInfo, null, scan.resolveTimeoutSeconds * 2));
|
|
288
544
|
}
|
|
289
545
|
resolveNext();
|
|
290
546
|
}
|
|
291
547
|
|
|
548
|
+
private void sendResolveTimeout(String serviceName, String scanId) {
|
|
549
|
+
zeroconfModule.sendError(ZeroconfModule.ERROR_DOMAIN_LIBRARY, ZeroconfModule.ERROR_CODE_TIMEOUT, "Resolving service " + serviceName + " failed: timed out", serviceName, scanId);
|
|
550
|
+
}
|
|
551
|
+
|
|
292
552
|
private void resolveNext() {
|
|
293
553
|
PendingResolve next;
|
|
294
554
|
synchronized (mResolveQueue) {
|
|
@@ -298,8 +558,11 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
298
558
|
next = mResolveQueue.poll();
|
|
299
559
|
mIsResolving = true;
|
|
300
560
|
}
|
|
561
|
+
final ZeroResolveListener listener = new ZeroResolveListener(next);
|
|
301
562
|
try {
|
|
302
|
-
getNsdManager().resolveService(next.serviceInfo,
|
|
563
|
+
getNsdManager().resolveService(next.serviceInfo, listener);
|
|
564
|
+
// The system keeps the resolve until it answers or fails, the queue waits for it
|
|
565
|
+
mHandler.postDelayed(listener::timedOut, (long) (next.timeoutSeconds * 1000));
|
|
303
566
|
} catch (Throwable e) {
|
|
304
567
|
Log.e(TAG, "resolveService failed", e);
|
|
305
568
|
String serviceName = next.serviceInfo.getServiceName();
|
|
@@ -316,33 +579,76 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
316
579
|
}
|
|
317
580
|
|
|
318
581
|
private class ZeroResolveListener implements NsdManager.ResolveListener {
|
|
319
|
-
private final
|
|
582
|
+
private final PendingResolve pending;
|
|
583
|
+
@Nullable private final String scanId;
|
|
584
|
+
@Nullable private final Promise promise;
|
|
585
|
+
// Settled: answered, failed or timed out. A late answer after the timeout only frees the queue
|
|
586
|
+
private boolean settled = false;
|
|
587
|
+
|
|
588
|
+
ZeroResolveListener(PendingResolve pending) {
|
|
589
|
+
this.pending = pending;
|
|
590
|
+
this.scanId = pending.scanId;
|
|
591
|
+
this.promise = pending.promise;
|
|
592
|
+
}
|
|
320
593
|
|
|
321
|
-
|
|
322
|
-
|
|
594
|
+
private synchronized boolean settle() {
|
|
595
|
+
if (settled) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
settled = true;
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
void timedOut() {
|
|
603
|
+
if (!settle()) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
String serviceName = pending.serviceInfo.getServiceName();
|
|
607
|
+
if (promise != null) {
|
|
608
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_LIBRARY, ZeroconfModule.ERROR_CODE_TIMEOUT, "Resolving service " + serviceName + " failed: timed out", serviceName);
|
|
609
|
+
} else if (mScans.containsKey(scanId)) {
|
|
610
|
+
sendResolveTimeout(serviceName, scanId);
|
|
611
|
+
}
|
|
323
612
|
}
|
|
324
613
|
|
|
325
614
|
@Override
|
|
326
615
|
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
327
616
|
if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) {
|
|
328
617
|
// Another resolve (possibly from another app or listener) is in flight: retry shortly
|
|
618
|
+
if (!settle()) {
|
|
619
|
+
onResolveDone();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
329
622
|
synchronized (mResolveQueue) {
|
|
330
|
-
mResolveQueue.addFirst(
|
|
623
|
+
mResolveQueue.addFirst(pending);
|
|
331
624
|
mIsResolving = false;
|
|
332
625
|
}
|
|
333
626
|
mHandler.postDelayed(NsdServiceImpl.this::resolveNext, RESOLVE_RETRY_DELAY_MS);
|
|
334
627
|
return;
|
|
335
628
|
}
|
|
336
|
-
if (
|
|
337
|
-
|
|
629
|
+
if (!settle()) {
|
|
630
|
+
onResolveDone();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
String message = "Resolving service " + serviceInfo.getServiceName() + " failed: " + ZeroconfModule.describeNsdError(errorCode);
|
|
634
|
+
if (promise != null) {
|
|
635
|
+
ZeroconfModule.reject(promise, ZeroconfModule.ERROR_DOMAIN_NSD, errorCode, message, serviceInfo.getServiceName());
|
|
636
|
+
} else if (mScans.containsKey(scanId)) {
|
|
637
|
+
zeroconfModule.sendError(ZeroconfModule.ERROR_DOMAIN_NSD, errorCode, message, serviceInfo.getServiceName(), scanId);
|
|
338
638
|
}
|
|
339
639
|
onResolveDone();
|
|
340
640
|
}
|
|
341
641
|
|
|
342
642
|
@Override
|
|
343
643
|
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
|
344
|
-
|
|
345
|
-
|
|
644
|
+
if (!settle()) {
|
|
645
|
+
onResolveDone();
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (promise != null) {
|
|
649
|
+
promise.resolve(serviceInfoToMap(serviceInfo));
|
|
650
|
+
} else if (mScans.containsKey(scanId)) {
|
|
651
|
+
// The scan may have been stopped while resolving
|
|
346
652
|
sendScanEvent(ZeroconfModule.EVENT_RESOLVE, serviceInfoToMap(serviceInfo), scanId);
|
|
347
653
|
}
|
|
348
654
|
onResolveDone();
|
|
@@ -352,9 +658,50 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
352
658
|
private class ServiceRegistrationListener implements NsdManager.RegistrationListener {
|
|
353
659
|
@Nullable private Promise registerPromise;
|
|
354
660
|
@Nullable private Promise unregisterPromise;
|
|
355
|
-
|
|
356
|
-
|
|
661
|
+
// What it was registered with, to register it again on updates
|
|
662
|
+
final String serviceType;
|
|
663
|
+
final int port;
|
|
664
|
+
@Nullable final Network network;
|
|
665
|
+
// Updates replace the registration without emitting unpublished and published
|
|
666
|
+
boolean announceRegistered = true;
|
|
667
|
+
boolean announceUnregistered = true;
|
|
668
|
+
@Nullable Runnable afterUnregistered;
|
|
669
|
+
// The registration callback doesn't carry the TXT record, report the one registered
|
|
670
|
+
private final Map<String, String> txt = new java.util.LinkedHashMap<>();
|
|
671
|
+
|
|
672
|
+
ServiceRegistrationListener(@Nullable Promise registerPromise, String serviceType, int port, @Nullable Network network) {
|
|
357
673
|
this.registerPromise = registerPromise;
|
|
674
|
+
this.serviceType = serviceType;
|
|
675
|
+
this.port = port;
|
|
676
|
+
this.network = network;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
NsdServiceInfo buildServiceInfo(String name, ReadableArray txt) {
|
|
680
|
+
NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
|
681
|
+
serviceInfo.setServiceName(name);
|
|
682
|
+
serviceInfo.setServiceType(serviceType);
|
|
683
|
+
serviceInfo.setPort(port);
|
|
684
|
+
if (network != null && Build.VERSION.SDK_INT >= 33) {
|
|
685
|
+
serviceInfo.setNetwork(network);
|
|
686
|
+
}
|
|
687
|
+
this.txt.clear();
|
|
688
|
+
for (int i = 0; i < txt.size(); i++) {
|
|
689
|
+
ReadableArray pair = txt.getArray(i);
|
|
690
|
+
serviceInfo.setAttribute(pair.getString(0), pair.getString(1));
|
|
691
|
+
this.txt.put(pair.getString(0), pair.getString(1));
|
|
692
|
+
}
|
|
693
|
+
return serviceInfo;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
WritableMap registeredToMap(NsdServiceInfo serviceInfo) {
|
|
697
|
+
WritableMap service = serviceInfoToMap(serviceInfo);
|
|
698
|
+
WritableMap txtMap = new WritableNativeMap();
|
|
699
|
+
for (Map.Entry<String, String> entry : txt.entrySet()) {
|
|
700
|
+
txtMap.putString(entry.getKey(), entry.getValue());
|
|
701
|
+
}
|
|
702
|
+
service.putMap(ZeroconfModule.KEY_SERVICE_TXT, txtMap);
|
|
703
|
+
service.putInt(ZeroconfModule.KEY_SERVICE_PORT, port);
|
|
704
|
+
return service;
|
|
358
705
|
}
|
|
359
706
|
|
|
360
707
|
@Override
|
|
@@ -365,11 +712,13 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
365
712
|
|
|
366
713
|
final String serviceName = NsdServiceInfo.getServiceName();
|
|
367
714
|
mPublishedServices.put(serviceName, this);
|
|
715
|
+
syncLocalServiceTypes();
|
|
368
716
|
|
|
369
|
-
|
|
370
|
-
|
|
717
|
+
if (announceRegistered) {
|
|
718
|
+
zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_PUBLISHED, registeredToMap(NsdServiceInfo));
|
|
719
|
+
}
|
|
371
720
|
if (registerPromise != null) {
|
|
372
|
-
registerPromise.resolve(
|
|
721
|
+
registerPromise.resolve(registeredToMap(NsdServiceInfo));
|
|
373
722
|
registerPromise = null;
|
|
374
723
|
}
|
|
375
724
|
}
|
|
@@ -386,10 +735,15 @@ public class NsdServiceImpl implements Zeroconf {
|
|
|
386
735
|
public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) {
|
|
387
736
|
// Service has been unregistered. This only happens when you call
|
|
388
737
|
// NsdManager.unregisterService() and pass in this listener.
|
|
389
|
-
|
|
390
|
-
|
|
738
|
+
if (announceUnregistered) {
|
|
739
|
+
zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_UNREGISTERED, registeredToMap(nsdServiceInfo));
|
|
740
|
+
}
|
|
741
|
+
if (afterUnregistered != null) {
|
|
742
|
+
afterUnregistered.run();
|
|
743
|
+
afterUnregistered = null;
|
|
744
|
+
}
|
|
391
745
|
if (unregisterPromise != null) {
|
|
392
|
-
unregisterPromise.resolve(
|
|
746
|
+
unregisterPromise.resolve(registeredToMap(nsdServiceInfo));
|
|
393
747
|
unregisterPromise = null;
|
|
394
748
|
}
|
|
395
749
|
}
|