@onekeyfe/react-native-range-downloader 3.0.81-alpha.0 → 3.0.81-alpha.10

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 (38) hide show
  1. package/ReactNativeRangeDownloader.podspec +1 -1
  2. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRules.kt +12 -5
  3. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweep.kt +111 -0
  4. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactStore.kt +185 -189
  5. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/ReactNativeRangeDownloader.kt +16 -20
  6. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRulesTest.kt +39 -0
  7. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDeadlineTest.kt +26 -0
  8. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweepTest.kt +107 -0
  9. package/ios/FirmwareArtifactStore.swift +662 -408
  10. package/ios/RangeDownloadLogic.swift +297 -0
  11. package/ios/ReactNativeRangeDownloader.swift +42 -471
  12. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts +3 -7
  13. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts.map +1 -1
  14. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeParams.hpp +7 -6
  15. package/nitrogen/generated/android/c++/JFirmwareArtifactDownloadParams.hpp +7 -7
  16. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.cpp +0 -19
  17. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.hpp +0 -1
  18. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeParams.kt +2 -2
  19. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDownloadParams.kt +3 -3
  20. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/HybridReactNativeRangeDownloaderSpec.kt +0 -4
  21. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.hpp +15 -0
  22. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Umbrella.hpp +0 -3
  23. package/nitrogen/generated/ios/c++/HybridReactNativeRangeDownloaderSpecSwift.hpp +0 -11
  24. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeParams.swift +32 -13
  25. package/nitrogen/generated/ios/swift/FirmwareArtifactDownloadParams.swift +39 -8
  26. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec.swift +0 -1
  27. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec_cxx.swift +0 -19
  28. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeParams.hpp +6 -5
  29. package/nitrogen/generated/shared/c++/FirmwareArtifactDownloadParams.hpp +9 -9
  30. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.cpp +0 -1
  31. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.hpp +0 -4
  32. package/package.json +2 -2
  33. package/src/ReactNativeRangeDownloader.nitro.ts +4 -11
  34. package/ios/FirmwareBackgroundSessionEventRouter.swift +0 -17
  35. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseReconcileParams.hpp +0 -76
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseReconcileParams.kt +0 -38
  37. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseReconcileParams.swift +0 -48
  38. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseReconcileParams.hpp +0 -76
