@onekeyfe/react-native-network-throttle 3.0.82 → 3.0.84-alpha.0
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
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
React Native native network throttle for OneKey iOS and Android development settings.
|
|
4
4
|
|
|
5
|
-
Current scope is
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Current scope is RN HTTP(S) latency and upload/download throughput. It does not
|
|
6
|
+
emulate offline mode, WebView traffic, or third-party native networking stacks.
|
|
7
|
+
|
|
8
|
+
`throttleUrlHosts` is an allowlist: when it is non-empty, only requests whose
|
|
9
|
+
host matches are throttled, and everything else is left untouched. An entry is
|
|
10
|
+
either an exact host or `*.example.com`, which matches sub-domains at any depth
|
|
11
|
+
but not the bare apex. An empty allowlist throttles nothing.
|
|
12
|
+
|
|
13
|
+
Hosts are registered additively for the lifetime of the native process, so
|
|
14
|
+
independently initialized React Native runtimes cannot clear each other's
|
|
15
|
+
configuration.
|
|
8
16
|
|
|
9
17
|
This package only owns native request throttling. Product settings, persistence, and UI controls should remain in the host app.
|
|
@@ -4,6 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import com.facebook.react.bridge.Arguments
|
|
6
6
|
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
import com.facebook.react.bridge.ReadableType
|
|
7
8
|
import com.facebook.react.bridge.WritableMap
|
|
8
9
|
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
9
10
|
import java.io.IOException
|
|
@@ -11,6 +12,8 @@ import java.io.InterruptedIOException
|
|
|
11
12
|
import java.util.concurrent.TimeUnit
|
|
12
13
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
13
14
|
import java.util.concurrent.atomic.AtomicLong
|
|
15
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
16
|
+
import okhttp3.HttpUrl
|
|
14
17
|
import okhttp3.Interceptor
|
|
15
18
|
import okhttp3.MediaType
|
|
16
19
|
import okhttp3.OkHttpClient
|
|
@@ -36,6 +39,7 @@ internal object NetworkThrottle {
|
|
|
36
39
|
private val downloadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
37
40
|
private val uploadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
38
41
|
private val installed = AtomicBoolean(false)
|
|
42
|
+
private val throttleUrlHosts = AtomicReference<Set<String>>(emptySet())
|
|
39
43
|
|
|
40
44
|
fun install(context: Context) {
|
|
41
45
|
if (!installed.compareAndSet(false, true)) {
|
|
@@ -85,6 +89,19 @@ internal object NetworkThrottle {
|
|
|
85
89
|
if (nextUploadBps <= 0) {
|
|
86
90
|
nextUploadBps = DEFAULT_THROUGHPUT_BPS.toLong()
|
|
87
91
|
}
|
|
92
|
+
if (config.hasKey("throttleUrlHosts") && !config.isNull("throttleUrlHosts")) {
|
|
93
|
+
val hosts = config.getArray("throttleUrlHosts")
|
|
94
|
+
val normalizedHosts = buildSet {
|
|
95
|
+
if (hosts != null) {
|
|
96
|
+
for (index in 0 until hosts.size()) {
|
|
97
|
+
if (hosts.getType(index) == ReadableType.String) {
|
|
98
|
+
normalizeHost(hosts.getString(index))?.let(::add)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throttleUrlHosts.updateAndGet { current -> current + normalizedHosts }
|
|
104
|
+
}
|
|
88
105
|
|
|
89
106
|
enabled.set(nextEnabled)
|
|
90
107
|
latencyNanos.set((nextLatencyMs * 1_000_000.0).toLong())
|
|
@@ -104,6 +121,9 @@ internal object NetworkThrottle {
|
|
|
104
121
|
map.putDouble("latencyMs", latencyNanos.get() / 1_000_000.0)
|
|
105
122
|
map.putDouble("downloadBps", downloadBps.get().toDouble())
|
|
106
123
|
map.putDouble("uploadBps", uploadBps.get().toDouble())
|
|
124
|
+
val hosts = Arguments.createArray()
|
|
125
|
+
throttleUrlHosts.get().sorted().forEach(hosts::pushString)
|
|
126
|
+
map.putArray("throttleUrlHosts", hosts)
|
|
107
127
|
return map
|
|
108
128
|
}
|
|
109
129
|
|
|
@@ -111,6 +131,33 @@ internal object NetworkThrottle {
|
|
|
111
131
|
private fun getDownloadBps(): Long = if (enabled.get()) downloadBps.get() else 0L
|
|
112
132
|
private fun getUploadBps(): Long = if (enabled.get()) uploadBps.get() else 0L
|
|
113
133
|
|
|
134
|
+
private fun normalizeHost(value: String?): String? =
|
|
135
|
+
value?.trim()?.lowercase()?.takeIf { it.isNotEmpty() }
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Hosts are matched as exact names, or as `*.example.com` which matches
|
|
139
|
+
* sub-domains at any depth but not the bare apex. This mirrors the URL
|
|
140
|
+
* patterns the desktop app installs, so both platforms throttle the same
|
|
141
|
+
* traffic.
|
|
142
|
+
*/
|
|
143
|
+
private fun matchesHost(host: String, pattern: String): Boolean =
|
|
144
|
+
if (pattern.startsWith("*.")) {
|
|
145
|
+
host.endsWith(pattern.substring(1))
|
|
146
|
+
} else {
|
|
147
|
+
host == pattern
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun shouldThrottle(requestUrl: HttpUrl): Boolean {
|
|
151
|
+
// The interceptor is installed in every build, so keep the empty case
|
|
152
|
+
// allocation-free. An empty allowlist throttles nothing.
|
|
153
|
+
val hosts = throttleUrlHosts.get()
|
|
154
|
+
if (hosts.isEmpty()) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
val host = requestUrl.host.lowercase()
|
|
158
|
+
return hosts.any { matchesHost(host, it) }
|
|
159
|
+
}
|
|
160
|
+
|
|
114
161
|
private fun sleepNanos(delayNanos: Long) {
|
|
115
162
|
if (delayNanos <= 0) {
|
|
116
163
|
return
|
|
@@ -201,9 +248,12 @@ internal object NetworkThrottle {
|
|
|
201
248
|
|
|
202
249
|
private class ThrottleInterceptor : Interceptor {
|
|
203
250
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
251
|
+
val request = chain.request()
|
|
252
|
+
if (!shouldThrottle(request.url)) {
|
|
253
|
+
return chain.proceed(request)
|
|
254
|
+
}
|
|
204
255
|
val requestStartNanos = System.nanoTime()
|
|
205
256
|
val delayNanos = getLatencyNanos()
|
|
206
|
-
val request = chain.request()
|
|
207
257
|
val requestBody = request.body
|
|
208
258
|
val activeUploadBps = getUploadBps()
|
|
209
259
|
val throttledRequest =
|
|
@@ -10,16 +10,35 @@ static const NSTimeInterval OneKeyNetworkThrottleDefaultLatencyMs = 562.5;
|
|
|
10
10
|
static const NSInteger OneKeyNetworkThrottleDefaultThroughputBps = 102 * 1024;
|
|
11
11
|
static const NSUInteger OneKeyNetworkThrottleMaxPendingDownloadBytes = 256 * 1024;
|
|
12
12
|
|
|
13
|
+
static NSString *OneKeyNetworkThrottleNormalizedHost(NSString *value)
|
|
14
|
+
{
|
|
15
|
+
NSString *host = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].lowercaseString;
|
|
16
|
+
return host.length > 0 ? host : nil;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Hosts match as exact names, or as `*.example.com` which matches sub-domains
|
|
20
|
+
// at any depth but not the bare apex. This mirrors the URL patterns the
|
|
21
|
+
// desktop app installs, so both platforms throttle the same traffic.
|
|
22
|
+
static BOOL OneKeyNetworkThrottleHostMatches(NSString *host, NSString *pattern)
|
|
23
|
+
{
|
|
24
|
+
if ([pattern hasPrefix:@"*."]) {
|
|
25
|
+
return [host hasSuffix:[pattern substringFromIndex:1]];
|
|
26
|
+
}
|
|
27
|
+
return [host isEqualToString:pattern];
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
@interface OneKeyNetworkThrottleState : NSObject
|
|
14
31
|
+ (NSDictionary *)currentConfig;
|
|
15
32
|
+ (BOOL)isEnabled;
|
|
16
33
|
+ (NSTimeInterval)latencyMs;
|
|
17
34
|
+ (NSInteger)downloadBps;
|
|
18
35
|
+ (NSInteger)uploadBps;
|
|
36
|
+
+ (BOOL)shouldThrottleURL:(NSURL *)url;
|
|
19
37
|
+ (NSDictionary *)setEnabled:(BOOL)enabled
|
|
20
38
|
latencyMs:(NSTimeInterval)latencyMs
|
|
21
39
|
downloadBps:(NSInteger)downloadBps
|
|
22
|
-
uploadBps:(NSInteger)uploadBps
|
|
40
|
+
uploadBps:(NSInteger)uploadBps
|
|
41
|
+
throttleUrlHosts:(NSArray *)throttleUrlHosts;
|
|
23
42
|
@end
|
|
24
43
|
|
|
25
44
|
@implementation OneKeyNetworkThrottleState
|
|
@@ -28,18 +47,25 @@ static atomic_bool _oneKeyNetworkThrottleEnabled = ATOMIC_VAR_INIT(false);
|
|
|
28
47
|
static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500);
|
|
29
48
|
static atomic_llong _oneKeyNetworkThrottleDownloadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
30
49
|
static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
50
|
+
static NSSet<NSString *> *_oneKeyNetworkThrottleHosts;
|
|
31
51
|
|
|
32
52
|
+ (NSDictionary *)currentConfig
|
|
33
53
|
{
|
|
34
54
|
BOOL enabled = atomic_load_explicit(&_oneKeyNetworkThrottleEnabled, memory_order_acquire);
|
|
35
55
|
NSTimeInterval latencyMs =
|
|
36
56
|
((NSTimeInterval)atomic_load_explicit(&_oneKeyNetworkThrottleLatencyMicros, memory_order_relaxed)) / 1000.0;
|
|
57
|
+
NSArray<NSString *> *throttleUrlHosts = nil;
|
|
58
|
+
@synchronized (self) {
|
|
59
|
+
throttleUrlHosts = [[_oneKeyNetworkThrottleHosts ?: [NSSet set] allObjects]
|
|
60
|
+
sortedArrayUsingSelector:@selector(compare:)];
|
|
61
|
+
}
|
|
37
62
|
return @{
|
|
38
63
|
@"enabled": @(enabled),
|
|
39
64
|
@"profile": OneKeyNetworkThrottleProfileSlow4G,
|
|
40
65
|
@"latencyMs": @(latencyMs),
|
|
41
66
|
@"downloadBps": @([self downloadBps]),
|
|
42
|
-
@"uploadBps": @([self uploadBps])
|
|
67
|
+
@"uploadBps": @([self uploadBps]),
|
|
68
|
+
@"throttleUrlHosts": throttleUrlHosts
|
|
43
69
|
};
|
|
44
70
|
}
|
|
45
71
|
|
|
@@ -63,6 +89,25 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
63
89
|
return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleUploadBps, memory_order_relaxed);
|
|
64
90
|
}
|
|
65
91
|
|
|
92
|
+
+ (BOOL)shouldThrottleURL:(NSURL *)url
|
|
93
|
+
{
|
|
94
|
+
NSString *host = OneKeyNetworkThrottleNormalizedHost(url.host);
|
|
95
|
+
if (host == nil) {
|
|
96
|
+
return NO;
|
|
97
|
+
}
|
|
98
|
+
// An empty allowlist throttles nothing.
|
|
99
|
+
NSSet<NSString *> *hosts = nil;
|
|
100
|
+
@synchronized (self) {
|
|
101
|
+
hosts = _oneKeyNetworkThrottleHosts;
|
|
102
|
+
}
|
|
103
|
+
for (NSString *pattern in hosts) {
|
|
104
|
+
if (OneKeyNetworkThrottleHostMatches(host, pattern)) {
|
|
105
|
+
return YES;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return NO;
|
|
109
|
+
}
|
|
110
|
+
|
|
66
111
|
+ (NSInteger)normalizeThroughputBps:(NSInteger)throughputBps
|
|
67
112
|
{
|
|
68
113
|
return throughputBps > 0 ? throughputBps : OneKeyNetworkThrottleDefaultThroughputBps;
|
|
@@ -72,10 +117,29 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
72
117
|
latencyMs:(NSTimeInterval)latencyMs
|
|
73
118
|
downloadBps:(NSInteger)downloadBps
|
|
74
119
|
uploadBps:(NSInteger)uploadBps
|
|
120
|
+
throttleUrlHosts:(NSArray *)throttleUrlHosts
|
|
75
121
|
{
|
|
76
122
|
NSTimeInterval normalizedLatencyMs = latencyMs > 0 ? latencyMs : OneKeyNetworkThrottleDefaultLatencyMs;
|
|
77
123
|
NSInteger normalizedDownloadBps = [self normalizeThroughputBps:downloadBps];
|
|
78
124
|
NSInteger normalizedUploadBps = [self normalizeThroughputBps:uploadBps];
|
|
125
|
+
if ([throttleUrlHosts isKindOfClass:[NSArray class]]) {
|
|
126
|
+
NSMutableSet<NSString *> *normalizedHosts = [NSMutableSet set];
|
|
127
|
+
for (id value in throttleUrlHosts) {
|
|
128
|
+
if (![value isKindOfClass:[NSString class]]) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
NSString *host = OneKeyNetworkThrottleNormalizedHost((NSString *)value);
|
|
132
|
+
if (host != nil) {
|
|
133
|
+
[normalizedHosts addObject:host];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
@synchronized (self) {
|
|
137
|
+
NSMutableSet<NSString *> *nextHosts =
|
|
138
|
+
[_oneKeyNetworkThrottleHosts mutableCopy] ?: [NSMutableSet set];
|
|
139
|
+
[nextHosts unionSet:normalizedHosts];
|
|
140
|
+
_oneKeyNetworkThrottleHosts = [nextHosts copy];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
79
143
|
atomic_store_explicit(
|
|
80
144
|
&_oneKeyNetworkThrottleLatencyMicros,
|
|
81
145
|
(long long)llround(normalizedLatencyMs * 1000.0),
|
|
@@ -137,6 +201,9 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
137
201
|
if ([NSURLProtocol propertyForKey:OneKeyNetworkThrottleHandledKey inRequest:request]) {
|
|
138
202
|
return NO;
|
|
139
203
|
}
|
|
204
|
+
if (![OneKeyNetworkThrottleState shouldThrottleURL:request.URL]) {
|
|
205
|
+
return NO;
|
|
206
|
+
}
|
|
140
207
|
NSString *scheme = request.URL.scheme.lowercaseString;
|
|
141
208
|
return [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"];
|
|
142
209
|
}
|
|
@@ -517,7 +584,14 @@ RCT_REMAP_METHOD(setConfig, setConfig:(NSDictionary *)config resolver:(RCTPromis
|
|
|
517
584
|
uploadBpsValue != nil && uploadBpsValue != [NSNull null]
|
|
518
585
|
? [uploadBpsValue integerValue]
|
|
519
586
|
: [OneKeyNetworkThrottleState uploadBps];
|
|
520
|
-
|
|
587
|
+
id throttleUrlHostsValue = config[@"throttleUrlHosts"];
|
|
588
|
+
NSArray *throttleUrlHosts = [throttleUrlHostsValue isKindOfClass:[NSArray class]] ? throttleUrlHostsValue : nil;
|
|
589
|
+
resolve([OneKeyNetworkThrottleState
|
|
590
|
+
setEnabled:enabled
|
|
591
|
+
latencyMs:latencyMs
|
|
592
|
+
downloadBps:downloadBps
|
|
593
|
+
uploadBps:uploadBps
|
|
594
|
+
throttleUrlHosts:throttleUrlHosts]);
|
|
521
595
|
}
|
|
522
596
|
|
|
523
597
|
@end
|
package/lib/module/index.js
CHANGED
|
@@ -10,17 +10,25 @@ const LINKING_ERROR = `The package '@onekeyfe/react-native-network-throttle' doe
|
|
|
10
10
|
default: ''
|
|
11
11
|
}) + '- rebuild the app after installing the package';
|
|
12
12
|
const nativeModule = NativeModules.OneKeyNetworkThrottle;
|
|
13
|
+
function normalizeNativeConfig(config) {
|
|
14
|
+
return {
|
|
15
|
+
...config,
|
|
16
|
+
throttleUrlHosts: config.throttleUrlHosts ?? []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
13
19
|
export const NetworkThrottle = nativeModule ? {
|
|
14
|
-
getConfig: () => nativeModule.getConfig(),
|
|
20
|
+
getConfig: async () => normalizeNativeConfig(await nativeModule.getConfig()),
|
|
15
21
|
setConfig: async config => {
|
|
16
|
-
const currentConfig = await nativeModule.getConfig();
|
|
17
|
-
|
|
22
|
+
const currentConfig = normalizeNativeConfig(await nativeModule.getConfig());
|
|
23
|
+
const nativeConfig = await nativeModule.setConfig({
|
|
18
24
|
enabled: config.enabled ?? currentConfig.enabled,
|
|
19
25
|
profile: config.profile ?? currentConfig.profile,
|
|
20
26
|
latencyMs: config.latencyMs ?? currentConfig.latencyMs,
|
|
21
27
|
downloadBps: config.downloadBps ?? currentConfig.downloadBps,
|
|
22
|
-
uploadBps: config.uploadBps ?? currentConfig.uploadBps
|
|
28
|
+
uploadBps: config.uploadBps ?? currentConfig.uploadBps,
|
|
29
|
+
throttleUrlHosts: config.throttleUrlHosts ?? currentConfig.throttleUrlHosts ?? []
|
|
23
30
|
});
|
|
31
|
+
return normalizeNativeConfig(nativeConfig);
|
|
24
32
|
}
|
|
25
33
|
} : new Proxy({}, {
|
|
26
34
|
get() {
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ export type NetworkThrottleConfig = {
|
|
|
8
8
|
latencyMs: number;
|
|
9
9
|
downloadBps: number;
|
|
10
10
|
uploadBps: number;
|
|
11
|
+
throttleUrlHosts: string[];
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
|
|
@@ -16,9 +17,18 @@ export const NETWORK_THROTTLE_SLOW_4G_DOWNLOAD_BPS =
|
|
|
16
17
|
NETWORK_THROTTLE_102_KIB_BPS;
|
|
17
18
|
export const NETWORK_THROTTLE_SLOW_4G_UPLOAD_BPS = NETWORK_THROTTLE_102_KIB_BPS;
|
|
18
19
|
|
|
20
|
+
type NativeNetworkThrottleConfig = Omit<
|
|
21
|
+
NetworkThrottleConfig,
|
|
22
|
+
'throttleUrlHosts'
|
|
23
|
+
> & {
|
|
24
|
+
throttleUrlHosts?: string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
19
27
|
type NativeNetworkThrottleModule = {
|
|
20
|
-
getConfig: () => Promise<
|
|
21
|
-
setConfig: (
|
|
28
|
+
getConfig: () => Promise<NativeNetworkThrottleConfig>;
|
|
29
|
+
setConfig: (
|
|
30
|
+
config: NativeNetworkThrottleConfig
|
|
31
|
+
) => Promise<NativeNetworkThrottleConfig>;
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
export type NetworkThrottleModule = {
|
|
@@ -37,18 +47,33 @@ const nativeModule = NativeModules.OneKeyNetworkThrottle as
|
|
|
37
47
|
| NativeNetworkThrottleModule
|
|
38
48
|
| undefined;
|
|
39
49
|
|
|
50
|
+
function normalizeNativeConfig(
|
|
51
|
+
config: NativeNetworkThrottleConfig
|
|
52
|
+
): NetworkThrottleConfig {
|
|
53
|
+
return {
|
|
54
|
+
...config,
|
|
55
|
+
throttleUrlHosts: config.throttleUrlHosts ?? [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export const NetworkThrottle: NetworkThrottleModule = nativeModule
|
|
41
60
|
? {
|
|
42
|
-
getConfig: () =>
|
|
61
|
+
getConfig: async () =>
|
|
62
|
+
normalizeNativeConfig(await nativeModule.getConfig()),
|
|
43
63
|
setConfig: async (config) => {
|
|
44
|
-
const currentConfig =
|
|
45
|
-
|
|
64
|
+
const currentConfig = normalizeNativeConfig(
|
|
65
|
+
await nativeModule.getConfig()
|
|
66
|
+
);
|
|
67
|
+
const nativeConfig = await nativeModule.setConfig({
|
|
46
68
|
enabled: config.enabled ?? currentConfig.enabled,
|
|
47
69
|
profile: config.profile ?? currentConfig.profile,
|
|
48
70
|
latencyMs: config.latencyMs ?? currentConfig.latencyMs,
|
|
49
71
|
downloadBps: config.downloadBps ?? currentConfig.downloadBps,
|
|
50
72
|
uploadBps: config.uploadBps ?? currentConfig.uploadBps,
|
|
73
|
+
throttleUrlHosts:
|
|
74
|
+
config.throttleUrlHosts ?? currentConfig.throttleUrlHosts ?? [],
|
|
51
75
|
});
|
|
76
|
+
return normalizeNativeConfig(nativeConfig);
|
|
52
77
|
},
|
|
53
78
|
}
|
|
54
79
|
: (new Proxy(
|