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.
Files changed (45) hide show
  1. package/NOTICE +0 -3
  2. package/README.md +3 -3
  3. package/android/build.gradle +0 -3
  4. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java +1 -1
  5. package/android/src/main/java/com/balthazargronon/RCTZeroconf/Zeroconf.java +12 -2
  6. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfModule.java +129 -4
  7. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfOptions.java +77 -0
  8. package/android/src/main/java/com/balthazargronon/RCTZeroconf/dnssd/DnssdImpl.java +654 -0
  9. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java +393 -39
  10. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceInfoCallbacks.java +50 -13
  11. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceTypesQuery.java +296 -0
  12. package/android/src/main/java/com/github/druk/dnssd/DNSSDEmbedded.java +1 -1
  13. package/android/src/main/jni/mdnsresponder/mDNSShared/dnssd_clientshim.c +59 -22
  14. package/dist/index.js +235 -48
  15. package/index.d.ts +105 -7
  16. package/ios/RNZeroconf/RNZeroconf.m +285 -22
  17. package/package.json +28 -4
  18. package/src/index.js +214 -44
  19. package/windows/ExperimentalFeatures.props +33 -0
  20. package/windows/NuGet.config +11 -0
  21. package/windows/RNZeroconf/RNZeroconf.cpp +228 -0
  22. package/windows/RNZeroconf/RNZeroconf.def +3 -0
  23. package/windows/RNZeroconf/RNZeroconf.h +70 -0
  24. package/windows/RNZeroconf/RNZeroconf.rc +0 -0
  25. package/windows/RNZeroconf/RNZeroconf.vcxproj +150 -0
  26. package/windows/RNZeroconf/RNZeroconf.vcxproj.filters +50 -0
  27. package/windows/RNZeroconf/ReactPackageProvider.cpp +20 -0
  28. package/windows/RNZeroconf/ReactPackageProvider.h +24 -0
  29. package/windows/RNZeroconf/ReactPackageProvider.idl +9 -0
  30. package/windows/RNZeroconf/ZeroconfCore.cpp +1095 -0
  31. package/windows/RNZeroconf/ZeroconfCore.h +167 -0
  32. package/windows/RNZeroconf/pch.cpp +1 -0
  33. package/windows/RNZeroconf/pch.h +30 -0
  34. package/windows/RNZeroconf/resource.h +5 -0
  35. package/windows/RNZeroconf/targetver.h +8 -0
  36. package/windows/RNZeroconf.sln +43 -0
  37. package/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java +0 -305
  38. package/android/src/main/java/com/github/druk/rx2dnssd/BonjourService.java +0 -400
  39. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2BrowseListener.java +0 -55
  40. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2Dnssd.java +0 -127
  41. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdCommon.java +0 -280
  42. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdEmbedded.java +0 -31
  43. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2QueryListener.java +0 -70
  44. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2RegisterListener.java +0 -47
  45. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2ResolveListener.java +0 -51
