Skip to content

Commit fd1246a

Browse files
RovicnowYoyipopoaichuiniu
authored andcommitted
[Feature]Add Local Network Check api
As above, add the ability to detect local network status. doc: #62
1 parent 0313365 commit fd1246a

16 files changed

+343
-14
lines changed

DebugRouter.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Pod::Spec.new do |s|
2222
ss.source_files = 'debug_router/iOS/public/*.{h,m,mm}', 'debug_router/iOS/public/base/*.{h,m,mm}', 'debug_router/iOS/*.{h,m,mm}', 'debug_router/iOS/net/*.{h,m,mm}', 'debug_router/iOS/report/*.{h,m,mm}', 'debug_router/iOS/base/*.{h,m,mm}', 'debug_router/iOS/base/report/*.{h,m,mm}', 'debug_router/iOS/base/service/*.{h,m,mm}'
2323
ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_TARGET_SRCROOT}/debug_router/iOS/\"" }
2424
ss.dependency 'DebugRouter/Native'
25-
ss.public_header_files = 'debug_router/iOS/public/DebugRouter.h', 'debug_router/iOS/public/DebugRouterMessageHandler.h', 'debug_router/iOS/public/DebugRouterCommon.h', 'debug_router/iOS/public/DebugRouterMessageHandleResult.h', "debug_router/iOS/public/DebugRouterEventSender.h", "debug_router/iOS/public/DebugRouterGlobalHandler.h", "debug_router/iOS/public/DebugRouterSlot.h", "debug_router/iOS/public/base/DebugRouterReportServiceUtil.h", "debug_router/iOS/public/base/DebugRouterToast.h", "debug_router/iOS/public/DebugRouterSessionHandler.h", "debug_router/iOS/public/base/DebugRouterDefines.h", "debug_router/iOS/public/base/DebugRouterReportServiceProtocol.h", "debug_router/iOS/public/base/DebugRouterService.h", "debug_router/iOS/public/base/DebugRouterServiceProtocol.h"
25+
ss.public_header_files = 'debug_router/iOS/public/DebugRouter.h', 'debug_router/iOS/public/DebugRouterMessageHandler.h', 'debug_router/iOS/public/DebugRouterCommon.h', 'debug_router/iOS/public/DebugRouterMessageHandleResult.h', "debug_router/iOS/public/DebugRouterEventSender.h", "debug_router/iOS/public/DebugRouterGlobalHandler.h", "debug_router/iOS/public/DebugRouterSlot.h", "debug_router/iOS/public/base/DebugRouterReportServiceUtil.h", "debug_router/iOS/public/base/DebugRouterToast.h", "debug_router/iOS/public/DebugRouterSessionHandler.h", "debug_router/iOS/public/base/DebugRouterDefines.h", "debug_router/iOS/public/base/DebugRouterReportServiceProtocol.h", "debug_router/iOS/public/base/DebugRouterService.h", "debug_router/iOS/public/base/DebugRouterServiceProtocol.h", "debug_router/iOS/public/LocalNetworkPermissionChecker.h"
2626
end
2727

2828
s.subspec 'Native' do |ss|

debug_router/iOS/DebugRouter.mm

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
#import "DebugRouterLog.h"
1212
#import "DebugRouterReport.h"
1313
#import "DebugRouterSlot.h"
14-
#include "DebugRouterUtil.h"
14+
#import "DebugRouterUtil.h"
1515
#import "DebugRouterVersion.h"
16+
#import "LocalNetworkPermissionChecker.h"
17+
18+
#include <json/json.h>
1619
#include "debug_router/native/core/debug_router_config.h"
1720
#include "debug_router/native/core/debug_router_core.h"
1821
#include "debug_router/native/report/debug_router_native_report.h"
1922

