Skip to content

Commit

Permalink
Merge pull request #34 from RichardRNStudio/feat/2
Browse files Browse the repository at this point in the history
feat(#2): extend IOS implementation
  • Loading branch information
RichardRNStudio authored Nov 10, 2024
2 parents 40e7e6d + a9f770d commit 50f0fd7
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 15,817 deletions.
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

0 comments on commit 50f0fd7

Please sign in to comment.