From 0ea2db2242e0c4cca28260d37bad5314f220611c Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Wed, 21 Jan 2026 17:42:37 +0100 Subject: [PATCH] Add resproxy support --- Sources/ipinfoKit/API/API.swift | 3 + .../ResproxyResponse/ResproxyResponse.swift | 38 ++++++++++++ Sources/ipinfoKit/Resproxy/Resproxy.swift | 31 ++++++++++ Tests/ipinfoKitTests/ResproxyTests.swift | 62 +++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 Sources/ipinfoKit/Model/ResproxyResponse/ResproxyResponse.swift create mode 100644 Sources/ipinfoKit/Resproxy/Resproxy.swift create mode 100644 Tests/ipinfoKitTests/ResproxyTests.swift diff --git a/Sources/ipinfoKit/API/API.swift b/Sources/ipinfoKit/API/API.swift index 2d460a9..07058fa 100644 --- a/Sources/ipinfoKit/API/API.swift +++ b/Sources/ipinfoKit/API/API.swift @@ -14,6 +14,7 @@ extension Service { case geoLocation(ipAddress: String) case ASN(asn: String) case batch(withFilter: Bool) + case resproxy(ipAddress: String) } } @@ -27,6 +28,8 @@ extension Service.Router { "\(Service.shared.ipInfoURL)/\(asn)/json" case .batch(let withFilter): "\(Service.shared.ipInfoURL)/batch" + (withFilter ? "&filter=1" : "") + case .resproxy(let ipAddress): + "\(Service.shared.ipInfoURL)/resproxy/\(ipAddress)" } } } diff --git a/Sources/ipinfoKit/Model/ResproxyResponse/ResproxyResponse.swift b/Sources/ipinfoKit/Model/ResproxyResponse/ResproxyResponse.swift new file mode 100644 index 0000000..608c206 --- /dev/null +++ b/Sources/ipinfoKit/Model/ResproxyResponse/ResproxyResponse.swift @@ -0,0 +1,38 @@ +// +// ResproxyResponse.swift +// +// +// + +import Foundation + +public struct ResproxyResponse: Codable, Sendable { + + // MARK: Lifecycle + + public init( + ip: String?, + lastSeen: String?, + percentDaysSeen: Double?, + service: String? + ) { + self.ip = ip + self.lastSeen = lastSeen + self.percentDaysSeen = percentDaysSeen + self.service = service + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case ip + case lastSeen = "last_seen" + case percentDaysSeen = "percent_days_seen" + case service + } + + public let ip: String? + public let lastSeen: String? + public let percentDaysSeen: Double? + public let service: String? +} diff --git a/Sources/ipinfoKit/Resproxy/Resproxy.swift b/Sources/ipinfoKit/Resproxy/Resproxy.swift new file mode 100644 index 0000000..81054b9 --- /dev/null +++ b/Sources/ipinfoKit/Resproxy/Resproxy.swift @@ -0,0 +1,31 @@ +// +// Resproxy.swift +// +// +// + +import Foundation + +extension IPINFO { + public func getResproxy( + ip: String, + completion: + @escaping (_ status: Response, _ data: ResproxyResponse?, _ msg: String?) -> Void + ) { + Service.shared.requestAPI(URL: .resproxy(ipAddress: ip), method: .get) { + status, data, msg in + switch status { + case .success: + do { + let decoder = JSONDecoder() + let response = try decoder.decode(ResproxyResponse.self, from: data) + completion(.success, response, nil) + } catch { + completion(.failure, nil, error.localizedDescription) + } + case .failure: + completion(.failure, nil, msg) + } + } + } +} diff --git a/Tests/ipinfoKitTests/ResproxyTests.swift b/Tests/ipinfoKitTests/ResproxyTests.swift new file mode 100644 index 0000000..b4c9ba6 --- /dev/null +++ b/Tests/ipinfoKitTests/ResproxyTests.swift @@ -0,0 +1,62 @@ +import Foundation +import Testing +import ipinfoKit + +@MainActor +struct ResproxyTests { + @Test func resproxyTest() async throws { + let response = try await withCheckedThrowingContinuation { continuation in + IPINFO.shared.getResproxy(ip: "175.107.211.204") { status, response, msg in + switch status { + case .success: + if let response = response { + continuation.resume(returning: response) + } else { + continuation.resume( + throwing: NSError( + domain: "ResproxyTests", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Response is nil"])) + } + case .failure: + continuation.resume( + throwing: NSError( + domain: "ResproxyTests", code: 2, + userInfo: [NSLocalizedDescriptionKey: msg ?? "Unknown error"])) + } + } + } + + #expect(response.ip == "175.107.211.204") + #expect(response.lastSeen != nil) + #expect(response.percentDaysSeen != nil) + #expect(response.service != nil) + } + + @Test func resproxyEmptyTest() async throws { + let response = try await withCheckedThrowingContinuation { continuation in + IPINFO.shared.getResproxy(ip: "8.8.8.8") { status, response, msg in + switch status { + case .success: + if let response = response { + continuation.resume(returning: response) + } else { + continuation.resume( + throwing: NSError( + domain: "ResproxyTests", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Response is nil"])) + } + case .failure: + continuation.resume( + throwing: NSError( + domain: "ResproxyTests", code: 2, + userInfo: [NSLocalizedDescriptionKey: msg ?? "Unknown error"])) + } + } + } + + #expect(response.ip == nil) + #expect(response.lastSeen == nil) + #expect(response.percentDaysSeen == nil) + #expect(response.service == nil) + } +}