package/NOTICE CHANGED
@@ -35,9 +35,6 @@ android/src/main/jni/*
35
35
  android/src/main/java/com/github/druk/dnssd/*
36
36
  - Java DNS-SD API classes
37
37
 
38
- android/src/main/java/com/github/druk/rx2dnssd/*
39
- - RxJava2 reactive wrappers for DNS-SD
40
-
41
38
  ================================================================================
42
39
 
43
40
  mDNSResponder
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <p align="center">
2
- <img src=".github/banner.png" alt="react-native-zeroconf: Bonjour / mDNS discovery and publishing for React Native, on iOS, Android, macOS, tvOS and Expo" width="100%">
2
+ <a href="https://zeroconf.balthazar.dev"><img src=".github/banner.png" alt="react-native-zeroconf: Bonjour / mDNS discovery and publishing for React Native, on iOS, Android, Windows, macOS, tvOS and Expo" width="100%"></a>
3
3
  </p>
4
4
 
5
- Find the services other devices advertise on the local network (printers, speakers, cameras, your own servers) and advertise your own, on iOS, Android, macOS and tvOS.
5
+ Find the services other devices advertise on the local network (printers, speakers, cameras, your own servers) and advertise your own, on iOS, Android, macOS, tvOS and Windows.
6
6
 
7
7
  **[Read the documentation](https://zeroconf.balthazar.dev)**
8
8
 
@@ -13,7 +13,7 @@ yarn add react-native-zeroconf
13
13
  cd ios && pod install
14
14
  ```
15
15
 
16
- On iOS, declare the service types you use in `NSBonjourServices`, see [Permissions and Setup](https://zeroconf.balthazar.dev/Permissions-and-Setup). Expo works with development builds, see [Installation](https://zeroconf.balthazar.dev/Installation).
16
+ On iOS, declare the service types you use in `NSBonjourServices`, see [Permissions and Setup](https://zeroconf.balthazar.dev/Permissions-and-Setup). Expo works with development builds, see [Installation with Expo](https://zeroconf.balthazar.dev/Installation-Expo).
17
17
 
18
18
  ## Usage
19
19
 
@@ -42,7 +42,4 @@ repositories {
42
42
 
43
43
  dependencies {
44
44
  implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
45
- // RxJava2 for Rx2Dnssd (bundled from Discord's RxDNSSD fork)
46
- implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
47
- implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
48
45
  }
@@ -1,7 +1,7 @@
1
1
  package com.balthazargronon.RCTZeroconf;
2
2
 
3
3
  import com.balthazargronon.RCTZeroconf.nsd.NsdServiceImpl;
4
- import com.balthazargronon.RCTZeroconf.rx2dnssd.DnssdImpl;
4
+ import com.balthazargronon.RCTZeroconf.dnssd.DnssdImpl;
5
5
  import com.facebook.react.bridge.ReactApplicationContext;
6
6
  import com.facebook.react.bridge.ReactContext;
7
7
 
@@ -10,7 +10,7 @@ public interface Zeroconf {
10
10
  /**
11
11
  * @param scanId id of the JS instance starting the scan, several scans can run at once
12
12
  */
13
- void scan(String scanId, String type, String protocol, String domain);
13
+ void scan(String scanId, String type, String protocol, String domain, ZeroconfOptions options);
14
14
 
15
15
  void stop(String scanId);
16
16
 
@@ -25,7 +25,17 @@ public interface Zeroconf {
25
25
  * @param txt TXT records as ordered [key, value] pairs
26
26
  * @param promise resolved with the service once registered
27
27
  */
28
- public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, Promise promise);
28
+ public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, ZeroconfOptions options, Promise promise);
29
+
30
+ /**
31
+ * Replaces the TXT record of a published service
32
+ */
33
+ void updateService(String serviceName, ReadableArray txt, Promise promise);
34
+
35
+ /**
36
+ * Resolves one service by name, without scanning
37
+ */
38
+ void resolveService(String name, String type, String protocol, String domain, ZeroconfOptions options, Promise promise);
29
39
 
