@onekeyfe/react-native-range-downloader 3.0.81-alpha.2 → 3.0.81-alpha.3

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.
@@ -9,6 +9,9 @@ import java.io.File
9
9
  import java.security.MessageDigest
10
10
  import java.util.concurrent.CopyOnWriteArrayList
11
11
  import java.util.concurrent.atomic.AtomicLong
12
+ import kotlinx.coroutines.CoroutineScope
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.SupervisorJob
12
15
 
13
16
  // P1: Nitro adapter for the Android concurrent multi-range downloader.
14
17
  //
@@ -34,6 +37,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
34
37
 
35
38
  private val listeners = CopyOnWriteArrayList<Listener>()
36
39
  private val nextListenerId = AtomicLong(1)
40
+ private val firmwareArtifactScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
37
41
 
38
42
  // Active downloads keyed by "channel|taskId" so cancel/discardArtifacts can
39
43
  // flip the abort flag + stop the worker pool BEFORE deleting files, instead of
@@ -278,7 +282,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
278
282
  override fun downloadFirmwareArtifact(
279
283
  params: FirmwareArtifactDownloadParams,
280
284
  ): Promise<FirmwareArtifactReceipt> {
281
- return Promise.async {
285
+ return Promise.async(firmwareArtifactScope) {
282
286
  val artifact = FirmwareArtifactStore.download(params)
283
287
  FirmwareArtifactReceipt(
284
288
  artifactRef = artifact.artifactRef,
@@ -291,7 +295,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
291
295
  override fun cancelFirmwareArtifactDownloads(
292
296
  params: FirmwareArtifactCancelParams,
293
297
  ): Promise<Unit> {
294
- return Promise.async {
298
+ return Promise.async(firmwareArtifactScope) {
295
299
  FirmwareArtifactStore.cancelDownloads(params.transactionId)
296
300
  }
297
301
  }
@@ -299,7 +303,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
299
303
  override fun discardFirmwareArtifact(
300
304
  params: FirmwareArtifactRefParams,
301
305
  ): Promise<Unit> {
302
- return Promise.async {
306
+ return Promise.async(firmwareArtifactScope) {
303
307
  FirmwareArtifactStore.discard(params.artifactRef)
304
308
  }
305
309
  }
@@ -307,7 +311,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
307
311
  override fun openFirmwareArtifact(
308
312
  params: FirmwareArtifactRefParams,
309
313
  ): Promise<FirmwareArtifactReaderInfo> {
310
- return Promise.async {
314
+ return Promise.async(firmwareArtifactScope) {
311
315
  val (readerId, size) = FirmwareArtifactStore.open(params.artifactRef)
312
316
  FirmwareArtifactReaderInfo(readerId = readerId, size = size.toDouble())
313
317
  }
@@ -316,7 +320,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
316
320
  override fun readFirmwareArtifact(
317
321
  params: FirmwareArtifactReaderReadParams,
318
322
  ): Promise<ArrayBuffer> {
319
- return Promise.async {
323
+ return Promise.async(firmwareArtifactScope) {
320
324
  require(
321
325
  params.offset.isFinite() &&
322
326
  params.offset >= 0 &&
@@ -340,7 +344,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
340
344
  override fun closeFirmwareArtifact(
341
345
  params: FirmwareArtifactReaderCloseParams,
342
346
  ): Promise<Unit> {
343
- return Promise.async {
347
+ return Promise.async(firmwareArtifactScope) {
344
348
  FirmwareArtifactStore.close(params.readerId)
345
349
  }
346
350
  }
@@ -348,7 +352,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
348
352
  override fun materializeFirmwareArchive(
349
353
  params: FirmwareArchiveMaterializeParams,
350
354
  ): Promise<FirmwareArchiveMaterializeResult> {
351
- return Promise.async {
355
+ return Promise.async(firmwareArtifactScope) {
352
356
  val artifacts = FirmwareArtifactStore.materializeArchive(
353
357
  params.leaseRef,
354
358
  params.archiveArtifactRef,
@@ -372,7 +376,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
372
376
  override fun createFirmwareArtifactLease(
373
377
  params: FirmwareArtifactLeaseCreateParams,
374
378
  ): Promise<FirmwareArtifactLease> {
375
- return Promise.async {
379
+ return Promise.async(firmwareArtifactScope) {
376
380
  FirmwareArtifactLease(
377
381
  leaseRef = FirmwareArtifactStore.createLease(params.transactionId),
378
382
  )
@@ -382,7 +386,7 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
382
386
  override fun retainFirmwareArtifact(
383
387
  params: FirmwareArtifactLeaseRetainParams,
384
388
  ): Promise<Unit> {
385
- return Promise.async {
389
+ return Promise.async(firmwareArtifactScope) {
386
390
  FirmwareArtifactStore.retain(params.leaseRef, params.artifactRef)
387
391
  }
388
392
  }
@@ -390,13 +394,13 @@ class ReactNativeRangeDownloader : HybridReactNativeRangeDownloaderSpec() {
390
394
  override fun releaseFirmwareArtifactLease(
391
395
  params: FirmwareArtifactLeaseReleaseParams,
392
396
  ): Promise<Unit> {
393
- return Promise.async {
397
+ return Promise.async(firmwareArtifactScope) {
394
398
  FirmwareArtifactStore.releaseLease(params.leaseRef, params.disposition)
395
399
  }
396
400
  }
397
401
 
398
402
  override fun sweepFirmwareArtifactOrphans(): Promise<FirmwareArtifactSweepResult> {
399
- return Promise.async {
403
+ return Promise.async(firmwareArtifactScope) {
400
404
  val (deletedFiles, deletedBytes) = FirmwareArtifactStore.sweepOrphans()
401
405
  FirmwareArtifactSweepResult(
402
406
  deletedFiles = deletedFiles.toDouble(),
@@ -219,7 +219,11 @@ final class FirmwareArtifactStore {
219
219
  transactionId: params.transactionId,
220
220
  artifactRef: "fw:\(expectedSha256)"
221
221
  )
222
- let key = "\(expectedSha256):\(Int64(params.expectedSize))"
222
+ let key = firmwareArtifactDownloadKey(
223
+ transactionId: params.transactionId,
224
+ expectedSize: Int64(params.expectedSize),
225
+ expectedSha256: expectedSha256
226
+ )
223
227
  markDownloadActive(expectedSha256, delta: 1)
224
228
  do {
225
229
  let artifact = try await downloadCoordinator.run(
@@ -913,6 +917,29 @@ final class FirmwareArtifactStore {
913
917
  _ params: FirmwareArtifactDownloadParams,
914
918
  partialURL: URL,
915
919
  resumeOffset: Int64
920
+ ) async throws {
921
+ do {
922
+ try await FirmwareArtifactWallClockDeadline.run(
923
+ timeoutSeconds:
924
+ params.overallDeadlineSeconds ?? Self.defaultDownloadDeadline
925
+ ) { [self] in
926
+ try await streamDownloadWithinDeadline(
927
+ params,
928
+ partialURL: partialURL,
929
+ resumeOffset: resumeOffset
930
+ )
931
+ }
932
+ } catch FirmwareArtifactDeadlineError.exceeded {
933
+ throw FirmwareArtifactStoreError.downloadFailed(
934
+ "ARTIFACT_DEADLINE_EXCEEDED: firmware download exceeded its deadline"
935
+ )
936
+ }
937
+ }
938
+
939
+ private func streamDownloadWithinDeadline(
940
+ _ params: FirmwareArtifactDownloadParams,
941
+ partialURL: URL,
942
+ resumeOffset: Int64
916
943
  ) async throws {
917
944
  guard let url = URL(string: params.url), let hostname = url.host else {
918
945
  throw FirmwareArtifactStoreError.invalidInput("Invalid firmware URL")
@@ -949,10 +976,10 @@ final class FirmwareArtifactStore {
949
976
  }
950
977
  }
951
978
 
952
- let bytes: URLSession.AsyncBytes
979
+ let downloadedURL: URL
953
980
  let response: URLResponse
954
981
  do {
955
- (bytes, response) = try await session.bytes(for: request)
982
+ (downloadedURL, response) = try await session.download(for: request)
956
983
  } catch {
957
984
  if isCancellationError(
958
985
  error,
@@ -971,9 +998,20 @@ final class FirmwareArtifactStore {
971
998
  "ARTIFACT_NETWORK_FAILED: firmware request failed"
972
999
  )
973
1000
  }
1001
+ defer { try? fileManager.removeItem(at: downloadedURL) }
974
1002
  guard let httpResponse = response as? HTTPURLResponse else {
975
1003
  throw FirmwareArtifactStoreError.downloadFailed("Firmware response is not HTTP")
976
1004
  }
1005
+ guard
1006
+ let responseURL = httpResponse.url,
1007
+ responseURL.scheme?.lowercased() == "https",
1008
+ responseURL.host?.lowercased() == hostname.lowercased(),
1009
+ responseURL.port == nil || responseURL.port == 443
1010
+ else {
1011
+ throw FirmwareArtifactStoreError.downloadFailed(
1012
+ "ARTIFACT_REDIRECT_REJECTED: firmware response changed canonical identity"
1013
+ )
1014
+ }
977
1015
  guard httpResponse.statusCode == 200 || httpResponse.statusCode == 206 else {
978
1016
  throw FirmwareArtifactStoreError.downloadFailed(
979
1017
  "ARTIFACT_HTTP_\(httpResponse.statusCode): firmware request failed"
@@ -1003,21 +1041,25 @@ final class FirmwareArtifactStore {
1003
1041
  }
1004
1042
 
1005
1043
  var written = append ? resumeOffset : 0
1006
- var buffer = Data()
1007
- buffer.reserveCapacity(64 * 1024)
1044
+ let source = try FileHandle(forReadingFrom: downloadedURL)
1045
+ defer { try? source.close() }
1008
1046
  do {
1009
- for try await byte in bytes {
1010
- buffer.append(byte)
1011
- if buffer.count >= 64 * 1024 {
1012
- written += Int64(buffer.count)
1013
- guard written <= Int64(params.maxBytes) else {
1014
- throw FirmwareArtifactStoreError.downloadFailed(
1015
- "ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
1016
- )
1017
- }
1018
- try handle.write(contentsOf: buffer)
1019
- buffer.removeAll(keepingCapacity: true)
1047
+ while true {
1048
+ try Task.checkCancellation()
1049
+ try rejectIfCancelled(transactionId: params.transactionId)
1050
+ guard
1051
+ let buffer = try source.read(upToCount: 64 * 1024),
1052
+ !buffer.isEmpty
1053
+ else {
1054
+ break
1020
1055
  }
1056
+ written += Int64(buffer.count)
1057
+ guard written <= Int64(params.maxBytes) else {
1058
+ throw FirmwareArtifactStoreError.downloadFailed(
1059
+ "ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
1060
+ )
1061
+ }
1062
+ try handle.write(contentsOf: buffer)
1021
1063
  }
1022
1064
  } catch let error as FirmwareArtifactStoreError {
1023
1065
  throw error
@@ -1039,15 +1081,6 @@ final class FirmwareArtifactStore {
1039
1081
  "ARTIFACT_NETWORK_FAILED: firmware response stream failed"
1040
1082
  )
1041
1083
  }
1042
- if !buffer.isEmpty {
1043
- written += Int64(buffer.count)
1044
- guard written <= Int64(params.maxBytes) else {
1045
- throw FirmwareArtifactStoreError.downloadFailed(
1046
- "ARTIFACT_PROTOCOL_INVALID: firmware artifact exceeds maxBytes"
1047
- )
1048
- }
1049
- try handle.write(contentsOf: buffer)
1050
- }
1051
1084
  try handle.synchronize()
1052
1085
  try rejectIfCancelled(transactionId: params.transactionId)
1053
1086
  }
@@ -1,6 +1,77 @@
1
1
  import Foundation
2
2
  import CommonCrypto
3
3
 
4
+ enum FirmwareArtifactDeadlineError: Error {
5
+ case exceeded
6
+ }
7
+
8
+ enum FirmwareArtifactWallClockDeadline {
9
+ static func run<T>(
10
+ timeoutSeconds: TimeInterval,
11
+ operation: @escaping @Sendable () async throws -> T
12
+ ) async throws -> T {
13
+ let timeoutNanoseconds = UInt64(max(0.001, timeoutSeconds) * 1_000_000_000.0)
14
+ return try await withThrowingTaskGroup(of: T.self) { group in
15
+ group.addTask {
16
+ try await operation()
17
+ }
18
+ group.addTask {
19
+ try await Task.sleep(nanoseconds: timeoutNanoseconds)
20
+ throw FirmwareArtifactDeadlineError.exceeded
21
+ }
22
+ defer { group.cancelAll() }
23
+ guard let result = try await group.next() else {
24
+ throw FirmwareArtifactDeadlineError.exceeded
25
+ }
26
+ return result
27
+ }
28
+ }
29
+ }
30
+
31
+ func firmwareArtifactDownloadKey(
32
+ transactionId: String,
33
+ expectedSize: Int64,
34
+ expectedSha256: String
35
+ ) -> String {
36
+ "\(transactionId)|\(expectedSize)|\(expectedSha256)"
37
+ }
38
+
39
+ enum FirmwareBackgroundDownloadError: LocalizedError, CustomStringConvertible {
40
+ case cancelled
41
+ case invalidTask
42
+ case deadlineExceeded
43
+ case redirectRejected
44
+ case responseRejected
45
+ case sizeRejected
46
+ case tlsRejected
47
+ case transferFailed
48
+
49
+ var errorDescription: String? {
50
+ switch self {
51
+ case .cancelled:
52
+ return "ARTIFACT_CANCELLED: firmware background download was cancelled"
53
+ case .invalidTask:
54
+ return "ARTIFACT_PROTOCOL_INVALID: firmware background task is invalid"
55
+ case .deadlineExceeded:
56
+ return "ARTIFACT_DEADLINE_EXCEEDED: firmware download exceeded its deadline"
57
+ case .redirectRejected:
58
+ return "ARTIFACT_REDIRECT_REJECTED: firmware redirect changed canonical identity"
59
+ case .responseRejected:
60
+ return "ARTIFACT_PROTOCOL_INVALID: firmware response is invalid"
61
+ case .sizeRejected:
62
+ return "ARTIFACT_PROTOCOL_INVALID: firmware artifact size is invalid"
63
+ case .tlsRejected:
64
+ return "ARTIFACT_TLS_FAILED: firmware TLS validation failed"
65
+ case .transferFailed:
66
+ return "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
67
+ }
68
+ }
69
+
70
+ var description: String {
71
+ errorDescription ?? "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
72
+ }
73
+ }
74
+
4
75
  // MARK: - Dependency-free RangeDownloader logic (OCDS §4 / §5)
5
76
  //
6
77
  // This file holds the DETERMINISTIC, dependency-light pieces of the range
@@ -323,49 +323,22 @@ private struct FirmwareBackgroundTaskDescriptor: Codable, Equatable {
323
323
  let deadlineAt: TimeInterval
324
324
 
325
325
  var key: String {
326
- "\(expectedSize)|\(expectedSha256)"
326
+ firmwareArtifactDownloadKey(
327
+ transactionId: transactionId,
328
+ expectedSize: expectedSize,
329
+ expectedSha256: expectedSha256
330
+ )
327
331
  }
328
332
 
329
- func hasSameArtifactIdentity(
333
+ func hasSameDownloadIdentity(
330
334
  as other: FirmwareBackgroundTaskDescriptor
331
335
  ) -> Bool {
332
- expectedSize == other.expectedSize &&
336
+ transactionId == other.transactionId &&
337
+ expectedSize == other.expectedSize &&
333
338
  expectedSha256 == other.expectedSha256
334
339
  }
335
340
  }
336
341
 
337
- private enum FirmwareBackgroundDownloadError: LocalizedError {
338
- case cancelled
339
- case invalidTask
340
- case deadlineExceeded
341
- case redirectRejected
342
- case responseRejected
343
- case sizeRejected
344
- case tlsRejected
345
- case transferFailed
346
-
347
- var errorDescription: String? {
348
- switch self {
349
- case .cancelled:
350
- return "ARTIFACT_CANCELLED: firmware background download was cancelled"
351
- case .invalidTask:
352
- return "ARTIFACT_PROTOCOL_INVALID: firmware background task is invalid"
353
- case .deadlineExceeded:
354
- return "ARTIFACT_DEADLINE_EXCEEDED: firmware download exceeded its deadline"
355
- case .redirectRejected:
356
- return "ARTIFACT_REDIRECT_REJECTED: firmware redirect changed canonical identity"
357
- case .responseRejected:
358
- return "ARTIFACT_PROTOCOL_INVALID: firmware response is invalid"
359
- case .sizeRejected:
360
- return "ARTIFACT_PROTOCOL_INVALID: firmware artifact size is invalid"
361
- case .tlsRejected:
362
- return "ARTIFACT_TLS_FAILED: firmware TLS validation failed"
363
- case .transferFailed:
364
- return "ARTIFACT_NETWORK_FAILED: firmware background transfer failed"
365
- }
366
- }
367
- }
368
-
369
342
  public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
370
343
 
371
344
  public static let shared = RangeDownloader()
@@ -836,9 +809,12 @@ public final class RangeDownloader: NSObject, URLSessionDownloadDelegate {
836
809
  else {
837
810
  continue
838
811
  }
839
- if candidate.hasSameArtifactIdentity(as: descriptor) {
812
+ if candidate.hasSameDownloadIdentity(as: descriptor) {
840
813
  matchingTaskFound = true
841
- } else if candidate.taskId == descriptor.taskId {
814
+ } else if
815
+ candidate.transactionId == descriptor.transactionId &&
816
+ candidate.taskId == descriptor.taskId
817
+ {
842
818
  task.cancel()
843
819
  }
844
820
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-range-downloader",
3
- "version": "3.0.81-alpha.2",
3
+ "version": "3.0.81-alpha.3",
4
4
  "description": "react-native-range-downloader",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -88,7 +88,7 @@
88
88
  "typescript": "^5.9.2"
89
89
  },
90
90
  "peerDependencies": {
91
- "@onekeyfe/react-native-sni-connect": "3.0.81-alpha.2",
91
+ "@onekeyfe/react-native-sni-connect": "3.0.81-alpha.3",
92
92
  "react": "*",
93
93
  "react-native": "*",
94
94
  "react-native-nitro-modules": "0.33.2"