@@ -1,6 +1,303 @@
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
+
146
+ func firmwareArtifactIdentifierIsSafe(_ value: String) -> Bool {
147
+ value.range(
148
+ of: "^[A-Za-z0-9._:-]{1,160}$",
149
+ options: .regularExpression
150
+ ) != nil
151
+ }
152
+
153
+ private func firmwareArtifactScratchNameIsValid(
154
+ _ name: String,
155
+ prefix: String
156
+ ) -> Bool {
157
+ guard name.hasPrefix(prefix) else {
158
+ return false
159
+ }
160
+ let rawUUID = String(name.dropFirst(prefix.count))
161
+ guard
162
+ rawUUID.count == 36,
163
+ let uuid = UUID(uuidString: rawUUID)
164
+ else {
165
+ return false
166
+ }
167
+ return uuid.uuidString.caseInsensitiveCompare(rawUUID) == .orderedSame
168
+ }
169
+
170
+ private func firmwareArtifactEntrySize(
171
+ _ entryURL: URL,
172
+ values: URLResourceValues,
173
+ fileManager: FileManager
174
+ ) -> Int64 {
175
+ if values.isRegularFile == true {
176
+ return Int64(values.fileSize ?? 0)
177
+ }
178
+ guard
179
+ values.isDirectory == true,
180
+ let enumerator = fileManager.enumerator(
181
+ at: entryURL,
182
+ includingPropertiesForKeys: [
183
+ .isRegularFileKey,
184
+ .isSymbolicLinkKey,
185
+ .fileSizeKey,
186
+ ],
187
+ options: [],
188
+ errorHandler: nil
189
+ )
190
+ else {
191
+ return 0
192
+ }
193
+ var size: Int64 = 0
194
+ for case let childURL as URL in enumerator {
195
+ guard
196
+ let childValues = try? childURL.resourceValues(
197
+ forKeys: [.isRegularFileKey, .isSymbolicLinkKey, .fileSizeKey]
198
+ ),
199
+ childValues.isRegularFile == true,
200
+ childValues.isSymbolicLink != true
201
+ else {
202
+ continue
203
+ }
204
+ size += Int64(childValues.fileSize ?? 0)
205
+ }
206
+ return size
207
+ }
208
+
209
+ func sweepFirmwareArtifactOrphansAtRoot(
210
+ _ rootURL: URL,
211
+ retainedSha256: Set<String>,
212
+ activeSha256: Set<String>,
213
+ openPaths: Set<String>,
214
+ now: Date = Date(),
215
+ fileManager: FileManager = .default
216
+ ) throws -> (deletedFiles: Int, deletedBytes: Int64) {
217
+ var deletedFiles = 0
218
+ var deletedBytes: Int64 = 0
219
+ let resourceKeys: Set<URLResourceKey> = [
220
+ .isRegularFileKey,
221
+ .isDirectoryKey,
222
+ .isSymbolicLinkKey,
223
+ .contentModificationDateKey,
224
+ .fileSizeKey,
225
+ ]
226
+ let entries = try fileManager.contentsOfDirectory(
227
+ at: rootURL,
228
+ includingPropertiesForKeys: Array(resourceKeys),
229
+ options: []
230
+ )
231
+ for entryURL in entries {
232
+ let name = entryURL.lastPathComponent
233
+ let values = try entryURL.resourceValues(forKeys: resourceKeys)
234
+ let isArchiveScratch = firmwareArtifactScratchNameIsValid(
235
+ name,
236
+ prefix: "archive-"
237
+ )
238
+ let isPromoteScratch = firmwareArtifactScratchNameIsValid(
239
+ name,
240
+ prefix: ".promote-"
241
+ )
242
+ let isScratchCandidate = values.isSymbolicLink != true &&
243
+ ((isArchiveScratch && values.isDirectory == true) ||
244
+ (isPromoteScratch && values.isRegularFile == true))
245
+ if isScratchCandidate {
246
+ guard
247
+ let modifiedAt = values.contentModificationDate,
248
+ now.timeIntervalSince(modifiedAt) >= firmwareArtifactScratchGrace
249
+ else {
250
+ continue
251
+ }
252
+ let size = firmwareArtifactEntrySize(
253
+ entryURL,
254
+ values: values,
255
+ fileManager: fileManager
256
+ )
257
+ try fileManager.removeItem(at: entryURL)
258
+ deletedFiles += 1
259
+ deletedBytes += size
260
+ continue
261
+ }
262
+
263
+ guard
264
+ values.isRegularFile == true,
265
+ values.isSymbolicLink != true,
266
+ name.count >= 64
267
+ else {
268
+ continue
269
+ }
270
+ let sha256 = String(name.prefix(64))
271
+ guard
272
+ sha256.range(of: "^[a-f0-9]{64}$", options: .regularExpression) != nil,
273
+ !retainedSha256.contains(sha256),
274
+ !activeSha256.contains(sha256),
275
+ !openPaths.contains(entryURL.path)
276
+ else {
277
+ continue
278
+ }
279
+ let grace: TimeInterval
280
+ if name.hasSuffix(".bin") {
281
+ grace = firmwareArtifactFinalGrace
282
+ } else if name.hasSuffix(".partial") {
283
+ grace = firmwareArtifactPartialGrace
284
+ } else {
285
+ continue
286
+ }
287
+ guard
288
+ let modifiedAt = values.contentModificationDate,
289
+ now.timeIntervalSince(modifiedAt) >= grace
290
+ else {
291
+ continue
292
+ }
293
+ let size = Int64(values.fileSize ?? 0)
294
+ try fileManager.removeItem(at: entryURL)
295
+ deletedFiles += 1
296
+ deletedBytes += size
297
+ }
298
+ return (deletedFiles, deletedBytes)
299
+ }
300
+
4
301
  // MARK: - Dependency-free RangeDownloader logic (OCDS §4 / §5)
5
302
  //
6
303
  // This file holds the DETERMINISTIC, dependency-light pieces of the range