20-
#include <json/json.h>
21-
2223
typedef enum : NSUInteger {
2324
CONNECTION_SCENE_UNINIT,
2425
FIRST_CONNECT,
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright 2025 The Lynx Authors. All rights reserved.
2+
// Licensed under the Apache License Version 2.0 that can be found in the
3+
// LICENSE file in the root directory of this source tree.
4+
5+
#import "LocalNetworkPermissionChecker.h"
6+
#import <Network/Network.h>
7+
#import <stdatomic.h>
8+
9+
@interface LocalNetworkPermissionChecker () <NSNetServiceDelegate>
10+
@property(nonatomic, strong) NSNetService *netService;
11+
@property(nonatomic, copy) LocalNetworkPermissionCompletion completion;
12+
@property(nonatomic, assign) BOOL didReturnResult;
13+
@end
14+
15+
@implementation LocalNetworkPermissionChecker
16+
17+
NSString *serviceType;
18+
static const NSTimeInterval kBonjourWaitTime = 3;
19+
static const NSTimeInterval kGlobalTimeout = 6;
20+
21+
static _Atomic(int) isChecking = 0;
22+
static LocalNetworkPermissionChecker *currentChecker = nil;
23+
24+
+ (void)checkPermissionWithCompletion:(LocalNetworkPermissionCompletion)completion {
25+
int expected = 0;
26+
// Thread safety
27+
if (!atomic_compare_exchange_strong(&isChecking, &expected, 1)) {
28+
NSError *error = [NSError
29+
errorWithDomain:@"LocalNetworkPermission"
30+
code:LocalNetworkPermissionNotDetermined
31+
userInfo:@{
32+
NSLocalizedDescriptionKey : @"Another permission check is already in progress"
33+
}];
34+
if (completion) {
35+
dispatch_async(dispatch_get_main_queue(), ^{
36+
completion(NO, error);
37+
});
38+
}
39+
return;
40+
}
41+
42+
// The first check after the APP is installed will pop up a prompt to guide you to open the local
43+
// network.
44+
if (![self isInfoPlistProperlyConfigured]) {
45+
NSError *error = [NSError
46+
errorWithDomain:@"LocalNetworkPermission"
47+
code:LocalNetworkPermissionErrorInfoPlistMissing
48+
userInfo:@{
49+
NSLocalizedDescriptionKey :
50+
@"Info.plist misses NSLocalNetworkUsageDescription or NSBonjourServices"
51+
}];
52+
// reset flag
53+
atomic_store(&isChecking, 0);
54+
completion(NO, error);
55+
return;
56+
}
57+
58+
currentChecker = [[LocalNetworkPermissionChecker alloc] init];
59+
currentChecker.completion = completion;
60+
[currentChecker startBonjourCheck];
61+
62+
// prevent timeout callback operation errors
63+
LocalNetworkPermissionChecker *capturedChecker = currentChecker;
64+
65+
dispatch_after(
66+
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kGlobalTimeout * NSEC_PER_SEC)),
67+
dispatch_get_main_queue(), ^{
68+
// ensure timeout callback only works on the corresponding checker instance
69+
if (capturedChecker == currentChecker && !capturedChecker.didReturnResult) {
70+
NSError *timeoutError = [NSError
71+
errorWithDomain:@"LocalNetworkPermission"
72+
code:LocalNetworkPermissionErrorTimeoutFallback
73+
userInfo:@{
74+
NSLocalizedDescriptionKey : @"Tiemout: Bonjour and TCP checking no resp"
75+
}];
76+
[capturedChecker returnResult:NO error:timeoutError];
77+
}
78+
});
79+
}
80+
81+
#pragma mark - Info.plist checking
82+
83+
+ (BOOL)isInfoPlistProperlyConfigured {
84+
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
85+
86+
NSString *desc = infoDict[@"NSLocalNetworkUsageDescription"];
87+
if (![desc isKindOfClass:[NSString class]] || desc.length == 0) {
88+
return NO;
89+
}
90+
91+
if (@available(iOS 14, *)) {
92+
NSArray *bonjourServices = infoDict[@"NSBonjourServices"];
93+
NSString *requiredService1 = @"_check_local_network_permission._tcp";
94+
NSString *requiredService2 = @"_check_local_network_permission._tcp.";
95+
if ([bonjourServices isKindOfClass:[NSArray class]]) {
96+
if ([bonjourServices containsObject:requiredService1]) {
97+
serviceType = requiredService1;
98+
} else if ([bonjourServices containsObject:requiredService2]) {
99+
serviceType = requiredService2;
100+
} else {
101+
return NO;
102+
}
103+
} else {
104+
return NO;
105+
}
106+
107+
} else {
108+
NSLog(@"System before iOS 14, local network permissions were always on");
109+
}
110+
111+
return YES;
112+
}
113+
114+
#pragma mark - Bonjour checking
115+
116+
- (void)startBonjourCheck {
117+
self.netService = [[NSNetService alloc] initWithDomain:@"local."
118+
type:serviceType
119+
name:@"LocalNetCheckService"
120+
port:12345];
121+
self.netService.delegate = self;
122+
[self.netService publish];
123+
124+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBonjourWaitTime * NSEC_PER_SEC)),
125+
dispatch_get_main_queue(), ^{
126+
if (!self.didReturnResult) {
127+
[self.netService stop];
128+
// Bonjour no resp, fallback to TCP checking
129+
[self fallbackTCPCheck];
130+
}
131+
});
132+
}
133+
134+
- (void)netServiceDidPublish:(NSNetService *)sender {
135+
[self.netService stop];
136+
[self returnResult:YES error:nil];
137+
}
138+
139+
- (void)netService:(NSNetService *)sender
140+
didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict {
141+
[self.netService stop];
142+
143+
// Bonjour publish failed,try TCP checking
144+
[self fallbackTCPCheck];
145+
}
146+
147+
#pragma mark - TCP checking
148+
149+
- (void)fallbackTCPCheck {
150+
nw_endpoint_t endpoint = nw_endpoint_create_host("192.168.1.1", "12345");
151+
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION,
152+
NW_PARAMETERS_DEFAULT_CONFIGURATION);
153+
nw_connection_t connection = nw_connection_create(endpoint, parameters);
154+
nw_connection_set_queue(connection, dispatch_get_main_queue());
155+
156+
nw_connection_set_state_changed_handler(
157+
connection, ^(nw_connection_state_t state, nw_error_t error) {
158+
if (state == nw_connection_state_ready) {
159+
nw_connection_cancel(connection);
160+
[self returnResult:YES error:nil];
161+
} else if (state == nw_connection_state_failed) {
162+
nw_connection_cancel(connection);
163+
NSError *tcpError =
164+
[NSError errorWithDomain:@"LocalNetworkPermission"
165+
code:LocalNetworkPermissionErrorTCPConnectionFailed
166+
userInfo:@{
167+
NSLocalizedDescriptionKey :
168+
@"TCP connection failed, LocalNetworking has been denied"
169+
}];
170+
[self returnResult:NO error:tcpError];
171+
}
172+
});
173+
174+
nw_connection_start(connection);
175+
}
176+
177+
#pragma mark - Result
178+
179+
- (void)returnResult:(BOOL)granted error:(NSError *)error {
180+
if (self.didReturnResult) return;
181+
self.didReturnResult = YES;
182+
183+
if (self.completion) {
184+
self.completion(granted, error);
185+
self.completion = nil;
186+
}
187+
188+
// reset flag
189+
atomic_store(&isChecking, 0);
190+
currentChecker = nil;
191+
}
192+
193+
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025 The Lynx Authors. All rights reserved.
2+
// Licensed under the Apache License Version 2.0 that can be found in the
3+
// LICENSE file in the root directory of this source tree.
4+
5+
#import <Foundation/Foundation.h>
6+
7+
NS_ASSUME_NONNULL_BEGIN
8+
9+
typedef NS_ENUM(NSInteger, LocalNetworkPermissionErrorCode) {
10+
LocalNetworkPermissionNotDetermined = -1, // Another check is already in progress
11+
LocalNetworkPermissionErrorNone = 0, // Succeed
12+
LocalNetworkPermissionErrorInfoPlistMissing = 1, // Info.plist missing
13+
LocalNetworkPermissionErrorTCPConnectionFailed = 2, // TCP checking failed
14+
LocalNetworkPermissionErrorTimeoutFallback = 3, // All timeout
15+
};
16+
17+
typedef void (^LocalNetworkPermissionCompletion)(BOOL granted, NSError* _Nullable error);
18+
19+
@interface LocalNetworkPermissionChecker : NSObject
20+
21+
+ (void)checkPermissionWithCompletion:(LocalNetworkPermissionCompletion _Nonnull)completion;
22+
23+
@end
24+
25+
NS_ASSUME_NONNULL_END

