@onekeyfe/react-native-network-throttle 3.0.69 → 3.0.74

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,4 +2,8 @@
2
2
 
3
3
  React Native native network throttle for OneKey iOS and Android development settings.
4
4
 
5
+ Current scope is an RN HTTP response latency gate. It does not emulate download
6
+ throughput, upload throughput, offline mode, WebView traffic, or third-party
7
+ native networking stacks.
8
+
5
9
  This package only owns native request throttling. Product settings, persistence, and UI controls should remain in the host app.
@@ -38,9 +38,16 @@ internal object NetworkThrottle {
38
38
  }
39
39
 
40
40
  fun setConfig(config: ReadableMap): WritableMap {
41
- val nextEnabled = config.hasKey("enabled") && config.getBoolean("enabled")
41
+ val hasEnabled = config.hasKey("enabled") && !config.isNull("enabled")
42
+ val hasLatencyMs = config.hasKey("latencyMs") && !config.isNull("latencyMs")
43
+ val nextEnabled =
44
+ if (hasEnabled) config.getBoolean("enabled") else enabled.get()
42
45
  var nextLatencyMs =
43
- if (config.hasKey("latencyMs")) config.getDouble("latencyMs") else DEFAULT_LATENCY_MS
46
+ if (hasLatencyMs) {
47
+ config.getDouble("latencyMs")
48
+ } else {
49
+ latencyNanos.get() / 1_000_000.0
50
+ }
44
51
  if (nextLatencyMs <= 0) {
45
52
  nextLatencyMs = DEFAULT_LATENCY_MS
46
53
  }
@@ -66,19 +73,27 @@ internal object NetworkThrottle {
66
73
 
67
74
  private class LatencyInterceptor : Interceptor {
68
75
  override fun intercept(chain: Interceptor.Chain): Response {
76
+ val requestStartNanos = System.nanoTime()
69
77
  val delayNanos = getLatencyNanos()
78
+ val response = chain.proceed(chain.request())
70
79
  if (delayNanos > 0) {
71
80
  try {
72
- val delayMs = TimeUnit.NANOSECONDS.toMillis(delayNanos)
81
+ val elapsedNanos = System.nanoTime() - requestStartNanos
82
+ val remainingDelayNanos = delayNanos - elapsedNanos
83
+ if (remainingDelayNanos <= 0) {
84
+ return response
85
+ }
86
+ val delayMs = TimeUnit.NANOSECONDS.toMillis(remainingDelayNanos)
73
87
  val remainingNanos =
74
- (delayNanos - TimeUnit.MILLISECONDS.toNanos(delayMs)).toInt()
88
+ (remainingDelayNanos - TimeUnit.MILLISECONDS.toNanos(delayMs)).toInt()
75
89
  Thread.sleep(delayMs, remainingNanos)
76
90
  } catch (error: InterruptedException) {
91
+ response.close()
77
92
  Thread.currentThread().interrupt()
78
93
  throw IOException("Interrupted while applying OneKey network throttle", error)
79
94
  }
80
95
  }
81
- return chain.proceed(chain.request())
96
+ return response
82
97
  }
83
98
  }
84
99
  }
@@ -66,6 +66,16 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
66
66
  @property (nonatomic, strong) NSURLSessionDataTask *task;
67
67
  @property (nonatomic, strong) NSURLSession *session;
68
68
  @property (atomic, assign) BOOL stopped;
69
+ @property (nonatomic, assign) CFAbsoluteTime requestStartTime;
70
+ @property (nonatomic, assign) NSTimeInterval latencySeconds;
71
+ @property (atomic, assign) BOOL responseDelivered;
72
+ @property (nonatomic, strong) NSMutableArray<NSData *> *pendingData;
73
+ @property (nonatomic, copy) void (^pendingResponseCompletionHandler)(NSURLSessionResponseDisposition disposition);
74
+ - (NSTimeInterval)remainingLatencyDelay;
75
+ - (void)deliverResponse:(NSURLResponse *)response;
76
+ - (void)flushPendingData;
77
+ - (void)cancelPendingResponseCompletionHandler;
78
+ - (void)invalidateSessionAndClearTaskWithCancel:(BOOL)cancel;
69
79
  @end
70
80
 
71
81
  @implementation OneKeyNetworkThrottleURLProtocol
@@ -92,28 +102,100 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
92
102
  NSMutableURLRequest *request = [self.request mutableCopy];
93
103
  [NSURLProtocol setProperty:@YES forKey:OneKeyNetworkThrottleHandledKey inRequest:request];
94
104
 
95
- NSTimeInterval delay = [OneKeyNetworkThrottleState latencyMs] / 1000.0;
96
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
105
+ self.requestStartTime = CFAbsoluteTimeGetCurrent();
106
+ self.latencySeconds = [OneKeyNetworkThrottleState latencyMs] / 1000.0;
107
+ self.pendingData = [NSMutableArray array];
108
+
109
+ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
110
+ NSNumber *useWifiOnly = [[NSBundle mainBundle].infoDictionary objectForKey:@"ReactNetworkForceWifiOnly"];
111
+ if (useWifiOnly) {
112
+ configuration.allowsCellularAccess = ![useWifiOnly boolValue];
113
+ }
114
+ configuration.HTTPShouldSetCookies = YES;
115
+ configuration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
116
+ configuration.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
117
+ self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
118
+ self.task = [self.session dataTaskWithRequest:request];
119
+ [self.task resume];
120
+ }
121
+
122
+ - (void)stopLoading
123
+ {
124
+ self.stopped = YES;
125
+ [self cancelPendingResponseCompletionHandler];
126
+ [self invalidateSessionAndClearTaskWithCancel:YES];
127
+ }
128
+
129
+ - (NSTimeInterval)remainingLatencyDelay
130
+ {
131
+ NSTimeInterval elapsed = CFAbsoluteTimeGetCurrent() - self.requestStartTime;
132
+ NSTimeInterval remainingDelay = self.latencySeconds - elapsed;
133
+ return remainingDelay > 0 ? remainingDelay : 0;
134
+ }
135
+
136
+ - (void)deliverResponse:(NSURLResponse *)response
137
+ {
138
+ void (^completionHandler)(NSURLSessionResponseDisposition disposition) = nil;
139
+ @synchronized (self) {
140
+ completionHandler = self.pendingResponseCompletionHandler;
141
+ self.pendingResponseCompletionHandler = nil;
142
+ }
143
+ if (!completionHandler) {
144
+ return;
145
+ }
146
+ if (self.stopped) {
147
+ completionHandler(NSURLSessionResponseCancel);
148
+ return;
149
+ }
150
+ self.responseDelivered = YES;
151
+ [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
152
+ [self flushPendingData];
153
+ completionHandler(NSURLSessionResponseAllow);
154
+ }
155
+
156
+ - (void)flushPendingData
157
+ {
158
+ NSArray<NSData *> *pendingData = nil;
159
+ @synchronized (self) {
160
+ pendingData = [self.pendingData copy];
161
+ [self.pendingData removeAllObjects];
162
+ }
163
+ for (NSData *data in pendingData) {
97
164
  if (self.stopped) {
98
165
  return;
99
166
  }
100
- NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
101
- configuration.HTTPShouldSetCookies = YES;
102
- configuration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
103
- configuration.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
104
- self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
105
- self.task = [self.session dataTaskWithRequest:request];
106
- [self.task resume];
107
- });
167
+ [self.client URLProtocol:self didLoadData:data];
168
+ }
108
169
  }
