@onekeyfe/react-native-network-throttle 3.0.82 → 3.0.83-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,19 @@
|
|
|
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
|
+
`bypassUrlOrigins` excludes exact HTTP(S) origins from all throttling. Origins
|
|
9
|
+
are canonicalized with their effective port and registered additively for the
|
|
10
|
+
lifetime of the native process. This allows independently initialized React
|
|
11
|
+
Native runtimes to register local development servers without clearing each
|
|
12
|
+
other's configuration.
|
|
13
|
+
|
|
14
|
+
Known limitation: on Android the bypass decision is made once per logical
|
|
15
|
+
request (OkHttp application interceptor), so a cross-origin redirect keeps the
|
|
16
|
+
initial request's bypass decision; iOS re-evaluates each request. Exact-origin
|
|
17
|
+
bypasses target local development servers, which do not redirect across
|
|
18
|
+
origins.
|
|
8
19
|
|
|
9
20
|
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,9 @@ 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
|
|
17
|
+
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
14
18
|
import okhttp3.Interceptor
|
|
15
19
|
import okhttp3.MediaType
|
|
16
20
|
import okhttp3.OkHttpClient
|
|
@@ -36,6 +40,7 @@ internal object NetworkThrottle {
|
|
|
36
40
|
private val downloadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
37
41
|
private val uploadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
38
42
|
private val installed = AtomicBoolean(false)
|
|
43
|
+
private val bypassUrlOrigins = AtomicReference<Set<String>>(emptySet())
|
|
39
44
|
|
|
40
45
|
fun install(context: Context) {
|
|
41
46
|
if (!installed.compareAndSet(false, true)) {
|
|
@@ -85,6 +90,19 @@ internal object NetworkThrottle {
|
|
|
85
90
|
if (nextUploadBps <= 0) {
|
|
86
91
|
nextUploadBps = DEFAULT_THROUGHPUT_BPS.toLong()
|
|
87
92
|
}
|
|
93
|
+
if (config.hasKey("bypassUrlOrigins") && !config.isNull("bypassUrlOrigins")) {
|
|
94
|
+
val origins = config.getArray("bypassUrlOrigins")
|
|
95
|
+
val normalizedOrigins = buildSet {
|
|
96
|
+
if (origins != null) {
|
|
97
|
+
for (index in 0 until origins.size()) {
|
|
98
|
+
if (origins.getType(index) == ReadableType.String) {
|
|
99
|
+
normalizeOrigin(origins.getString(index))?.let(::add)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
bypassUrlOrigins.updateAndGet { current -> current + normalizedOrigins }
|
|
105
|
+
}
|
|
88
106
|
|
|
89
107
|
enabled.set(nextEnabled)
|
|
90
108
|
latencyNanos.set((nextLatencyMs * 1_000_000.0).toLong())
|
|
@@ -104,6 +122,9 @@ internal object NetworkThrottle {
|
|
|
104
122
|
map.putDouble("latencyMs", latencyNanos.get() / 1_000_000.0)
|
|
105
123
|
map.putDouble("downloadBps", downloadBps.get().toDouble())
|
|
106
124
|
map.putDouble("uploadBps", uploadBps.get().toDouble())
|
|
125
|
+
val origins = Arguments.createArray()
|
|
126
|
+
bypassUrlOrigins.get().sorted().forEach(origins::pushString)
|
|
127
|
+
map.putArray("bypassUrlOrigins", origins)
|
|
107
128
|
return map
|
|
108
129
|
}
|
|
109
130
|
|
|
@@ -111,6 +132,28 @@ internal object NetworkThrottle {
|
|
|
111
132
|
private fun getDownloadBps(): Long = if (enabled.get()) downloadBps.get() else 0L
|
|
112
133
|
private fun getUploadBps(): Long = if (enabled.get()) uploadBps.get() else 0L
|
|
113
134
|
|
|
135
|
+
private fun canonicalOrigin(url: HttpUrl): String =
|
|
136
|
+
HttpUrl.Builder()
|
|
137
|
+
.scheme(url.scheme)
|
|
138
|
+
.host(url.host)
|
|
139
|
+
.port(url.port)
|
|
140
|
+
.build()
|
|
141
|
+
.toString()
|
|
142
|
+
.removeSuffix("/")
|
|
143
|
+
|
|
144
|
+
private fun normalizeOrigin(value: String?): String? =
|
|
145
|
+
value?.toHttpUrlOrNull()?.let(::canonicalOrigin)
|
|
146
|
+
|
|
147
|
+
private fun shouldBypass(requestUrl: HttpUrl): Boolean {
|
|
148
|
+
// The interceptor is installed in every build; skip the per-request
|
|
149
|
+
// canonicalization allocation while no origin is registered.
|
|
150
|
+
val origins = bypassUrlOrigins.get()
|
|
151
|
+
if (origins.isEmpty()) {
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
return origins.contains(canonicalOrigin(requestUrl))
|
|
155
|
+
}
|
|
156
|
+
|
|
114
157
|
private fun sleepNanos(delayNanos: Long) {
|
|
115
158
|
if (delayNanos <= 0) {
|
|
116
159
|
return
|
|
@@ -201,9 +244,12 @@ internal object NetworkThrottle {
|
|
|
201
244
|
|
|
202
245
|
private class ThrottleInterceptor : Interceptor {
|
|
203
246
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
247
|
+
val request = chain.request()
|
|
248
|
+
if (shouldBypass(request.url)) {
|
|
249
|
+
return chain.proceed(request)
|
|
250
|
+
}
|
|
204
251
|
val requestStartNanos = System.nanoTime()
|
|
205
252
|
val delayNanos = getLatencyNanos()
|
|
206
|
-
val request = chain.request()
|
|
207
253
|
val requestBody = request.body
|
|
208
254
|
val activeUploadBps = getUploadBps()
|
|
209
255
|
val throttledRequest =
|
|
@@ -10,16 +10,32 @@ 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 *OneKeyNetworkThrottleCanonicalOrigin(NSURL *url)
|
|
14
|
+
{
|
|
15
|
+
NSString *scheme = url.scheme.lowercaseString;
|
|
16
|
+
NSString *host = url.host.lowercaseString;
|
|
17
|
+
if ((!([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])) || host.length == 0) {
|
|
18
|
+
return nil;
|
|
19
|
+
}
|
|
20
|
+
NSURLComponents *components = [[NSURLComponents alloc] init];
|
|
21
|
+
components.scheme = scheme;
|
|
22
|
+
components.host = host;
|
|
23
|
+
components.port = url.port ?: @([scheme isEqualToString:@"https"] ? 443 : 80);
|
|
24
|
+
return components.string;
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
@interface OneKeyNetworkThrottleState : NSObject
|
|
14
28
|
+ (NSDictionary *)currentConfig;
|
|
15
29
|
+ (BOOL)isEnabled;
|
|
16
30
|
+ (NSTimeInterval)latencyMs;
|
|
17
31
|
+ (NSInteger)downloadBps;
|
|
18
32
|
+ (NSInteger)uploadBps;
|
|
33
|
+
+ (BOOL)shouldBypassURL:(NSURL *)url;
|
|
19
34
|
+ (NSDictionary *)setEnabled:(BOOL)enabled
|
|
20
35
|
latencyMs:(NSTimeInterval)latencyMs
|
|
21
36
|
downloadBps:(NSInteger)downloadBps
|
|
22
|
-
uploadBps:(NSInteger)uploadBps
|
|
37
|
+
uploadBps:(NSInteger)uploadBps
|
|
38
|
+
bypassUrlOrigins:(NSArray *)bypassUrlOrigins;
|
|
23
39
|
@end
|
|
24
40
|
|
|
25
41
|
@implementation OneKeyNetworkThrottleState
|
|
@@ -28,18 +44,25 @@ static atomic_bool _oneKeyNetworkThrottleEnabled = ATOMIC_VAR_INIT(false);
|
|
|
28
44
|
static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500);
|
|
29
45
|
static atomic_llong _oneKeyNetworkThrottleDownloadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
30
46
|
static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
47
|
+
static NSSet<NSString *> *_oneKeyNetworkThrottleBypassOrigins;
|
|
31
48
|
|
|
32
49
|
+ (NSDictionary *)currentConfig
|
|
33
50
|
{
|
|
34
51
|
BOOL enabled = atomic_load_explicit(&_oneKeyNetworkThrottleEnabled, memory_order_acquire);
|
|
35
52
|
NSTimeInterval latencyMs =
|
|
36
53
|
((NSTimeInterval)atomic_load_explicit(&_oneKeyNetworkThrottleLatencyMicros, memory_order_relaxed)) / 1000.0;
|
|
54
|
+
NSArray<NSString *> *bypassUrlOrigins = nil;
|
|
55
|
+
@synchronized (self) {
|
|
56
|
+
bypassUrlOrigins = [[_oneKeyNetworkThrottleBypassOrigins ?: [NSSet set] allObjects]
|
|
57
|
+
sortedArrayUsingSelector:@selector(compare:)];
|
|
58
|
+
}
|
|
37
59
|
return @{
|
|
38
60
|
@"enabled": @(enabled),
|
|
39
61
|
@"profile": OneKeyNetworkThrottleProfileSlow4G,
|
|
40
62
|
@"latencyMs": @(latencyMs),
|
|
41
63
|
@"downloadBps": @([self downloadBps]),
|
|
42
|
-
@"uploadBps": @([self uploadBps])
|
|
64
|
+
@"uploadBps": @([self uploadBps]),
|
|
65
|
+
@"bypassUrlOrigins": bypassUrlOrigins
|
|
43
66
|
};
|
|
44
67
|
}
|
|
45
68
|
|
|
@@ -63,6 +86,17 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
63
86
|
return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleUploadBps, memory_order_relaxed);
|
|
64
87
|
}
|
|
65
88
|
|
|
89
|
+
+ (BOOL)shouldBypassURL:(NSURL *)url
|
|
90
|
+
{
|
|
91
|
+
NSString *origin = OneKeyNetworkThrottleCanonicalOrigin(url);
|
|
92
|
+
if (origin == nil) {
|
|
93
|
+
return NO;
|
|
94
|
+
}
|
|
95
|
+
@synchronized (self) {
|
|
96
|
+
return [_oneKeyNetworkThrottleBypassOrigins containsObject:origin];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
66
100
|
+ (NSInteger)normalizeThroughputBps:(NSInteger)throughputBps
|
|
67
101
|
{
|
|
68
102
|
return throughputBps > 0 ? throughputBps : OneKeyNetworkThrottleDefaultThroughputBps;
|
|
@@ -72,10 +106,29 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
72
106
|
latencyMs:(NSTimeInterval)latencyMs
|
|
73
107
|
downloadBps:(NSInteger)downloadBps
|
|
74
108
|
uploadBps:(NSInteger)uploadBps
|
|
109
|
+
bypassUrlOrigins:(NSArray *)bypassUrlOrigins
|
|
75
110
|
{
|
|
76
111
|
NSTimeInterval normalizedLatencyMs = latencyMs > 0 ? latencyMs : OneKeyNetworkThrottleDefaultLatencyMs;
|
|
77
112
|
NSInteger normalizedDownloadBps = [self normalizeThroughputBps:downloadBps];
|
|
78
113
|
NSInteger normalizedUploadBps = [self normalizeThroughputBps:uploadBps];
|
|
114
|
+
if ([bypassUrlOrigins isKindOfClass:[NSArray class]]) {
|
|
115
|
+
NSMutableSet<NSString *> *normalizedOrigins = [NSMutableSet set];
|
|
116
|
+
for (id value in bypassUrlOrigins) {
|
|
117
|
+
if (![value isKindOfClass:[NSString class]]) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
NSString *origin = OneKeyNetworkThrottleCanonicalOrigin([NSURL URLWithString:(NSString *)value]);
|
|
121
|
+
if (origin != nil) {
|
|
122
|
+
[normalizedOrigins addObject:origin];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
@synchronized (self) {
|
|
126
|
+
NSMutableSet<NSString *> *nextOrigins =
|
|
127
|
+
[_oneKeyNetworkThrottleBypassOrigins mutableCopy] ?: [NSMutableSet set];
|
|
128
|
+
[nextOrigins unionSet:normalizedOrigins];
|
|
129
|
+
_oneKeyNetworkThrottleBypassOrigins = [nextOrigins copy];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
79
132
|
atomic_store_explicit(
|
|
80
133
|
&_oneKeyNetworkThrottleLatencyMicros,
|
|
81
134
|
(long long)llround(normalizedLatencyMs * 1000.0),
|
|
@@ -137,6 +190,9 @@ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024
|
|
|
137
190
|
if ([NSURLProtocol propertyForKey:OneKeyNetworkThrottleHandledKey inRequest:request]) {
|
|
138
191
|
return NO;
|
|
139
192
|
}
|
|
193
|
+
if ([OneKeyNetworkThrottleState shouldBypassURL:request.URL]) {
|
|
194
|
+
return NO;
|
|
195
|
+
}
|
|
140
196
|
NSString *scheme = request.URL.scheme.lowercaseString;
|
|
141
197
|
return [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"];
|
|
142
198
|
}
|
|
@@ -517,7 +573,14 @@ RCT_REMAP_METHOD(setConfig, setConfig:(NSDictionary *)config resolver:(RCTPromis
|
|
|
517
573
|
uploadBpsValue != nil && uploadBpsValue != [NSNull null]
|
|
518
574
|
? [uploadBpsValue integerValue]
|
|
519
575
|
: [OneKeyNetworkThrottleState uploadBps];
|
|
520
|
-
|
|
576
|
+
id bypassUrlOriginsValue = config[@"bypassUrlOrigins"];
|
|
577
|
+
NSArray *bypassUrlOrigins = [bypassUrlOriginsValue isKindOfClass:[NSArray class]] ? bypassUrlOriginsValue : nil;
|
|
578
|
+
resolve([OneKeyNetworkThrottleState
|
|
579
|
+
setEnabled:enabled
|
|
580
|
+
latencyMs:latencyMs
|
|
581
|
+
downloadBps:downloadBps
|
|
582
|
+
uploadBps:uploadBps
|
|
583
|
+
bypassUrlOrigins:bypassUrlOrigins]);
|
|
521
584
|
}
|
|
522
585
|
|
|
523
586
|
@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
|
+
bypassUrlOrigins: config.bypassUrlOrigins ?? []
|
|
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
|
+
bypassUrlOrigins: config.bypassUrlOrigins ?? currentConfig.bypassUrlOrigins ?? []
|
|
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
|
+
bypassUrlOrigins: 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
|
+
'bypassUrlOrigins'
|
|
23
|
+
> & {
|
|
24
|
+
bypassUrlOrigins?: 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
|
+
bypassUrlOrigins: config.bypassUrlOrigins ?? [],
|
|
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
|
+
bypassUrlOrigins:
|
|
74
|
+
config.bypassUrlOrigins ?? currentConfig.bypassUrlOrigins ?? [],
|
|
51
75
|
});
|
|
76
|
+
return normalizeNativeConfig(nativeConfig);
|
|
52
77
|
},
|
|
53
78
|
}
|
|
54
79
|
: (new Proxy(
|