30
40
  /**
31
41
  * Unregister every service published through this implementation
@@ -1,6 +1,12 @@
1
1
  package com.balthazargronon.RCTZeroconf;
2
2
 
3
3
 
4
+ import android.content.Context;
5
+ import android.content.pm.PackageManager;
6
+ import android.net.ConnectivityManager;
7
+ import android.net.LinkProperties;
8
+ import android.net.Network;
9
+ import android.os.Build;
4
10
  import android.util.Log;
5
11
 
6
12
  import com.facebook.react.bridge.Promise;
@@ -9,6 +15,7 @@ import com.facebook.react.bridge.ReactContext;
9
15
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
16
  import com.facebook.react.bridge.ReactMethod;
11
17
  import com.facebook.react.bridge.ReadableArray;
18
+ import com.facebook.react.bridge.ReadableMap;
12
19
  import com.facebook.react.bridge.WritableMap;
13
20
  import com.facebook.react.bridge.WritableNativeMap;
14
21
  import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -46,6 +53,11 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
46
53
  public static final String ERROR_DOMAIN_DNSSD = "DNSSD";
47
54
  public static final String ERROR_DOMAIN_LIBRARY = "RNZeroconf";
48
55
  public static final String ERROR_CODE_EXCEPTION = "EXCEPTION";
56
+ public static final String ERROR_CODE_TIMEOUT = "TIMEOUT";
57
+ public static final String ERROR_CODE_UNKNOWN_INTERFACE = "UNKNOWN_INTERFACE";
58
+ public static final String ERROR_CODE_UNSUPPORTED = "UNSUPPORTED";
59
+ // Manifest.permission.ACCESS_LOCAL_NETWORK, API 37
60
+ public static final String PERMISSION_ACCESS_LOCAL_NETWORK = "android.permission.ACCESS_LOCAL_NETWORK";
49
61
  public static final String ERROR_CODE_NOT_PUBLISHED = "NOT_PUBLISHED";
50
62
 
51
63
  private ZeroConfImplFactory zeroConfFactory;
@@ -61,9 +73,9 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
61
73
  }
62
74
 
63
75
  @ReactMethod
64
- public void scan(String scanId, String type, String protocol, String domain, String implType) {
76
+ public void scan(String scanId, String type, String protocol, String domain, String implType, @Nullable ReadableMap options) {
65
77
  try {
66
- getZeroconfImpl(implType).scan(scanId, type, protocol, domain);
78
+ getZeroconfImpl(implType).scan(scanId, type, protocol, domain, ZeroconfOptions.from(options));
67
79
  } catch (Throwable e) {
68
80
  Log.e(getClass().getName(), e.getMessage(), e);
69
81
  sendError(ERROR_DOMAIN_LIBRARY, ERROR_CODE_EXCEPTION, "Exception during scan: " + e.getMessage(), null, scanId);
@@ -93,9 +105,9 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
93
105
  }
94
106
 
95
107
  @ReactMethod
96
- public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, String implType, Promise promise) {
108
+ public void registerService(String type, String protocol, String domain, String name, int port, ReadableArray txt, String implType, @Nullable ReadableMap options, Promise promise) {
97
109
  try {
98
- getZeroconfImpl(implType).registerService(type, protocol, domain, name, port, txt, promise);
110
+ getZeroconfImpl(implType).registerService(type, protocol, domain, name, port, txt, ZeroconfOptions.from(options), promise);
99
111
  } catch (Throwable e) {
100
112
  Log.e(getClass().getName(), e.getMessage(), e);
101
113
  String message = "Exception while registering service: " + e.getMessage();
@@ -104,6 +116,43 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
104
116
  }
105
117
  }
106
118
 
119
+ @ReactMethod
120
+ public void updateService(String serviceName, ReadableArray txt, String implType, Promise promise) {
121
+ try {
122
+ getZeroconfImpl(implType).updateService(serviceName, txt, promise);
123
+ } catch (Throwable e) {
124
+ Log.e(getClass().getName(), e.getMessage(), e);
125
+ reject(promise, ERROR_DOMAIN_LIBRARY, ERROR_CODE_EXCEPTION, "Exception while updating service: " + e.getMessage(), serviceName);
126
+ }
127
+ }
128
+
129
+ @ReactMethod
130
+ public void resolveService(String name, String type, String protocol, String domain, String implType, @Nullable ReadableMap options, Promise promise) {
131
+ try {
132
+ getZeroconfImpl(implType).resolveService(name, type, protocol, domain, ZeroconfOptions.from(options), promise);
133
+ } catch (Throwable e) {
134
+ Log.e(getClass().getName(), e.getMessage(), e);
135
+ reject(promise, ERROR_DOMAIN_LIBRARY, ERROR_CODE_EXCEPTION, "Exception while resolving service: " + e.getMessage(), name);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Resolves "granted" or "denied". On Android 17 (API 37) devices, the ACCESS_LOCAL_NETWORK runtime permission
141
+ * is enforced for apps targeting API 37 and for apps declaring it in their manifest. Otherwise there is nothing to grant.
142
+ */
143
+ @ReactMethod
144
+ public void checkLocalNetworkAccess(Promise promise) {
145
+ Context context = getReactApplicationContext();
146
+ boolean required = Build.VERSION.SDK_INT >= 37
147
+ && (context.getApplicationInfo().targetSdkVersion >= 37 || declaresPermission(context, PERMISSION_ACCESS_LOCAL_NETWORK));
148
+ if (!required) {
149
+ promise.resolve("granted");
150
+ return;
151
+ }
152
+ boolean granted = context.checkSelfPermission(PERMISSION_ACCESS_LOCAL_NETWORK) == PackageManager.PERMISSION_GRANTED;
153
+ promise.resolve(granted ? "granted" : "denied");
154
+ }
155
+
107
156
  @ReactMethod
