@hot-updater/react-native 0.29.2 → 0.29.4

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 (55) hide show
  1. package/HotUpdater.podspec +0 -4
  2. package/android/src/oldarch/HotUpdaterModule.kt +12 -4
  3. package/android/src/oldarch/HotUpdaterSpec.kt +3 -5
  4. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +178 -0
  5. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +82 -47
  6. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +359 -0
  7. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +386 -0
  8. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +7 -213
  9. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +8 -126
  10. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -2
  11. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +462 -0
  12. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +4 -113
  13. package/lib/commonjs/DefaultResolver.js.map +1 -1
  14. package/lib/commonjs/checkForUpdate.js.map +1 -1
  15. package/lib/commonjs/index.js +0 -7
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/native.js.map +1 -1
  18. package/lib/commonjs/native.spec.js.map +1 -1
  19. package/lib/commonjs/store.js.map +1 -1
  20. package/lib/commonjs/types.js.map +1 -1
  21. package/lib/commonjs/wrap.js.map +1 -1
  22. package/lib/module/DefaultResolver.js.map +1 -1
  23. package/lib/module/checkForUpdate.js.map +1 -1
  24. package/lib/module/index.js +0 -7
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/native.js.map +1 -1
  27. package/lib/module/native.spec.js.map +1 -1
  28. package/lib/module/store.js.map +1 -1
  29. package/lib/module/types.js.map +1 -1
  30. package/lib/module/wrap.js.map +1 -1
  31. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  38. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
  39. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  40. package/lib/typescript/module/index.d.ts.map +1 -1
  41. package/lib/typescript/module/native.d.ts.map +1 -1
  42. package/lib/typescript/module/store.d.ts.map +1 -1
  43. package/lib/typescript/module/types.d.ts.map +1 -1
  44. package/lib/typescript/module/wrap.d.ts.map +1 -1
  45. package/package.json +9 -9
  46. package/plugin/build/transformers.js +83 -97
  47. package/plugin/build/withHotUpdater.js +159 -239
  48. package/src/DefaultResolver.ts +1 -0
  49. package/src/checkForUpdate.ts +1 -0
  50. package/src/index.ts +0 -7
  51. package/src/native.spec.ts +4 -6
  52. package/src/native.ts +1 -0
  53. package/src/store.ts +1 -0
  54. package/src/types.ts +1 -0
  55. package/src/wrap.tsx +1 -0
