@onekeyfe/react-native-network-throttle 3.0.78 → 3.0.80

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(LatencyInterceptor())
48
+ builder.addInterceptor(ThrottleInterceptor())
35
49
  builder.build()
36
50
  }
37
- Log.i(TAG, "[onekey-network-throttle] installed RN OkHttp latency interceptor")
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
- private class LatencyInterceptor : Interceptor {
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 response = chain.proceed(chain.request())
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
- val delayMs = TimeUnit.NANOSECONDS.toMillis(remainingDelayNanos)
87
- val remainingNanos =
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,27 @@
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;
11
+ static const NSUInteger OneKeyNetworkThrottleMaxPendingDownloadBytes = 256 * 1024;
10
12
 
11
13
  @interface OneKeyNetworkThrottleState : NSObject
12
14
  + (NSDictionary *)currentConfig;
13
15
  + (BOOL)isEnabled;
14
16
  + (NSTimeInterval)latencyMs;
15
- + (NSDictionary *)setEnabled:(BOOL)enabled latencyMs:(NSTimeInterval)latencyMs;
17
+ + (NSInteger)downloadBps;
18
+ + (NSInteger)uploadBps;
19
+ + (NSDictionary *)setEnabled:(BOOL)enabled
20
+ latencyMs:(NSTimeInterval)latencyMs
21
+ downloadBps:(NSInteger)downloadBps
22
+ uploadBps:(NSInteger)uploadBps;
16
23
  @end
17
24
 
18
25
  @implementation OneKeyNetworkThrottleState
19
26
 
20
27
  static atomic_bool _oneKeyNetworkThrottleEnabled = ATOMIC_VAR_INIT(false);
21
28
  static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500);
29
+ static atomic_llong _oneKeyNetworkThrottleDownloadBps = ATOMIC_VAR_INIT(102 * 1024);
30
+ static atomic_llong _oneKeyNetworkThrottleUploadBps = ATOMIC_VAR_INIT(102 * 1024);
22
31
 
23
32
  + (NSDictionary *)currentConfig
24
33
  {
@@ -28,7 +37,9 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
28
37
  return @{
29
38
  @"enabled": @(enabled),
30
39
  @"profile": OneKeyNetworkThrottleProfileSlow4G,
31
- @"latencyMs": @(latencyMs)
40
+ @"latencyMs": @(latencyMs),
41
+ @"downloadBps": @([self downloadBps]),
42
+ @"uploadBps": @([self uploadBps])
32
43
  };
33
44
  }
34
45
 
@@ -42,20 +53,44 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
42
53
  return ((NSTimeInterval)atomic_load_explicit(&_oneKeyNetworkThrottleLatencyMicros, memory_order_relaxed)) / 1000.0;
43
54
  }
44
55
 
45
- + (NSDictionary *)setEnabled:(BOOL)enabled latencyMs:(NSTimeInterval)latencyMs
56
+ + (NSInteger)downloadBps
57
+ {
58
+ return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleDownloadBps, memory_order_relaxed);
59
+ }
60
+
61
+ + (NSInteger)uploadBps
62
+ {
63
+ return (NSInteger)atomic_load_explicit(&_oneKeyNetworkThrottleUploadBps, memory_order_relaxed);
64
+ }
65
+
66
+ + (NSInteger)normalizeThroughputBps:(NSInteger)throughputBps
67
+ {
68
+ return throughputBps > 0 ? throughputBps : OneKeyNetworkThrottleDefaultThroughputBps;
69
+ }
70
+
71
+ + (NSDictionary *)setEnabled:(BOOL)enabled
72
+ latencyMs:(NSTimeInterval)latencyMs
73
+ downloadBps:(NSInteger)downloadBps
74
+ uploadBps:(NSInteger)uploadBps
46
75
  {
47
76
  NSTimeInterval normalizedLatencyMs = latencyMs > 0 ? latencyMs : OneKeyNetworkThrottleDefaultLatencyMs;
77
+ NSInteger normalizedDownloadBps = [self normalizeThroughputBps:downloadBps];
78
+ NSInteger normalizedUploadBps = [self normalizeThroughputBps:uploadBps];
48
79
  atomic_store_explicit(
49
80
  &_oneKeyNetworkThrottleLatencyMicros,
50
81
  (long long)llround(normalizedLatencyMs * 1000.0),
51
82
  memory_order_relaxed
52
83
  );
84
+ atomic_store_explicit(&_oneKeyNetworkThrottleDownloadBps, (long long)normalizedDownloadBps, memory_order_relaxed);
85
+ atomic_store_explicit(&_oneKeyNetworkThrottleUploadBps, (long long)normalizedUploadBps, memory_order_relaxed);
53
86
  atomic_store_explicit(&_oneKeyNetworkThrottleEnabled, enabled, memory_order_release);
54
87
  NSLog(
55
- @"[onekey-network-throttle] native config enabled=%@ profile=%@ latencyMs=%.1f",
88
+ @"[onekey-network-throttle] native config enabled=%@ profile=%@ latencyMs=%.1f downloadBps=%ld uploadBps=%ld",
56
89
  enabled ? @"true" : @"false",
57
90
  OneKeyNetworkThrottleProfileSlow4G,
58
- normalizedLatencyMs
91
+ normalizedLatencyMs,
92
+ (long)normalizedDownloadBps,
93
+ (long)normalizedUploadBps
59
94
  );
60
95
  return [self currentConfig];
61
96
  }
