@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(
|
|
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,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
|
-
+ (
|
|
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
|
-
+ (
|
|
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
|
|
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
|
-
|
|
255
|
+
if (self.stopped || !self.responseDelivered) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
NSData *data = nil;
|
|
159
259
|
@synchronized (self) {
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
436
|
+
if (error) {
|
|
437
|
+
if (!self.responseDelivered) {
|
|
438
|
+
[self cancelPendingResponseCompletionHandler];
|
|
439
|
+
}
|
|
440
|
+
if (!self.stopped) {
|
|
250
441
|
[self.client URLProtocol:self didFailWithError:error];
|
|
251
|
-
|
|
252
|
-
[self.client URLProtocolDidFinishLoading:self];
|
|
442
|
+
self.stopped = YES;
|
|
253
443
|
}
|
|
444
|
+
[self clearPendingData];
|
|
445
|
+
[self invalidateSessionAndClearTaskWithCancel:NO];
|
|
446
|
+
return;
|
|
254
447
|
}
|
|
255
|
-
|
|
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
|
-
|
|
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
|
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
|
}
|