Skip to content

Commit d0641db

Browse files
committed
Release 0.0.1
1 parent 0099a88 commit d0641db

5 files changed

Lines changed: 321 additions & 62 deletions

File tree

.gitignore

Lines changed: 7 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,8 @@
1-
# Xcode
2-
#
3-
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4-
5-
## User settings
1+
.DS_Store
2+
/.build
3+
/Packages
64
xcuserdata/
7-
8-
## Obj-C/Swift specific
9-
*.hmap
10-
11-
## App packaging
12-
*.ipa
13-
*.dSYM.zip
14-
*.dSYM
15-
16-
## Playgrounds
17-
timeline.xctimeline
18-
playground.xcworkspace
19-
20-
# Swift Package Manager
21-
#
22-
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
23-
# Packages/
24-
# Package.pins
25-
# Package.resolved
26-
# *.xcodeproj
27-
#
28-
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
29-
# hence it is not needed unless you have added a package configuration file to your project
30-
# .swiftpm
31-
32-
.build/
33-
34-
# CocoaPods
35-
#
36-
# We recommend against adding the Pods directory to your .gitignore. However
37-
# you should judge for yourself, the pros and cons are mentioned at:
38-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
39-
#
40-
# Pods/
41-
#
42-
# Add this line if you want to avoid checking in source code from the Xcode workspace
43-
# *.xcworkspace
44-
45-
# Carthage
46-
#
47-
# Add this line if you want to avoid checking in source code from Carthage dependencies.
48-
# Carthage/Checkouts
49-
50-
Carthage/Build/
51-
52-
# fastlane
53-
#
54-
# It is recommended to not store the screenshots in the git repo.
55-
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
56-
# For more information about the recommended setup visit:
57-
# https://docs.fastlane.tools/best-practices/source-control/#source-control
58-
59-
fastlane/report.xml
60-
fastlane/Preview.html
61-
fastlane/screenshots/**/*.png
62-
fastlane/test_output
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Package.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// swift-tools-version: 5.10
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "NetworkConnectivityKit",
8+
platforms: [
9+
.iOS(.v13),
10+
.macOS(.v10_15),
11+
.watchOS(.v6),
12+
.tvOS(.v13),
13+
.visionOS(.v1),
14+
],
15+
products: [
16+
// Products define the executables and libraries a package produces, making them visible to other packages.
17+
.library(
18+
name: "NetworkConnectivityKit",
19+
targets: ["NetworkConnectivityKit"]
20+
),
21+
],
22+
targets: [
23+
// Targets are the basic building blocks of a package, defining a module or a test suite.
24+
// Targets can depend on other targets in this package and products from dependencies.
25+
.target(
26+
name: "NetworkConnectivityKit"
27+
),
28+
.testTarget(
29+
name: "NetworkConnectivityKitTests",
30+
dependencies: ["NetworkConnectivityKit"]
31+
),
32+
]
33+
)

