@onekeyfe/react-native-range-downloader 3.0.81-alpha.1 → 3.0.81-alpha.11

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.
Files changed (43) hide show
  1. package/ReactNativeRangeDownloader.podspec +1 -2
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRules.kt +12 -5
  4. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweep.kt +111 -0
  5. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactStore.kt +194 -214
  6. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/ReactNativeRangeDownloader.kt +21 -21
  7. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRulesTest.kt +39 -0
  8. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDeadlineTest.kt +26 -0
  9. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweepTest.kt +107 -0
  10. package/ios/FirmwareArtifactStore.swift +667 -435
  11. package/ios/RangeDownloadLogic.swift +317 -0
  12. package/ios/ReactNativeRangeDownloader.swift +60 -484
  13. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts +4 -7
  14. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts.map +1 -1
  15. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeParams.hpp +7 -6
  16. package/nitrogen/generated/android/c++/JFirmwareArtifactDownloadParams.hpp +7 -7
  17. package/nitrogen/generated/android/c++/JFirmwareArtifactReceipt.hpp +7 -3
  18. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.cpp +0 -19
  19. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.hpp +0 -1
  20. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeParams.kt +2 -2
  21. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDownloadParams.kt +3 -3
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReceipt.kt +6 -3
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/HybridReactNativeRangeDownloaderSpec.kt +0 -4
  24. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.hpp +15 -0
  25. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Umbrella.hpp +0 -3
  26. package/nitrogen/generated/ios/c++/HybridReactNativeRangeDownloaderSpecSwift.hpp +0 -11
  27. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeParams.swift +32 -13
  28. package/nitrogen/generated/ios/swift/FirmwareArtifactDownloadParams.swift +39 -8
  29. package/nitrogen/generated/ios/swift/FirmwareArtifactReceipt.swift +13 -2
  30. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec.swift +0 -1
  31. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec_cxx.swift +0 -19
  32. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeParams.hpp +6 -5
  33. package/nitrogen/generated/shared/c++/FirmwareArtifactDownloadParams.hpp +9 -9
  34. package/nitrogen/generated/shared/c++/FirmwareArtifactReceipt.hpp +6 -2
  35. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.cpp +0 -1
  36. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.hpp +0 -4
  37. package/package.json +1 -2
  38. package/src/ReactNativeRangeDownloader.nitro.ts +7 -11
  39. package/ios/FirmwareBackgroundSessionEventRouter.swift +0 -17
  40. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseReconcileParams.hpp +0 -76
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseReconcileParams.kt +0 -38
  42. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseReconcileParams.swift +0 -48
  43. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseReconcileParams.hpp +0 -76
@@ -1,6 +1,323 @@
1
1
  import Foundation
2
2
  import CommonCrypto
3
3
 