@@ -68,13 +103,27 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
68
103
  @property (atomic, assign) BOOL stopped;
69
104
  @property (nonatomic, assign) CFAbsoluteTime requestStartTime;
70
105
  @property (nonatomic, assign) NSTimeInterval latencySeconds;
106
+ @property (nonatomic, assign) NSInteger downloadBps;
107
+ @property (nonatomic, assign) NSInteger uploadBps;
108
+ @property (nonatomic, assign) CFAbsoluteTime downloadStartTime;
109
+ @property (nonatomic, assign) long long downloadedBytes;
71
110
  @property (atomic, assign) BOOL responseDelivered;
111
+ @property (atomic, assign) BOOL flushingData;
112
+ @property (atomic, assign) BOOL upstreamCompleted;
113
+ @property (nonatomic, strong) NSError *upstreamError;
72
114
  @property (nonatomic, strong) NSMutableArray<NSData *> *pendingData;
115
+ @property (nonatomic, assign) NSUInteger pendingDataBytes;
73
116
  @property (nonatomic, copy) void (^pendingResponseCompletionHandler)(NSURLSessionResponseDisposition disposition);
117
+ @property (atomic, assign) BOOL upstreamSuspendedForBackpressure;
74
118
  - (NSTimeInterval)remainingLatencyDelay;
119
+ - (NSTimeInterval)uploadDelayForRequest:(NSURLRequest *)request;
120
+ - (NSTimeInterval)downloadDelayForDataLength:(NSUInteger)dataLength;
75
121
  - (void)deliverResponse:(NSURLResponse *)response;
76
122
  - (void)flushPendingData;
123
+ - (void)finishIfPossible;
77
124
  - (void)cancelPendingResponseCompletionHandler;
125
+ - (void)applyUpstreamBackpressureIfNeeded;
126
+ - (void)clearPendingData;
78
127
  - (void)invalidateSessionAndClearTaskWithCancel:(BOOL)cancel;
79
128
  @end
80
129
 
@@ -104,7 +153,15 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
104
153
 
105
154
  self.requestStartTime = CFAbsoluteTimeGetCurrent();
106
155
  self.latencySeconds = [OneKeyNetworkThrottleState latencyMs] / 1000.0;
156
+ self.downloadBps = [OneKeyNetworkThrottleState downloadBps];
157
+ self.uploadBps = [OneKeyNetworkThrottleState uploadBps];
158
+ self.downloadStartTime = 0;
159
+ self.downloadedBytes = 0;
160
+ self.upstreamCompleted = NO;
161
+ self.upstreamError = nil;
107
162
  self.pendingData = [NSMutableArray array];
163
+ self.pendingDataBytes = 0;
164
+ self.upstreamSuspendedForBackpressure = NO;
108
165
 
109
166
  NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
110
167
  NSNumber *useWifiOnly = [[NSBundle mainBundle].infoDictionary objectForKey:@"ReactNetworkForceWifiOnly"];
@@ -116,13 +173,23 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
116
173
  configuration.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
117
174
  self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
118
175
  self.task = [self.session dataTaskWithRequest:request];
119
- [self.task resume];
176
+ NSTimeInterval uploadDelay = [self uploadDelayForRequest:request];
177
+ if (uploadDelay <= 0) {
178
+ [self.task resume];
179
+ return;
180
+ }
181
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(uploadDelay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
182
+ if (!self.stopped) {
183
+ [self.task resume];
184
+ }
185
+ });
120
186
  }
121
187
 
122
188
  - (void)stopLoading
123
189
  {
124
190
  self.stopped = YES;
125
191
  [self cancelPendingResponseCompletionHandler];
192
+ [self clearPendingData];
126
193
  [self invalidateSessionAndClearTaskWithCancel:YES];
127
194
  }
128
195
 
@@ -133,6 +200,34 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
133
200
  return remainingDelay > 0 ? remainingDelay : 0;
134
201
  }
135
202
 
