Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
node_modules/
dist/
*.log

*.pbxuser
Expand Down
2 changes: 0 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ repositories {

dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
// https://mvnrepository.com/artifact/com.github.andriydruk/rx2dnssd
api group: 'com.github.andriydruk', name: 'rx2dnssd', version: '0.9.16'
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
api group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,23 @@
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;
import java.util.Map;

public class ZeroConfImplFactory {
public static final String NSD_IMPL = "NSD";
public static final String DNSSD_IMPL = "DNSSD";

private Map<String, Zeroconf> zeroconfMap = new HashMap<>();

private ZeroconfModule zeroconfModule;
private ReactApplicationContext context;
private Zeroconf nsdService;

public ZeroConfImplFactory(ZeroconfModule zeroconfModule, ReactApplicationContext context) {
this.zeroconfModule = zeroconfModule;
this.context = context;
}

public Zeroconf getZeroconf(String implType) {
if (StringUtils.isBlank(implType)) implType = ZeroConfImplFactory.NSD_IMPL;
return getOrCreateImpl(implType);
}

private Zeroconf getOrCreateImpl(String implType) {
if (!zeroconfMap.containsKey(implType)) {
switch (implType) {
case NSD_IMPL:
zeroconfMap.put(NSD_IMPL, new NsdServiceImpl(zeroconfModule, context));
break;
case DNSSD_IMPL:
zeroconfMap.put(DNSSD_IMPL, new DnssdImpl(zeroconfModule, context));
break;
default:
throw new IllegalArgumentException(String.format("%s implType is not supported. Only %s and %s are supported", implType, NSD_IMPL, DNSSD_IMPL));
}
}

return zeroconfMap.get(implType);
public Zeroconf getZeroconf() {
return new NsdServiceImpl(zeroconfModule, context)
}

}
}
Original file line number Diff line number Diff line change
@@ -1,114 +1,177 @@
package com.balthazargronon.RCTZeroconf;


import android.util.Log;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import javax.annotation.Nullable;


/**
* React Native module for ZeroConf (NSD) service discovery and registration.
* Provides methods to scan, stop scanning, register, and unregister services.
*
* Created by Jeremy White on 8/1/2016.
* Copyright © 2016 Balthazar Gronon MIT
*/
public class ZeroconfModule extends ReactContextBaseJavaModule {

// Event names
public static final String EVENT_START = "RNZeroconfStart";
public static final String EVENT_STOP = "RNZeroconfStop";
public static final String EVENT_ERROR = "RNZeroconfError";
public static final String EVENT_FOUND = "RNZeroconfFound";
public static final String EVENT_REMOVE = "RNZeroconfRemove";
public static final String EVENT_RESOLVE = "RNZeroconfResolved";

public static final String EVENT_PUBLISHED = "RNZeroconfServiceRegistered";
public static final String EVENT_UNREGISTERED = "RNZeroconfServiceUnregistered";

// Service info keys
public static final String KEY_SERVICE_NAME = "name";
public static final String KEY_SERVICE_FULL_NAME = "fullName";
public static final String KEY_SERVICE_HOST = "host";
public static final String KEY_SERVICE_PORT = "port";
public static final String KEY_SERVICE_ADDRESSES = "addresses";
public static final String KEY_SERVICE_TXT = "txt";

private ZeroConfImplFactory zeroConfFactory;
// Logging tag
private static final String TAG = "ZeroconfModule";

// Factory for creating Zeroconf implementations
private final ZeroConfImplFactory zeroConfFactory;

/**
* Constructor for ZeroconfModule.
*
* @param reactContext The React application context.
*/
public ZeroconfModule(ReactApplicationContext reactContext) {
super(reactContext);
zeroConfFactory = new ZeroConfImplFactory(this, getReactApplicationContext());
this.zeroConfFactory = new ZeroConfImplFactory(this, reactContext);
}

/**
* Returns the name of this module. This is used to refer to the module from JavaScript.
*
* @return The name of the module.
*/
@Override
public String getName() {
return "RNZeroconf";
}

/**
* Initiates a scan for services with the specified type, protocol, and domain.
*
* @param type The service type (e.g., "http").
* @param protocol The protocol (e.g., "tcp").
* @param domain The domain (e.g., "local").
*/
@ReactMethod
public void scan(String type, String protocol, String domain, String implType) {
try {
getZeroconfImpl(implType).scan(type, protocol, domain);
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage(), e);
sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, "Exception During Scan: " + e.getMessage());
}
public void scan(String type, String protocol, String domain) {
executeZeroconfAction(() -> {
Zeroconf zeroconf = getZeroconfImpl();
zeroconf.scan(type, protocol, domain);
}, "Exception During Scan");
}

/**
* Stops any ongoing service discovery.
*/
@ReactMethod
public void stop(String implType) {
try {
getZeroconfImpl(implType).stop();
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage(), e);
sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, "Exception During Stop: " + e.getMessage());
}
public void stop() {
executeZeroconfAction(() -> {
Zeroconf zeroconf = getZeroconfImpl();
zeroconf.stop();
}, "Exception During Stop");
}