109
170
 
110
- - (void)stopLoading
171
+ - (void)cancelPendingResponseCompletionHandler
111
172
  {
112
- self.stopped = YES;
113
- [self.task cancel];
114
- [self.session invalidateAndCancel];
115
- self.task = nil;
116
- self.session = nil;
173
+ void (^completionHandler)(NSURLSessionResponseDisposition disposition) = nil;
174
+ @synchronized (self) {
175
+ completionHandler = self.pendingResponseCompletionHandler;
176
+ self.pendingResponseCompletionHandler = nil;
177
+ }
178
+ if (completionHandler) {
179
+ completionHandler(NSURLSessionResponseCancel);
180
+ }
181
+ }
182
+
183
+ - (void)invalidateSessionAndClearTaskWithCancel:(BOOL)cancel
184
+ {
185
+ NSURLSessionDataTask *task = nil;
186
+ NSURLSession *session = nil;
187
+ @synchronized (self) {
188
+ task = self.task;
189
+ session = self.session;
190
+ self.task = nil;
191
+ self.session = nil;
192
+ }
193
+ if (cancel) {
194
+ [task cancel];
195
+ [session invalidateAndCancel];
196
+ } else {
197
+ [session finishTasksAndInvalidate];
198
+ }
117
199
  }
118
200
 