203
+ - (NSTimeInterval)uploadDelayForRequest:(NSURLRequest *)request
204
+ {
205
+ if (self.uploadBps <= 0) {
206
+ return 0;
207
+ }
208
+ long long bodyLength = (long long)request.HTTPBody.length;
209
+ if (bodyLength <= 0) {
210
+ NSString *contentLength = [request valueForHTTPHeaderField:@"Content-Length"];
211
+ bodyLength = contentLength != nil ? contentLength.longLongValue : 0;
212
+ }
213
+ return bodyLength > 0 ? ((NSTimeInterval)bodyLength) / ((NSTimeInterval)self.uploadBps) : 0;
214
+ }
215
+
216
+ - (NSTimeInterval)downloadDelayForDataLength:(NSUInteger)dataLength
217
+ {
218
+ if (self.downloadBps <= 0 || dataLength == 0) {
219
+ return 0;
220
+ }
221
+ if (self.downloadStartTime <= 0) {
222
+ self.downloadStartTime = CFAbsoluteTimeGetCurrent();
223
+ }
224
+ long long bytesAfterData = self.downloadedBytes + (long long)dataLength;
225
+ NSTimeInterval expectedElapsed = ((NSTimeInterval)bytesAfterData) / ((NSTimeInterval)self.downloadBps);
226
+ NSTimeInterval actualElapsed = CFAbsoluteTimeGetCurrent() - self.downloadStartTime;
227
+ NSTimeInterval delay = expectedElapsed - actualElapsed;
228
+ return delay > 0 ? delay : 0;
229
+ }
230
+
136
231
  - (void)deliverResponse:(NSURLResponse *)response
137
232
  {
138
233
  void (^completionHandler)(NSURLSessionResponseDisposition disposition) = nil;
@@ -148,24 +243,85 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
148
243
  return;
149
244
  }
150
245
  self.responseDelivered = YES;
246
+ self.downloadStartTime = CFAbsoluteTimeGetCurrent();
151
247
  [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
152
- [self flushPendingData];
153
248
  completionHandler(NSURLSessionResponseAllow);
249
+ [self flushPendingData];
250
+ [self finishIfPossible];
154
251
  }
155
252
 
156
253
  - (void)flushPendingData
157
254
  {
158
- NSArray<NSData *> *pendingData = nil;
255
+ if (self.stopped || !self.responseDelivered) {
256
+ return;
257
+ }
258
+ NSData *data = nil;
159
259
  @synchronized (self) {
160
- pendingData = [self.pendingData copy];
161
- [self.pendingData removeAllObjects];
260
+ if (self.flushingData) {
261
+ return;
262
+ }
263
+ data = self.pendingData.firstObject;
264
+ if (data) {
265
+ [self.pendingData removeObjectAtIndex:0];
266
+ self.flushingData = YES;
267
+ }
268
+ }
269
+ if (!data) {
270
+ [self finishIfPossible];
271
+ return;
162
272
  }
163
- for (NSData *data in pendingData) {
273
+ NSTimeInterval delay = [self downloadDelayForDataLength:data.length];
274
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
164
275
  if (self.stopped) {
276
+ @synchronized (self) {
277
+ self.flushingData = NO;
278
+ }
165
279
  return;
166
280
  }
167
281
  [self.client URLProtocol:self didLoadData:data];
282
+ @synchronized (self) {
283
+ self.downloadedBytes += (long long)data.length;
284
+ if (self.pendingDataBytes >= data.length) {
285
+ self.pendingDataBytes -= data.length;
286
+ } else {
287
+ self.pendingDataBytes = 0;
288
+ }
289
+ self.flushingData = NO;
290
+ }
291
+ [self applyUpstreamBackpressureIfNeeded];
292
+ [self flushPendingData];
293
+ });
294
+ }
295
+
296
+ - (void)finishIfPossible
297
+ {
298
+ NSError *error = nil;
299
+ BOOL shouldFinish = NO;
300
+ @synchronized (self) {
301
+ if (
302
+ self.stopped ||
303
+ !self.upstreamCompleted ||
304
+ !self.responseDelivered ||
305
+ self.flushingData ||
306
+ self.pendingData.count > 0
307
+ ) {
308
+ return;
309
+ }
310
+ error = self.upstreamError;
311
+ self.upstreamCompleted = NO;
312
+ shouldFinish = YES;
313
+ }
314
+ if (!shouldFinish || self.stopped) {
315
+ return;
316
+ }
317
+ if (error) {
318
+ [self.client URLProtocol:self didFailWithError:error];
319
+ } else {
320
+ [self.client URLProtocolDidFinishLoading:self];
168
321
  }
322
+ self.stopped = YES;
323
+ [self clearPendingData];
324
+ [self invalidateSessionAndClearTaskWithCancel:NO];
169
325
  }
170
326
 
171
327
  - (void)cancelPendingResponseCompletionHandler
@@ -180,6 +336,40 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
180
336
  }
181
337
  }
182
338
 