README.md

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,94 @@
1-
# NetworkConnectivityKit
1+
# NetworkConnectivityKit
2+
3+
NetworkConnectivityKit is a lightweight Swift library for testing network connectivity using multiple validation methods. It provides a reliable way to check internet connectivity by attempting connections to various well-known endpoints.
4+
5+
## Requirements
6+
7+
- Swift 5.9+
8+
- iOS/macOS/tvOS/watchOS/visionOS
9+
10+
## Installation
11+
12+
### Swift Package Manager
13+
14+
Add the following to your `Package.swift` file:
15+
16+
17+
```swift
18+
dependencies: [
19+
.package(url: "https://github.com/iranqiu/NetworkConnectivityKit.git", from: "0.0.1")
20+
]
21+
```
22+
23+
## Features
24+
25+
- Multiple connectivity testing methods
26+
- Concurrent connectivity checks
27+
- Customizable validation endpoints
28+
- Built-in support for common connectivity check endpoints:
29+
- Apple Captive Portal
30+
- Apple Library
31+
- Google Generate_204
32+
- Cloudflare
33+
- Vivo WiFi
34+
- MIUI Connect
35+
- Custom endpoint support with configurable validation
36+
37+
## Usage
38+
39+
### Basic Usage
40+
41+
```swift
42+
import NetworkConnectivityKit
43+
// Test connectivity using the default method (Apple Captive)
44+
let isConnected = await NetworkConnectivityKit.checkConnectivity()
45+
// Test connectivity using a specific method
46+
let isConnected = await NetworkConnectivityKit.checkConnectivity(using: .googleGstatic)
47+
```
48+
49+
### Testing Multiple Methods Concurrently
50+
51+
```swift
52+
// Test multiple endpoints concurrently
53+
let methods: Set<NetworkConnectivityKit.ConnectivityMethod> = [
54+
.appleCaptive,
55+
.googleGstatic,
56+
.cloudflare
57+
]
58+
59+
let isConnected = await NetworkConnectivityKit.checkConnectivity(using: methods)
60+
```
61+
62+
### Custom Endpoint
63+
64+
```swift
65+
// Create a custom endpoint with specific validation
66+
let customValidation = NetworkConnectivityKit.ConnectivityValidation { url, response, data in
67+
response.statusCode == 200
68+
}
69+
70+
let customMethod = NetworkConnectivityKit.ConnectivityMethod.custom(
71+
url: "https://your-endpoint.com/check",
72+
validation: customValidation
73+
)
74+
75+
let isConnected = await NetworkConnectivityKit.checkConnectivity(using: customMethod)
76+
```
77+
78+
## How It Works
79+
80+
NetworkConnectivityKit attempts to connect to specified endpoints and validates the responses. The library:
81+
82+
- Uses lightweight HTTP requests
83+
- Implements concurrent checking
84+
- Ignores cache data
85+
- Has a 3-second timeout for quick results
86+
- Cancels remaining requests once a successful connection is established
87+
88+
## License
89+
90+
MIT
91+
92+
## Author
93+
94+
iran.qiu@gmail.com
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//
2+
// NetworkConnectivityKit.swift
3+
// NetworkConnectivityKit
4+
//
5+
// Created by iran.qiu on 2024/11/14.
6+
//
7+
8+
import Foundation
9+
10+
// Enforce minimum Swift version for all platforms and build systems.
11+
#if swift(<5.9)
12+
#error("NetworkConnectivityKit doesn't support Swift versions below 5.9.")
13+
#endif
14+
15+
/// Current NetworkConnectivityKit version 0.0.1. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate.
16+
let version = "0.0.1"
17+
18+
public enum NetworkConnectivityKit {}
19+
20+
public extension NetworkConnectivityKit {
21+
/// Test connectivity using multiple methods.
22+
/// - Parameter methods: Set of ConnectivityMethod. Default is `appleCaptive`, `googleGstatic`, `vivoWifi`.
23+
/// - Returns: `true` if any of the methods succeeds, `false` otherwise.
24+
static func checkConnectivity(using methods: Set<ConnectivityMethod> = [.appleCaptive, .googleGstatic, .vivoWifi]) async -> Bool {
25+
guard !methods.isEmpty else {
26+
return false
27+
}
28+
if methods.count == 1, let method = methods.first {
29+
return await checkConnectivity(using: method)
30+
}
31+
// Initiate multiple requests concurrently, return true if one succeeds, and cancel the other Tasks.
32+
return await withTaskGroup(of: Bool.self) { group in
33+
for method in methods {
34+
group.addTask {
35+
await checkConnectivity(using: method)
36+
}
37+
}
38+
for await value in group {
39+
if value {
40+
// Return as soon as 1 succeeds, cancel if there are other requests
41+
group.cancelAll()
42+
return true
43+
}
44+
}
45+
return false
46+
}
47+
}
48+
49+
/// Test connectivity using a single method.
50+
/// - Parameter method: ConnectivityMethod. Default is `appleCaptive`.
51+
/// - Returns: `true` if the method succeeds, `false` otherwise.
52+
static func checkConnectivity(using method: ConnectivityMethod = .appleCaptive) async -> Bool {
53+
let success = await method.performRequest()
54+
return success
55+
}
56+
}
57+
58+
// MARK: - ConnectivityValidation
59+
60+
public extension NetworkConnectivityKit {
61+
struct ConnectivityValidation {
62+
public typealias Validation = (URL, HTTPURLResponse, Data) -> Bool
63+
64+
var validation: Validation
65+
66+
public init(validation: @escaping Validation) {
67+
self.validation = validation
68+
}
69+
70+
public init(statusCode: Int) {
71+
self.init { _, response, _ in
72+
response.statusCode == statusCode
73+
}
74+
}
75+
76+
public static func validation(_ validation: @escaping Validation) -> Self {
77+
ConnectivityValidation(validation: validation)
78+
}
79+
80+
public static let generate204Validation = ConnectivityValidation(statusCode: 204)
81+
82+
public static let generate200Validation = ConnectivityValidation(statusCode: 200)
83+
}
84+
}
85+
86+
// MARK: - ConnectivityMethod
87+
88+
public extension NetworkConnectivityKit {
89+
enum ConnectivityMethod {
90+
case appleCaptive
91+
case appleLibrary
92+
case googleGstatic
93+
case cloudflare
94+
case vivoWifi
95+
case miuiConnect
96+
case custom(url: String, validation: ConnectivityValidation)
97+
}
98+
}
99+
100+
extension NetworkConnectivityKit.ConnectivityMethod {
101+
var urlString: String {
102+
switch self {
103+
case .appleCaptive:
104+
return "http://captive.apple.com"
105+
case .appleLibrary:
106+
return "http://www.apple.com/library/test/success.html"
107+
case .googleGstatic:
108+
return "http://www.gstatic.com/generate_204"
109+
case .cloudflare:
110+
return "http://cp.cloudflare.com/generate_204"
111+
case .vivoWifi:
112+
return "http://wifi.vivo.com.cn/generate_204"
113+
case .miuiConnect:
114+
return "http://connect.rom.miui.com/generate_204"
115+
case .custom(let url, _):
116+
return url
117+
}
118+
}
119+
120+
func performRequest() async -> Bool {
121+
guard let url = URL(string: urlString) else {
122+
return false
123+
}
124+
do {
125+
let response = try await defaultURLSession.data(from: url)
126+
guard let httpResponse = response.1 as? HTTPURLResponse else {
127+
return false
128+
}
129+
let statusCode = httpResponse.statusCode
130+
switch self {
131+
case .appleCaptive, .appleLibrary:
132+
return statusCode == 200
133+
case .googleGstatic, .cloudflare, .vivoWifi, .miuiConnect:
134+
return statusCode == 204
135+
case .custom(_, let validation):
136+
return validation.validation(url, httpResponse, response.0)
137+
}
138+
} catch {
139+
return false
140+
}
141+
}
142+
143+
var defaultURLSession: URLSession { URLSession(configuration: Self.defaultURLSessionConfiguration) }
144+
145+
static var defaultURLSessionConfiguration: URLSessionConfiguration {
146+
let config = URLSessionConfiguration.default
147+
config.requestCachePolicy = .reloadIgnoringCacheData
148+
config.urlCache = nil
149+
config.timeoutIntervalForRequest = 3
150+
config.timeoutIntervalForResource = 3
151+
return config
152+
}
153+
}
154+
155+
extension NetworkConnectivityKit.ConnectivityMethod: Hashable, Equatable {
156+
public func hash(into hasher: inout Hasher) {
157+
hasher.combine(urlString)
158+
}
159+
160+
public static func == (lhs: NetworkConnectivityKit.ConnectivityMethod, rhs: NetworkConnectivityKit.ConnectivityMethod) -> Bool {
161+
lhs.urlString == rhs.urlString
162+
}
163+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@testable import NetworkConnectivityKit
2+
import XCTest
3+
4+
final class NetworkConnectivityKitTests: XCTestCase {
5+
func testSingle() async throws {
6+
let result = await NetworkConnectivityKit.checkConnectivity(using: .googleGstatic)
7+
debugPrint(result)
8+
}
9+
10+
func testMulti() async throws {
11+
let result = await NetworkConnectivityKit.checkConnectivity(using: [.appleCaptive, .googleGstatic, .vivoWifi])
12+
debugPrint(result)
13+
}
14+
15+
func testCustom() async throws {
16+
let result = await NetworkConnectivityKit.checkConnectivity(using: .custom(url: "http://www.v2ex.com/generate_204", validation: .generate204Validation))
17+
debugPrint(result)
18+
19+
let result2 = await NetworkConnectivityKit.checkConnectivity(using: .custom(url: "http://connectivitycheck.platform.hicloud.com/generate_204", validation: .validation { url, response, data in
20+
debugPrint("url: \(url), response: \(response), data: \(data)")
21+
return true
22+
}))
23+
}
24+
}

0 commit comments

Comments
 (0)