108
157
  public void unregisterService(String serviceName, String implType, Promise promise) {
109
158
  try {
@@ -173,6 +222,65 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
173
222
  return error;
174
223
  }
175
224
 
225
+ public static final String SERVICE_TYPES_TYPE = "services._dns-sd";
226
+ public static final String SERVICE_TYPES_PROTOCOL = "udp";
227
+
228
+ /**
229
+ * Whether a scan browses _services._dns-sd._udp, whose results are service types
230
+ */
231
+ public static boolean isServiceTypesScan(String type, String protocol) {
232
+ return SERVICE_TYPES_TYPE.equals(type) && SERVICE_TYPES_PROTOCOL.equals(protocol);
233
+ }
234
+
235
+ /**
236
+ * Service type from a _services._dns-sd._udp result: "_http" and "_tcp.local." give "_http._tcp"
237
+ */
238
+ public static String serviceTypeFromResult(String name, @Nullable String regType) {
239
+ if (regType != null) {
240
+ for (String label : regType.split("\\.")) {
241
+ if (label.startsWith("_")) {
242
+ return name + "." + label;
243
+ }
244
+ }
245
+ }
246
+ return name;
247
+ }
248
+
249
+ private static boolean declaresPermission(Context context, String permission) {
250
+ try {
251
+ String[] requested = context.getPackageManager()
252
+ .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
253
+ if (requested != null) {
254
+ for (String declared : requested) {
255
+ if (permission.equals(declared)) {
256
+ return true;
257
+ }
258
+ }
259
+ }
260
+ } catch (PackageManager.NameNotFoundException e) {
261
+ // Our own package is always found
262
+ }
263
+ return false;
264
+ }
265
+
266
+ /**
267
+ * The Android network of a network interface ("wlan0"), null when there is none
268
+ */
269
+ @Nullable
270
+ public static Network findNetwork(Context context, String interfaceName) {
271
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
272
+ if (connectivityManager == null) {
273
+ return null;
274
+ }
275
+ for (Network network : connectivityManager.getAllNetworks()) {
276
+ LinkProperties properties = connectivityManager.getLinkProperties(network);
277
+ if (properties != null && interfaceName.equals(properties.getInterfaceName())) {
278
+ return network;
279
+ }
280
+ }
281
+ return null;
282
+ }
283
+
176
284
  /**
177
285
  * Readable description of an NsdManager failure code
178
286
  */
@@ -183,6 +291,23 @@ public class ZeroconfModule extends ReactContextBaseJavaModule {
183
291
  case 4: return "maximum number of requests reached"; // FAILURE_MAX_LIMIT
184
292
  case 5: return "operation not running"; // FAILURE_OPERATION_NOT_RUNNING (API 34)
185
293
  case 6: return "bad parameters"; // FAILURE_BAD_PARAMETERS (API 34)
294
+ case 7: return "permission denied, the app needs the ACCESS_LOCAL_NETWORK permission"; // FAILURE_PERMISSION_DENIED (API 37)
295
+ default: return "error " + errorCode;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Readable description of a DNSServiceErrorType code, as on iOS
301
+ */
302
+ public static String describeDnssdError(int errorCode) {
303
+ switch (errorCode) {
304
+ case -65548: return "name already in use"; // kDNSServiceErr_NameConflict
305
+ case -65540: return "bad parameter"; // kDNSServiceErr_BadParam
306
+ case -65538: return "no such name"; // kDNSServiceErr_NoSuchName
307
+ case -65554: return "no such record"; // kDNSServiceErr_NoSuchRecord
308
+ case -65563: return "mDNSResponder is not running"; // kDNSServiceErr_ServiceNotRunning
309
+ case -65539: return "out of memory"; // kDNSServiceErr_NoMemory
310
+ case -65544: return "unsupported"; // kDNSServiceErr_Unsupported
186
311
  default: return "error " + errorCode;
187
312
  }
188
313
  }
@@ -0,0 +1,77 @@
1
+ package com.balthazargronon.RCTZeroconf;
2
+
3
+ import com.facebook.react.bridge.ReadableArray;
4
+ import com.facebook.react.bridge.ReadableMap;
5
+ import com.facebook.react.bridge.ReadableType;
6
+
7
+ import java.util.ArrayList;
8
+ import java.util.List;
9
+
10
+ import javax.annotation.Nullable;
11
+
12
+ /**
13
+ * Options of scan, registerService and resolveService
14
+ */
15
+ public class ZeroconfOptions {
16
+ /** Scan: subtype to browse, "_printer" */
17
+ @Nullable public String subtype;
18
+ /** Publish: subtypes to register, "_printer" */
19
+ public final List<String> subtypes = new ArrayList<>();
20
+ /** Network interface name, "wlan0" */
21
+ @Nullable public String networkInterface;
22
+ /** resolveService: seconds before giving up */
23
+ public double timeoutSeconds = 5;
24
+ /** Scan: seconds to resolve each service, retried once before a TIMEOUT error */
25
+ public double resolveTimeoutSeconds = 5;
26
+
27
+ public static ZeroconfOptions from(@Nullable ReadableMap map) {
28
+ ZeroconfOptions options = new ZeroconfOptions();
29
+ if (map == null) {
30
+ return options;
31
+ }
32
+ options.subtype = subtypeLabel(getString(map, "subtype"));
33
+ if (map.hasKey("subtypes") && map.getType("subtypes") == ReadableType.Array) {
34
+ ReadableArray subtypes = map.getArray("subtypes");
35
+ for (int i = 0; i < subtypes.size(); i++) {
36
+ String label = subtypes.getType(i) == ReadableType.String ? subtypeLabel(subtypes.getString(i)) : null;
37
+ if (label != null) {
38
+ options.subtypes.add(label);
39
+ }
40
+ }
41
+ }
42
+ String networkInterface = getString(map, "networkInterface");
43
+ options.networkInterface = networkInterface == null || networkInterface.isEmpty() ? null : networkInterface;
44
+ if (map.hasKey("timeout") && map.getType("timeout") == ReadableType.Number && map.getDouble("timeout") > 0) {
45
+ options.timeoutSeconds = map.getDouble("timeout");
46
+ }
47
+ if (map.hasKey("resolveTimeout") && map.getType("resolveTimeout") == ReadableType.Number && map.getDouble("resolveTimeout") > 0) {
48
+ options.resolveTimeoutSeconds = map.getDouble("resolveTimeout");
49
+ }
50
+ return options;
51
+ }
52
+
53
+ /**
54
+ * "_ipp._tcp" with the subtypes: "_ipp._tcp,_printer", the syntax of mDNSResponder and NsdManager
55
+ */
56
+ public static String withSubtypes(String serviceType, List<String> subtypes) {
57
+ StringBuilder type = new StringBuilder(serviceType);
58
+ for (String subtype : subtypes) {
59
+ type.append(',').append(subtype);
60
+ }
61
+ return type.toString();
62
+ }
63
+
64
+ @Nullable
65
+ private static String getString(ReadableMap map, String key) {
66
+ return map.hasKey(key) && map.getType(key) == ReadableType.String ? map.getString(key) : null;
67
+ }
68
+
69
+ // "printer" or "_printer" -> "_printer"
70
+ @Nullable
71
+ private static String subtypeLabel(@Nullable String subtype) {
72
+ if (subtype == null || subtype.isEmpty()) {
73
+ return null;
74
+ }
75
+ return subtype.startsWith("_") ? subtype : "_" + subtype;
76
+ }
77
+ }