react-native-zeroconf icon indicating copy to clipboard operation
react-native-zeroconf copied to clipboard

on found doesnt find anything on Android (works fine on iOS)

Open oscadev opened this issue 4 years ago • 13 comments

on.start runs per the console.log callback, but on.scan never triggers the callback (as if not finding anything). The exact same code works fine in iOS, and I have added the user-permissions mentioned:

oscadev avatar Jun 25 '20 05:06 oscadev

Same issue for me as well, please help to resolve

jalesingh avatar Aug 07 '20 11:08 jalesingh

Same issue. Any solution?

antoniodalessio avatar Oct 05 '20 14:10 antoniodalessio

I have same issue. found event is firing, but resolved is not. zeroconf.getServices() returns object with name field only.

wielski avatar Nov 08 '20 13:11 wielski

This npm package [1]: https://github.com/tableau/react-native-dns-lookup is the only option if you want to get the ip address (local ip too) using mdns hostname. [2]: https://github.com/avertin/DnsTest/blob/master/App.js#L30 Here is an example by the contributor

whatdtech avatar Dec 15 '20 14:12 whatdtech

I got mine working, on Android it turns out battery optimizations were causing the resolve failures, I set com.android.server.NetworkPermissionConfig to 'don't optimise' in my android settings under: Apps & Notifications -> Advanced -> Special App Access -> Battery Optimization, now it works every time.

jwh-hutchison avatar Jan 25 '21 15:01 jwh-hutchison

Hello, has anyone found a solution to this problem? I am using react-native version 0.63.4

LaRenarde avatar Mar 23 '21 12:03 LaRenarde

iOS "resolved" is working great. What's up with Android? Can't get it to go.

jkoutavas avatar Jul 21 '21 23:07 jkoutavas

This appears to be a duplicate of https://github.com/balthazar/react-native-zeroconf/issues/85

And a related issue in a different repo: https://github.com/watson/bonjour/issues/25

MixMasterMitch avatar Jul 23 '21 17:07 MixMasterMitch

Using the Android debugger, I see this line being repeatedly invoked. I think this retry loop is intentional, but the NSD state never changes.

MixMasterMitch avatar Jul 23 '21 17:07 MixMasterMitch

I do not have any issue when there is only 1 device to resolve. Per this stack overflow question, it looks like there is a concurrency problem, so the issue can likely be fixed by resolving services one at a time.

MixMasterMitch avatar Jul 23 '21 17:07 MixMasterMitch

I've been struggling to get this package to work properly. Before anything here is my latest patch-package that aims to solve the issues. A detailed breakdown at the end.

diff --git a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java
index 3550238..27a4218 100644
--- a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java
+++ b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java
@@ -3,8 +3,6 @@ package com.balthazargronon.RCTZeroconf;
 import com.balthazargronon.RCTZeroconf.nsd.NsdServiceImpl;
 import com.balthazargronon.RCTZeroconf.rx2dnssd.DnssdImpl;
 import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContext;
-
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.HashMap;
diff --git a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java
index c59850d..6a90efd 100644
--- a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java
+++ b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java
@@ -1,6 +1,6 @@
 package com.balthazargronon.RCTZeroconf.nsd;
 
-import android.annotation.SuppressLint;
+import java.lang.Thread;
 import android.content.Context;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
