@onekeyfe/react-native-sni-connect 3.0.81-alpha.8 → 3.0.81

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.
@@ -32,6 +32,7 @@ enum SniConnectValidation {
32
32
  static let maxTotalHeaderBytes = 32 * 1024
33
33
  static let maxActiveRequests = 64
34
34
  static let maxActiveRequestsPerPair = 16
35
+ static let maxPendingRequests = 256
35
36
 
36
37
  /// HTTP methods the module is allowed to issue.
37
38
  private static let allowedMethods: Set<String> = [
@@ -212,6 +213,16 @@ enum SniConnectValidation {
212
213
  throw ValidationError.invalidIP(ip)
213
214
  }
214
215
 
216
+ static func canonicalIPKey(_ ip: String) -> String {
217
+ if let v4 = parseIPv4(ip) {
218
+ return "4:" + v4.map { String($0) }.joined(separator: ".")
219
+ }
220
+ if let v6 = parseIPv6(ip) {
221
+ return "6:" + v6.map { String($0) }.joined(separator: ".")
222
+ }
223
+ return ip
224
+ }
225
+
215
226
  private static func parseIPv4(_ ip: String) -> [UInt8]? {
216
227
  var addr = in_addr()
217
228
  guard ip.withCString({ inet_pton(AF_INET, $0, &addr) }) == 1 else { return nil }
@@ -301,16 +312,27 @@ enum SniConnectValidation {
301
312
  }
302
313
  }
303
314
 
304
- final class SniConnectRequestLimiter {
305
- final class Token {
315
+ final class SniConnectRequestLimiter: @unchecked Sendable {
316
+ static let shared = SniConnectRequestLimiter()
317
+
318
+ struct Snapshot {
319
+ let activeRequests: Int
320
+ let activeRequestsForPair: Int
321
+ let pendingRequests: Int
322
+ let pendingRequestsForPair: Int
323
+ let activeRequestIdsForPair: [String]
324
+ let pendingRequestIdsForPair: [String]
325
+ }
326
+
327
+ final class Token: @unchecked Sendable {
306
328
  private weak var limiter: SniConnectRequestLimiter?
307
- private let key: String
329
+ private let id: UUID
308
330
  private let lock = NSLock()
309
331
  private var released = false
310
332
 
311
- fileprivate init(limiter: SniConnectRequestLimiter, key: String) {
333
+ fileprivate init(limiter: SniConnectRequestLimiter, id: UUID) {
312
334
  self.limiter = limiter
313
- self.key = key
335
+ self.id = id
314
336
  }
315
337
 
316
338
  func release() {
@@ -321,7 +343,7 @@ final class SniConnectRequestLimiter {
321
343
  }
322
344
  released = true
323
345
  lock.unlock()
324
- limiter?.release(key: key)
346
+ limiter?.release(id: id)
325
347
  }
326
348
 
327
349
  deinit {
@@ -329,65 +351,216 @@ final class SniConnectRequestLimiter {
329
351
  }
330
352
  }
331
353
 
354
+ private final class CancellationState: @unchecked Sendable {
355
+ private let lock = NSLock()
356
+ private var cancelled = false
357
+
358
+ var isCancelled: Bool {
359
+ lock.lock()
360
+ defer { lock.unlock() }
361
+ return cancelled
362
+ }
363
+
364
+ func cancel() {
365
+ lock.lock()
366
+ cancelled = true
367
+ lock.unlock()
368
+ }
369
+ }
370
+
371
+ private struct PendingRequest {
372
+ let id: UUID
373
+ let key: String
374
+ let requestId: String?
375
+ let continuation: CheckedContinuation<Token, Error>
376
+ }
377
+
378
+ private struct ActiveRequest {
379
+ let key: String
380
+ let requestId: String?
381
+ }
382
+
332
383
  private let maxActiveRequests: Int
333
384
  private let maxActiveRequestsPerPair: Int
385
+ private let maxPendingRequests: Int
334
386
  private let queue = DispatchQueue(label: "com.onekey.sni.connect.request-limiter")
335
387
  private var activeRequests = 0
336
388
  private var activeRequestsByPair: [String: Int] = [:]
389
+ private var activeRequestsByID: [UUID: ActiveRequest] = [:]
390
+ private var pendingRequests: [PendingRequest] = []
337
391
 
338
392
  init(
339
393
  maxActiveRequests: Int = SniConnectValidation.maxActiveRequests,
340
- maxActiveRequestsPerPair: Int = SniConnectValidation.maxActiveRequestsPerPair
394
+ maxActiveRequestsPerPair: Int = SniConnectValidation.maxActiveRequestsPerPair,
395
+ maxPendingRequests: Int = SniConnectValidation.maxPendingRequests
341
396
  ) {
342
397
  self.maxActiveRequests = maxActiveRequests
343
398
  self.maxActiveRequestsPerPair = maxActiveRequestsPerPair
399
+ self.maxPendingRequests = maxPendingRequests
344
400
  }
345
401
 
346
- func acquire(hostname: String, ip: String) throws -> Token {
402
+ var pendingRequestCount: Int {
403
+ queue.sync {
404
+ pendingRequests.count
405
+ }
406
+ }
407
+
408
+ func snapshot(hostname: String, ip: String) -> Snapshot {
347
409
  let key = pairKey(hostname: hostname, ip: ip)
348
- return try queue.sync {
349
- if activeRequests >= maxActiveRequests {
350
- SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
351
- ("activeCount", activeRequests),
352
- ("pairCount", activeRequestsByPair[key] ?? 0),
353
- ("limit", maxActiveRequests),
354
- ("reason", "max_active_requests"),
355
- ("hostname", hostname.lowercased()),
356
- ("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
357
- ]))
358
- throw SniConnectValidation.ValidationError.resourceLimit("Too many active SNI requests")
410
+ return queue.sync {
411
+ let activeForPair = activeRequestsByID.values.filter { $0.key == key }
412
+ let pendingForPair = pendingRequests.filter { $0.key == key }
413
+ return Snapshot(
414
+ activeRequests: activeRequests,
415
+ activeRequestsForPair: activeForPair.count,
416
+ pendingRequests: pendingRequests.count,
417
+ pendingRequestsForPair: pendingForPair.count,
418
+ activeRequestIdsForPair: activeForPair.compactMap { $0.requestId }.sorted(),
419
+ pendingRequestIdsForPair: pendingForPair.compactMap { $0.requestId }.sorted()
420
+ )
421
+ }
422
+ }
423
+
424
+ func acquire(hostname: String, ip: String, requestId: String? = nil) async throws -> Token {
425
+ try Task.checkCancellation()
426
+
427
+ let id = UUID()
428
+ let key = pairKey(hostname: hostname, ip: ip)
429
+ let trackedRequestId = requestId.flatMap { $0.isEmpty ? nil : $0 }
430
+ let cancellationState = CancellationState()
431
+
432
+ let token = try await withTaskCancellationHandler {
433
+ try Task.checkCancellation()
434
+ return try await withCheckedThrowingContinuation { continuation in
435
+ var immediateToken: Token?
436
+ var immediateError: Error?
437
+
438
+ queue.sync {
439
+ if cancellationState.isCancelled {
440
+ immediateError = CancellationError()
441
+ return
442
+ }
443
+
444
+ if hasCapacity(for: key) {
445
+ retainSlot(id: id, key: key, requestId: trackedRequestId)
446
+ immediateToken = Token(limiter: self, id: id)
447
+ return
448
+ }
449
+
450
+ if pendingRequests.count >= maxPendingRequests {
451
+ SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
452
+ ("activeCount", activeRequests),
453
+ ("pairCount", activeRequestsByPair[key] ?? 0),
454
+ ("pendingCount", pendingRequests.count),
455
+ ("limit", maxPendingRequests),
456
+ ("reason", "max_pending_requests"),
457
+ ("hostname", hostname.lowercased()),
458
+ ("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
459
+ ]))
460
+ immediateError = SniConnectValidation.ValidationError.resourceLimit(
461
+ "Too many pending SNI requests"
462
+ )
463
+ return
464
+ }
465
+
466
+ pendingRequests.append(PendingRequest(
467
+ id: id,
468
+ key: key,
469
+ requestId: trackedRequestId,
470
+ continuation: continuation
471
+ ))
472
+ }
473
+
474
+ if let immediateToken {
475
+ continuation.resume(returning: immediateToken)
476
+ } else if let immediateError {
477
+ continuation.resume(throwing: immediateError)
478
+ }
479
+ }
480
+ } onCancel: {
481
+ cancellationState.cancel()
482
+ self.cancelPendingRequest(id: id)
483
+ }
484
+
485
+ // Cancellation can race with a pending-to-active handoff. Never return a
486
+ // token to an already-cancelled caller without releasing its retained slot.
487
+ do {
488
+ try Task.checkCancellation()
489
+ return token
490
+ } catch {
491
+ token.release()
492
+ throw error
493
+ }
494
+ }
495
+
496
+ private func release(id: UUID) {
497
+ var nextRequest: PendingRequest?
498
+
499
+ queue.sync {
500
+ guard releaseSlot(id: id) else { return }
501
+
502
+ guard activeRequests < maxActiveRequests,
503
+ let index = pendingRequests.firstIndex(where: { hasCapacity(for: $0.key) }) else {
504
+ return
359
505
  }
360
- let pairCount = activeRequestsByPair[key] ?? 0
361
- if pairCount >= maxActiveRequestsPerPair {
362
- SniConnectCoreDiagnostics.warn(SniConnectCoreDiagnostics.event("sni_resource_limit", [
363
- ("activeCount", activeRequests),
364
- ("pairCount", pairCount),
365
- ("limit", maxActiveRequestsPerPair),
366
- ("reason", "max_active_requests_per_pair"),
367
- ("hostname", hostname.lowercased()),
368
- ("ipHash", SniConnectCoreDiagnostics.shortHash(ip)),
369
- ]))
370
- throw SniConnectValidation.ValidationError.resourceLimit("Too many active SNI requests for destination")
506
+
507
+ nextRequest = pendingRequests.remove(at: index)
508
+ if let nextRequest {
509
+ retainSlot(
510
+ id: nextRequest.id,
511
+ key: nextRequest.key,
512
+ requestId: nextRequest.requestId
513
+ )
371
514
  }
372
- activeRequests += 1
373
- activeRequestsByPair[key] = pairCount + 1
374
- return Token(limiter: self, key: key)
515
+ }
516
+
517
+ if let nextRequest {
518
+ nextRequest.continuation.resume(returning: Token(limiter: self, id: nextRequest.id))
375
519
  }
376
520
  }
377
521
 
378
- private func release(key: String) {
522
+ private func cancelPendingRequest(id: UUID) {
523
+ var cancelledRequest: PendingRequest?
524
+
379
525
  queue.sync {
380
- activeRequests = max(0, activeRequests - 1)
381
- guard let pairCount = activeRequestsByPair[key] else { return }
382
- if pairCount <= 1 {
383
- activeRequestsByPair.removeValue(forKey: key)
384
- } else {
385
- activeRequestsByPair[key] = pairCount - 1
526
+ guard let index = pendingRequests.firstIndex(where: { $0.id == id }) else {
527
+ return
386
528
  }
529
+ cancelledRequest = pendingRequests.remove(at: index)
530
+ }
531
+
532
+ cancelledRequest?.continuation.resume(throwing: CancellationError())
533
+ }
534
+
535
+ private func hasCapacity(for key: String) -> Bool {
536
+ activeRequests < maxActiveRequests &&
537
+ (activeRequestsByPair[key] ?? 0) < maxActiveRequestsPerPair
538
+ }
539
+
540
+ private func retainSlot(id: UUID, key: String, requestId: String?) {
541
+ activeRequestsByID[id] = ActiveRequest(key: key, requestId: requestId)
542
+ activeRequests += 1
543
+ activeRequestsByPair[key, default: 0] += 1
544
+ }
545
+
546
+ private func releaseSlot(id: UUID) -> Bool {
547
+ guard let activeRequest = activeRequestsByID.removeValue(forKey: id) else {
548
+ return false
549
+ }
550
+ let key = activeRequest.key
551
+ activeRequests = max(0, activeRequests - 1)
552
+ guard let pairCount = activeRequestsByPair[key] else {
553
+ return true
554
+ }
555
+ if pairCount <= 1 {
556
+ activeRequestsByPair.removeValue(forKey: key)
557
+ } else {
558
+ activeRequestsByPair[key] = pairCount - 1
387
559
  }
560
+ return true
388
561
  }
389
562
 
390
563
  private func pairKey(hostname: String, ip: String) -> String {
391
- return "\(hostname.lowercased())|\(ip)"
564
+ return "\(hostname.lowercased())|\(SniConnectValidation.canonicalIPKey(ip))"
392
565
  }
393
566
  }
@@ -113,12 +113,18 @@ final class SniConnectValidationTests: XCTestCase {
113
113
  }
114
114
 
115
115
  func testEnforcesRequestIdTimeoutAndBodyLimits() {
116
+ XCTAssertNoThrow(
117
+ try SniConnectValidation.validateRequestId(String(repeating: "界", count: 42))
118
+ )
116
119
  assertValidationFails {
117
120
  try SniConnectValidation.validateRequestId("")
118
121
  }
119
122
  assertValidationFails {
120
123
  try SniConnectValidation.validateRequestId(String(repeating: "x", count: 129))
121
124
  }
125
+ assertValidationFails {
126
+ try SniConnectValidation.validateRequestId(String(repeating: "界", count: 43))
127
+ }
122
128
  assertValidationFails {
123
129
  try SniConnectValidation.validateRequestId("req\n1")
124
130
  }
@@ -195,24 +201,196 @@ final class SniConnectValidationTests: XCTestCase {
195
201
  XCTAssertNoThrow(try SniConnectValidation.validateMethodBody(method: "OPTIONS", body: nil))
196
202
  }
197
203
 
198
- func testRequestLimiterEnforcesGlobalAndPerDestinationLimits() throws {
204
+ func testTwentySamePairRequestsProduceSixteenActiveAndFourPending() async throws {
205
+ let limiter = SniConnectRequestLimiter()
206
+ var tasks: [String: Task<SniConnectRequestLimiter.Token, Error>] = [:]
207
+
208
+ for index in 0..<20 {
209
+ let requestId = String(format: "req-%02d", index)
210
+ tasks[requestId] = Task {
211
+ try await limiter.acquire(
212
+ hostname: "Example.com",
213
+ ip: index.isMultiple(of: 2)
214
+ ? "2001:4860:4860::8888"
215
+ : "2001:4860:4860:0:0:0:0:8888",
216
+ requestId: requestId
217
+ )
218
+ }
219
+ }
220
+
221
+ await waitForPendingRequests(4, in: limiter)
222
+ let saturated = limiter.snapshot(
223
+ hostname: "EXAMPLE.COM",
224
+ ip: "2001:4860:4860::8888"
225
+ )
226
+ XCTAssertEqual(saturated.activeRequests, 16)
227
+ XCTAssertEqual(saturated.activeRequestsForPair, 16)
228
+ XCTAssertEqual(saturated.pendingRequests, 4)
229
+ XCTAssertEqual(saturated.pendingRequestsForPair, 4)
230
+ XCTAssertEqual(saturated.activeRequestIdsForPair.count, 16)
231
+ XCTAssertEqual(saturated.pendingRequestIdsForPair.count, 4)
232
+
233
+ for requestId in saturated.pendingRequestIdsForPair {
234
+ tasks[requestId]?.cancel()
235
+ }
236
+ for requestId in saturated.pendingRequestIdsForPair {
237
+ do {
238
+ _ = try await tasks[requestId]!.value
239
+ XCTFail("Expected pending request \(requestId) to be cancelled")
240
+ } catch is CancellationError {
241
+ // Expected.
242
+ }
243
+ }
244
+
245
+ XCTAssertEqual(limiter.pendingRequestCount, 0)
246
+ for requestId in saturated.activeRequestIdsForPair {
247
+ let token = try await tasks[requestId]!.value
248
+ token.release()
249
+ }
250
+ let drained = limiter.snapshot(
251
+ hostname: "example.com",
252
+ ip: "2001:4860:4860:0:0:0:0:8888"
253
+ )
254
+ XCTAssertEqual(drained.activeRequests, 0)
255
+ XCTAssertEqual(drained.pendingRequests, 0)
256
+
257
+ let recovery = try await limiter.acquire(
258
+ hostname: "example.com",
259
+ ip: "2001:4860:4860::8888",
260
+ requestId: "recovery"
261
+ )
262
+ recovery.release()
263
+ }
264
+
265
+ func testRequestLimiterQueuesGlobalAndPerDestinationLimits() async throws {
199
266
  let limiter = SniConnectRequestLimiter(maxActiveRequests: 2, maxActiveRequestsPerPair: 1)
200
- let firstToken = try limiter.acquire(hostname: "Example.com", ip: "93.184.216.34")
267
+ let firstToken = try await limiter.acquire(hostname: "Example.com", ip: "93.184.216.34")
201
268
 
202
- assertValidationFails {
203
- _ = try limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
269
+ let sameDestinationTask = Task {
270
+ try await limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
204
271
  }
272
+ await waitForPendingRequests(1, in: limiter)
205
273
 
206
- let secondToken = try limiter.acquire(hostname: "example.com", ip: "93.184.216.35")
207
- assertValidationFails {
208
- _ = try limiter.acquire(hostname: "example.net", ip: "93.184.216.36")
274
+ let secondToken = try await limiter.acquire(hostname: "example.com", ip: "93.184.216.35")
275
+ let globallyQueuedTask = Task {
276
+ try await limiter.acquire(hostname: "example.net", ip: "93.184.216.36")
209
277
  }
278
+ await waitForPendingRequests(2, in: limiter)
210
279
 
211
280
  firstToken.release()
212
- let replacementToken = try limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
281
+ let replacementToken = try await sameDestinationTask.value
213
282
  firstToken.release()
214
283
  secondToken.release()
284
+ let globallyQueuedToken = try await globallyQueuedTask.value
215
285
  replacementToken.release()
286
+ globallyQueuedToken.release()
287
+ }
288
+
289
+ func testRequestLimiterSnapshotCanonicalizesTargetAndReturnsRequestIDs() async throws {
290
+ let limiter = SniConnectRequestLimiter(maxActiveRequests: 2, maxActiveRequestsPerPair: 1)
291
+ let activeTarget = try await limiter.acquire(
292
+ hostname: "Example.com",
293
+ ip: "2001:4860:4860::8888",
294
+ requestId: "active-target"
295
+ )
296
+ let activeOther = try await limiter.acquire(
297
+ hostname: "example.com",
298
+ ip: "93.184.216.34",
299
+ requestId: "active-other"
300
+ )
301
+ let pendingTargetTask = Task {
302
+ try await limiter.acquire(
303
+ hostname: "example.com",
304
+ ip: "2001:4860:4860:0:0:0:0:8888",
305
+ requestId: "pending-target"
306
+ )
307
+ }
308
+ let pendingWithoutIDTask = Task {
309
+ try await limiter.acquire(
310
+ hostname: "example.com",
311
+ ip: "2001:4860:4860::8888",
312
+ requestId: ""
313
+ )
314
+ }
315
+ await waitForPendingRequests(2, in: limiter)
316
+
317
+ let snapshot = limiter.snapshot(
318
+ hostname: "EXAMPLE.COM",
319
+ ip: "2001:4860:4860:0:0:0:0:8888"
320
+ )
321
+ XCTAssertEqual(snapshot.activeRequests, 2)
322
+ XCTAssertEqual(snapshot.activeRequestsForPair, 1)
323
+ XCTAssertEqual(snapshot.pendingRequests, 2)
324
+ XCTAssertEqual(snapshot.pendingRequestsForPair, 2)
325
+ XCTAssertEqual(snapshot.activeRequestIdsForPair, ["active-target"])
326
+ XCTAssertEqual(snapshot.pendingRequestIdsForPair, ["pending-target"])
327
+
328
+ pendingTargetTask.cancel()
329
+ pendingWithoutIDTask.cancel()
330
+ _ = try? await pendingTargetTask.value
331
+ _ = try? await pendingWithoutIDTask.value
332
+ activeTarget.release()
333
+ activeOther.release()
334
+ }
335
+
336
+ func testRequestLimiterRejectsTheTwoHundredFiftySeventhPendingRequest() async throws {
337
+ let limiter = SniConnectRequestLimiter(
338
+ maxActiveRequests: 1,
339
+ maxActiveRequestsPerPair: 1,
340
+ maxPendingRequests: 256
341
+ )
342
+ let activeToken = try await limiter.acquire(
343
+ hostname: "example.com",
344
+ ip: "93.184.216.34"
345
+ )
346
+ let queuedTasks = (0..<256).map { index in
347
+ Task {
348
+ try await limiter.acquire(
349
+ hostname: "example.com",
350
+ ip: "93.184.216.34",
351
+ requestId: "pending-\(index)"
352
+ )
353
+ }
354
+ }
355
+ await waitForPendingRequests(256, in: limiter)
356
+
357
+ do {
358
+ _ = try await limiter.acquire(hostname: "example.com", ip: "93.184.216.34")
359
+ XCTFail("Expected the bounded pending queue to reject overflow")
360
+ } catch SniConnectValidation.ValidationError.resourceLimit(_) {
361
+ // Expected.
362
+ }
363
+
364
+ queuedTasks.forEach { $0.cancel() }
365
+ for task in queuedTasks {
366
+ _ = try? await task.value
367
+ }
368
+ XCTAssertEqual(limiter.pendingRequestCount, 0)
369
+ activeToken.release()
370
+ }
371
+
372
+ func testWallClockDeadlineCancelsPendingLimiterWait() async throws {
373
+ let limiter = SniConnectRequestLimiter(maxActiveRequests: 1, maxActiveRequestsPerPair: 1)
374
+ let activeToken = try await limiter.acquire(
375
+ hostname: "example.com",
376
+ ip: "93.184.216.34"
377
+ )
378
+
379
+ do {
380
+ _ = try await SniConnectWallClockDeadline.run(timeoutMilliseconds: 20) {
381
+ try await limiter.acquire(
382
+ hostname: "example.com",
383
+ ip: "93.184.216.34",
384
+ requestId: "deadline-pending"
385
+ )
386
+ }
387
+ XCTFail("Expected queue wait to consume the wall-clock deadline")
388
+ } catch let error as SniConnectTimeout {
389
+ XCTAssertEqual(error, .deadlineExceeded)
390
+ }
391
+
392
+ XCTAssertEqual(limiter.pendingRequestCount, 0)
393
+ activeToken.release()
216
394
  }
217
395
 
218
396
  func testResolverRegistryKeepsResolverClassUntilAllSessionsReleaseIt() throws {
@@ -322,4 +500,24 @@ final class SniConnectValidationTests: XCTestCase {
322
500
  private func assertValidationFails(_ block: () throws -> Void, file: StaticString = #filePath, line: UInt = #line) {
323
501
  XCTAssertThrowsError(try block(), file: file, line: line)
324
502
  }
503
+
504
+ private func waitForPendingRequests(
505
+ _ expectedCount: Int,
506
+ in limiter: SniConnectRequestLimiter,
507
+ file: StaticString = #filePath,
508
+ line: UInt = #line
509
+ ) async {
510
+ let deadline = Date().addingTimeInterval(5)
511
+ while Date() < deadline {
512
+ if limiter.pendingRequestCount == expectedCount {
513
+ return
514
+ }
515
+ try? await Task.sleep(nanoseconds: 1_000_000)
516
+ }
517
+ XCTFail(
518
+ "Expected \(expectedCount) pending requests, got \(limiter.pendingRequestCount)",
519
+ file: file,
520
+ line: line
521
+ )
522
+ }
325
523
  }
@@ -13,6 +13,9 @@ export function cancelAllRequests() {
13
13
  export function clearDNSCache() {
14
14
  return NativeSniConnect.clearDNSCache();
15
15
  }
16
+ export function getDebugSnapshot(target) {
17
+ return NativeSniConnect.getDebugSnapshot(target);
18
+ }
16
19
  export function isProxyActiveForUrl(url) {
17
20
  return NativeSniConnect.isProxyActiveForUrl(url);
18
21
  }
@@ -39,6 +39,18 @@ export type SniConnectResponse = {
39
39
  headers: HeaderMap;
40
40
  multiValueHeaders?: MultiValueHeaderMap;
41
41
  };
42
+ export type SniConnectDebugTarget = {
43
+ ip: string;
44
+ hostname: string;
45
+ };
46
+ export type SniConnectDebugSnapshot = {
47
+ activeRequests: Int32;
48
+ activeRequestsForPair: Int32;
49
+ pendingRequests: Int32;
50
+ pendingRequestsForPair: Int32;
51
+ activeRequestIdsForPair: string[];
52
+ pendingRequestIdsForPair: string[];
53
+ };
42
54
  export interface Spec extends TurboModule {
43
55
  request(config: NativeSniConnectRequest): Promise<SniConnectResponse>;
44
56
  cancelRequest(requestId: string): Promise<{
@@ -50,6 +62,7 @@ export interface Spec extends TurboModule {
50
62
  clearDNSCache(): Promise<{
51
63
  success: boolean;
52
64
  }>;
65
+ getDebugSnapshot(target: SniConnectDebugTarget): Promise<SniConnectDebugSnapshot>;
53
66
  isProxyActiveForUrl(url: string): Promise<boolean>;
54
67
  }
55
68
  export type SniConnectModule = Spec;
@@ -1,4 +1,4 @@
1
- import { type SniConnectRequest, type SniConnectResponse } from './NativeSniConnect';
1
+ import { type SniConnectDebugSnapshot, type SniConnectDebugTarget, type SniConnectRequest, type SniConnectResponse } from './NativeSniConnect';
2
2
  export declare function request(config: SniConnectRequest): Promise<SniConnectResponse>;
3
3
  export declare function cancelRequest(requestId: string): Promise<{
4
4
  success: boolean;
@@ -9,6 +9,7 @@ export declare function cancelAllRequests(): Promise<{
9
9
  export declare function clearDNSCache(): Promise<{
10
10
  success: boolean;
11
11
  }>;
12
+ export declare function getDebugSnapshot(target: SniConnectDebugTarget): Promise<SniConnectDebugSnapshot>;
12
13
  export declare function isProxyActiveForUrl(url: string): Promise<boolean>;
13
- export type { SniConnectBodylessMethod, SniConnectMethod, SniConnectOptionalBodyMethod, SniConnectRequest, SniConnectRequiredBodyMethod, SniConnectResponse, } from './NativeSniConnect';
14
+ export type { SniConnectBodylessMethod, SniConnectDebugSnapshot, SniConnectDebugTarget, SniConnectMethod, SniConnectOptionalBodyMethod, SniConnectRequest, SniConnectRequiredBodyMethod, SniConnectResponse, } from './NativeSniConnect';
14
15
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-sni-connect",
3
- "version": "3.0.81-alpha.8",
3
+ "version": "3.0.81",
4
4
  "description": "A React Native library for SNI-based HTTP requests with DNS caching and request management",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -161,6 +161,5 @@
161
161
  "languages": "kotlin-objc",
162
162
  "type": "turbo-module",
163
163
  "version": "0.54.8"
164
- },
165
- "stableVersion": "3.0.80"
164
+ }
166
165
  }
@@ -58,11 +58,28 @@ export type SniConnectResponse = {
58
58
  multiValueHeaders?: MultiValueHeaderMap;
59
59
  };
60
60
 
61
+ export type SniConnectDebugTarget = {
62
+ ip: string;
63
+ hostname: string;
64
+ };
65
+
66
+ export type SniConnectDebugSnapshot = {
67
+ activeRequests: Int32;
68
+ activeRequestsForPair: Int32;
69
+ pendingRequests: Int32;
70
+ pendingRequestsForPair: Int32;
71
+ activeRequestIdsForPair: string[];
72
+ pendingRequestIdsForPair: string[];
73
+ };
74
+
61
75
  export interface Spec extends TurboModule {
62
76
  request(config: NativeSniConnectRequest): Promise<SniConnectResponse>;
63
77
  cancelRequest(requestId: string): Promise<{ success: boolean }>;
64
78
  cancelAllRequests(): Promise<{ success: boolean }>;
65
79
  clearDNSCache(): Promise<{ success: boolean }>;
80
+ getDebugSnapshot(
81
+ target: SniConnectDebugTarget
82
+ ): Promise<SniConnectDebugSnapshot>;
66
83
  isProxyActiveForUrl(url: string): Promise<boolean>;
67
84
  }
68
85