Skip to content
Merged
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: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ android.iml
# Cocoapods
#
example/ios/Pods
example/ios/.xcode.env.local

# Ruby
example/vendor/
Expand Down
16 changes: 6 additions & 10 deletions example/ios/FindLocalDevicesExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@
baseConfigurationReference = 5826AF35579CB8523C3F6260 /* Pods-FindLocalDevicesExample-FindLocalDevicesExampleTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = TGL8X23Y88;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
Expand Down Expand Up @@ -441,6 +442,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = TGL8X23Y88;
INFOPLIST_FILE = FindLocalDevicesExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -466,7 +468,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 443N9AHV6P;
DEVELOPMENT_TEAM = TGL8X23Y88;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = FindLocalDevicesExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -494,7 +496,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 443N9AHV6P;
DEVELOPMENT_TEAM = TGL8X23Y88;
INFOPLIST_FILE = FindLocalDevicesExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -582,10 +584,7 @@
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = false;
Expand Down Expand Up @@ -653,10 +652,7 @@
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = false;
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ PODS:
- React-Mapbuffer (0.73.6):
- glog
- React-debug
- react-native-find-local-devices (2.0.11):
- react-native-find-local-devices (2.0.12):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
Expand Down Expand Up @@ -1308,7 +1308,7 @@ SPEC CHECKSUMS:
React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066
React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec
React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab
react-native-find-local-devices: 6f376c462282b0cfa6ec6383b1a6210400ee55b5
react-native-find-local-devices: c18479978549629f1a6ca878f27b75efd4906a14
React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f
React-NativeModulesApple: ae99dc0e80c9027f54572c45635449fbdc36e4f1
React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2
Expand All @@ -1334,4 +1334,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 2b0d399bfd4ebfbe27ac21cd2e6de729af603252

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
76 changes: 42 additions & 34 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Button, SafeAreaView, StyleSheet, Text, View } from 'react-native';
import React, { useEffect, useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import PortScanner, { type IDevice } from 'react-native-find-local-devices';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -30,40 +30,46 @@ const styles = StyleSheet.create({
export default function App() {
const [deviceFound, setDeviceFound] = useState<IDevice[]>([]);
const [isFinished, setIsFinished] = useState<boolean>(false);
const [scanner, setScanner] = useState<PortScanner | null>(null);
const [checkedDevice, setCheckedDevice] = useState<IDevice | null>(null);

const scanner = new PortScanner({
timeout: 40,
ports: [78999],
onDeviceFound: (device) => {
console.log('Found device!', device);
setDeviceFound((prev) => [...prev, device]);
},
onFinish: (devices) => {
console.log('Finished scanning', devices);
scanner.stop();
setIsFinished(true);
setCheckedDevice(null);
},
onCheck: (device) => {
console.log('Checking IP: ', device.ip, device.port);
setCheckedDevice(device);
},
onNoDevices: () => {
console.log('Done without results!');
setIsFinished(true);
setCheckedDevice(null);
},
onError: (error) => {
// Handle error messages for each socket connection
console.log('Error', error);
},
});
const init = () => {
setScanner(
new PortScanner({
timeout: 40,
ports: [50001],
onDeviceFound: (device) => {
console.log('Found device!', device);
setDeviceFound((prev) => [...prev, device]);
},
onFinish: (devices) => {
console.log('Finished scanning', devices);
setIsFinished(true);
setCheckedDevice(null);
},
onCheck: (device) => {
console.log('Checking IP: ', device.ip, device.port);
setCheckedDevice(device);
},
onNoDevices: () => {
console.log('Done without results!');
setIsFinished(true);
setCheckedDevice(null);
},
onError: (error) => {
// Handle error messages for each socket connection
console.log('Error', error);
},
})
);
};

useEffect(() => {
init();
}, []);

const start = () => {
console.log('init');
setDeviceFound([]);
setIsFinished(false);
scanner?.start();
};

Expand All @@ -72,14 +78,16 @@ export default function App() {
setCheckedDevice(null);
setIsFinished(false);
setDeviceFound([]);
setScanner(null);
init();
};

return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<Text style={styles.warning}>Wi-Fi connection is required!</Text>
{!checkedDevice && (
<View style={styles.wrapper}>
<Button title="Discover devices" color="steelblue" onPress={start} />
<Button title="Discover devices!" color="steelblue" onPress={start} />
</View>
)}
{checkedDevice && (
Expand Down Expand Up @@ -108,6 +116,6 @@ export default function App() {
<Button title="Cancel discovering" color="red" onPress={stop} />
</View>
)}
</SafeAreaView>
</View>
);
}
71 changes: 53 additions & 18 deletions ios/FindLocalDevices.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#import <sys/socket.h>
#import <ifaddrs.h>
#import <arpa/inet.h>
#include <fcntl.h>

@implementation FindLocalDevices {
BOOL isDiscovering;
Expand Down Expand Up @@ -65,27 +66,29 @@ - (void)discoverDevicesWithTimeout:(int)timeout ports:(NSArray *)ports {
NSMutableArray *devices = [NSMutableArray new];
dispatch_group_t group = dispatch_group_create();

dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); // Limit to 10 concurrent scans

for (NSNumber *port in ports) {
int portInt = [port intValue];

for (int i = 1; i <= 255 && isDiscovering; i++) {
NSString *host = [NSString stringWithFormat:@"%@.%d", subnet, i];

// Enter the dispatch group before starting the async operation
// Enter the dispatch group and wait for the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_enter(group);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDictionary *deviceInfo = @{@"ip": host, @"port": @(portInt)};
[self sendEventWithName:@"FLD_CHECK" body:deviceInfo];
BOOL isAvailable = [self socketIsAvailableForHost:host port:portInt timeout:timeout];

if (isAvailable) {
NSDictionary *deviceInfo = @{@"ip": host, @"port": @(portInt)};
[devices addObject:deviceInfo];
if (hasListeners) {
[self sendEventWithName:@"FLD_NEW_DEVICE_FOUND" body:deviceInfo];
}
[devices addObject:deviceInfo];
}

// Leave the dispatch group after the check
dispatch_semaphore_signal(semaphore); // Release semaphore
dispatch_group_leave(group);
});
}
Expand Down Expand Up @@ -134,41 +137,73 @@ - (NSString *)getSubnetAddress {
return subnet;
}

void setSocketTimeout(int sock, long milliseconds) {
struct timeval timeout;
timeout.tv_sec = milliseconds / 1000; // Convert to seconds
timeout.tv_usec = (milliseconds % 1000) * 1000; // Convert remainder to microseconds

// Set receive timeout
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
throw std::runtime_error("Failed to set receive timeout");
}

// Set send timeout
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
throw std::runtime_error("Failed to set send timeout");
}
}

- (BOOL)socketIsAvailableForHost:(NSString *)host port:(int)port timeout:(int)timeout {
// Create a socket
int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
RCTLogError(@"Failed to create socket.");
return NO;
}

// Define socket address
// Set the socket to non-blocking mode
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
close(sock);
return NO;
}

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, [host UTF8String], &addr.sin_addr);

// Configure the timeout in microseconds (milliseconds * 1000)
int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));

if (result == 0) {
close(sock);
return YES; // Connected successfully
} else if (errno != EINPROGRESS) {
close(sock);
return NO; // Connection failed immediately for non-timeout reasons
}

// Use select() to wait for the socket to become writable within the timeout
fd_set writefds;
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
tv.tv_sec = timeout / 1000; // Seconds
tv.tv_usec = (timeout % 1000) * 1000; // Microseconds

setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
FD_ZERO(&writefds);
FD_SET(sock, &writefds);

// Attempt to connect
int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
int selectResult = select(sock + 1, NULL, &writefds, NULL, &tv);
close(sock);

if (result == 0) {
if (selectResult > 0 && FD_ISSET(sock, &writefds)) {
// The connection attempt was successful
return YES;
} else {
// Report connection error if listeners are active
if (hasListeners) {
RCTLogInfo(@"FindLocalDevices: No open port at %@:%d within %d ms timeout", host, port, timeout);
NSDictionary *errorInfo = @{@"ip": host, @"port": @(port)};
[self sendEventWithName:@"FLD_CONNECTION_ERROR" body:errorInfo];
}
return NO;
return NO; // Connection failed or timed out
}
}

Expand Down
Loading