119
201
  - (void)URLSession:(NSURLSession *)session
@@ -131,22 +213,46 @@ static atomic_llong _oneKeyNetworkThrottleLatencyMicros = ATOMIC_VAR_INIT(562500
131
213
  didReceiveResponse:(NSURLResponse *)response
132
214
  completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
133
215
  {
134
- [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
135
- completionHandler(NSURLSessionResponseAllow);
216
+ @synchronized (self) {
217
+ self.pendingResponseCompletionHandler = completionHandler;
218
+ }
219
+ NSTimeInterval delay = [self remainingLatencyDelay];
220
+ if (delay <= 0) {
221
+ [self deliverResponse:response];
222
+ return;
223
+ }
224
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
225
+ [self deliverResponse:response];
226
+ });
136
227
  }
137
228
 
138
229
  - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
139
230
  {
140
- [self.client URLProtocol:self didLoadData:data];
231
+ if (self.stopped) {
232
+ return;
233
+ }
234
+ if (self.responseDelivered) {
235
+ [self.client URLProtocol:self didLoadData:data];
236
+ } else {
237
+ @synchronized (self) {
238
+ [self.pendingData addObject:data];
239
+ }
240
+ }
141
241
  }
142
242
 
143
243
  - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
144
244
  {
145
- if (error) {
146
- [self.client URLProtocol:self didFailWithError:error];
147
- } else {
148
- [self.client URLProtocolDidFinishLoading:self];
245
+ if (!self.responseDelivered) {
246
+ [self cancelPendingResponseCompletionHandler];
149
247
  }
248
+ if (!self.stopped) {
249
+ if (error) {
250
+ [self.client URLProtocol:self didFailWithError:error];
251
+ } else {
252
+ [self.client URLProtocolDidFinishLoading:self];
253
+ }
254
+ }
255
+ [self invalidateSessionAndClearTaskWithCancel:NO];
150
256
  }
151
257
 
152
258
  @end
@@ -197,9 +303,12 @@ RCT_REMAP_METHOD(getConfig, getConfigWithResolver:(RCTPromiseResolveBlock)resolv
197
303
 
198
304
  RCT_REMAP_METHOD(setConfig, setConfig:(NSDictionary *)config resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
199
305
  {
200
- BOOL enabled = [config[@"enabled"] boolValue];
201
- NSNumber *latencyValue = config[@"latencyMs"];
202
- NSTimeInterval latencyMs = latencyValue != nil ? [latencyValue doubleValue] : OneKeyNetworkThrottleDefaultLatencyMs;
306
+ id enabledValue = config[@"enabled"];
307
+ BOOL enabled =
308
+ enabledValue != nil && enabledValue != [NSNull null] ? [enabledValue boolValue] : [OneKeyNetworkThrottleState isEnabled];
309
+ id latencyValue = config[@"latencyMs"];
310
+ NSTimeInterval latencyMs =
311
+ latencyValue != nil && latencyValue != [NSNull null] ? [latencyValue doubleValue] : [OneKeyNetworkThrottleState latencyMs];
203
312
  resolve([OneKeyNetworkThrottleState setEnabled:enabled latencyMs:latencyMs]);
204
313
  }
205
314
 
@@ -7,7 +7,17 @@ const LINKING_ERROR = `The package '@onekeyfe/react-native-network-throttle' doe
7
7
  default: ''
8
8
  }) + '- rebuild the app after installing the package';
9
9
  const nativeModule = NativeModules.OneKeyNetworkThrottle;
10
- export const NetworkThrottle = nativeModule ? nativeModule : new Proxy({}, {
10
+ export const NetworkThrottle = nativeModule ? {
11
+ getConfig: () => nativeModule.getConfig(),
12
+ setConfig: async config => {
13
+ const currentConfig = await nativeModule.getConfig();
14
+ return nativeModule.setConfig({
15
+ enabled: config.enabled ?? currentConfig.enabled,
16
+ profile: config.profile ?? currentConfig.profile,
17
+ latencyMs: config.latencyMs ?? currentConfig.latencyMs
18
+ });
19
+ }
20
+ } : new Proxy({}, {
11
21
  get() {
12
22
  throw new Error(LINKING_ERROR);
13
23
  }
@@ -5,10 +5,10 @@ export type NetworkThrottleConfig = {
5
5
  latencyMs: number;
6
6
  };
7
7
  export declare const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
8
- type NativeNetworkThrottleModule = {
8
+ export type NetworkThrottleModule = {
9
9
  getConfig: () => Promise<NetworkThrottleConfig>;
10
10
  setConfig: (config: Partial<NetworkThrottleConfig>) => Promise<NetworkThrottleConfig>;
11
11
  };
12
- export declare const NetworkThrottle: NativeNetworkThrottleModule;
12
+ export declare const NetworkThrottle: NetworkThrottleModule;
13
13
  export default NetworkThrottle;
14
14
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-network-throttle",
3
- "version": "3.0.69",
3
+ "version": "3.0.74",
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
@@ -11,6 +11,11 @@ export type NetworkThrottleConfig = {
11
11
  export const NETWORK_THROTTLE_SLOW_4G_LATENCY_MS = 562.5;
12
12
 
13
13
  type NativeNetworkThrottleModule = {
14
+ getConfig: () => Promise<NetworkThrottleConfig>;
15
+ setConfig: (config: NetworkThrottleConfig) => Promise<NetworkThrottleConfig>;
16
+ };
17
+
18
+ export type NetworkThrottleModule = {
14
19
  getConfig: () => Promise<NetworkThrottleConfig>;
15
20
  setConfig: (
16
21
  config: Partial<NetworkThrottleConfig>
@@ -26,8 +31,18 @@ const nativeModule = NativeModules.OneKeyNetworkThrottle as
26
31
  | NativeNetworkThrottleModule
27
32
  | undefined;
28
33
 
29
- export const NetworkThrottle: NativeNetworkThrottleModule = nativeModule
30
- ? nativeModule
34
+ export const NetworkThrottle: NetworkThrottleModule = nativeModule
35
+ ? {
36
+ getConfig: () => nativeModule.getConfig(),
37
+ setConfig: async (config) => {
38
+ const currentConfig = await nativeModule.getConfig();
39
+ return nativeModule.setConfig({
40
+ enabled: config.enabled ?? currentConfig.enabled,
41
+ profile: config.profile ?? currentConfig.profile,
42
+ latencyMs: config.latencyMs ?? currentConfig.latencyMs,
43
+ });
44
+ },
45
+ }
31
46
  : (new Proxy(
32
47
  {},
33
48
  {
@@ -35,6 +50,6 @@ export const NetworkThrottle: NativeNetworkThrottleModule = nativeModule
35
50
  throw new Error(LINKING_ERROR);
36
51
  },
37
52
  }
38
- ) as NativeNetworkThrottleModule);
53
+ ) as NetworkThrottleModule);
39
54
 
40
55
  export default NetworkThrottle;