@onekeyfe/react-native-sni-connect 3.0.71 → 3.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import Foundation
2
+ import CFNetwork
2
3
  import React
3
4
 
4
5
  private enum SniConnectError: Error {
@@ -25,7 +26,12 @@ final class SniConnectImpl: NSObject {
25
26
  let parsed = try Self.parseDictionary(config)
26
27
  handleRequest(config: parsed, resolve: resolve, reject: reject)
27
28
  } catch {
28
- SniConnectLog.error("Config parsing failed: \(error)")
29
+ SniConnectLog.error(SniConnectLog.event("sni_request_result", [
30
+ ("result", "error"),
31
+ ("code", "SNI_INVALID_CONFIG"),
32
+ ("nativeErrorClass", String(describing: type(of: error))),
33
+ ("stage", "parse_config"),
34
+ ]))
29
35
  reject("SNI_INVALID_CONFIG", "\(error)", error)
30
36
  }
31
37
  }
@@ -38,29 +44,41 @@ final class SniConnectImpl: NSObject {
38
44
  do {
39
45
  try Self.validate(config)
40
46
  } catch {
41
- SniConnectLog.error("Config validation failed: \(error)")
47
+ SniConnectLog.error(SniConnectLog.event("sni_request_result", [
48
+ ("result", "error"),
49
+ ("code", "SNI_INVALID_CONFIG"),
50
+ ("nativeErrorClass", String(describing: type(of: error))),
51
+ ("stage", "validate_config"),
52
+ ("requestIdHash", SniConnectLog.shortHash(config.requestId)),
53
+ ("hostname", config.hostname.lowercased()),
54
+ ("ipHash", SniConnectLog.shortHash(config.ip)),
55
+ ("ipFamily", SniConnectLog.ipFamily(config.ip)),
56
+ ("method", config.method.uppercased()),
57
+ ("timeoutMs", Int(config.effectiveTotalTimeout)),
58
+ ]))
42
59
  reject("SNI_INVALID_CONFIG", "\(error)", error)
43
60
  return
44
61
  }
45
62
 
46
63
  // Create the task and register it synchronously before JS can cancel it.
64
+ // Unregistering is owned by the result waiter below, so even a task that
65
+ // completes immediately cannot unregister before it has been registered.
47
66
  let task = Task { () -> SniConnectClient.Response in
48
67
  return try await client.performRequest(config: config)
49
68
  }
50
69
 
51
- // Register task if requestId is provided
52
- if let requestId = config.requestId {
53
- client.registerTask(task, for: requestId)
54
- }
70
+ let registrationToken = client.registerTask(task, for: config.requestId)
55
71
 
56
72
  // Handle the task result asynchronously
57
73
  Task {
74
+ defer {
75
+ client.unregisterTask(requestId: config.requestId, token: registrationToken)
76
+ }
77
+
58
78
  do {
59
79
  let result = try await task.value
60
- let responseBody = Self.serializeResponseData(result.data)
61
-
62
80
  resolve([
63
- "data": responseBody,
81
+ "data": result.data,
64
82
  "status": result.status,
65
83
  "statusText": result.statusText,
66
84
  "headers": result.headers,
@@ -71,7 +89,17 @@ final class SniConnectImpl: NSObject {
71
89
  } catch is CancellationError {
72
90
  reject("SNI_CANCELLED", "Request cancelled", nil)
73
91
  } catch {
74
- SniConnectLog.error("Request failed: \(error.localizedDescription)")
92
+ SniConnectLog.error(SniConnectLog.event("sni_request_result", [
93
+ ("result", "error"),
94
+ ("code", "SNI_UNKNOWN_ERROR"),
95
+ ("nativeErrorClass", String(describing: type(of: error))),
96
+ ("requestIdHash", SniConnectLog.shortHash(config.requestId)),
97
+ ("hostname", config.hostname.lowercased()),
98
+ ("ipHash", SniConnectLog.shortHash(config.ip)),
99
+ ("ipFamily", SniConnectLog.ipFamily(config.ip)),
100
+ ("method", config.method.uppercased()),
101
+ ("timeoutMs", Int(config.effectiveTotalTimeout)),
102
+ ]))
75
103
  reject("SNI_UNKNOWN_ERROR", error.localizedDescription, error)
76
104
  }
77
105
  }
@@ -83,8 +111,8 @@ final class SniConnectImpl: NSObject {
83
111
  resolve: @escaping RCTPromiseResolveBlock,
84
112
  reject: @escaping RCTPromiseRejectBlock
85
113
  ) {
86
- client.cancelRequest(requestId: requestId)
87
- resolve(["success": true])
114
+ let success = client.cancelRequest(requestId: requestId)
115
+ resolve(["success": success])
88
116
  }
89
117
 
90
118
  @objc
@@ -105,6 +133,19 @@ final class SniConnectImpl: NSObject {
105
133
  resolve(["success": true])
106
134
  }
107
135
 
136
+ @objc
137
+ public func isProxyActiveForUrl(
138
+ _ url: String,
139
+ resolve: @escaping RCTPromiseResolveBlock,
140
+ reject: @escaping RCTPromiseRejectBlock
141
+ ) {
142
+ do {
143
+ resolve(try Self.isProxyActive(forUrl: url))
144
+ } catch {
145
+ reject("SNI_INVALID_URL", "\(error)", error)
146
+ }
147
+ }
148
+
108
149
  private static func parseDictionary(_ dictionary: NSDictionary) throws -> SniConnectClient.RequestConfig {
109
150
  guard let ip = dictionary["ip"] as? String, !ip.isEmpty else {
110
151
  throw SniConnectError.invalidConfig("Missing ip")
@@ -141,22 +182,78 @@ final class SniConnectImpl: NSObject {
141
182
  private static func validate(_ config: SniConnectClient.RequestConfig) throws {
142
183
  try SniConnectValidation.validatePublicIP(config.ip)
143
184
  try SniConnectValidation.validateHostname(config.hostname)
144
- try SniConnectValidation.validateHeaders(config.headers)
145
- _ = try SniConnectValidation.normalizeMethod(config.method)
185
+ _ = try SniConnectValidation.normalizeHeaders(config.headers)
186
+ let method = try SniConnectValidation.normalizeMethod(config.method)
146
187
  _ = try SniConnectValidation.normalizePath(config.path)
188
+ try SniConnectValidation.validateRequestId(config.requestId)
189
+ try SniConnectValidation.validateTimeout(config.effectiveTotalTimeout)
190
+ try SniConnectValidation.validateBody(config.body)
191
+ try SniConnectValidation.validateMethodBody(method: method, body: config.body)
147
192
  }
148
193
 
149
- private static func serializeResponseData(_ data: Any) -> String {
150
- if let stringValue = data as? String {
151
- return stringValue
194
+ private static func isProxyActive(forUrl urlString: String) throws -> Bool {
195
+ let startedAt = Date()
196
+ guard let url = URL(string: urlString),
197
+ let scheme = url.scheme?.lowercased(),
198
+ ["http", "https"].contains(scheme),
199
+ let host = url.host else {
200
+ SniConnectLog.warn(SniConnectLog.event("proxy_preflight", [
201
+ ("platform", "ios"),
202
+ ("scheme", "unknown"),
203
+ ("host", "unknown"),
204
+ ("result", "invalid_url"),
205
+ ("source", "CFNetwork"),
206
+ ("proxyCount", 0),
207
+ ("elapsedMs", SniConnectLog.elapsedMs(since: startedAt)),
208
+ ]))
209
+ throw SniConnectError.invalidConfig("Invalid URL")
152
210
  }
153
211
 
154
- if JSONSerialization.isValidJSONObject(data),
155
- let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
156
- let jsonString = String(data: jsonData, encoding: .utf8) {
157
- return jsonString
212
+ guard let settings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
213
+ SniConnectLog.info(SniConnectLog.event("proxy_preflight", [
214
+ ("platform", "ios"),
215
+ ("scheme", scheme),
216
+ ("host", host),
217
+ ("result", false),
218
+ ("source", "CFNetwork"),
219
+ ("proxyCount", 0),
220
+ ("elapsedMs", SniConnectLog.elapsedMs(since: startedAt)),
221
+ ]))
222
+ return false
158
223
  }
159
224
 
160
- return String(describing: data)
225
+ let proxies = CFNetworkCopyProxiesForURL(url as CFURL, settings).takeRetainedValue() as NSArray
226
+ var proxyTypes: [String] = []
227
+ for proxy in proxies {
228
+ guard let proxyDictionary = proxy as? NSDictionary,
229
+ let type = proxyDictionary[kCFProxyTypeKey] as? String else {
230
+ continue
231
+ }
232
+ proxyTypes.append(type)
233
+ if type != (kCFProxyTypeNone as String) {
234
+ SniConnectLog.info(SniConnectLog.event("proxy_preflight", [
235
+ ("platform", "ios"),
236
+ ("scheme", scheme),
237
+ ("host", host),
238
+ ("result", true),
239
+ ("source", "CFNetwork"),
240
+ ("proxyCount", proxies.count),
241
+ ("proxyTypes", proxyTypes.joined(separator: ",")),
242
+ ("elapsedMs", SniConnectLog.elapsedMs(since: startedAt)),
243
+ ]))
244
+ return true
245
+ }
246
+ }
247
+ SniConnectLog.info(SniConnectLog.event("proxy_preflight", [
248
+ ("platform", "ios"),
249
+ ("scheme", scheme),
250
+ ("host", host),
251
+ ("result", false),
252
+ ("source", "CFNetwork"),
253
+ ("proxyCount", proxies.count),
254
+ ("proxyTypes", proxyTypes.joined(separator: ",")),
255
+ ("elapsedMs", SniConnectLog.elapsedMs(since: startedAt)),
256
+ ]))
257
+ return false
161
258
  }
162
259
  }