@@ -17,12 +17,15 @@ import com.facebook.react.bridge.WritableArray;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.bridge.WritableNativeArray;
 import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.util.RNLog;
 
 import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
 
 public class NsdServiceImpl implements Zeroconf {
     private NsdManager mNsdManager;
@@ -31,6 +34,7 @@ public class NsdServiceImpl implements Zeroconf {
     private Map<String, NsdManager.RegistrationListener> mPublishedServices;
     private ZeroconfModule zeroconfModule;
     private ReactApplicationContext reactApplicationContext;
+    private final Semaphore semaphore = new Semaphore(1);
 
     public NsdServiceImpl(ZeroconfModule zeroconfModule, ReactApplicationContext reactApplicationContext) {
         this.zeroconfModule = zeroconfModule;
@@ -47,7 +51,7 @@ public class NsdServiceImpl implements Zeroconf {
         this.stop();
 
         if (multicastLock == null) {
-            @SuppressLint("WifiManagerLeak") WifiManager wifi = (WifiManager) getReactApplicationContext().getSystemService(Context.WIFI_SERVICE);
+            WifiManager wifi = (WifiManager) getReactApplicationContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
             multicastLock = wifi.createMulticastLock("multicastLock");
             multicastLock.setReferenceCounted(true);
             multicastLock.acquire();
@@ -57,35 +61,42 @@ public class NsdServiceImpl implements Zeroconf {
             @Override
             public void onStartDiscoveryFailed(String serviceType, int errorCode) {
                 String error = "Starting service discovery failed with code: " + errorCode;
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
+                // zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
             }
 
             @Override
             public void onStopDiscoveryFailed(String serviceType, int errorCode) {
                 String error = "Stopping service discovery failed with code: " + errorCode;
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
+                // zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
             }
 
             @Override
             public void onDiscoveryStarted(String serviceType) {
                 System.out.println("On Discovery Started");
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_START, null);
+                // zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_START, null);
             }
 
             @Override
             public void onDiscoveryStopped(String serviceType) {
                 System.out.println("On Discovery Stopped");
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_STOP, null);
+                // zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_STOP, null);
             }
 
             @Override
             public void onServiceFound(NsdServiceInfo serviceInfo) {
-                System.out.println("On Service Found");
                 WritableMap service = new WritableNativeMap();
                 service.putString(ZeroconfModule.KEY_SERVICE_NAME, serviceInfo.getServiceName());
 
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_FOUND, service);
-                mNsdManager.resolveService(serviceInfo, new NsdServiceImpl.ZeroResolveListener());
+                // put it into a "thread" so the semaphore doesn't block everything
+                Executors.newSingleThreadExecutor().submit(() -> {
+                    try {
+                        semaphore.acquire();
+                        mNsdManager.resolveService(serviceInfo, new NsdServiceImpl.ZeroResolveListener());
+                    } catch (InterruptedException e) {
+                        RNLog.e("Zeroconf: Could not aquire semaphore");
+                    }
+                });
+
             }
 
             @Override
@@ -162,18 +173,22 @@ public class NsdServiceImpl implements Zeroconf {
     private class ZeroResolveListener implements NsdManager.ResolveListener {
         @Override
         public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
-            if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) {
-                mNsdManager.resolveService(serviceInfo, this);
-            } else {
-                String error = "Resolving service failed with code: " + errorCode;
-                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
-            }
+            String error = "Resolving service failed with code: " + errorCode;
+            zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
+            semaphore.release();
+//            if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) {
+//                mNsdManager.resolveService(serviceInfo, this);
+//            } else {
+//                String error = "Resolving service failed with code: " + errorCode;
+//                zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, error);
+//            }
         }
 
         @Override
         public void onServiceResolved(NsdServiceInfo serviceInfo) {
             WritableMap service = serviceInfoToMap(serviceInfo);
             zeroconfModule.sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_RESOLVE, service);
+            semaphore.release();
         }
     }
 
diff --git a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java
index fda45d6..1cf6027 100644
--- a/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java
+++ b/node_modules/react-native-zeroconf/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.wifi.WifiManager;
 import android.util.Log;
+import android.os.Build;
 
 import com.balthazargronon.RCTZeroconf.Zeroconf;
 import com.balthazargronon.RCTZeroconf.ZeroconfModule;
@@ -17,6 +18,7 @@ import com.facebook.react.bridge.WritableNativeMap;
 import com.github.druk.rx2dnssd.BonjourService;
 import com.github.druk.rx2dnssd.Rx2Dnssd;
 import com.github.druk.rx2dnssd.Rx2DnssdBindable;
+import com.github.druk.rx2dnssd.Rx2DnssdEmbedded;
 
 import java.net.InetAddress;
 import java.util.HashMap;
@@ -49,7 +51,11 @@ public class DnssdImpl implements Zeroconf {
         this.reactApplicationContext = reactApplicationContext;
         mPublishedServices = new HashMap<String, BonjourService>();
         mRegisteredDisposables = new HashMap<String, Disposable>();
-        rxDnssd = new Rx2DnssdBindable(reactApplicationContext);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            rxDnssd = new Rx2DnssdEmbedded(reactApplicationContext);
+        } else {
+            rxDnssd = new Rx2DnssdBindable(reactApplicationContext);
+        }
     }
 
     @Override
