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

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.
package/README.md CHANGED
@@ -14,7 +14,21 @@ This package is published as part of the OneKey `app-modules` workspace:
14
14
  yarn add @onekeyfe/react-native-sni-connect
15
15
  ```
16
16
 
17
- iOS: run `pod install`. Android autolinks.
17
+ iOS: add Aliyun's spec repo and make `EMASCurl` modular in your app Podfile,
18
+ then run `pod install`:
19
+
20
+ ```ruby
21
+ source 'https://github.com/CocoaPods/Specs.git'
22
+ source 'https://github.com/aliyun/aliyun-specs.git'
23
+
24
+ target 'YourApp' do
25
+ # React Native autolinks SniConnect. This explicit pod keeps Swift
26
+ # `import EMASCurl` visible when building static libraries.
27
+ pod 'EMASCurl', :modular_headers => true
28
+ end
29
+ ```
30
+
31
+ Android autolinks.
18
32
 
19
33
  ## Usage
20
34
 
@@ -24,8 +38,15 @@ import {
24
38
  cancelRequest,
25
39
  cancelAllRequests,
26
40
  clearDNSCache,
41
+ isProxyActiveForUrl,
27
42
  } from '@onekeyfe/react-native-sni-connect';
28
43
 
44
+ const proxyActive = await isProxyActiveForUrl('https://example.com/api/v1/ping');
45
+ if (proxyActive) {
46
+ // Product adapters should avoid entering SNI mode when a per-URL/system proxy
47
+ // is active. Low-level SNI requests still bypass proxies directly.
48
+ }
49
+
29
50
  const res = await request({
30
51
  // requestId is optional; required only if you want to cancel the request.
31
52
  requestId: 'health-check-1',
@@ -47,9 +68,31 @@ await cancelAllRequests();
47
68
  await clearDNSCache();
48
69
  ```
49
70
 
71
+ `multiValueHeaders` preserves repeated response headers when the native transport
72
+ exposes them as raw entries. On iOS, EMASCurl 1.5.5 currently hands this module a
73
+ `HTTPURLResponse` dictionary view, so duplicate header names may already be
74
+ collapsed by the transport before this adapter can observe them. Full duplicate
75
+ header preservation on iOS requires an EMASCurl version that exposes the raw
76
+ response header list.
77
+
78
+ ## Specification
79
+
80
+ The behavior of the SNI connect module is governed by a normative,
81
+ platform-agnostic standard that all implementations (iOS, Android, Node/Desktop,
82
+ shared JS adapters, and any future platform) must conform to:
83
+
84
+ **-> [OneKey SNI Connect Standard (OSCS)](./SPEC.md)**
85
+
86
+ Any change to request validation, destination pinning, TLS validation, redirect
87
+ handling, cancellation, response shape, or cache behavior must be checked
88
+ against OSCS.
89
+ When an implementation and the standard disagree, the implementation is wrong.
90
+
50
91
  ### Security notes
51
92
 
52
93
  - The request scheme is always `https` on port `443`; `path` cannot override scheme, host or port.
94
+ - `isProxyActiveForUrl(url)` is a preflight probe for adapters. It does not enable proxying;
95
+ native SNI transport still bypasses system proxy configuration.
53
96
  - `ip` must be an IPv4/IPv6 literal that routes to a public destination; loopback, private,
54
97
  link-local (incl. cloud metadata), CGNAT, multicast and reserved ranges are rejected.
55
98
  - Header names/values containing CR/LF/control characters are rejected; the `Host` header is
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
16
16
  s.static_framework = true
17
17
 
18
18
  s.source_files = [
19
- "ios/**/*.{swift}",
20
- "ios/**/*.{m,mm}"
19
+ "ios/*.{swift}",
20
+ "ios/*.{m,mm}"
21
21
  ]
22
22
 
23
23
  s.dependency 'React-Core'
@@ -65,4 +65,5 @@ dependencies {
65
65
  implementation "com.facebook.react:react-android"
66
66
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
67
67
  implementation "com.squareup.okhttp3:okhttp:4.12.0"
68
+ testImplementation "junit:junit:4.13.2"
68
69
  }
@@ -40,6 +40,26 @@ internal object SniConnectLogger {
40
40
  @JvmStatic
41
41
  fun error(message: String) = log("error", message, android.util.Log.ERROR)
42
42
 
43
+ fun event(name: String, vararg fields: Pair<String, Any?>): String =
44
+ (listOf("event" to name) + fields).joinToString(separator = " ") { (key, value) ->
45
+ "$key=${sanitize(value)}"
46
+ }
47
+
48
+ fun shortHash(value: String?): String {
49
+ if (value.isNullOrEmpty()) return "none"
50
+ var hash = 0xcbf29ce484222325UL
51
+ value.encodeToByteArray().forEach { byte ->
52
+ hash = hash xor byte.toUByte().toULong()
53
+ hash *= 0x100000001b3UL
54
+ }
55
+ return hash.toString(16).padStart(16, '0').take(12)
56
+ }
57
+
58
+ fun elapsedMs(startedAtMs: Long): Long =
59
+ (android.os.SystemClock.elapsedRealtime() - startedAtMs).coerceAtLeast(0)
60
+
61
+ fun ipFamily(ip: String): String = if (ip.contains(":")) "ipv6" else "ipv4"
62
+
43
63
  private fun log(level: String, message: String, androidLogLevel: Int) {
44
64
  val method = methods?.get(level)
45
65
  if (method != null) {
@@ -50,6 +70,17 @@ internal object SniConnectLogger {
50
70
  // Fall through to android.util.Log
51
71
  }
52
72
  }
53
- android.util.Log.println(androidLogLevel, TAG, message)
73
+ try {
74
+ android.util.Log.println(androidLogLevel, TAG, message)
75
+ } catch (_: RuntimeException) {
76
+ // Android JVM unit tests do not mock android.util.Log.
77
+ }
54
78
  }
79
+
80
+ private fun sanitize(value: Any?): String =
81
+ value?.toString()
82
+ ?.replace('\n', '_')
83
+ ?.replace('\r', '_')
84
+ ?.replace(' ', '_')
85
+ ?: "none"
55
86
  }