balthazar / react-native-zeroconf

:satellite: Discover Zeroconf services using react-native
MIT License
222 stars 99 forks source link

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

Open oscadev opened 4 years ago

oscadev commented 4 years ago

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:

jalesingh commented 4 years ago

Same issue for me as well, please help to resolve

antoniodalessio commented 4 years ago

Same issue. Any solution?

wielski commented 4 years ago

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

whatdtech commented 3 years ago

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.

Here is an example by the contributor

jwh-hutchison commented 3 years ago

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.

LaRenarde commented 3 years ago

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

jkoutavas commented 3 years ago

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

MixMasterMitch commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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.

ospfranco commented 1 year ago

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:

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

MagnasiePro commented 1 year ago

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 commented 1 year ago

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