rn-network-quality 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +899 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/rnnetworkquality/CellularInfo.kt +34 -0
- package/android/src/main/java/com/rnnetworkquality/DownloadPolicy.kt +26 -0
- package/android/src/main/java/com/rnnetworkquality/Mappers.kt +106 -0
- package/android/src/main/java/com/rnnetworkquality/NetworkMonitor.kt +263 -0
- package/android/src/main/java/com/rnnetworkquality/NetworkProbe.kt +668 -0
- package/android/src/main/java/com/rnnetworkquality/NetworkQualityModule.kt +287 -0
- package/android/src/main/java/com/rnnetworkquality/NetworkQualityPackage.kt +25 -0
- package/android/src/main/java/com/rnnetworkquality/NetworkSnapshot.kt +83 -0
- package/android/src/main/java/com/rnnetworkquality/SnapshotBuilder.kt +92 -0
- package/android/src/main/java/com/rnnetworkquality/Throttler.kt +100 -0
- package/ios/CellularInfo.swift +47 -0
- package/ios/NetworkProbe.swift +391 -0
- package/ios/NetworkQuality.h +8 -0
- package/ios/NetworkQuality.mm +88 -0
- package/ios/NetworkQualityImpl.swift +371 -0
- package/ios/PathSnapshot.swift +109 -0
- package/ios/PrivacyInfo.xcprivacy +23 -0
- package/ios/Throttler.swift +174 -0
- package/lib/module/NativeNetworkQuality.js +5 -0
- package/lib/module/NativeNetworkQuality.js.map +1 -0
- package/lib/module/classify.js +126 -0
- package/lib/module/classify.js.map +1 -0
- package/lib/module/constants.js +52 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/errors.js +18 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/hooks.js +78 -0
- package/lib/module/hooks.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/manager.js +595 -0
- package/lib/module/manager.js.map +1 -0
- package/lib/module/normalize.js +35 -0
- package/lib/module/normalize.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeNetworkQuality.d.ts +50 -0
- package/lib/typescript/src/NativeNetworkQuality.d.ts.map +1 -0
- package/lib/typescript/src/classify.d.ts +12 -0
- package/lib/typescript/src/classify.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +15 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +13 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/hooks.d.ts +18 -0
- package/lib/typescript/src/hooks.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/manager.d.ts +98 -0
- package/lib/typescript/src/manager.d.ts.map +1 -0
- package/lib/typescript/src/normalize.d.ts +5 -0
- package/lib/typescript/src/normalize.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +171 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/mock.js +318 -0
- package/package.json +161 -0
- package/rn-network-quality.podspec +32 -0
- package/src/NativeNetworkQuality.ts +58 -0
- package/src/classify.ts +208 -0
- package/src/constants.ts +48 -0
- package/src/errors.ts +23 -0
- package/src/hooks.ts +110 -0
- package/src/index.tsx +25 -0
- package/src/manager.ts +891 -0
- package/src/normalize.ts +81 -0
- package/src/types.ts +207 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class NetworkProbe: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
|
|
4
|
+
struct Failure {
|
|
5
|
+
let code: String
|
|
6
|
+
let message: String
|
|
7
|
+
let error: Error?
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
typealias Completion = (Result<[String: Any], Failure>) -> Void
|
|
11
|
+
|
|
12
|
+
private enum Phase {
|
|
13
|
+
case latency(index: Int)
|
|
14
|
+
case download
|
|
15
|
+
case finished
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private static let minimumDownloadBytes = 64 * 1_024
|
|
19
|
+
private static let minimumDownloadDurationMs = 100.0
|
|
20
|
+
|
|
21
|
+
private let latencyURL: URL
|
|
22
|
+
private let downloadURL: URL?
|
|
23
|
+
private let latencySamples: Int
|
|
24
|
+
private let timeoutMs: Double
|
|
25
|
+
private let downloadMaxDurationMs: Double
|
|
26
|
+
private let downloadMaxBytes: Int
|
|
27
|
+
private let completion: Completion
|
|
28
|
+
private let queue = DispatchQueue(label: "com.rnnetworkquality.probe")
|
|
29
|
+
private let delegateQueue: OperationQueue
|
|
30
|
+
|
|
31
|
+
private lazy var session: URLSession = {
|
|
32
|
+
let configuration = URLSessionConfiguration.ephemeral
|
|
33
|
+
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
34
|
+
configuration.urlCache = nil
|
|
35
|
+
configuration.httpCookieStorage = nil
|
|
36
|
+
configuration.httpShouldSetCookies = false
|
|
37
|
+
configuration.waitsForConnectivity = false
|
|
38
|
+
return URLSession(
|
|
39
|
+
configuration: configuration,
|
|
40
|
+
delegate: self,
|
|
41
|
+
delegateQueue: delegateQueue
|
|
42
|
+
)
|
|
43
|
+
}()
|
|
44
|
+
|
|
45
|
+
private var phase: Phase = .latency(index: 0)
|
|
46
|
+
private var activeTask: URLSessionDataTask?
|
|
47
|
+
private var requestStartedAt = 0.0
|
|
48
|
+
private var responseHeadersAt: Double?
|
|
49
|
+
private var metricRttByTask: [Int: Double] = [:]
|
|
50
|
+
private var latencyResults: [Double] = []
|
|
51
|
+
private var receivedBytes = 0
|
|
52
|
+
private var downloadFirstByteAt: Double?
|
|
53
|
+
private var downloadFinishedAt: Double?
|
|
54
|
+
private var reachedDownloadLimit = false
|
|
55
|
+
private let startedAt: Double
|
|
56
|
+
private var timeoutItem: DispatchWorkItem?
|
|
57
|
+
private var downloadTimeoutItem: DispatchWorkItem?
|
|
58
|
+
|
|
59
|
+
init(
|
|
60
|
+
latencyURL: URL,
|
|
61
|
+
downloadURL: URL?,
|
|
62
|
+
latencySamples: Int,
|
|
63
|
+
timeoutMs: Double,
|
|
64
|
+
downloadMaxDurationMs: Double,
|
|
65
|
+
downloadMaxBytes: Int,
|
|
66
|
+
startedAt: Double,
|
|
67
|
+
completion: @escaping Completion
|
|
68
|
+
) {
|
|
69
|
+
self.latencyURL = latencyURL
|
|
70
|
+
self.downloadURL = downloadURL
|
|
71
|
+
self.latencySamples = max(1, latencySamples)
|
|
72
|
+
self.timeoutMs = timeoutMs
|
|
73
|
+
self.downloadMaxDurationMs = downloadMaxDurationMs
|
|
74
|
+
self.downloadMaxBytes = downloadMaxBytes
|
|
75
|
+
self.startedAt = startedAt
|
|
76
|
+
self.completion = completion
|
|
77
|
+
let delegateQueue = OperationQueue()
|
|
78
|
+
delegateQueue.name = "com.rnnetworkquality.probe.delegate"
|
|
79
|
+
delegateQueue.maxConcurrentOperationCount = 1
|
|
80
|
+
delegateQueue.underlyingQueue = queue
|
|
81
|
+
self.delegateQueue = delegateQueue
|
|
82
|
+
super.init()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static func validatedURL(_ value: String) -> URL? {
|
|
86
|
+
guard
|
|
87
|
+
let components = URLComponents(string: value),
|
|
88
|
+
let scheme = components.scheme?.lowercased(),
|
|
89
|
+
scheme == "http" || scheme == "https",
|
|
90
|
+
components.host?.isEmpty == false,
|
|
91
|
+
let url = components.url
|
|
92
|
+
else {
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
return url
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func start() {
|
|
99
|
+
queue.async { [weak self] in
|
|
100
|
+
guard let self else { return }
|
|
101
|
+
let remainingMs = timeoutMs - elapsedMilliseconds
|
|
102
|
+
guard remainingMs > 0 else {
|
|
103
|
+
handleWholeProbeTimeout()
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
let timeout = DispatchWorkItem { [weak self] in
|
|
107
|
+
self?.handleWholeProbeTimeout()
|
|
108
|
+
}
|
|
109
|
+
timeoutItem = timeout
|
|
110
|
+
queue.asyncAfter(deadline: .now() + remainingMs / 1_000, execute: timeout)
|
|
111
|
+
startLatencyRequest(index: 0)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func cancel() {
|
|
116
|
+
queue.async { [self] in
|
|
117
|
+
guard !isFinished else { return }
|
|
118
|
+
fail(
|
|
119
|
+
code: "E_PROBE_FAILED",
|
|
120
|
+
message: "The network probe was cancelled.",
|
|
121
|
+
error: nil
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func urlSession(
|
|
127
|
+
_ session: URLSession,
|
|
128
|
+
dataTask: URLSessionDataTask,
|
|
129
|
+
didReceive response: URLResponse,
|
|
130
|
+
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
|
131
|
+
) {
|
|
132
|
+
responseHeadersAt = Self.monotonicMilliseconds()
|
|
133
|
+
completionHandler(.allow)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func urlSession(
|
|
137
|
+
_ session: URLSession,
|
|
138
|
+
dataTask: URLSessionDataTask,
|
|
139
|
+
didReceive data: Data
|
|
140
|
+
) {
|
|
141
|
+
guard case .download = phase else { return }
|
|
142
|
+
let receivedAt = Self.monotonicMilliseconds()
|
|
143
|
+
if downloadFirstByteAt == nil {
|
|
144
|
+
downloadFirstByteAt = receivedAt
|
|
145
|
+
let timeout = DispatchWorkItem { [weak self] in
|
|
146
|
+
self?.handleDownloadTimeLimit()
|
|
147
|
+
}
|
|
148
|
+
downloadTimeoutItem = timeout
|
|
149
|
+
queue.asyncAfter(
|
|
150
|
+
deadline: .now() + downloadMaxDurationMs / 1_000,
|
|
151
|
+
execute: timeout
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let remaining = downloadMaxBytes - receivedBytes
|
|
156
|
+
if remaining <= 0 {
|
|
157
|
+
reachedDownloadLimit = true
|
|
158
|
+
downloadFinishedAt = receivedAt
|
|
159
|
+
dataTask.cancel()
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
receivedBytes += min(remaining, data.count)
|
|
163
|
+
downloadFinishedAt = receivedAt
|
|
164
|
+
if receivedBytes >= downloadMaxBytes {
|
|
165
|
+
reachedDownloadLimit = true
|
|
166
|
+
dataTask.cancel()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func urlSession(
|
|
171
|
+
_ session: URLSession,
|
|
172
|
+
task: URLSessionTask,
|
|
173
|
+
didFinishCollecting metrics: URLSessionTaskMetrics
|
|
174
|
+
) {
|
|
175
|
+
guard
|
|
176
|
+
let transaction = metrics.transactionMetrics.last,
|
|
177
|
+
let requestStart = transaction.requestStartDate,
|
|
178
|
+
let responseStart = transaction.responseStartDate
|
|
179
|
+
else {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
metricRttByTask[task.taskIdentifier] =
|
|
183
|
+
responseStart.timeIntervalSince(requestStart) * 1_000
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func urlSession(
|
|
187
|
+
_ session: URLSession,
|
|
188
|
+
task: URLSessionTask,
|
|
189
|
+
didCompleteWithError error: Error?
|
|
190
|
+
) {
|
|
191
|
+
guard !isFinished else { return }
|
|
192
|
+
|
|
193
|
+
switch phase {
|
|
194
|
+
case let .latency(index):
|
|
195
|
+
if let error {
|
|
196
|
+
let timedOut = (error as? URLError)?.code == .timedOut
|
|
197
|
+
fail(
|
|
198
|
+
code: timedOut ? "E_PROBE_TIMEOUT" : "E_PROBE_FAILED",
|
|
199
|
+
message: timedOut
|
|
200
|
+
? "The latency phase exceeded the probe timeout."
|
|
201
|
+
: "The latency request failed: \(error.localizedDescription)",
|
|
202
|
+
error: error
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if index > 0 {
|
|
208
|
+
let fallbackRtt = max(
|
|
209
|
+
0,
|
|
210
|
+
(responseHeadersAt ?? Self.monotonicMilliseconds()) - requestStartedAt
|
|
211
|
+
)
|
|
212
|
+
latencyResults.append(metricRttByTask[task.taskIdentifier] ?? fallbackRtt)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if index < latencySamples {
|
|
216
|
+
startLatencyRequest(index: index + 1)
|
|
217
|
+
} else if downloadURL != nil {
|
|
218
|
+
startDownloadRequest()
|
|
219
|
+
} else {
|
|
220
|
+
finish(downloadError: nil)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case .download:
|
|
224
|
+
if let error, !reachedDownloadLimit {
|
|
225
|
+
finish(downloadError: error.localizedDescription)
|
|
226
|
+
} else {
|
|
227
|
+
finish(downloadError: nil)
|
|
228
|
+
}
|
|
229
|
+
case .finished:
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private var isFinished: Bool {
|
|
235
|
+
if case .finished = phase { return true }
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func startLatencyRequest(index: Int) {
|
|
240
|
+
phase = .latency(index: index)
|
|
241
|
+
startRequest(url: latencyURL)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private func startDownloadRequest() {
|
|
245
|
+
guard let downloadURL else {
|
|
246
|
+
finish(downloadError: nil)
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
phase = .download
|
|
250
|
+
receivedBytes = 0
|
|
251
|
+
downloadFirstByteAt = nil
|
|
252
|
+
downloadFinishedAt = nil
|
|
253
|
+
reachedDownloadLimit = false
|
|
254
|
+
startRequest(url: downloadURL)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private func startRequest(url: URL) {
|
|
258
|
+
let remainingMs = timeoutMs - elapsedMilliseconds
|
|
259
|
+
guard remainingMs > 0 else {
|
|
260
|
+
handleWholeProbeTimeout()
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
guard let requestURL = Self.cacheBustedURL(url) else {
|
|
265
|
+
fail(
|
|
266
|
+
code: "E_INVALID_URL",
|
|
267
|
+
message: "Could not add a cache-busting query parameter to the probe URL.",
|
|
268
|
+
error: nil
|
|
269
|
+
)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
var request = URLRequest(url: requestURL)
|
|
274
|
+
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
275
|
+
request.timeoutInterval = max(0.001, remainingMs / 1_000)
|
|
276
|
+
request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
|
|
277
|
+
|
|
278
|
+
requestStartedAt = Self.monotonicMilliseconds()
|
|
279
|
+
responseHeadersAt = nil
|
|
280
|
+
let task = session.dataTask(with: request)
|
|
281
|
+
activeTask = task
|
|
282
|
+
task.resume()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func handleWholeProbeTimeout() {
|
|
286
|
+
guard !isFinished else { return }
|
|
287
|
+
switch phase {
|
|
288
|
+
case .download:
|
|
289
|
+
activeTask?.cancel()
|
|
290
|
+
finish(downloadError: "The throughput phase exceeded the probe timeout.")
|
|
291
|
+
case .latency:
|
|
292
|
+
fail(
|
|
293
|
+
code: "E_PROBE_TIMEOUT",
|
|
294
|
+
message: "The latency phase exceeded the probe timeout.",
|
|
295
|
+
error: nil
|
|
296
|
+
)
|
|
297
|
+
case .finished:
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func handleDownloadTimeLimit() {
|
|
303
|
+
guard case .download = phase, !isFinished else { return }
|
|
304
|
+
reachedDownloadLimit = true
|
|
305
|
+
downloadFinishedAt = Self.monotonicMilliseconds()
|
|
306
|
+
activeTask?.cancel()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private func finish(downloadError: String?) {
|
|
310
|
+
guard !isFinished else { return }
|
|
311
|
+
downloadTimeoutItem?.cancel()
|
|
312
|
+
let finishedAt = downloadFinishedAt ?? Self.monotonicMilliseconds()
|
|
313
|
+
let downloadDuration = max(0, finishedAt - (downloadFirstByteAt ?? finishedAt))
|
|
314
|
+
var resolvedDownloadError = downloadError
|
|
315
|
+
if resolvedDownloadError == nil, downloadURL != nil {
|
|
316
|
+
if receivedBytes < Self.minimumDownloadBytes {
|
|
317
|
+
resolvedDownloadError = "too-little-data"
|
|
318
|
+
} else if downloadDuration < Self.minimumDownloadDurationMs {
|
|
319
|
+
resolvedDownloadError = "too-fast-to-measure"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
let downlinkKbps: Double?
|
|
323
|
+
if resolvedDownloadError == nil,
|
|
324
|
+
downloadURL != nil,
|
|
325
|
+
downloadDuration > 0
|
|
326
|
+
{
|
|
327
|
+
downlinkKbps = Double(receivedBytes) * 8 / downloadDuration
|
|
328
|
+
} else {
|
|
329
|
+
downlinkKbps = nil
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let result: [String: Any] = [
|
|
333
|
+
"rttMs": Self.nullable(Self.median(latencyResults)),
|
|
334
|
+
"downlinkKbps": Self.nullable(downlinkKbps),
|
|
335
|
+
"bytesReceived": receivedBytes,
|
|
336
|
+
"durationMs": elapsedMilliseconds,
|
|
337
|
+
"downloadError": Self.nullable(resolvedDownloadError),
|
|
338
|
+
"timestamp": Date().timeIntervalSince1970 * 1_000,
|
|
339
|
+
]
|
|
340
|
+
complete(.success(result))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private func fail(code: String, message: String, error: Error?) {
|
|
344
|
+
complete(.failure(Failure(code: code, message: message, error: error)))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private func complete(_ result: Result<[String: Any], Failure>) {
|
|
348
|
+
guard !isFinished else { return }
|
|
349
|
+
phase = .finished
|
|
350
|
+
timeoutItem?.cancel()
|
|
351
|
+
downloadTimeoutItem?.cancel()
|
|
352
|
+
activeTask?.cancel()
|
|
353
|
+
session.invalidateAndCancel()
|
|
354
|
+
completion(result)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private var elapsedMilliseconds: Double {
|
|
358
|
+
Self.monotonicMilliseconds() - startedAt
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private static func cacheBustedURL(_ url: URL) -> URL? {
|
|
362
|
+
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
|
363
|
+
else {
|
|
364
|
+
return nil
|
|
365
|
+
}
|
|
366
|
+
var items = components.queryItems ?? []
|
|
367
|
+
items.append(URLQueryItem(name: "_nq", value: UUID().uuidString))
|
|
368
|
+
components.queryItems = items
|
|
369
|
+
return components.url
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private static func median(_ values: [Double]) -> Double? {
|
|
373
|
+
guard !values.isEmpty else { return nil }
|
|
374
|
+
let sorted = values.sorted()
|
|
375
|
+
let middle = sorted.count / 2
|
|
376
|
+
if sorted.count.isMultiple(of: 2) {
|
|
377
|
+
return (sorted[middle - 1] + sorted[middle]) / 2
|
|
378
|
+
}
|
|
379
|
+
return sorted[middle]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private static func nullable(_ value: Any?) -> Any {
|
|
383
|
+
value ?? NSNull()
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
static func monotonicMilliseconds() -> Double {
|
|
387
|
+
Double(DispatchTime.now().uptimeNanoseconds) / 1_000_000
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
extension NetworkProbe.Failure: Error {}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#import "NetworkQuality.h"
|
|
2
|
+
|
|
3
|
+
#if __has_include(<rn_network_quality/rn_network_quality-Swift.h>)
|
|
4
|
+
#import <rn_network_quality/rn_network_quality-Swift.h>
|
|
5
|
+
#else
|
|
6
|
+
#import "rn_network_quality-Swift.h"
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
@interface NetworkQuality ()
|
|
10
|
+
@property(nonatomic, strong) NetworkQualityImpl *impl;
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation NetworkQuality
|
|
14
|
+
|
|
15
|
+
- (instancetype)init
|
|
16
|
+
{
|
|
17
|
+
self = [super init];
|
|
18
|
+
if (self) {
|
|
19
|
+
_impl = [NetworkQualityImpl new];
|
|
20
|
+
__weak NetworkQuality *weakSelf = self;
|
|
21
|
+
_impl.onChange = ^(NSDictionary<NSString *, id> *snapshot) {
|
|
22
|
+
NetworkQuality *strongSelf = weakSelf;
|
|
23
|
+
if (strongSelf != nil) {
|
|
24
|
+
[strongSelf emitOnNetworkStateChange:snapshot];
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return self;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
+ (NSString *)moduleName
|
|
32
|
+
{
|
|
33
|
+
return @"NetworkQuality";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
+ (BOOL)requiresMainQueueSetup
|
|
37
|
+
{
|
|
38
|
+
return NO;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
- (void)getCurrentState:(RCTPromiseResolveBlock)resolve
|
|
42
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
43
|
+
{
|
|
44
|
+
[_impl getCurrentStateWithResolve:resolve reject:reject];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
- (void)startMonitoring:(JS::NativeNetworkQuality::NativeMonitorOptions &)options
|
|
48
|
+
{
|
|
49
|
+
[_impl startMonitoringWithThrottleMs:@(options.throttleMs())
|
|
50
|
+
bandwidthChangeThresholdPct:@(options.bandwidthChangeThresholdPct())];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (void)stopMonitoring
|
|
54
|
+
{
|
|
55
|
+
[_impl stopMonitoring];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)probe:(JS::NativeNetworkQuality::NativeProbeOptions &)options
|
|
59
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
60
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
61
|
+
{
|
|
62
|
+
[_impl probeWithLatencyUrl:options.latencyUrl()
|
|
63
|
+
downloadUrl:options.downloadUrl()
|
|
64
|
+
latencySamples:@(options.latencySamples())
|
|
65
|
+
timeoutMs:@(options.timeoutMs())
|
|
66
|
+
downloadMaxDurationMs:@(options.downloadMaxDurationMs())
|
|
67
|
+
downloadMaxBytes:@(options.downloadMaxBytes())
|
|
68
|
+
resolve:resolve
|
|
69
|
+
reject:reject];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)invalidate
|
|
73
|
+
{
|
|
74
|
+
[_impl invalidate];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (void)dealloc
|
|
78
|
+
{
|
|
79
|
+
[_impl invalidate];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
83
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
84
|
+
{
|
|
85
|
+
return std::make_shared<facebook::react::NativeNetworkQualitySpecJSI>(params);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@end
|