@@ -57,7 +63,7 @@ public class DnssdImpl implements Zeroconf {
         this.stop();
 
         if (multicastLock == null) {
-            @SuppressLint("WifiManagerLeak") WifiManager wifi = (WifiManager) reactApplicationContext.getSystemService(Context.WIFI_SERVICE);
+            WifiManager wifi = (WifiManager) reactApplicationContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
             multicastLock = wifi.createMulticastLock("multicastLock");
             multicastLock.setReferenceCounted(true);
             multicastLock.acquire();
diff --git a/node_modules/react-native-zeroconf/ios/RNZeroconf/RNZeroconf.m b/node_modules/react-native-zeroconf/ios/RNZeroconf/RNZeroconf.m
index 1ff5371..e0fe5ef 100644
--- a/node_modules/react-native-zeroconf/ios/RNZeroconf/RNZeroconf.m
+++ b/node_modules/react-native-zeroconf/ios/RNZeroconf/RNZeroconf.m
@@ -85,8 +85,8 @@ RCT_EXPORT_METHOD(unregisterService:(NSString *) serviceName)
       return;
     }
 
-    NSDictionary *serviceInfo = [RNNetServiceSerializer serializeServiceToDictionary:service resolved:NO];
-    [self.bridge.eventDispatcher sendDeviceEventWithName:@"RNZeroconfFound" body:serviceInfo];
+    // NSDictionary *serviceInfo = [RNNetServiceSerializer serializeServiceToDictionary:service resolved:NO];
+    // [self.bridge.eventDispatcher sendDeviceEventWithName:@"RNZeroconfFound" body:serviceInfo];
 
     // resolving services must be strongly referenced or they will be garbage collected
     // and will never resolve or timeout.

Here is a detailed rundown of my findings and workarounds:

  • The Android implementation has two internal mdns implementations. One uses NsdService, the official Android API for using Bounjour/Mdns, and one based on RxDNSSD which is a custom implementation that uses RxJava to stream events.
  • As mentioned in this thread there seems to be a race condition on the NsdService implementation (resolving multiple services concurrently seems to create an infinite loop).
  • RxDNDSD seemed to work a lot more reliable (no race condition), except on Android 12+ you HAVE to use NsdService because Android tries to save battery when not listening for messages using the official API. There is a workaround using an embeddable version of RxDNSSD, which I also had to monkey patch (still in the patch I posted but not used). Even though RxDNSSD is implemented and the package takes (in theory) a parameter to use it, I had to monkey patch the Java sources to be able to use it. (removed in current patch).
  • However, on some Samsung devices I still faced dropped events after using the embedded version of RxDNSSD.
  • I'm back at trying to make NsdService work. I've implemented the solution suggested by @MixMasterMitch in this StackOverflow question (using a simple semaphore to resolve one service at the time)
  • I also got rid of some of the other events to just make sure the RN bridge is not chocking and loosing events.

I also started with a new JSI C++ implementation but emitting events from Java->C++->Java->C++ is too complex and I also have to deal with memory sharing and the awfulness that is the JNI interface. I haven't given up, but the resulting code is a lot harder to understand that the current implementation and might still not work.

I still need to test this latest package thoroughly but I've tried a lot of things and thought it might be useful if somebody has already worked around this issue

ospfranco avatar Jan 11 '23 11:01 ospfranco

It seems that Android cached previously founded devices and send this in the question request. Cited devices will not answer this question request because the app already know them.

But the problem is that the app already know them but don't has IP Adresses on Android.

It's possible to check that with Wireshark and filter by IP Adresses of mobile and wanted devices

MagnasiePro avatar Mar 06 '23 10:03 MagnasiePro

I don't really know why, but adding DNSSD like this zeroconf.scan(undefined, undefined, undefined, 'DNSSD') works every time on android

MagnasiePro avatar Mar 08 '23 09:03 MagnasiePro