@onekeyfe/react-native-network-throttle 3.0.77 → 3.0.79
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.
|
@@ -7,20 +7,34 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
7
7
|
import com.facebook.react.bridge.WritableMap
|
|
8
8
|
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
9
9
|
import java.io.IOException
|
|
10
|
+
import java.io.InterruptedIOException
|
|
10
11
|
import java.util.concurrent.TimeUnit
|
|
11
12
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
12
13
|
import java.util.concurrent.atomic.AtomicLong
|
|
13
14
|
import okhttp3.Interceptor
|
|
15
|
+
import okhttp3.MediaType
|
|
14
16
|
import okhttp3.OkHttpClient
|
|
17
|
+
import okhttp3.RequestBody
|
|
15
18
|
import okhttp3.Response
|
|
19
|
+
import okhttp3.ResponseBody
|
|
20
|
+
import okio.Buffer
|
|
21
|
+
import okio.BufferedSink
|
|
22
|
+
import okio.BufferedSource
|
|
23
|
+
import okio.ForwardingSink
|
|
24
|
+
import okio.ForwardingSource
|
|
25
|
+
import okio.Source
|
|
26
|
+
import okio.buffer
|
|
16
27
|
|
|
17
28
|
internal object NetworkThrottle {
|
|
18
29
|
private const val TAG = "OneKeyNetworkThrottle"
|
|
19
30
|
private const val PROFILE_SLOW_4G = "slow4g"
|
|
20
31
|
private const val DEFAULT_LATENCY_MS = 562.5
|
|
32
|
+
private const val DEFAULT_THROUGHPUT_BPS = 102 * 1024
|
|
21
33
|
|
|
22
34
|
private val enabled = AtomicBoolean(false)
|
|
23
35
|
private val latencyNanos = AtomicLong((DEFAULT_LATENCY_MS * 1_000_000.0).toLong())
|
|
36
|
+
private val downloadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
37
|
+
private val uploadBps = AtomicLong(DEFAULT_THROUGHPUT_BPS.toLong())
|
|
24
38
|
private val installed = AtomicBoolean(false)
|
|
25
39
|
|
|
26
40
|
fun install(context: Context) {
|
|
@@ -31,15 +45,17 @@ internal object NetworkThrottle {
|
|
|
31
45
|
OkHttpClientProvider.setOkHttpClientFactory {
|
|
32
46
|
val builder: OkHttpClient.Builder =
|
|
33
47
|
OkHttpClientProvider.createClientBuilder(applicationContext)
|
|
34
|
-
builder.addInterceptor(
|
|
48
|
+
builder.addInterceptor(ThrottleInterceptor())
|
|
35
49
|
builder.build()
|
|
36
50
|
}
|
|
37
|
-
Log.i(TAG, "[onekey-network-throttle] installed RN OkHttp
|
|
51
|
+
Log.i(TAG, "[onekey-network-throttle] installed RN OkHttp throttle interceptor")
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
fun setConfig(config: ReadableMap): WritableMap {
|
|
41
55
|
val hasEnabled = config.hasKey("enabled") && !config.isNull("enabled")
|
|
42
56
|
val hasLatencyMs = config.hasKey("latencyMs") && !config.isNull("latencyMs")
|
|
57
|
+
val hasDownloadBps = config.hasKey("downloadBps") && !config.isNull("downloadBps")
|
|
58
|
+
val hasUploadBps = config.hasKey("uploadBps") && !config.isNull("uploadBps")
|
|
43
59
|
val nextEnabled =
|
|
44
60
|
if (hasEnabled) config.getBoolean("enabled") else enabled.get()
|
|
45
61
|
var nextLatencyMs =
|
|
@@ -51,12 +67,32 @@ internal object NetworkThrottle {
|
|
|
51
67
|
if (nextLatencyMs <= 0) {
|
|
52
68
|
nextLatencyMs = DEFAULT_LATENCY_MS
|
|
53
69
|
}
|
|
70
|
+
var nextDownloadBps =
|
|
71
|
+
if (hasDownloadBps) {
|
|
72
|
+
config.getDouble("downloadBps").toLong()
|
|
73
|
+
} else {
|
|
74
|
+
downloadBps.get()
|
|
75
|
+
}
|
|
76
|
+
if (nextDownloadBps <= 0) {
|
|
77
|
+
nextDownloadBps = DEFAULT_THROUGHPUT_BPS.toLong()
|
|
78
|
+
}
|
|
79
|
+
var nextUploadBps =
|
|
80
|
+
if (hasUploadBps) {
|
|
81
|
+
config.getDouble("uploadBps").toLong()
|
|
82
|
+
} else {
|
|
83
|
+
uploadBps.get()
|
|
84
|
+
}
|
|
85
|
+
if (nextUploadBps <= 0) {
|
|
86
|
+
nextUploadBps = DEFAULT_THROUGHPUT_BPS.toLong()
|
|
87
|
+
}
|
|
54
88
|
|
|
55
89
|
enabled.set(nextEnabled)
|
|
56
90
|
latencyNanos.set((nextLatencyMs * 1_000_000.0).toLong())
|
|
91
|
+
downloadBps.set(nextDownloadBps)
|
|
92
|
+
uploadBps.set(nextUploadBps)
|
|
57
93
|
Log.i(
|
|
58
94
|
TAG,
|
|
59
|
-
"[onekey-network-throttle] native config enabled=$nextEnabled profile=$PROFILE_SLOW_4G latencyMs=$nextLatencyMs"
|
|
95
|
+
"[onekey-network-throttle] native config enabled=$nextEnabled profile=$PROFILE_SLOW_4G latencyMs=$nextLatencyMs downloadBps=$nextDownloadBps uploadBps=$nextUploadBps"
|
|
60
96
|
)
|
|
61
97
|
return getConfig()
|
|
62
98
|
}
|
|
@@ -66,34 +102,147 @@ internal object NetworkThrottle {
|
|
|
66
102
|
map.putBoolean("enabled", enabled.get())
|
|
67
103
|
map.putString("profile", PROFILE_SLOW_4G)
|
|
68
104
|
map.putDouble("latencyMs", latencyNanos.get() / 1_000_000.0)
|
|
105
|
+
map.putDouble("downloadBps", downloadBps.get().toDouble())
|
|
106
|
+
map.putDouble("uploadBps", uploadBps.get().toDouble())
|
|
69
107
|
return map
|
|
70
108
|
}
|
|
71
109
|
|
|
72
110
|
private fun getLatencyNanos(): Long = if (enabled.get()) latencyNanos.get() else 0L
|
|
111
|
+
private fun getDownloadBps(): Long = if (enabled.get()) downloadBps.get() else 0L
|
|
112
|
+
private fun getUploadBps(): Long = if (enabled.get()) uploadBps.get() else 0L
|
|
113
|
+
|
|
114
|
+
private fun sleepNanos(delayNanos: Long) {
|
|
115
|
+
if (delayNanos <= 0) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
val delayMs = TimeUnit.NANOSECONDS.toMillis(delayNanos)
|
|
120
|
+
val remainingNanos =
|
|
121
|
+
(delayNanos - TimeUnit.MILLISECONDS.toNanos(delayMs)).toInt()
|
|
122
|
+
Thread.sleep(delayMs, remainingNanos)
|
|
123
|
+
} catch (error: InterruptedException) {
|
|
124
|
+
Thread.currentThread().interrupt()
|
|
125
|
+
val interruptedIOException =
|
|
126
|
+
InterruptedIOException("Interrupted while applying OneKey network throttle")
|
|
127
|
+
interruptedIOException.initCause(error)
|
|
128
|
+
throw interruptedIOException
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private class BandwidthLimiter(private val bytesPerSecond: Long) {
|
|
133
|
+
private val startNanos = System.nanoTime()
|
|
134
|
+
private var transferredBytes = 0L
|
|
135
|
+
|
|
136
|
+
fun throttle(byteCount: Long) {
|
|
137
|
+
if (bytesPerSecond <= 0 || byteCount <= 0) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
transferredBytes += byteCount
|
|
141
|
+
val expectedElapsedNanos =
|
|
142
|
+
(transferredBytes.toDouble() * 1_000_000_000.0 / bytesPerSecond.toDouble())
|
|
143
|
+
.toLong()
|
|
144
|
+
val elapsedNanos = System.nanoTime() - startNanos
|
|
145
|
+
sleepNanos(expectedElapsedNanos - elapsedNanos)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private class ThrottledRequestBody(
|
|
150
|
+
private val delegate: RequestBody,
|
|
151
|
+
private val bytesPerSecond: Long
|
|
152
|
+
) : RequestBody() {
|
|
153
|
+
override fun contentType(): MediaType? = delegate.contentType()
|
|
154
|
+
|
|
155
|
+
override fun contentLength(): Long = delegate.contentLength()
|
|
156
|
+
|
|
157
|
+
override fun isDuplex(): Boolean = delegate.isDuplex()
|
|
73
158
|
|
|
74
|
-
|
|
159
|
+
override fun isOneShot(): Boolean = delegate.isOneShot()
|
|
160
|
+
|
|
161
|
+
override fun writeTo(sink: BufferedSink) {
|
|
162
|
+
val limiter = BandwidthLimiter(bytesPerSecond)
|
|
163
|
+
val throttledSink = object : ForwardingSink(sink) {
|
|
164
|
+
override fun write(source: Buffer, byteCount: Long) {
|
|
165
|
+
super.write(source, byteCount)
|
|
166
|
+
limiter.throttle(byteCount)
|
|
167
|
+
}
|
|
168
|
+
}.buffer()
|
|
169
|
+
delegate.writeTo(throttledSink)
|
|
170
|
+
throttledSink.flush()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private class ThrottledResponseBody(
|
|
175
|
+
private val delegate: ResponseBody,
|
|
176
|
+
private val bytesPerSecond: Long
|
|
177
|
+
) : ResponseBody() {
|
|
178
|
+
private var bufferedSource: BufferedSource? = null
|
|
179
|
+
|
|
180
|
+
override fun contentType(): MediaType? = delegate.contentType()
|
|
181
|
+
|
|
182
|
+
override fun contentLength(): Long = delegate.contentLength()
|
|
183
|
+
|
|
184
|
+
override fun source(): BufferedSource {
|
|
185
|
+
if (bufferedSource == null) {
|
|
186
|
+
val limiter = BandwidthLimiter(bytesPerSecond)
|
|
187
|
+
val throttledSource: Source = object : ForwardingSource(delegate.source()) {
|
|
188
|
+
override fun read(sink: Buffer, byteCount: Long): Long {
|
|
189
|
+
val bytesRead = super.read(sink, byteCount)
|
|
190
|
+
if (bytesRead > 0) {
|
|
191
|
+
limiter.throttle(bytesRead)
|
|
192
|
+
}
|
|
193
|
+
return bytesRead
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
bufferedSource = throttledSource.buffer()
|
|
197
|
+
}
|
|
198
|
+
return bufferedSource!!
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private class ThrottleInterceptor : Interceptor {
|
|
75
203
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
76
204
|
val requestStartNanos = System.nanoTime()
|
|
77
205
|
val delayNanos = getLatencyNanos()
|
|
78
|
-
val
|
|
206
|
+
val request = chain.request()
|
|
207
|
+
val requestBody = request.body
|
|
208
|
+
val activeUploadBps = getUploadBps()
|
|
209
|
+
val throttledRequest =
|
|
210
|
+
if (activeUploadBps > 0 && requestBody != null) {
|
|
211
|
+
request.newBuilder()
|
|
212
|
+
.method(
|
|
213
|
+
request.method,
|
|
214
|
+
ThrottledRequestBody(requestBody, activeUploadBps)
|
|
215
|
+
)
|
|
216
|
+
.build()
|
|
217
|
+
} else {
|
|
218
|
+
request
|
|
219
|
+
}
|
|
220
|
+
val response = chain.proceed(throttledRequest)
|
|
79
221
|
if (delayNanos > 0) {
|
|
80
222
|
try {
|
|
81
223
|
val elapsedNanos = System.nanoTime() - requestStartNanos
|
|
82
224
|
val remainingDelayNanos = delayNanos - elapsedNanos
|
|
83
225
|
if (remainingDelayNanos <= 0) {
|
|
84
|
-
return response
|
|
226
|
+
return wrapResponseBody(response)
|
|
85
227
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(remainingDelayNanos - TimeUnit.MILLISECONDS.toNanos(delayMs)).toInt()
|
|
89
|
-
Thread.sleep(delayMs, remainingNanos)
|
|
90
|
-
} catch (error: InterruptedException) {
|
|
228
|
+
sleepNanos(remainingDelayNanos)
|
|
229
|
+
} catch (error: InterruptedIOException) {
|
|
91
230
|
response.close()
|
|
92
|
-
Thread.currentThread().interrupt()
|
|
93
231
|
throw IOException("Interrupted while applying OneKey network throttle", error)
|
|
94
232
|
}
|
|
95
233
|
}
|
|
96
|
-
return response
|
|
234
|
+
return wrapResponseBody(response)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private fun wrapResponseBody(response: Response): Response {
|
|
238
|
+
val responseBody = response.body ?: return response
|
|
239
|
+
val activeDownloadBps = getDownloadBps()
|
|
240
|
+
if (activeDownloadBps <= 0) {
|
|
241
|
+
return response
|
|
242
|
+
}
|
|
243
|
+
return response.newBuilder()
|
|
244
|
+
.body(ThrottledResponseBody(responseBody, activeDownloadBps))
|
|
245
|
+
.build()
|
|
97
246
|
}
|
|
98
247
|
}
|
|
99
248
|
}
|
|
@@ -7,18 +7,26 @@
|
|
|
7
7
|
static NSString *const OneKeyNetworkThrottleHandledKey = @"OneKeyNetworkThrottleHandled";
|
|
8
8
|
static NSString *const OneKeyNetworkThrottleProfileSlow4G = @"slow4g";
|
|
9
9
|
static const NSTimeInterval OneKeyNetworkThrottleDefaultLatencyMs = 562.5;
|
|
10
|
+
static const NSInteger OneKeyNetworkThrottleDefaultThroughputBps = 102 * 1024;
|
|
10
11
|
|
|
11
12
|
@interface OneKeyNetworkThrottleState : NSObject
|
|
12
13
|
+ (NSDictionary *)currentConfig;
|
|
13
14
|
+ (BOOL)isEnabled;
|
|
14
15
|
+ (NSTimeInterval)latencyMs;
|
|
15
|
-
+ (
|
|
16
|
+
+ (NSInteger)downloadBps;
|
|
17
|
+
+ (NSInteger)uploadBps;
|
|
18
|
+
+ (NSDictionary *)setEnabled:(BOOL)enabled
|
|
19
|
+
latencyMs:(NSTimeInterval)latencyMs
|
|
20
|
+
downloadBps:(NSInteger)downloadBps
|
|
21
|
+
uploadBps:(NSInteger)uploadBps;
|
|
16
22
|
@end
|
|
17
23
|
|
|
18
24
|
@implementation OneKeyNetworkThrottleState
|
|
19
25
|
|
|
20
26
|
static atomic_bool _oneKeyNetworkThrottleEnabled = ATOMIC_VAR_INIT(false);
|
|
21
27
|
static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500);
|
|
28
|
+
static atomic_llong _oneKeyNetworkThrottleDownloadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
29
|
+
static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024);
|
|
22
30
|
|
|
23
31
|
+ (NSDictionary *)currentConfig
|
|
24
32
|
{
|
|
@@ -28,7 +36,9 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
28
36
|
return @{
|
|
29
37
|
@"enabled": @(enabled),
|
|
30
38
|
@"profile": OneKeyNetworkThrottleProfileSlow4G,
|
|
31
|
-
@"latencyMs": @(latencyMs)
|
|
39
|
+
@"latencyMs": @(latencyMs),
|
|
40
|
+
@"downloadBps": @([self downloadBps]),
|
|
41
|
+
@"uploadBps": @([self uploadBps])
|
|
32
42
|
};
|
|
33
43
|
}
|
|
34
44
|
|
|
@@ -42,20 +52,44 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
42
52
|
return ((NSTimeInterval)atomic_load_explicit(&_oneKeyNetworkThrottleLatencyMicros, memory_order_relaxed)) / 1000.0;
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
+ (
|
|
55
|
+
+ (NSInteger)downloadBps
|
|
56
|
+
{
|
|
57
|
+
return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleDownloadBps, memory_order_relaxed);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
+ (NSInteger)uploadBps
|
|
61
|
+
{
|
|
62
|
+
return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleUploadBps, memory_order_relaxed);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
+ (NSInteger)normalizeThroughputBps:(NSInteger)throughputBps
|
|
66
|
+
{
|
|
67
|
+
return throughputBps > 0 ? throughputBps : OneKeyNetworkThrottleDefaultThroughputBps;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
+ (NSDictionary *)setEnabled:(BOOL)enabled
|
|
71
|
+
latencyMs:(NSTimeInterval)latencyMs
|
|
72
|
+
downloadBps:(NSInteger)downloadBps
|
|
73
|
+
uploadBps:(NSInteger)uploadBps
|
|
46
74
|
{
|
|
47
75
|
NSTimeInterval normalizedLatencyMs = latencyMs > 0 ? latencyMs : OneKeyNetworkThrottleDefaultLatencyMs;
|
|
76
|
+
NSInteger normalizedDownloadBps = [self normalizeThroughputBps:downloadBps];
|
|
77
|
+
NSInteger normalizedUploadBps = [self normalizeThroughputBps:uploadBps];
|
|
48
78
|
atomic_store_explicit(
|
|
49
79
|
&_oneKeyNetworkThrottleLatencyMicros,
|
|
50
80
|
(long long)llround(normalizedLatencyMs * 1000.0),
|
|
51
81
|
memory_order_relaxed
|
|
52
82
|
);
|
|
83
|
+
atomic_store_explicit(&_oneKeyNetworkThrottleDownloadBps, (long long)normalizedDownloadBps, memory_order_relaxed);
|
|
84
|
+
atomic_store_explicit(&_oneKeyNetworkThrottleUploadBps, (long long)normalizedUploadBps, memory_order_relaxed);
|
|
53
85
|
atomic_store_explicit(&_oneKeyNetworkThrottleEnabled, enabled, memory_order_release);
|
|
54
86
|
NSLog(
|
|
55
|
-
@"[onekey-network-throttle] native config enabled=%@ profile=%@ latencyMs=%.1f",
|
|
87
|
+
@"[onekey-network-throttle] native config enabled=%@ profile=%@ latencyMs=%.1f downloadBps=%ld uploadBps=%ld",
|
|
56
88
|
enabled ? @"true" : @"false",
|
|
57
89
|
OneKeyNetworkThrottleProfileSlow4G,
|
|
58
|
-
normalizedLatencyMs
|
|
90
|
+
normalizedLatencyMs,
|
|
91
|
+
(long)normalizedDownloadBps,
|
|
92
|
+
(long)normalizedUploadBps
|
|
59
93
|
);
|
|
60
94
|
return [self currentConfig];
|
|
61
95
|
}
|
|
@@ -68,12 +102,22 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
68
102
|
@property (atomic, assign) BOOL stopped;
|
|
69
103
|
@property (nonatomic, assign) CFAbsoluteTime requestStartTime;
|
|
70
104
|
@property (nonatomic, assign) NSTimeInterval latencySeconds;
|
|
105
|
+
@property (nonatomic, assign) NSInteger downloadBps;
|
|
106
|
+
@property (nonatomic, assign) NSInteger uploadBps;
|
|
107
|
+
@property (nonatomic, assign) CFAbsoluteTime downloadStartTime;
|
|
108
|
+
@property (nonatomic, assign) long long downloadedBytes;
|
|
71
109
|
@property (atomic, assign) BOOL responseDelivered;
|
|
110
|
+
@property (atomic, assign) BOOL flushingData;
|
|
111
|
+
@property (atomic, assign) BOOL upstreamCompleted;
|
|
112
|
+
@property (nonatomic, strong) NSError *upstreamError;
|
|
72
113
|
@property (nonatomic, strong) NSMutableArray<NSData *> *pendingData;
|
|
73
114
|
@property (nonatomic, copy) void (^pendingResponseCompletionHandler)(NSURLSessionResponseDisposition disposition);
|
|
74
115
|
- (NSTimeInterval)remainingLatencyDelay;
|
|
116
|
+
- (NSTimeInterval)uploadDelayForRequest:(NSURLRequest *)request;
|
|
117
|
+
- (NSTimeInterval)downloadDelayForDataLength:(NSUInteger)dataLength;
|
|
75
118
|
- (void)deliverResponse:(NSURLResponse *)response;
|
|
76
119
|
- (void)flushPendingData;
|
|
120
|
+
- (void)finishIfPossible;
|
|
77
121
|
- (void)cancelPendingResponseCompletionHandler;
|
|
78
122
|
- (void)invalidateSessionAndClearTaskWithCancel:(BOOL)cancel;
|
|
79
123
|
@end
|
|
@@ -104,6 +148,12 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
104
148
|
|
|
105
149
|
self.requestStartTime = CFAbsoluteTimeGetCurrent();
|
|
106
150
|
self.latencySeconds = [OneKeyNetworkThrottleState latencyMs] / 1000.0;
|
|
151
|
+
self.downloadBps = [OneKeyNetworkThrottleState downloadBps];
|
|
152
|
+
self.uploadBps = [OneKeyNetworkThrottleState uploadBps];
|
|
153
|
+
self.downloadStartTime = 0;
|
|
154
|
+
self.downloadedBytes = 0;
|
|
155
|
+
self.upstreamCompleted = NO;
|
|
156
|
+
self.upstreamError = nil;
|
|
107
157
|
self.pendingData = [NSMutableArray array];
|
|
108
158
|
|
|
109
159
|
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
@@ -116,7 +166,16 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
116
166
|
configuration.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
|
117
167
|
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
|
|
118
168
|
self.task = [self.session dataTaskWithRequest:request];
|
|
119
|
-
[self
|
|
169
|
+
NSTimeInterval uploadDelay = [self uploadDelayForRequest:request];
|
|
170
|
+
if (uploadDelay <= 0) {
|
|
171
|
+
[self.task resume];
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(uploadDelay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
175
|
+
if (!self.stopped) {
|
|
176
|
+
[self.task resume];
|
|
177
|
+
}
|
|
178
|
+
});
|
|
120
179
|
}
|
|
121
180
|
|
|
122
181
|
- (void)stopLoading
|
|
@@ -133,6 +192,34 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
133
192
|
return remainingDelay > 0 ? remainingDelay : 0;
|
|
134
193
|
}
|
|
135
194
|
|
|
195
|
+
- (NSTimeInterval)uploadDelayForRequest:(NSURLRequest *)request
|
|
196
|
+
{
|
|
197
|
+
if (self.uploadBps <= 0) {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
long long bodyLength = (long long)request.HTTPBody.length;
|
|
201
|
+
if (bodyLength <= 0) {
|
|
202
|
+
NSString *contentLength = [request valueForHTTPHeaderField:@"Content-Length"];
|
|
203
|
+
bodyLength = contentLength != nil ? contentLength.longLongValue : 0;
|
|
204
|
+
}
|
|
205
|
+
return bodyLength > 0 ? ((NSTimeInterval)bodyLength) / ((NSTimeInterval)self.uploadBps) : 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
- (NSTimeInterval)downloadDelayForDataLength:(NSUInteger)dataLength
|
|
209
|
+
{
|
|
210
|
+
if (self.downloadBps <= 0 || dataLength == 0) {
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
if (self.downloadStartTime <= 0) {
|
|
214
|
+
self.downloadStartTime = CFAbsoluteTimeGetCurrent();
|
|
215
|
+
}
|
|
216
|
+
long long bytesAfterData = self.downloadedBytes + (long long)dataLength;
|
|
217
|
+
NSTimeInterval expectedElapsed = ((NSTimeInterval)bytesAfterData) / ((NSTimeInterval)self.downloadBps);
|
|
218
|
+
NSTimeInterval actualElapsed = CFAbsoluteTimeGetCurrent() - self.downloadStartTime;
|
|
219
|
+
NSTimeInterval delay = expectedElapsed - actualElapsed;
|
|
220
|
+
return delay > 0 ? delay : 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
136
223
|
- (void)deliverResponse:(NSURLResponse *)response
|
|
137
224
|
{
|
|
138
225
|
void (^completionHandler)(NSURLSessionResponseDisposition disposition) = nil;
|
|
@@ -148,24 +235,78 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
|
|
|
148
235
|
return;
|
|
149
236
|
}
|
|
150
237
|
self.responseDelivered = YES;
|
|
238
|
+
self.downloadStartTime = CFAbsoluteTimeGetCurrent();
|
|
151
239
|
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
|
152
|
-
[self flushPendingData];
|
|
153
240
|
completionHandler(NSURLSessionResponseAllow);
|
|
241
|
+
[self flushPendingData];
|
|
242
|
+
[self finishIfPossible];
|
|
154
243
|
}
|
|
155
244
|
|
|
156
245
|
- (void)flushPendingData
|
|
157
246
|
{
|
|
158
|
-
|
|
247
|
+
if (self.stopped || !self.responseDelivered) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
NSData *data = nil;
|
|
159
251
|
@synchronized (self) {
|
|
160
|
-
|
|
161
|
-
|
|
252
|
+
if (self.flushingData) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
data = self.pendingData.firstObject;
|
|
256
|
+
if (data) {
|
|
257
|
+
[self.pendingData removeObjectAtIndex:0];
|
|
258
|
+
self.flushingData = YES;
|
|
259
|
+
}
|
|
162
260
|
}
|
|
163
|
-
|
|
261
|
+
if (!data) {
|
|
262
|
+
[self finishIfPossible];
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
NSTimeInterval delay = [self downloadDelayForDataLength:data.length];
|
|
266
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
164
267
|
if (self.stopped) {
|
|
268
|
+
@synchronized (self) {
|
|
269
|
+
self.flushingData = NO;
|
|
270
|
+
}
|
|
165
271
|
return;
|
|
166
272
|
}
|
|
167
273
|
[self.client URLProtocol:self didLoadData:data];
|
|
274
|
+
@synchronized (self) {
|
|
275
|
+
self.downloadedBytes += (long long)data.length;
|
|
276
|
+
self.flushingData = NO;
|
|
277
|
+
}
|
|
278
|
+
[self flushPendingData];
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
- (void)finishIfPossible
|
|
283
|
+
{
|
|
284
|
+
NSError *error = nil;
|
|
285
|
+
BOOL shouldFinish = NO;
|
|
286
|
+
@synchronized (self) {
|
|
287
|
+
if (
|
|
288
|
+
self.stopped ||
|
|
289
|
+
!self.upstreamCompleted ||
|
|
290
|
+
!self.responseDelivered ||
|
|
291
|
+
self.flushingData ||
|
|
292
|
+
self.pendingData.count > 0
|
|
293
|
+
) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
error = self.upstreamError;
|
|
297
|
+
self.upstreamCompleted = NO;
|
|
298
|
+
shouldFinish = YES;
|
|
299
|
+
}
|
|
300
|
+
if (!shouldFinish || self.stopped) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (error) {
|
|
304
|
+
[self.client URLProtocol:self didFailWithError:error];
|
|
305
|
+
} else {
|
|
306
|
+
[self.client URLProtocolDidFinishLoading:self];
|
|
168
307
|
}
|
|
308
|
+
self.stopped = YES;
|
|
309
|
+
[self invalidateSessionAndClearTaskWithCancel:NO];
|
|
169
310
|
}
|
|
170
311
|
|
|
171
312
|
- (void)cancelPendingResponseCompletionHandler
|
|
@@ -231,28 +372,33 @@ didReceiveResponse:(NSURLResponse *)response
|
|
|
231
372
|
if (self.stopped) {
|
|
232
373
|
return;
|
|
233
374
|
}
|
|
375
|
+
@synchronized (self) {
|
|
376
|
+
[self.pendingData addObject:data];
|
|
377
|
+
}
|
|
234
378
|
if (self.responseDelivered) {
|
|
235
|
-
[self
|
|
236
|
-
} else {
|
|
237
|
-
@synchronized (self) {
|
|
238
|
-
[self.pendingData addObject:data];
|
|
239
|
-
}
|
|
379
|
+
[self flushPendingData];
|
|
240
380
|
}
|
|
241
381
|
}
|
|
242
382
|
|
|
243
383
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
|
244
384
|
{
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
385
|
+
if (error) {
|
|
386
|
+
if (!self.responseDelivered) {
|
|
387
|
+
[self cancelPendingResponseCompletionHandler];
|
|
388
|
+
}
|
|
389
|
+
if (!self.stopped) {
|
|
250
390
|
[self.client URLProtocol:self didFailWithError:error];
|
|
251
|
-
|
|
252
|
-
[self.client URLProtocolDidFinishLoading:self];
|
|
391
|
+
self.stopped = YES;
|
|
253
392
|
}
|
|
393
|
+
[self invalidateSessionAndClearTaskWithCancel:NO];
|
|
394
|
+
return;
|
|
254
395
|
}
|
|
255
|
-
|
|
396
|
+
|
|
397
|
+
@synchronized (self) {
|
|
398
|
+
self.upstreamCompleted = YES;
|
|
399
|
+
self.upstreamError = error;
|
|
400
|
+
}
|
|
401
|
+
[self finishIfPossible];
|
|
256
402
|
}
|
|
257
403
|
|
|
258
404
|
@end
|
|
@@ -309,7 +455,17 @@ RCT_REMAP_METHOD(setConfig, setConfig:(NSDictionary *)config resolver:(RCTPromis
|
|
|
309
455
|
id latencyValue = config[@"latencyMs"];
|
|
310
456
|
NSTimeInterval latencyMs =
|
|
311
457
|
latencyValue != nil && latencyValue != [NSNull null] ? [latencyValue doubleValue] : [OneKeyNetworkThrottleState latencyMs];
|
|
312
|
-
|
|
458
|
+
id downloadBpsValue = config[@"downloadBps"];
|
|
459
|
+
NSInteger downloadBps =
|
|
460
|
+
downloadBpsValue != nil && downloadBpsValue != [NSNull null]
|
|
461
|
+
? [downloadBpsValue integerValue]
|
|
462
|
+
: [OneKeyNetworkThrottleState downloadBps];
|
|
463
|
+
id uploadBpsValue = config[@"uploadBps"];
|
|
464
|
+
NSInteger uploadBps =
|
|
465
|
+
uploadBpsValue != nil && uploadBpsValue != [NSNull null]
|
|
466
|
+
? [uploadBpsValue integerValue]
|
|
467
|
+
: [OneKeyNetworkThrottleState uploadBps];
|
|
468
|
+
resolve([OneKeyNetworkThrottleState setEnabled:enabled latencyMs:latencyMs downloadBps:downloadBps uploadBps:uploadBps]);
|
|
313
469
|
}
|
|
314
470
|
|
|
315
471
|
@end
|
package/lib/module/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { NativeModules, Platform } from 'react-native';
|
|
4
4
|
export const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
|
|
5
|
+
export const NETWORK_THROTTLE_102_KIB_BPS = 102 * 1024;
|
|
6
|
+
export const NETWORK_THROTTLE_SLOW_4G_DOWNLOAD_BPS = NETWORK_THROTTLE_102_KIB_BPS;
|
|
7
|
+
export const NETWORK_THROTTLE_SLOW_4G_UPLOAD_BPS = NETWORK_THROTTLE_102_KIB_BPS;
|
|
5
8
|
const LINKING_ERROR = `The package '@onekeyfe/react-native-network-throttle' doesn't seem to be linked. ` + Platform.select({
|
|
6
9
|
ios: "- run 'pod install'\n",
|
|
7
10
|
default: ''
|
|
@@ -14,7 +17,9 @@ export const NetworkThrottle = nativeModule ? {
|
|
|
14
17
|
return nativeModule.setConfig({
|
|
15
18
|
enabled: config.enabled ?? currentConfig.enabled,
|
|
16
19
|
profile: config.profile ?? currentConfig.profile,
|
|
17
|
-
latencyMs: config.latencyMs ?? currentConfig.latencyMs
|
|
20
|
+
latencyMs: config.latencyMs ?? currentConfig.latencyMs,
|
|
21
|
+
downloadBps: config.downloadBps ?? currentConfig.downloadBps,
|
|
22
|
+
uploadBps: config.uploadBps ?? currentConfig.uploadBps
|
|
18
23
|
});
|
|
19
24
|
}
|
|
20
25
|
} : new Proxy({}, {
|
|
@@ -3,8 +3,13 @@ export type NetworkThrottleConfig = {
|
|
|
3
3
|
enabled: boolean;
|
|
4
4
|
profile: NetworkThrottleProfile;
|
|
5
5
|
latencyMs: number;
|
|
6
|
+
downloadBps: number;
|
|
7
|
+
uploadBps: number;
|
|
6
8
|
};
|
|
7
9
|
export declare const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
|
|
10
|
+
export declare const NETWORK_THROTTLE_102_KIB_BPS: number;
|
|
11
|
+
export declare const NETWORK_THROTTLE_SLOW_4G_DOWNLOAD_BPS: number;
|
|
12
|
+
export declare const NETWORK_THROTTLE_SLOW_4G_UPLOAD_BPS: number;
|
|
8
13
|
export type NetworkThrottleModule = {
|
|
9
14
|
getConfig: () => Promise<NetworkThrottleConfig>;
|
|
10
15
|
setConfig: (config: Partial<NetworkThrottleConfig>) => Promise<NetworkThrottleConfig>;
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -6,9 +6,15 @@ export type NetworkThrottleConfig = {
|
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
profile: NetworkThrottleProfile;
|
|
8
8
|
latencyMs: number;
|
|
9
|
+
downloadBps: number;
|
|
10
|
+
uploadBps: number;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
|
|
14
|
+
export const NETWORK_THROTTLE_102_KIB_BPS = 102 * 1024;
|
|
15
|
+
export const NETWORK_THROTTLE_SLOW_4G_DOWNLOAD_BPS =
|
|
16
|
+
NETWORK_THROTTLE_102_KIB_BPS;
|
|
17
|
+
export const NETWORK_THROTTLE_SLOW_4G_UPLOAD_BPS = NETWORK_THROTTLE_102_KIB_BPS;
|
|
12
18
|
|
|
13
19
|
type NativeNetworkThrottleModule = {
|
|
14
20
|
getConfig: () => Promise<NetworkThrottleConfig>;
|
|
@@ -40,6 +46,8 @@ export const NetworkThrottle: NetworkThrottleModule = nativeModule
|
|
|
40
46
|
enabled: config.enabled ?? currentConfig.enabled,
|
|
41
47
|
profile: config.profile ?? currentConfig.profile,
|
|
42
48
|
latencyMs: config.latencyMs ?? currentConfig.latencyMs,
|
|
49
|
+
downloadBps: config.downloadBps ?? currentConfig.downloadBps,
|
|
50
|
+
uploadBps: config.uploadBps ?? currentConfig.uploadBps,
|
|
43
51
|
});
|
|
44
52
|
},
|
|
45
53
|
}
|