Skip to content

Commit 50f0fd7

Browse files
Merge pull request #34 from RichardRNStudio/feat/2
feat(#2): extend IOS implementation
2 parents 40e7e6d + a9f770d commit 50f0fd7

File tree

6 files changed

+105
-15817
lines changed

6 files changed

+105
-15817
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ android.iml
4343
# Cocoapods
4444
#
4545
example/ios/Pods
46+
example/ios/.xcode.env.local
4647

4748
# Ruby
4849
example/vendor/

example/ios/FindLocalDevicesExample.xcodeproj/project.pbxproj

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@
413413
baseConfigurationReference = 5826AF35579CB8523C3F6260 /* Pods-FindLocalDevicesExample-FindLocalDevicesExampleTests.debug.xcconfig */;
414414
buildSettings = {
415415
BUNDLE_LOADER = "$(TEST_HOST)";
416+
DEVELOPMENT_TEAM = TGL8X23Y88;
416417
GCC_PREPROCESSOR_DEFINITIONS = (
417418
"DEBUG=1",
418419
"$(inherited)",
@@ -441,6 +442,7 @@
441442
buildSettings = {
442443
BUNDLE_LOADER = "$(TEST_HOST)";
443444
COPY_PHASE_STRIP = NO;
445+
DEVELOPMENT_TEAM = TGL8X23Y88;
444446
INFOPLIST_FILE = FindLocalDevicesExampleTests/Info.plist;
445447
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
446448
LD_RUNPATH_SEARCH_PATHS = (
@@ -466,7 +468,7 @@
466468
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
467469
CLANG_ENABLE_MODULES = YES;
468470
CURRENT_PROJECT_VERSION = 1;
469-
DEVELOPMENT_TEAM = 443N9AHV6P;
471+
DEVELOPMENT_TEAM = TGL8X23Y88;
470472
ENABLE_BITCODE = NO;
471473
INFOPLIST_FILE = FindLocalDevicesExample/Info.plist;
472474
LD_RUNPATH_SEARCH_PATHS = (
@@ -494,7 +496,7 @@
494496
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
495497
CLANG_ENABLE_MODULES = YES;
496498
CURRENT_PROJECT_VERSION = 1;
497-
DEVELOPMENT_TEAM = 443N9AHV6P;
499+
DEVELOPMENT_TEAM = TGL8X23Y88;
498500
INFOPLIST_FILE = FindLocalDevicesExample/Info.plist;
499501
LD_RUNPATH_SEARCH_PATHS = (
500502
"$(inherited)",
@@ -582,10 +584,7 @@
582584
"-DFOLLY_USE_LIBCPP=1",
583585
"-DFOLLY_CFG_NO_COROUTINES=1",
584586
);
585-
OTHER_LDFLAGS = (
586-
"$(inherited)",
587-
" ",
588-
);
587+
OTHER_LDFLAGS = "$(inherited) ";
589588
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
590589
SDKROOT = iphoneos;
591590
USE_HERMES = false;
@@ -653,10 +652,7 @@
653652
"-DFOLLY_USE_LIBCPP=1",
654653
"-DFOLLY_CFG_NO_COROUTINES=1",
655654
);
656-
OTHER_LDFLAGS = (
657-
"$(inherited)",
658-
" ",
659-
);
655+
OTHER_LDFLAGS = "$(inherited) ";
660656
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
661657
SDKROOT = iphoneos;
662658
USE_HERMES = false;

example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,7 @@ PODS:
911911
- React-Mapbuffer (0.73.6):
912912
- glog
913913
- React-debug
914-
- react-native-find-local-devices (2.0.11):
914+
- react-native-find-local-devices (2.0.12):
915915
- glog
916916
- RCT-Folly (= 2022.05.16.00)
917917
- React-Core
@@ -1308,7 +1308,7 @@ SPEC CHECKSUMS:
13081308
React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066
13091309
React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec
13101310
React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab
1311-
react-native-find-local-devices: 6f376c462282b0cfa6ec6383b1a6210400ee55b5
1311+
react-native-find-local-devices: c18479978549629f1a6ca878f27b75efd4906a14
13121312
React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f
13131313
React-NativeModulesApple: ae99dc0e80c9027f54572c45635449fbdc36e4f1
13141314
React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2
@@ -1334,4 +1334,4 @@ SPEC CHECKSUMS:
13341334

13351335
PODFILE CHECKSUM: 2b0d399bfd4ebfbe27ac21cd2e6de729af603252
13361336

1337-
COCOAPODS: 1.15.2
1337+
COCOAPODS: 1.16.2

example/src/App.tsx

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useState } from 'react';
2-
import { Button, SafeAreaView, StyleSheet, Text, View } from 'react-native';
1+
import React, { useEffect, useState } from 'react';
2+
import { Button, StyleSheet, Text, View } from 'react-native';
33
import PortScanner, { type IDevice } from 'react-native-find-local-devices';
44

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

35-
const scanner = new PortScanner({
36-
timeout: 40,
37-
ports: [78999],
38-
onDeviceFound: (device) => {
39-
console.log('Found device!', device);
40-
setDeviceFound((prev) => [...prev, device]);
41-
},
42-
onFinish: (devices) => {
43-
console.log('Finished scanning', devices);
44-
scanner.stop();
45-
setIsFinished(true);
46-
setCheckedDevice(null);
47-
},
48-
onCheck: (device) => {
49-
console.log('Checking IP: ', device.ip, device.port);
50-
setCheckedDevice(device);
51-
},
52-
onNoDevices: () => {
53-
console.log('Done without results!');
54-
setIsFinished(true);
55-
setCheckedDevice(null);
56-
},
57-
onError: (error) => {
58-
// Handle error messages for each socket connection
59-
console.log('Error', error);
60-
},
61-
});
36+
const init = () => {
37+
setScanner(
38+
new PortScanner({
39+
timeout: 40,
40+
ports: [50001],
41+
onDeviceFound: (device) => {
42+
console.log('Found device!', device);
43+
setDeviceFound((prev) => [...prev, device]);
44+
},
45+
onFinish: (devices) => {
46+
console.log('Finished scanning', devices);
47+
setIsFinished(true);
48+
setCheckedDevice(null);
49+
},
50+
onCheck: (device) => {
51+
console.log('Checking IP: ', device.ip, device.port);
52+
setCheckedDevice(device);
53+
},
54+
onNoDevices: () => {
55+
console.log('Done without results!');
56+
setIsFinished(true);
57+
setCheckedDevice(null);
58+
},
59+
onError: (error) => {
60+
// Handle error messages for each socket connection
61+
console.log('Error', error);
62+
},
63+
})
64+
);
65+
};
66+
67+
useEffect(() => {
68+
init();
69+
}, []);
6270

6371
const start = () => {
6472
console.log('init');
65-
setDeviceFound([]);
66-
setIsFinished(false);
6773
scanner?.start();
6874
};
6975

@@ -72,14 +78,16 @@ export default function App() {
7278
setCheckedDevice(null);
7379
setIsFinished(false);
7480
setDeviceFound([]);
81+
setScanner(null);
82+
init();
7583
};
7684

7785
return (
78-
<SafeAreaView style={styles.container}>
86+
<View style={styles.container}>
7987
<Text style={styles.warning}>Wi-Fi connection is required!</Text>
8088
{!checkedDevice && (
8189
<View style={styles.wrapper}>
82-
<Button title="Discover devices" color="steelblue" onPress={start} />
90+
<Button title="Discover devices!" color="steelblue" onPress={start} />
8391
</View>
8492
)}
8593
{checkedDevice && (
@@ -108,6 +116,6 @@ export default function App() {
108116
<Button title="Cancel discovering" color="red" onPress={stop} />
109117
</View>
110118
)}
111-
</SafeAreaView>
119+
</View>
112120
);
113121
}

ios/FindLocalDevices.mm

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#import <sys/socket.h>
88
#import <ifaddrs.h>
99
#import <arpa/inet.h>
10+
#include <fcntl.h>
1011

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

69+
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); // Limit to 10 concurrent scans
70+
6871
for (NSNumber *port in ports) {
6972
int portInt = [port intValue];
70-
7173
for (int i = 1; i <= 255 && isDiscovering; i++) {
7274
NSString *host = [NSString stringWithFormat:@"%@.%d", subnet, i];
7375

74-
// Enter the dispatch group before starting the async operation
76+
// Enter the dispatch group and wait for the semaphore
77+
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
7578
dispatch_group_enter(group);
7679

7780
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
81+
NSDictionary *deviceInfo = @{@"ip": host, @"port": @(portInt)};
82+
[self sendEventWithName:@"FLD_CHECK" body:deviceInfo];
7883
BOOL isAvailable = [self socketIsAvailableForHost:host port:portInt timeout:timeout];
79-
8084
if (isAvailable) {
8185
NSDictionary *deviceInfo = @{@"ip": host, @"port": @(portInt)};
86+
[devices addObject:deviceInfo];
8287
if (hasListeners) {
8388
[self sendEventWithName:@"FLD_NEW_DEVICE_FOUND" body:deviceInfo];
8489
}
85-
[devices addObject:deviceInfo];
8690
}
87-
88-
// Leave the dispatch group after the check
91+
dispatch_semaphore_signal(semaphore); // Release semaphore
8992
dispatch_group_leave(group);
9093
});
9194
}
@@ -134,41 +137,73 @@ - (NSString *)getSubnetAddress {
134137
return subnet;
135138
}
136139

140+
void setSocketTimeout(int sock, long milliseconds) {
141+
struct timeval timeout;
142+
timeout.tv_sec = milliseconds / 1000; // Convert to seconds
143+
timeout.tv_usec = (milliseconds % 1000) * 1000; // Convert remainder to microseconds
144+
145+
// Set receive timeout
146+
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
147+
throw std::runtime_error("Failed to set receive timeout");
148+
}
149+
150+
// Set send timeout
151+
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
152+
throw std::runtime_error("Failed to set send timeout");
153+
}
154+
}
155+
137156
- (BOOL)socketIsAvailableForHost:(NSString *)host port:(int)port timeout:(int)timeout {
138-
// Create a socket
139157
int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
140158
if (sock < 0) {
141159
RCTLogError(@"Failed to create socket.");
142160
return NO;
143161
}
144162

145-
// Define socket address
163+
// Set the socket to non-blocking mode
164+
int flags = fcntl(sock, F_GETFL, 0);
165+
if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
166+
close(sock);
167+
return NO;
168+
}
169+
146170
struct sockaddr_in addr;
147171
addr.sin_family = AF_INET;
148172
addr.sin_port = htons(port);
149173
inet_pton(AF_INET, [host UTF8String], &addr.sin_addr);
150174