private Zeroconf getZeroconfImpl(String implType) {
return zeroConfFactory.getZeroconf(implType);
/**
* Registers a service with the specified parameters.
*
* @param type The service type (e.g., "http").
* @param protocol The protocol (e.g., "tcp").
* @param domain The domain (e.g., "local").
* @param name The service name.
* @param port The service port.
* @param txt TXT records as a map.
*/
@ReactMethod
public void registerService(String type, String protocol, String domain, String name, int port, ReadableMap txt) {
executeZeroconfAction(() -> {
Zeroconf zeroconf = getZeroconfImpl();
zeroconf.registerService(type, protocol, domain, name, port, txt);
}, "Exception During Register Service");
}

/**
* Unregisters a previously registered service by its name.
*
* @param serviceName The name of the service to unregister.
*/
@ReactMethod
public void registerService(String type, String protocol, String domain, String name, int port, ReadableMap txt, String implType) {
try {
getZeroconfImpl(implType).registerService(type, protocol, domain, name, port, txt);
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage(), e);
sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, "Exception During Register Service: " + e.getMessage());
public void unregisterService(String serviceName) {
executeZeroconfAction(() -> {
Zeroconf zeroconf = getZeroconfImpl();
zeroconf.unregisterService(serviceName);
}, "Exception During Unregister Service");
}

/**
* Sends an event to the JavaScript side.
*
* @param eventName The name of the event.
* @param params The event parameters.
*/
private void sendEvent(String eventName, @Nullable Object params) {
ReactApplicationContext reactContext = getReactApplicationContext();
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
} else {
Log.w(TAG, "ReactContext does not have an active Catalyst instance. Event not sent: " + eventName);
}
}

@ReactMethod
public void unregisterService(String serviceName, String implType) {
/**
* Executes a Zeroconf action with standardized error handling.
*
* @param action The Zeroconf action to execute.
* @param errorContext The context string for error messages.
*/
private void executeZeroconfAction(Runnable action, String errorContext) {
try {
getZeroconfImpl(implType).unregisterService(serviceName);
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage(), e);
sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, "Exception During Unregister Service: " + e.getMessage());
action.run();
} catch (Exception e) {
Log.e(TAG, errorContext + ": " + e.getMessage(), e);
sendEvent(EVENT_ERROR, errorContext + ": " + e.getMessage());
}
}

public void sendEvent(ReactContext reactContext,
String eventName,
@Nullable Object params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
/**
* Retrieves the Zeroconf implementation from the factory.
*
* @return The Zeroconf instance.
*/
private Zeroconf getZeroconfImpl() {
return zeroConfFactory.getZeroconf();
}

/**
* Cleans up resources when the Catalyst instance is destroyed.
*/
@Override
public void onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy();
try {
stop(ZeroConfImplFactory.NSD_IMPL);
stop(ZeroConfImplFactory.DNSSD_IMPL);
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage(), e);
sendEvent(getReactApplicationContext(), ZeroconfModule.EVENT_ERROR, "Exception During Catalyst Destroy: " + e.getMessage());
stop();
} catch (Exception e) {
Log.e(TAG, "Exception During Catalyst Destroy: " + e.getMessage(), e);
sendEvent(EVENT_ERROR, "Exception During Catalyst Destroy: " + e.getMessage());
}
}
}
}
Loading