@@ -0,0 +1,359 @@
1
+ import Compression
2
+ import Foundation
3
+ import zlib
4
+
5
+ enum CompressedTarAlgorithm {
6
+ case gzip
7
+ case brotli
8
+ }
9
+
10
+ enum StreamingTarArchiveExtractor {
11
+ private static let bufferSize = 64 * 1024
12
+ private static let decompressionProgressWeight = 0.45
13
+
14
+ static func extractCompressedTar(
15
+ file: String,
16
+ to destination: String,
17
+ algorithm: CompressedTarAlgorithm,
18
+ progressHandler: @escaping (Double) -> Void
19
+ ) throws {
20
+ try withTemporaryTarFile { temporaryTarURL in
21
+ switch algorithm {
22
+ case .gzip:
23
+ try decompressGzipArchive(
24
+ from: file,
25
+ to: temporaryTarURL.path,
26
+ progressHandler: { progress in
27
+ progressHandler(progress * decompressionProgressWeight)
28
+ }
29
+ )
30
+ case .brotli:
31
+ try decompressBrotliArchive(
32
+ from: file,
33
+ to: temporaryTarURL.path,
34
+ progressHandler: { progress in
35
+ progressHandler(progress * decompressionProgressWeight)
36
+ }
37
+ )
38
+ }
39
+
40
+ try extractTarArchive(
41
+ from: temporaryTarURL.path,
42
+ to: destination,
43
+ progressHandler: { progress in
44
+ let extractionStart = decompressionProgressWeight
45
+ let extractionWeight = 1.0 - decompressionProgressWeight
46
+ progressHandler(extractionStart + (progress * extractionWeight))
47
+ }
48
+ )
49
+ }
50
+ }
51
+
52
+ static func containsTarEntries(file: String, algorithm: CompressedTarAlgorithm) -> Bool {
53
+ do {
54
+ return try withTemporaryTarFile { temporaryTarURL in
55
+ switch algorithm {
56
+ case .gzip:
57
+ try decompressGzipArchive(
58
+ from: file,
59
+ to: temporaryTarURL.path,
60
+ progressHandler: { _ in }
61
+ )
62
+ case .brotli:
63
+ try decompressBrotliArchive(
64
+ from: file,
65
+ to: temporaryTarURL.path,
66
+ progressHandler: { _ in }
67
+ )
68
+ }
69
+
70
+ return try tarArchiveHasEntries(at: temporaryTarURL.path)
71
+ }
72
+ } catch {
73
+ NSLog("[TarStreamExtractor] Validation failed: \(error.localizedDescription)")
74
+ return false
75
+ }
76
+ }
77
+
78
+ private static func withTemporaryTarFile<T>(
79
+ perform: (URL) throws -> T
80
+ ) throws -> T {
81
+ let temporaryTarURL = FileManager.default.temporaryDirectory
82
+ .appendingPathComponent("hot-updater-\(UUID().uuidString)")
83
+ .appendingPathExtension("tar")
84
+
85
+ defer {
86
+ try? FileManager.default.removeItem(at: temporaryTarURL)
87
+ }
88
+
89
+ return try perform(temporaryTarURL)
90
+ }
91
+
92
+ private static func tarArchiveHasEntries(at tarPath: String) throws -> Bool {
93
+ try TarArchiveExtractor.containsEntries(at: tarPath)
94
+ }
95
+
96
+ private static func extractTarArchive(
97
+ from tarPath: String,
98
+ to destination: String,
99
+ progressHandler: @escaping (Double) -> Void
100
+ ) throws {
101
+ try TarArchiveExtractor.extract(
102
+ from: tarPath,
103
+ to: destination,
104
+ progressHandler: progressHandler
105
+ )
106
+ }
107
+
108
+ private static func decompressGzipArchive(
109
+ from sourcePath: String,
110
+ to outputPath: String,
111
+ progressHandler: @escaping (Double) -> Void
112
+ ) throws {
113
+ let totalSourceSize = try fileSize(atPath: sourcePath)
114
+ try prepareEmptyFile(at: outputPath)
115
+
116
+ let outputHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
117
+
118
+ defer {
119
+ try? outputHandle.close()
120
+ }
121
+
122
+ guard let gzipFile = sourcePath.withCString({ pathPointer in
123
+ "rb".withCString { modePointer in
124
+ gzopen(pathPointer, modePointer)
125
+ }
126
+ }) else {
127
+ throw NSError(
128
+ domain: "StreamingTarArchiveExtractor",
129
+ code: 5,
130
+ userInfo: [NSLocalizedDescriptionKey: "Failed to open gzip archive"]
131
+ )
132
+ }
133
+
134
+ defer {
135
+ gzclose(gzipFile)
136
+ }
137
+
138
+ var buffer = [UInt8](repeating: 0, count: bufferSize)
139
+
140
+ while true {
141
+ let bytesRead = gzread(gzipFile, &buffer, UInt32(buffer.count))
142
+
143
+ if bytesRead > 0 {
144
+ outputHandle.write(Data(buffer[0..<Int(bytesRead)]))
145
+
146
+ if totalSourceSize > 0 {
147
+ let compressedOffset = max(Int64(gzoffset(gzipFile)), 0)
148
+ let progress = min(Double(compressedOffset) / Double(totalSourceSize), 1.0)
149
+ progressHandler(progress)
150
+ }
151
+
152
+ continue
153
+ }
154
+
155
+ if bytesRead == 0 {
156
+ progressHandler(1.0)
157
+ return
158
+ }
159
+
160
+ var errorCode: Int32 = 0
161
+ let messagePointer = gzerror(gzipFile, &errorCode)
162
+ let message = messagePointer.map { String(cString: $0) } ?? "Unknown gzip error"
163
+
164
+ throw NSError(
165
+ domain: "StreamingTarArchiveExtractor",
166
+ code: Int(errorCode),
167
+ userInfo: [NSLocalizedDescriptionKey: "GZIP decompression failed: \(message)"]
168
+ )
169
+ }
170
+ }
171
+
172
+ private static func decompressBrotliArchive(
173
+ from sourcePath: String,
174
+ to outputPath: String,
175
+ progressHandler: @escaping (Double) -> Void
176
+ ) throws {
177
+ let totalSourceSize = try fileSize(atPath: sourcePath)
178
+ try prepareEmptyFile(at: outputPath)
179
+
180
+ let inputHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: sourcePath))
181
+ let outputHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
182
+
183
+ defer {
184
+ try? inputHandle.close()
185
+ try? outputHandle.close()
186
+ }
187
+
188
+ var stream = compression_stream(
189
+ dst_ptr: UnsafeMutablePointer<UInt8>(bitPattern: 1)!,
190
+ dst_size: 0,
191
+ src_ptr: UnsafePointer<UInt8>(bitPattern: 1)!,
192
+ src_size: 0,
193
+ state: nil
194
+ )
195
+ let status = compression_stream_init(&stream, COMPRESSION_STREAM_DECODE, COMPRESSION_BROTLI)
196
+
197
+ guard status == COMPRESSION_STATUS_OK else {
198
+ throw NSError(
199
+ domain: "StreamingTarArchiveExtractor",
200
+ code: 6,
201
+ userInfo: [NSLocalizedDescriptionKey: "Failed to initialize Brotli decompression stream"]
202
+ )
203
+ }
204
+
205
+ defer {
206
+ compression_stream_destroy(&stream)
207
+ }
208
+
209
+ let outputBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
210
+
211
+ defer {
212
+ outputBuffer.deallocate()
213
+ }
214
+
215
+ var processedSourceBytes: UInt64 = 0
216
+ var reachedStreamEnd = false
217
+
218
+ while !reachedStreamEnd {
219
+ let chunk = try ArchiveExtractionUtilities.readUpToCount(
220
+ from: inputHandle,
221
+ count: bufferSize
222
+ ) ?? Data()
223
+ processedSourceBytes += UInt64(chunk.count)
224
+
225
+ let streamStatus: compression_status
226
+ if chunk.isEmpty {
227
+ stream.src_ptr = UnsafePointer<UInt8>(bitPattern: 1)!
228
+ stream.src_size = 0
229
+ streamStatus = try flushCompressionStream(
230
+ &stream,
231
+ into: outputHandle,
232
+ outputBuffer: outputBuffer,
233
+ finalize: true
234
+ )
235
+ } else {
236
+ streamStatus = try chunk.withUnsafeBytes { rawBuffer in
237
+ guard let baseAddress = rawBuffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
238
+ return COMPRESSION_STATUS_OK
239
+ }
240
+
241
+ stream.src_ptr = baseAddress
242
+ stream.src_size = chunk.count
243
+
244
+ return try flushCompressionStream(
245
+ &stream,
246
+ into: outputHandle,
247
+ outputBuffer: outputBuffer,
248
+ finalize: false
249
+ )
250
+ }
251
+ }
252
+
253
+ if totalSourceSize > 0 {
254
+ let progress = min(Double(processedSourceBytes) / Double(totalSourceSize), 1.0)
255
+ progressHandler(progress)
256
+ }
257
+
258
+ if streamStatus == COMPRESSION_STATUS_END {
259
+ reachedStreamEnd = true
260
+ } else if chunk.isEmpty {
261
+ throw NSError(
262
+ domain: "StreamingTarArchiveExtractor",
263
+ code: 7,
264
+ userInfo: [NSLocalizedDescriptionKey: "Brotli decompression ended before reaching the end of stream"]
265
+ )
266
+ }
267
+ }
268
+
269
+ progressHandler(1.0)
270
+ }
271
+
272
+ private static func flushCompressionStream(
273
+ _ stream: inout compression_stream,
274
+ into outputHandle: FileHandle,
275
+ outputBuffer: UnsafeMutablePointer<UInt8>,
276
+ finalize: Bool
277
+ ) throws -> compression_status {
278
+ let flags = finalize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
279
+ var lastStatus = COMPRESSION_STATUS_OK
280
+
281
+ repeat {
282
+ let previousSourceSize = stream.src_size
283
+
284
+ stream.dst_ptr = outputBuffer
285
+ stream.dst_size = bufferSize
286
+
287
+ lastStatus = compression_stream_process(&stream, flags)
288
+
289
+ switch lastStatus {
290
+ case COMPRESSION_STATUS_OK, COMPRESSION_STATUS_END:
291
+ let producedBytes = bufferSize - stream.dst_size
292
+ if producedBytes > 0 {
293
+ outputHandle.write(
294
+ Data(bytes: outputBuffer, count: producedBytes)
295
+ )
296
+ }
297
+
298
+ if finalize,
299
+ lastStatus == COMPRESSION_STATUS_OK,
300
+ producedBytes == 0,
301
+ previousSourceSize == stream.src_size {
302
+ throw NSError(
303
+ domain: "StreamingTarArchiveExtractor",
304
+ code: 8,
305
+ userInfo: [NSLocalizedDescriptionKey: "Brotli decompression stalled before reaching the end of stream"]
306
+ )
307
+ }
308
+
309
+ default:
310
+ throw NSError(
311
+ domain: "StreamingTarArchiveExtractor",
312
+ code: 9,
313
+ userInfo: [NSLocalizedDescriptionKey: "Brotli decompression failed"]
314
+ )
315
+ }
316
+ } while stream.src_size > 0 || stream.dst_size == 0 || (finalize && lastStatus == COMPRESSION_STATUS_OK)
317
+
318
+ return lastStatus
319
+ }
320
+
321
+ private static func prepareEmptyFile(at path: String) throws {
322
+ let fileManager = FileManager.default
323
+ let parentDirectory = (path as NSString).deletingLastPathComponent
324
+
325
+ if !fileManager.fileExists(atPath: parentDirectory) {
326
+ try fileManager.createDirectory(
327
+ atPath: parentDirectory,
328
+ withIntermediateDirectories: true,
329
+ attributes: nil
330
+ )
331
+ }
332
+
333
+ if fileManager.fileExists(atPath: path) {
334
+ try fileManager.removeItem(atPath: path)
335
+ }
336
+
337
+ guard fileManager.createFile(atPath: path, contents: nil) else {
338
+ throw NSError(
339
+ domain: "StreamingTarArchiveExtractor",
340
+ code: 10,
341
+ userInfo: [NSLocalizedDescriptionKey: "Failed to create temporary archive file"]
342
+ )
343
+ }
344
+ }
345
+
346
+ private static func fileSize(atPath path: String) throws -> UInt64 {
347
+ let attributes = try FileManager.default.attributesOfItem(atPath: path)
348
+ if let value = attributes[.size] as? NSNumber {
349
+ return value.uint64Value
350
+ }
351
+
352
+ if let value = attributes[.size] as? UInt64 {
353
+ return value
354
+ }
355
+
356
+ return 0
357
+ }
358
+
359
+ }