151-
// Configure the timeout in microseconds (milliseconds * 1000)
175+
int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
176+
177+
if (result == 0) {
178+
close(sock);
179+
return YES; // Connected successfully
180+
} else if (errno != EINPROGRESS) {
181+
close(sock);
182+
return NO; // Connection failed immediately for non-timeout reasons
183+
}
184+
185+
// Use select() to wait for the socket to become writable within the timeout
186+
fd_set writefds;
152187
struct timeval tv;
153-
tv.tv_sec = timeout / 1000;
154-
tv.tv_usec = (timeout % 1000) * 1000;
188+
tv.tv_sec = timeout / 1000; // Seconds
189+
tv.tv_usec = (timeout % 1000) * 1000; // Microseconds
155190

156-
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
157-
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
191+
FD_ZERO(&writefds);
192+
FD_SET(sock, &writefds);
158193

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

163-
if (result == 0) {
197+
if (selectResult > 0 && FD_ISSET(sock, &writefds)) {
198+
// The connection attempt was successful
164199
return YES;
165200
} else {
201+
// Report connection error if listeners are active
166202
if (hasListeners) {
167-
RCTLogInfo(@"FindLocalDevices: No open port at %@:%d within %d ms timeout", host, port, timeout);
168203
NSDictionary *errorInfo = @{@"ip": host, @"port": @(port)};
169204
[self sendEventWithName:@"FLD_CONNECTION_ERROR" body:errorInfo];
170205
}
171-
return NO;
206+
return NO; // Connection failed or timed out
172207
}
173208
}
174209

0 commit comments

Comments
 (0)