test/e2e_test/iOSExample/DebugRouter.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
00BD225CC117980DF0C1A3FA /* libPods-DebugRouter_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D4DD3EFFBD5903AD9E874224 /* libPods-DebugRouter_Example.a */; };
11+
2B5E77842DE4475900377B3B /* DebugRouterSceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B5E77832DE4475700377B3B /* DebugRouterSceneDelegate.m */; };
1112
3B4BE69F093F97F728381BED /* (null) in Frameworks */ = {isa = PBXBuildFile; };
1213
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
1314
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
@@ -41,6 +42,8 @@
4142
2033F20FBD8AFE7D8271E46F /* DebugRouter.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = DebugRouter.podspec; path = ../DebugRouter.podspec; sourceTree = "<group>"; };
4243
2B0529182A9D93C900F5FB18 /* libDebugRouter.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libDebugRouter.a; sourceTree = BUILT_PRODUCTS_DIR; };
4344
2B05291E2A9D93C900F5FB18 /* libPods-DebugRouter_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-DebugRouter_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
45+
2B5E77822DE4473800377B3B /* DebugRouterSceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DebugRouterSceneDelegate.h; sourceTree = "<group>"; };
46+
2B5E77832DE4475700377B3B /* DebugRouterSceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DebugRouterSceneDelegate.m; sourceTree = "<group>"; };
4447
2B6EEC6A2A9DF1E100F1E004 /* libPods-DebugRouter_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-DebugRouter_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
4548
3965160A962805297A5F058F /* Pods-DebugRouter_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DebugRouter_Example.release.xcconfig"; path = "Target Support Files/Pods-DebugRouter_Example/Pods-DebugRouter_Example.release.xcconfig"; sourceTree = "<group>"; };
4649
6003F58A195388D20070C39A /* DebugRouter_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DebugRouter_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -151,6 +154,8 @@
151154
6003F593195388D20070C39A /* Example for DebugRouter */ = {
152155
isa = PBXGroup;
153156
children = (
157+
2B5E77832DE4475700377B3B /* DebugRouterSceneDelegate.m */,
158+
2B5E77822DE4473800377B3B /* DebugRouterSceneDelegate.h */,
154159
6003F59C195388D20070C39A /* DebugRouterAppDelegate.h */,
155160
6003F59D195388D20070C39A /* DebugRouterAppDelegate.m */,
156161
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
@@ -359,6 +364,7 @@
359364
files = (
360365
6003F59E195388D20070C39A /* DebugRouterAppDelegate.m in Sources */,
361366
6003F5A7195388D20070C39A /* DebugRouterViewController.m in Sources */,
367+
2B5E77842DE4475900377B3B /* DebugRouterSceneDelegate.m in Sources */,
362368
6003F59A195388D20070C39A /* main.m in Sources */,
363369
);
364370
runOnlyForDeploymentPostprocessing = 0;

test/e2e_test/iOSExample/DebugRouter/Base.lproj/Main.storyboard

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<!--View Controller-->
1313
<scene sceneID="wQg-tq-qST">
1414
<objects>
15-
<viewController id="whP-gf-Uak" customClass="DEBUGROUTERViewController" sceneMemberID="viewController">
15+
<viewController id="whP-gf-Uak" customClass="DebugRouterViewController" sceneMemberID="viewController">
1616
<layoutGuides>
1717
<viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
1818
<viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>

test/e2e_test/iOSExample/DebugRouter/DebugRouter-Info.plist

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,30 @@
4545
<string>UIInterfaceOrientationLandscapeLeft</string>
4646
<string>UIInterfaceOrientationLandscapeRight</string>
4747
</array>
48+
<key>UIApplicationSceneManifest</key>
49+
<dict>
50+
<key>UIApplicationSupportsMultipleScenes</key>
51+
<false/>
52+
<key>UISceneConfigurations</key>
53+
<dict>
54+
<key>UIWindowSceneSessionRoleApplication</key>
55+
<array>
56+
<dict>
57+
<key>UISceneConfigurationName</key>
58+
<string>Default Configuration</string>
59+
<key>UISceneDelegateClassName</key>
60+
<string>DebugRouterSceneDelegate</string>
61+
<key>UISceneStoryboardFile</key>
62+
<string>Main</string>
63+
</dict>
64+
</array>
65+
</dict>
66+
</dict>
67+
<key>NSLocalNetworkUsageDescription</key>
68+
<string>We need local network permission</string>
69+
<key>NSBonjourServices</key>
70+
<array>
71+
<string>_check_local_network_permission._tcp</string>
72+
</array>
4873
</dict>
4974
</plist>

test/e2e_test/iOSExample/DebugRouter/DebugRouterAppDelegate.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
// Licensed under the Apache License Version 2.0 that can be found in the
33
// LICENSE file in the root directory of this source tree.
44

5-
@import UIKit;
5+
#import <UIKit/UIKit.h>
66

77
@interface DebugRouterAppDelegate : UIResponder <UIApplicationDelegate>
88

9-
@property(strong, nonatomic) UIWindow *window;
10-
119
@end

test/e2e_test/iOSExample/DebugRouter/DebugRouterAppDelegate.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ - (BOOL)application:(UIApplication *)application
1414
return YES;
1515
}
1616

17+
- (UISceneConfiguration *)application:(UIApplication *)application
18+
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
19+
options:(UISceneConnectionOptions *)options {
20+
// Called when a new scene session is being created.
21+
// Use this method to select a configuration to create the new scene with.
22+
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
23+
sessionRole:connectingSceneSession.role];
24+
}
25+
1726
- (void)applicationWillResignActive:(UIApplication *)application {
1827
// Sent when the application is about to move from active to inactive state.
1928
// This can occur for certain types of temporary interruptions (such as an
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2025 The Lynx Authors. All rights reserved.
2+
// Licensed under the Apache License Version 2.0 that can be found in the
3+
// LICENSE file in the root directory of this source tree.
4+
5+
#import <UIKit/UIKit.h>
6+
7+
@interface DebugRouterSceneDelegate : UIResponder <UIWindowSceneDelegate>
8+
9+
@property(strong, nonatomic) UIWindow* window;
10+
11+
@end

0 commit comments

Comments
 (0)