339
+ - (void)applyUpstreamBackpressureIfNeeded
340
+ {
341
+ NSURLSessionDataTask *taskToSuspend = nil;
342
+ NSURLSessionDataTask *taskToResume = nil;
343
+ @synchronized (self) {
344
+ if (self.stopped || !self.task || self.downloadBps <= 0) {
345
+ return;
346
+ }
347
+ BOOL shouldSuspend = self.pendingDataBytes >= OneKeyNetworkThrottleMaxPendingDownloadBytes;
348
+ if (shouldSuspend && !self.upstreamSuspendedForBackpressure) {
349
+ self.upstreamSuspendedForBackpressure = YES;
350
+ taskToSuspend = self.task;
351
+ } else if (!shouldSuspend && self.upstreamSuspendedForBackpressure) {
352
+ self.upstreamSuspendedForBackpressure = NO;
353
+ taskToResume = self.task;
354
+ }
355
+ }
356
+ if (taskToSuspend) {
357
+ [taskToSuspend suspend];
358
+ }
359
+ if (taskToResume) {
360
+ [taskToResume resume];
361
+ }
362
+ }
363
+
364
+ - (void)clearPendingData
365
+ {
366
+ @synchronized (self) {
367
+ [self.pendingData removeAllObjects];
368
+ self.pendingDataBytes = 0;
369
+ self.upstreamSuspendedForBackpressure = NO;
370
+ }
371
+ }
372
+
183
373
  - (void)invalidateSessionAndClearTaskWithCancel:(BOOL)cancel
184
374
  {
185
375
  NSURLSessionDataTask *task = nil;
@@ -231,28 +421,36 @@ didReceiveResponse:(NSURLResponse *)response
231
421
  if (self.stopped) {
232
422
  return;
233
423
  }
424
+ @synchronized (self) {
425
+ [self.pendingData addObject:data];
426
+ self.pendingDataBytes += data.length;
427
+ }
428
+ [self applyUpstreamBackpressureIfNeeded];
234
429
  if (self.responseDelivered) {
235
- [self.client URLProtocol:self didLoadData:data];
236
- } else {
237
- @synchronized (self) {
238
- [self.pendingData addObject:data];
239
- }
430
+ [self flushPendingData];
240
431
  }
241
432
  }
242
433
 
243
434
  - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
244
435
  {
245
- if (!self.responseDelivered) {
246
- [self cancelPendingResponseCompletionHandler];
247
- }
248
- if (!self.stopped) {
249
- if (error) {
436
+ if (error) {
437
+ if (!self.responseDelivered) {
438
+ [self cancelPendingResponseCompletionHandler];
439
+ }
440
+ if (!self.stopped) {
250
441
  [self.client URLProtocol:self didFailWithError:error];
251
- } else {
252
- [self.client URLProtocolDidFinishLoading:self];
442
+ self.stopped = YES;
253
443
  }
444
+ [self clearPendingData];
445
+ [self invalidateSessionAndClearTaskWithCancel:NO];
446
+ return;
254
447
  }
255
- [self invalidateSessionAndClearTaskWithCancel:NO];
448
+
449
+ @synchronized (self) {
450
+ self.upstreamCompleted = YES;
451
+ self.upstreamError = error;
452
+ }
453
+ [self finishIfPossible];
256
454
  }
257
455
 
258
456
  @end
@@ -309,7 +507,17 @@ RCT_REMAP_METHOD(setConfig, setConfig:(NSDictionary *)config resolver:(RCTPromis
309
507
  id latencyValue = config[@"latencyMs"];
310
508
  NSTimeInterval latencyMs =
311
509
  latencyValue != nil && latencyValue != [NSNull null] ? [latencyValue doubleValue] : [OneKeyNetworkThrottleState latencyMs];
312
- resolve([OneKeyNetworkThrottleState setEnabled:enabled latencyMs:latencyMs]);
510
+ id downloadBpsValue = config[@"downloadBps"];
511
+ NSInteger downloadBps =
512
+ downloadBpsValue != nil && downloadBpsValue != [NSNull null]
513
+ ? [downloadBpsValue integerValue]
514
+ : [OneKeyNetworkThrottleState downloadBps];
515
+ id uploadBpsValue = config[@"uploadBps"];
516
+ NSInteger uploadBps =
517
+ uploadBpsValue != nil && uploadBpsValue != [NSNull null]
518
+ ? [uploadBpsValue integerValue]
519
+ : [OneKeyNetworkThrottleState uploadBps];
520
+ resolve([OneKeyNetworkThrottleState setEnabled:enabled latencyMs:latencyMs downloadBps:downloadBps uploadBps:uploadBps]);
313
521
  }
314
522
 
315
523
  @end
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-network-throttle",
3
- "version": "3.0.78",
3
+ "version": "3.0.80",
4
4
  "description": "react-native-network-throttle",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
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
  }