4
+ enum FirmwareArtifactStoreError: Error, CustomStringConvertible {
5
+ case invalidInput(String)
6
+ case downloadFailed(String)
7
+ case integrityMismatch(String)
8
+ case readerInvalid(String)
9
+ case archiveInvalid(String)
10
+
11
+ var description: String {
12
+ switch self {
13
+ case let .invalidInput(message), let .downloadFailed(message),
14
+ let .integrityMismatch(message), let .readerInvalid(message),
15
+ let .archiveInvalid(message):
16
+ return message
17
+ }
18
+ }
19
+ }
20
+
21
+ enum FirmwareArtifactDeadlineError: Error {
22
+ case exceeded
23
+ }
24
+
25
+ enum FirmwareArtifactWallClockDeadline {
26
+ static func run<T>(
27
+ timeoutSeconds: TimeInterval,
28
+ operation: @escaping @Sendable () async throws -> T
29
+ ) async throws -> T {
30
+ let timeoutNanoseconds = UInt64(max(0.001, timeoutSeconds) * 1_000_000_000.0)
31
+ return try await withThrowingTaskGroup(of: T.self) { group in
32
+ group.addTask {
33
+ try await operation()
34
+ }
35
+ group.addTask {
36
+ try await Task.sleep(nanoseconds: timeoutNanoseconds)
37
+ throw FirmwareArtifactDeadlineError.exceeded
38
+ }
39
+ defer { group.cancelAll() }
40
+ guard let result = try await group.next() else {
41
+ throw FirmwareArtifactDeadlineError.exceeded
42
+ }
43
+ return result
44
+ }
45
+ }
46
+ }
47
+
48
+ func firmwareArtifactDownloadKey(
49
+ transactionId: String,
50
+ taskId: String,
51
+ expectedSize: Int64?,
52
+ expectedSha256: String?,
53
+ downloadToken: String
54
+ ) -> String {
55
+ "\(transactionId)|\(taskId)|\(expectedSize.map(String.init) ?? "unknown")|\(expectedSha256 ?? "unknown")|\(downloadToken)"
56
+ }
57
+
58
+ enum FirmwareArchiveDiscoveredEntriesIssue: Equatable {
59
+ case empty
60
+ case duplicateName
61
+ }
62
+
63
+ func firmwareArchiveDiscoveredEntriesIssue(
64
+ _ entryNames: [String]
65
+ ) -> FirmwareArchiveDiscoveredEntriesIssue? {
66
+ guard !entryNames.isEmpty else {
67
+ return .empty
68
+ }
69
+ var names = Set<String>()
70
+ guard entryNames.allSatisfy({ names.insert($0).inserted }) else {
71
+ return .duplicateName
72
+ }
73
+ return nil
74
+ }
75
+
76
+ func firmwareArtifactDownloadToken(
77
+ expectedSha256: String?,
78
+ url: String
79
+ ) -> String {
80
+ if let expectedSha256 {
81
+ return expectedSha256.lowercased()
82
+ }
83
+ let urlData = Data(url.utf8)
84
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
85
+ urlData.withUnsafeBytes {
86
+ _ = CC_SHA256($0.baseAddress, CC_LONG(urlData.count), &hash)
87
+ }
88
+ return hash.map { String(format: "%02x", $0) }.joined()
89
+ }
90
+
91
+ func firmwareArtifactPartialFileName(
92
+ transactionId: String,
93
+ taskId: String,
94
+ downloadToken: String
95
+ ) -> String {
96
+ let transactionData = Data(transactionId.utf8)
97
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
98
+ transactionData.withUnsafeBytes {
99
+ _ = CC_SHA256($0.baseAddress, CC_LONG(transactionData.count), &hash)
100
+ }
101
+ let transactionToken = hash
102
+ .prefix(8)
103
+ .map { String(format: "%02x", $0) }
104
+ .joined()
105
+ return "\(downloadToken).\(taskId).\(transactionToken).partial"
106
+ }
107
+
108
+ func firmwareArtifactContentRangeIsValid(
109
+ _ value: String,
110
+ expectedStart: Int64,
111
+ expectedTotal: Int64?,
112
+ maxBytes: Int64
113
+ ) -> Bool {
114
+ guard value.lowercased().hasPrefix("bytes ") else {
115
+ return false
116
+ }
117
+ guard
118
+ let bounds = RangeDownloadLogic.parseContentRangeBounds(value),
119
+ let total = RangeDownloadLogic.parseContentRangeTotal(value)
120
+ else {
121
+ return false
122
+ }
123
+ let totalMatches = expectedTotal.map { total == $0 } ?? (total > 0 && total <= maxBytes)
124
+ return bounds.start == expectedStart &&
125
+ bounds.end >= bounds.start &&
126
+ bounds.end < total &&
127
+ totalMatches
128
+ }
129
+
130
+ func firmwareArtifactResponseFits(
131
+ expectedContentLength: Int64,
132
+ baseOffset: Int64,
133
+ maxBytes: Int64
134
+ ) -> Bool {
135
+ guard baseOffset >= 0, baseOffset <= maxBytes else {
136
+ return false
137
+ }
138
+ return expectedContentLength < 0 ||
139
+ expectedContentLength <= maxBytes - baseOffset
140
+ }
141
+
142
+ let firmwareArtifactFinalGrace: TimeInterval = 24 * 60 * 60
143
+ let firmwareArtifactPartialGrace: TimeInterval = 7 * 24 * 60 * 60
144
+ let firmwareArtifactScratchGrace: TimeInterval = firmwareArtifactPartialGrace
145
+ let firmwareArtifactMaxBytes: Int64 = 512 * 1024 * 1024
146
+ let firmwareArtifactMaxReadBytes: Int64 = 256 * 1024
147
+
148
+ func firmwareArtifactExactInt64(
149
+ _ value: Double,
150
+ minimum: Int64,
151
+ maximum: Int64
152
+ ) -> Int64? {
153
+ guard
154
+ minimum <= maximum,
155
+ value.isFinite,
156
+ value.rounded() == value,
157
+ let converted = Int64(exactly: value),
158
+ converted >= minimum,
159
+ converted <= maximum
160
+ else {
161
+ return nil
162
+ }
163
+ return converted
164
+ }
165
+
166
+ func firmwareArtifactIdentifierIsSafe(_ value: String) -> Bool {
167
+ value.range(
168
+ of: "^[A-Za-z0-9._:-]{1,160}$",
169
+ options: .regularExpression
170
+ ) != nil
171
+ }
172
+
173
+ private func firmwareArtifactScratchNameIsValid(
174
+ _ name: String,
175
+ prefix: String
176
+ ) -> Bool {
177
+ guard name.hasPrefix(prefix) else {
178
+ return false
179
+ }
180
+ let rawUUID = String(name.dropFirst(prefix.count))
181
+ guard
182
+ rawUUID.count == 36,
183
+ let uuid = UUID(uuidString: rawUUID)
184
+ else {
185
+ return false
186
+ }
187
+ return uuid.uuidString.caseInsensitiveCompare(rawUUID) == .orderedSame
188
+ }
189
+
190
+ private func firmwareArtifactEntrySize(
191
+ _ entryURL: URL,
192
+ values: URLResourceValues,
193
+ fileManager: FileManager
194
+ ) -> Int64 {
195
+ if values.isRegularFile == true {
196
+ return Int64(values.fileSize ?? 0)
197
+ }
198
+ guard
199
+ values.isDirectory == true,
200
+ let enumerator = fileManager.enumerator(
201
+ at: entryURL,
202
+ includingPropertiesForKeys: [
203
+ .isRegularFileKey,
204
+ .isSymbolicLinkKey,
205
+ .fileSizeKey,
206
+ ],
207
+ options: [],
208
+ errorHandler: nil
209
+ )
210
+ else {
211
+ return 0
212
+ }
213
+ var size: Int64 = 0
214
+ for case let childURL as URL in enumerator {
215
+ guard
216
+ let childValues = try? childURL.resourceValues(
217
+ forKeys: [.isRegularFileKey, .isSymbolicLinkKey, .fileSizeKey]
218
+ ),
219
+ childValues.isRegularFile == true,
220
+ childValues.isSymbolicLink != true
221
+ else {
222
+ continue
223
+ }
224
+ size += Int64(childValues.fileSize ?? 0)
225
+ }
226
+ return size
227
+ }
228
+
229
+ func sweepFirmwareArtifactOrphansAtRoot(
230
+ _ rootURL: URL,
231
+ retainedSha256: Set<String>,
232
+ activeSha256: Set<String>,
233
+ openPaths: Set<String>,
234
+ now: Date = Date(),
235
+ fileManager: FileManager = .default
236
+ ) throws -> (deletedFiles: Int, deletedBytes: Int64) {
237
+ var deletedFiles = 0
238
+ var deletedBytes: Int64 = 0
239
+ let resourceKeys: Set<URLResourceKey> = [
240
+ .isRegularFileKey,
241
+ .isDirectoryKey,
242
+ .isSymbolicLinkKey,
243
+ .contentModificationDateKey,
244
+ .fileSizeKey,
245
+ ]
246
+ let entries = try fileManager.contentsOfDirectory(
247
+ at: rootURL,
248
+ includingPropertiesForKeys: Array(resourceKeys),
249
+ options: []
250
+ )
251
+ for entryURL in entries {
252
+ let name = entryURL.lastPathComponent
253
+ let values = try entryURL.resourceValues(forKeys: resourceKeys)
254
+ let isArchiveScratch = firmwareArtifactScratchNameIsValid(
255
+ name,
256
+ prefix: "archive-"
257
+ )
258
+ let isPromoteScratch = firmwareArtifactScratchNameIsValid(
259
+ name,
260
+ prefix: ".promote-"
261
+ )
262
+ let isScratchCandidate = values.isSymbolicLink != true &&
263
+ ((isArchiveScratch && values.isDirectory == true) ||
264
+ (isPromoteScratch && values.isRegularFile == true))
265
+ if isScratchCandidate {
266
+ guard
267
+ let modifiedAt = values.contentModificationDate,
268
+ now.timeIntervalSince(modifiedAt) >= firmwareArtifactScratchGrace
269
+ else {
270
+ continue
271
+ }
272
+ let size = firmwareArtifactEntrySize(
273
+ entryURL,
274
+ values: values,
275
+ fileManager: fileManager
276
+ )
277
+ try fileManager.removeItem(at: entryURL)
278
+ deletedFiles += 1
279
+ deletedBytes += size
280
+ continue
281
+ }
282
+
283
+ guard
284
+ values.isRegularFile == true,
285
+ values.isSymbolicLink != true,
286
+ name.count >= 64
287
+ else {
288
+ continue
289
+ }
290
+ let sha256 = String(name.prefix(64))
291
+ guard
292
+ sha256.range(of: "^[a-f0-9]{64}$", options: .regularExpression) != nil,
293
+ !retainedSha256.contains(sha256),
294
+ !activeSha256.contains(sha256),
295
+ !openPaths.contains(entryURL.path)
296
+ else {
297
+ continue
298
+ }
299
+ let grace: TimeInterval
300
+ if name.hasSuffix(".bin") {
301
+ grace = firmwareArtifactFinalGrace
302
+ } else if name.hasSuffix(".partial") {
303
+ grace = firmwareArtifactPartialGrace
304
+ } else {
305
+ continue
306
+ }
307
+ guard
308
+ let modifiedAt = values.contentModificationDate,
309
+ now.timeIntervalSince(modifiedAt) >= grace
310
+ else {
311
+ continue
312
+ }
313
+ let size = Int64(values.fileSize ?? 0)
314
+ try fileManager.removeItem(at: entryURL)
315
+ deletedFiles += 1
316
+ deletedBytes += size
317
+ }
318
+ return (deletedFiles, deletedBytes)
319
+ }
320
+
4
321
  // MARK: - Dependency-free RangeDownloader logic (OCDS §4 / §5)
5
322
  //
6
323
  // This file holds the DETERMINISTIC, dependency-light pieces of the range