@hot-updater/react-native 0.24.7 → 0.25.1

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 (44) hide show
  1. package/LICENSE +12 -0
  2. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +144 -20
  3. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +27 -2
  4. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +17 -2
  5. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +11 -38
  6. package/android/src/newarch/HotUpdaterModule.kt +5 -0
  7. package/android/src/oldarch/HotUpdaterModule.kt +6 -0
  8. package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
  9. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +168 -34
  10. package/ios/HotUpdater/Internal/BundleMetadata.swift +17 -1
  11. package/ios/HotUpdater/Internal/HotUpdater.mm +14 -0
  12. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +20 -3
  13. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +24 -40
  14. package/lib/commonjs/global.d.js +6 -0
  15. package/lib/commonjs/global.d.js.map +1 -0
  16. package/lib/commonjs/index.js +25 -0
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/native.js +17 -1
  19. package/lib/commonjs/native.js.map +1 -1
  20. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  21. package/lib/commonjs/wrap.js +1 -2
  22. package/lib/commonjs/wrap.js.map +1 -1
  23. package/lib/module/global.d.js +8 -0
  24. package/lib/module/global.d.js.map +1 -0
  25. package/lib/module/index.js +26 -1
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/native.js +15 -0
  28. package/lib/module/native.js.map +1 -1
  29. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  30. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/native.d.ts +8 -0
  32. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -0
  34. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  35. package/lib/typescript/module/index.d.ts.map +1 -1
  36. package/lib/typescript/module/native.d.ts +8 -0
  37. package/lib/typescript/module/native.d.ts.map +1 -1
  38. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -0
  39. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  40. package/package.json +7 -7
  41. package/src/global.d.ts +23 -0
  42. package/src/index.ts +26 -0
  43. package/src/native.ts +15 -0
  44. package/src/specs/NativeHotUpdater.ts +9 -0
@@ -112,6 +112,12 @@ public protocol BundleStorageService {
112
112
  func notifyAppReady(bundleId: String) -> [String: Any]
113
113
  func getCrashHistory() -> CrashedHistory
114
114
  func clearCrashHistory() -> Bool
115
+
116
+ /**
117
+ * Gets the base URL for the current active bundle directory
118
+ * @return Base URL string (e.g., "file:///data/.../bundle-store/abc123") or empty string
119
+ */
120
+ func getBaseURL() -> String
115
121
  }
116
122
 
117
123
  class BundleFileStorageService: BundleStorageService {
@@ -119,6 +125,7 @@ class BundleFileStorageService: BundleStorageService {
119
125
  private let downloadService: DownloadService
120
126
  private let decompressService: DecompressService
121
127
  private let preferences: PreferencesService
128
+ private let isolationKey: String
122
129
 
123
130
  private let id = Int.random(in: 1..<100)
124
131
 
@@ -133,17 +140,25 @@ class BundleFileStorageService: BundleStorageService {
133
140
  public init(fileSystem: FileSystemService,
134
141
  downloadService: DownloadService,
135
142
  decompressService: DecompressService,
136
- preferences: PreferencesService) {
143
+ preferences: PreferencesService,
144
+ isolationKey: String) {
137
145
 
138
146
  self.fileSystem = fileSystem
139
147
  self.downloadService = downloadService
140
148
  self.decompressService = decompressService
141
149
  self.preferences = preferences
150
+ self.isolationKey = isolationKey
142
151
 
143
152
  // Create queue for file operations
144
153
  self.fileOperationQueue = DispatchQueue(label: "com.hotupdater.fileoperations",
145
154
  qos: .utility,
146
155
  attributes: .concurrent)
156
+
157
+ // Ensure bundle store directory exists
158
+ _ = bundleStoreDir()
159
+
160
+ // Clean up old bundles if isolationKey format changed
161
+ checkAndCleanupIfIsolationKeyChanged()
147
162
  }
148
163
 
149
164
  // MARK: - Metadata File Paths
@@ -168,14 +183,83 @@ class BundleFileStorageService: BundleStorageService {
168
183
  guard let file = metadataFileURL() else {
169
184
  return nil
170
185
  }
171
- return BundleMetadata.load(from: file)
186
+ return BundleMetadata.load(from: file, expectedIsolationKey: isolationKey)
172
187
  }
173
188
 
174
189
  private func saveMetadata(_ metadata: BundleMetadata) -> Bool {
175
190
  guard let file = metadataFileURL() else {
176
191
  return false
177
192
  }
178
- return metadata.save(to: file)
193
+ var updatedMetadata = metadata
194
+ updatedMetadata.isolationKey = isolationKey
195
+ return updatedMetadata.save(to: file)
196
+ }
197
+
198
+ /**
199
+ * Checks if isolationKey has changed and cleans up old bundles if needed.
200
+ * This handles migration when isolationKey format changes.
201
+ */
202
+ private func checkAndCleanupIfIsolationKeyChanged() {
203
+ guard let metadataURL = metadataFileURL() else {
204
+ return
205
+ }
206
+
207
+ let metadataPath = metadataURL.path
208
+
209
+ guard fileSystem.fileExists(atPath: metadataPath) else {
210
+ // First launch - no cleanup needed
211
+ return
212
+ }
213
+
214
+ do {
215
+ let jsonString = try String(contentsOf: metadataURL, encoding: .utf8)
216
+ if let jsonData = jsonString.data(using: .utf8),
217
+ let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
218
+ let storedKey = json["isolationKey"] as? String {
219
+
220
+ if storedKey != isolationKey {
221
+ NSLog("[BundleStorage] isolationKey changed: \(storedKey) -> \(isolationKey)")
222
+ NSLog("[BundleStorage] Cleaning up old bundles for migration")
223
+ cleanupAllBundlesForMigration()
224
+ }
225
+ }
226
+ } catch {
227
+ NSLog("[BundleStorage] Error checking isolationKey: \(error.localizedDescription)")
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Removes all bundle directories during migration.
233
+ * Called when isolationKey format changes.
234
+ */
235
+ private func cleanupAllBundlesForMigration() {
236
+ guard case .success(let storeDir) = bundleStoreDir() else {
237
+ return
238
+ }
239
+
240
+ do {
241
+ let contents = try fileSystem.contentsOfDirectory(atPath: storeDir)
242
+ var cleanedCount = 0
243
+
244
+ for item in contents {
245
+ let fullPath = (storeDir as NSString).appendingPathComponent(item)
246
+
247
+ // Skip metadata files
248
+ if item == "metadata.json" || item == "crashed-history.json" {
249
+ continue
250
+ }
251
+
252
+ if fileSystem.fileExists(atPath: fullPath) {
253
+ try fileSystem.removeItem(atPath: fullPath)
254
+ cleanedCount += 1
255
+ NSLog("[BundleStorage] Migration: removed old bundle \(item)")
256
+ }
257
+ }
258
+
259
+ NSLog("[BundleStorage] Migration cleanup complete: removed \(cleanedCount) bundles")
260
+ } catch {
261
+ NSLog("[BundleStorage] Error during migration cleanup: \(error.localizedDescription)")
262
+ }
179
263
  }
180
264
 
181
265
  // MARK: - Crashed History Operations
@@ -717,47 +801,43 @@ class BundleFileStorageService: BundleStorageService {
717
801
  let bundleFileName = fileUrl.lastPathComponent.isEmpty ? "bundle.zip" : fileUrl.lastPathComponent
718
802
  let tempBundleFile = (tempDirectory as NSString).appendingPathComponent(bundleFileName)
719
803
 
720
- NSLog("[BundleStorage] Checking file size and disk space...")
804
+ NSLog("[BundleStorage] Starting download from \(fileUrl)")
805
+
806
+ // Download with integrated disk space check
807
+ var diskSpaceError: BundleStorageError? = nil
721
808
 
722
- // 5) Check file size and disk space before download
723
- self.downloadService.getFileSize(from: fileUrl) { [weak self] sizeResult in
724
- guard let self = self else { return }
809
+ _ = self.downloadService.downloadFile(
810
+ from: fileUrl,
811
+ to: tempBundleFile,
812
+ fileSizeHandler: { [weak self] fileSize in
813
+ // This will be called when Content-Length is received
814
+ guard let self = self else { return }
815
+
816
+ NSLog("[BundleStorage] File size received: \(fileSize) bytes")
725
817
 
726
- if case .success(let fileSize) = sizeResult {
727
818
  // Check available disk space
728
819
  do {
729
820
  let attributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory())
730
821
  if let freeSize = attributes[.systemFreeSize] as? Int64 {
731
822
  let requiredSpace = fileSize * 2 // ZIP + extracted files
732
823
 
733
- NSLog("[BundleStorage] File size: \(fileSize) bytes, Available: \(freeSize) bytes, Required: \(requiredSpace) bytes")
824
+ NSLog("[BundleStorage] Available: \(freeSize) bytes, Required: \(requiredSpace) bytes")
734
825
 
735
826
  if freeSize < requiredSpace {
736
- NSLog("[BundleStorage] Insufficient disk space")
737
- self.cleanupTemporaryFiles([tempDirectory])
738
- completion(.failure(BundleStorageError.insufficientDiskSpace))
739
- return
827
+ NSLog("[BundleStorage] Insufficient disk space detected: need \(requiredSpace) bytes, available \(freeSize) bytes")
828
+ // Store error to be returned in completion handler
829
+ diskSpaceError = .insufficientDiskSpace
740
830
  }
741
831
  }
742
832
  } catch {
743
833
  NSLog("[BundleStorage] Failed to check disk space: \(error.localizedDescription)")
744
- // Continue with download despite disk check failure
745
834
  }
746
- } else {
747
- NSLog("[BundleStorage] Unable to determine file size, proceeding with download")
748
- }
749
-
750
- NSLog("[BundleStorage] Starting download from \(fileUrl)")
751
-
752
- // 6) DownloadService handles its own threading for the download task.
753
- // The completion handler for downloadService.downloadFile is then dispatched to fileOperationQueue.
754
- let task = self.downloadService.downloadFile(from: fileUrl,
755
- to: tempBundleFile,
756
- progressHandler: { downloadProgress in
757
- // Map download progress to 0.0 - 0.8
758
- progressHandler(downloadProgress * 0.8)
759
- },
760
- completion: { [weak self] result in
835
+ },
836
+ progressHandler: { downloadProgress in
837
+ // Map download progress to 0.0 - 0.8
838
+ progressHandler(downloadProgress * 0.8)
839
+ },
840
+ completion: { [weak self] result in
761
841
  guard let self = self else {
762
842
  let error = NSError(domain: "HotUpdaterError", code: 998,
763
843
  userInfo: [NSLocalizedDescriptionKey: "Self deallocated during download"])
@@ -765,6 +845,14 @@ class BundleFileStorageService: BundleStorageService {
765
845
  return
766
846
  }
767
847
 
848
+ // Check for disk space error first before processing download result
849
+ if let diskError = diskSpaceError {
850
+ NSLog("[BundleStorage] Throwing disk space error")
851
+ self.cleanupTemporaryFiles([tempDirectory])
852
+ completion(.failure(diskError))
853
+ return
854
+ }
855
+
768
856
  // Dispatch the processing of the downloaded file to the file operation queue
769
857
  let workItem = DispatchWorkItem {
770
858
  switch result {
@@ -791,12 +879,8 @@ class BundleFileStorageService: BundleStorageService {
791
879
  }
792
880
  }
793
881
  self.fileOperationQueue.async(execute: workItem)
794
- })
795
-
796
- if let task = task {
797
- self.activeTasks.append(task) // Manage active tasks
798
- }
799
882
  }
883
+ )
800
884
  }
801
885
 
802
886
  /**
@@ -1090,6 +1174,56 @@ class BundleFileStorageService: BundleStorageService {
1090
1174
  history.clear()
1091
1175
  return saveCrashedHistory(history)
1092
1176
  }
1177
+
1178
+ /**
1179
+ * Gets the base URL for the current active bundle directory
1180
+ * Returns the file:// URL to the bundle directory without trailing slash
1181
+ */
1182
+ func getBaseURL() -> String {
1183
+ do {
1184
+ let metadata = loadMetadataOrNull()
1185
+ let activeBundleId: String?
1186
+
1187
+ // Prefer staging bundle if verification is pending
1188
+ if let meta = metadata, meta.verificationPending, let staging = meta.stagingBundleId {
1189
+ activeBundleId = staging
1190
+ } else if let stable = metadata?.stableBundleId {
1191
+ activeBundleId = stable
1192
+ } else {
1193
+ // Fall back to current bundle ID from preferences
1194
+ if let savedURL = try preferences.getItem(forKey: "HotUpdaterBundleURL") {
1195
+ // Extract bundle ID from path like "bundle-store/abc123/index.ios.bundle"
1196
+ if let range = savedURL.range(of: "bundle-store/([^/]+)/", options: .regularExpression) {
1197
+ let match = savedURL[range]
1198
+ let components = match.split(separator: "/")
1199
+ if components.count >= 2 {
1200
+ activeBundleId = String(components[1])
1201
+ } else {
1202
+ activeBundleId = nil
1203
+ }
1204
+ } else {
1205
+ activeBundleId = nil
1206
+ }
1207
+ } else {
1208
+ activeBundleId = nil
1209
+ }
1210
+ }
1211
+
1212
+ if let bundleId = activeBundleId {
1213
+ if case .success(let storeDir) = bundleStoreDir() {
1214
+ let bundleDir = (storeDir as NSString).appendingPathComponent(bundleId)
1215
+ if fileSystem.fileExists(atPath: bundleDir) {
1216
+ return "file://\(bundleDir)"
1217
+ }
1218
+ }
1219
+ }
1220
+
1221
+ return ""
1222
+ } catch {
1223
+ NSLog("[BundleStorage] Error getting base URL: \(error)")
1224
+ return ""
1225
+ }
1226
+ }
1093
1227
  }
1094
1228
 
1095
1229
  // Helper to get the associated error from a Result, if it's a failure
@@ -8,6 +8,7 @@ public struct BundleMetadata: Codable {
8
8
  static let metadataFilename = "metadata.json"
9
9
 
10
10
  let schema: String
11
+ var isolationKey: String?
11
12
  var stableBundleId: String?
12
13
  var stagingBundleId: String?
13
14
  var verificationPending: Bool
@@ -17,6 +18,7 @@ public struct BundleMetadata: Codable {
17
18
 
18
19
  enum CodingKeys: String, CodingKey {
19
20
  case schema
21
+ case isolationKey = "isolation_key"
20
22
  case stableBundleId = "stable_bundle_id"
21
23
  case stagingBundleId = "staging_bundle_id"
22
24
  case verificationPending = "verification_pending"
@@ -27,6 +29,7 @@ public struct BundleMetadata: Codable {
27
29
 
28
30
  init(
29
31
  schema: String = BundleMetadata.schemaVersion,
32
+ isolationKey: String? = nil,
30
33
  stableBundleId: String? = nil,
31
34
  stagingBundleId: String? = nil,
32
35
  verificationPending: Bool = false,
@@ -35,6 +38,7 @@ public struct BundleMetadata: Codable {
35
38
  updatedAt: Double = Date().timeIntervalSince1970 * 1000
36
39
  ) {
37
40
  self.schema = schema
41
+ self.isolationKey = isolationKey
38
42
  self.stableBundleId = stableBundleId
39
43
  self.stagingBundleId = stagingBundleId
40
44
  self.verificationPending = verificationPending
@@ -43,7 +47,7 @@ public struct BundleMetadata: Codable {
43
47
  self.updatedAt = updatedAt
44
48
  }
45
49
 
46
- static func load(from file: URL) -> BundleMetadata? {
50
+ static func load(from file: URL, expectedIsolationKey: String) -> BundleMetadata? {
47
51
  guard FileManager.default.fileExists(atPath: file.path) else {
48
52
  print("[BundleMetadata] Metadata file does not exist: \(file.path)")
49
53
  return nil
@@ -53,6 +57,18 @@ public struct BundleMetadata: Codable {
53
57
  let data = try Data(contentsOf: file)
54
58
  let decoder = JSONDecoder()
55
59
  let metadata = try decoder.decode(BundleMetadata.self, from: data)
60
+
61
+ // Validate isolation key
62
+ if let metadataKey = metadata.isolationKey {
63
+ if metadataKey != expectedIsolationKey {
64
+ print("[BundleMetadata] Isolation key mismatch: expected=\(expectedIsolationKey), got=\(metadataKey)")
65
+ return nil
66
+ }
67
+ } else {
68
+ print("[BundleMetadata] Missing isolation key in metadata, treating as invalid")
69
+ return nil
70
+ }
71
+
56
72
  return metadata
57
73
  } catch {
58
74
  print("[BundleMetadata] Failed to load metadata from file: \(error)")
@@ -313,6 +313,13 @@ RCT_EXPORT_MODULE();
313
313
  return @(result);
314
314
  }
315
315
 
316
+ - (NSString *)getBaseURL {
317
+ NSLog(@"[HotUpdater.mm] getBaseURL called");
318
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
319
+ NSString *baseURL = [impl getBaseURL];
320
+ return baseURL ?: @"";
321
+ }
322
+
316
323
  - (void)addListener:(NSString *)eventName {
317
324
  // No-op for New Architecture - handled by event emitter
318
325
  }
@@ -390,6 +397,13 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(clearCrashHistory) {
390
397
  return @(result);
391
398
  }
392
399
 
400
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBaseURL) {
401
+ NSLog(@"[HotUpdater.mm] getBaseURL called");
402
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
403
+ NSString *baseURL = [impl getBaseURL];
404
+ return baseURL ?: @"";
405
+ }
406
+
393
407
  - (NSDictionary *)constantsToExport {
394
408
  return [self _buildConstantsDictionary];
395
409
  }
@@ -14,6 +14,7 @@ import React
14
14
  */
15
15
  public convenience override init() {
16
16
  let fileSystem = FileManagerService()
17
+ let isolationKey = HotUpdaterImpl.getIsolationKey()
17
18
  let preferences = VersionedPreferencesService()
18
19
  let downloadService = URLSessionDownloadService()
19
20
  let decompressService = DecompressService()
@@ -22,7 +23,8 @@ import React
22
23
  fileSystem: fileSystem,
23
24
  downloadService: downloadService,
24
25
  decompressService: decompressService,
25
- preferences: preferences
26
+ preferences: preferences,
27
+ isolationKey: isolationKey
26
28
  )
27
29
 
28
30
  self.init(bundleStorage: bundleStorage, preferences: preferences)
@@ -71,8 +73,13 @@ import React
71
73
  let appVersion = self.appVersion ?? "unknown"
72
74
  let appChannel = self.appChannel
73
75
 
74
- // Use fingerprint if available, otherwise use app version
75
- let baseKey = (fingerprintHash != nil && !fingerprintHash!.isEmpty) ? fingerprintHash! : appVersion
76
+ // Include both fingerprint hash and app version for complete isolation
77
+ let baseKey: String
78
+ if let hash = fingerprintHash, !hash.isEmpty {
79
+ baseKey = "\(hash)_\(appVersion)"
80
+ } else {
81
+ baseKey = appVersion
82
+ }
76
83
 
77
84
  return "hotupdater_\(baseKey)_\(appChannel)_"
78
85
  }
@@ -263,4 +270,14 @@ import React
263
270
  public func clearCrashHistory() -> Bool {
264
271
  return bundleStorage.clearCrashHistory()
265
272
  }
273
+
274
+ /**
275
+ * Gets the base URL for the current active bundle directory.
276
+ * Returns the file:// URL to the bundle directory with trailing slash.
277
+ * This is used for Expo DOM components to construct full asset paths.
278
+ * @return Base URL string (e.g., "file:///var/.../bundle-store/abc123/") or empty string
279
+ */
280
+ public func getBaseURL() -> String {
281
+ return bundleStorage.getBaseURL()
282
+ }
266
283
  }
@@ -4,22 +4,16 @@ import UIKit
4
4
  #endif
5
5
 
6
6
  protocol DownloadService {
7
- /**
8
- * Gets the file size from the URL without downloading.
9
- * @param url The URL to check
10
- * @param completion Callback with file size or error
11
- */
12
- func getFileSize(from url: URL, completion: @escaping (Result<Int64, Error>) -> Void)
13
-
14
7
  /**
15
8
  * Downloads a file from a URL.
16
9
  * @param url The URL to download from
17
10
  * @param destination The local path to save to
11
+ * @param fileSizeHandler Optional callback called when file size is known
18
12
  * @param progressHandler Callback for download progress updates
19
13
  * @param completion Callback with downloaded file URL or error
20
14
  * @return The download task (optional)
21
15
  */
22
- func downloadFile(from url: URL, to destination: String, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask?
16
+ func downloadFile(from url: URL, to destination: String, fileSizeHandler: ((Int64) -> Void)?, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask?
23
17
  }
24
18
 
25
19
 
@@ -42,6 +36,7 @@ class URLSessionDownloadService: NSObject, DownloadService {
42
36
  private var progressHandlers: [URLSessionTask: (Double) -> Void] = [:]
43
37
  private var completionHandlers: [URLSessionTask: (Result<URL, Error>) -> Void] = [:]
44
38
  private var destinations: [URLSessionTask: String] = [:]
39
+ private var fileSizeHandlers: [URLSessionTask: (Int64) -> Void] = [:]
45
40
  private var taskStates: [Int: TaskState] = [:]
46
41
 
47
42
  override init() {
@@ -94,35 +89,7 @@ class URLSessionDownloadService: NSObject, DownloadService {
94
89
  }
95
90
  }
96
91
 
97
- func getFileSize(from url: URL, completion: @escaping (Result<Int64, Error>) -> Void) {
98
- var request = URLRequest(url: url)
99
- request.httpMethod = "HEAD"
100
-
101
- let task = session.dataTask(with: request) { _, response, error in
102
- if let error = error {
103
- NSLog("[DownloadService] HEAD request failed: \(error.localizedDescription)")
104
- completion(.failure(error))
105
- return
106
- }
107
-
108
- guard let httpResponse = response as? HTTPURLResponse else {
109
- completion(.failure(DownloadError.invalidContentLength))
110
- return
111
- }
112
-
113
- let contentLength = httpResponse.expectedContentLength
114
- if contentLength > 0 {
115
- NSLog("[DownloadService] File size from HEAD request: \(contentLength) bytes")
116
- completion(.success(contentLength))
117
- } else {
118
- NSLog("[DownloadService] Invalid content length: \(contentLength)")
119
- completion(.failure(DownloadError.invalidContentLength))
120
- }
121
- }
122
- task.resume()
123
- }
124
-
125
- func downloadFile(from url: URL, to destination: String, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask? {
92
+ func downloadFile(from url: URL, to destination: String, fileSizeHandler: ((Int64) -> Void)?, progressHandler: @escaping (Double) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask? {
126
93
  // Determine if we should use background session
127
94
  #if !os(macOS)
128
95
  let appState = UIApplication.shared.applicationState
@@ -141,6 +108,9 @@ class URLSessionDownloadService: NSObject, DownloadService {
141
108
  progressHandlers[task] = progressHandler
142
109
  completionHandlers[task] = completion
143
110
  destinations[task] = destination
111
+ if let handler = fileSizeHandler {
112
+ fileSizeHandlers[task] = handler
113
+ }
144
114
 
145
115
  // Extract bundleId from destination path (e.g., "bundle-store/{bundleId}/bundle.zip")
146
116
  let bundleId = (destination as NSString).pathComponents
@@ -170,6 +140,7 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
170
140
  progressHandlers.removeValue(forKey: downloadTask)
171
141
  completionHandlers.removeValue(forKey: downloadTask)
172
142
  destinations.removeValue(forKey: downloadTask)
143
+ fileSizeHandlers.removeValue(forKey: downloadTask)
173
144
  removeTaskState(downloadTask.taskIdentifier)
174
145
 
175
146
  // 다운로드 완료 알림
@@ -223,6 +194,7 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
223
194
  progressHandlers.removeValue(forKey: task)
224
195
  completionHandlers.removeValue(forKey: task)
225
196
  destinations.removeValue(forKey: task)
197
+ fileSizeHandlers.removeValue(forKey: task)
226
198
  removeTaskState(task.taskIdentifier)
227
199
 
228
200
  NotificationCenter.default.post(name: .downloadDidFinish, object: task)
@@ -235,11 +207,23 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
235
207
 
236
208
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
237
209
  let progressHandler = progressHandlers[downloadTask]
238
-
210
+
211
+ // Call file size handler on first callback when size is known
212
+ if totalBytesWritten == bytesWritten && bytesWritten > 0 {
213
+ if let fileSizeHandler = fileSizeHandlers[downloadTask] {
214
+ if totalBytesExpectedToWrite > 0 {
215
+ fileSizeHandler(totalBytesExpectedToWrite)
216
+ } else {
217
+ NSLog("[DownloadService] Content-Length not available, proceeding without disk space check")
218
+ }
219
+ fileSizeHandlers.removeValue(forKey: downloadTask)
220
+ }
221
+ }
222
+
239
223
  if totalBytesExpectedToWrite > 0 {
240
224
  let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
241
225
  progressHandler?(progress)
242
-
226
+
243
227
  let progressInfo: [String: Any] = [
244
228
  "progress": progress,
245
229
  "totalBytesReceived": totalBytesWritten,
@@ -248,7 +232,7 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
248
232
  NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: progressInfo)
249
233
  } else {
250
234
  progressHandler?(0)
251
-
235
+
252
236
  NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: ["progress": 0.0, "totalBytesReceived": 0, "totalBytesExpected": 0])
253
237
  }
254
238
  }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ //# sourceMappingURL=global.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["global.d.ts"],"mappings":"","ignoreList":[]}
@@ -46,6 +46,31 @@ var _types = require("./types.js");
46
46
  });
47
47
  });
48
48
 
49
+ /**
50
+ * Register getBaseURL to global objects for use without imports.
51
+ * This is needed for Expo DOM components and Babel plugin generated code.
52
+ */
53
+ const registerGlobalGetBaseURL = () => {
54
+ const fn = _native.getBaseURL;
55
+
56
+ // Register to globalThis (modern, cross-platform)
57
+ if (typeof globalThis !== "undefined") {
58
+ if (!globalThis.HotUpdaterGetBaseURL) {
59
+ globalThis.HotUpdaterGetBaseURL = fn;
60
+ }
61
+ }
62
+
63
+ // Register to global (React Native, Node.js)
64
+ if (typeof global !== "undefined") {
65
+ if (!global.HotUpdaterGetBaseURL) {
66
+ global.HotUpdaterGetBaseURL = fn;
67
+ }
68
+ }
69
+ };
70
+
71
+ // Call registration immediately on module load
72
+ registerGlobalGetBaseURL();
73
+
49
74
  /**
50
75
  * Creates a HotUpdater client instance with all update management methods.
51
76
  * This function is called once on module initialization to create a singleton instance.
@@ -1 +1 @@
1
- {"version":3,"names":["_checkForUpdate","require","_DefaultResolver","_native","_store","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_wrap","_types","addListener","progress","hotUpdaterStore","setState","createHotUpdaterClient","globalConfig","resolver","ensureGlobalResolver","methodName","Error","wrap","options","normalizedOptions","baseURL","rest","createDefaultResolver","requestHeaders","requestTimeout","reload","isUpdateDownloaded","getSnapshot","getAppVersion","getBundleId","getMinBundleId","getChannel","checkForUpdate","config","mergedConfig","updateBundle","params","getFingerprintHash","getCrashHistory","clearCrashHistory","HotUpdater"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIxB,WAAW,EAAXA,mBAAW;IAEX;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIyB,cAAc,EAAGC,MAA6B,IAAK;MACjD,MAAMpB,QAAQ,GAAGC,oBAAoB,CAAC,gBAAgB,CAAC;MAEvD,MAAMoB,YAA2C,GAAG;QAClD,GAAGD,MAAM;QACTpB,QAAQ;QACRU,cAAc,EAAE;UACd,GAAGX,YAAY,CAACW,cAAc;UAC9B,GAAGU,MAAM,CAACV;QACZ,CAAC;QACDC,cAAc,EAAES,MAAM,CAACT,cAAc,IAAIZ,YAAY,CAACY;MACxD,CAAC;MAED,OAAO,IAAAQ,8BAAc,EAACE,YAAY,CAAC;IACrC,CAAC;IAED;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,YAAY,EAAGC,MAAoB,IAAK;MACtCtB,oBAAoB,CAAC,cAAc,CAAC;MACpC,OAAO,IAAAqB,oBAAY,EAACC,MAAM,CAAC;IAC7B,CAAC;IAED;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,kBAAkB,EAAlBA,0BAAkB;IAElB;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,eAAe,EAAfA,uBAAe;IAEf;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,iBAAiB,EAAjBA;EACF,CAAC;AACH;AAEO,MAAMC,UAAU,GAAAvC,OAAA,CAAAuC,UAAA,GAAG7B,sBAAsB,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_checkForUpdate","require","_DefaultResolver","_native","_store","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_wrap","_types","addListener","progress","hotUpdaterStore","setState","registerGlobalGetBaseURL","fn","getBaseURL","globalThis","HotUpdaterGetBaseURL","global","createHotUpdaterClient","globalConfig","resolver","ensureGlobalResolver","methodName","Error","wrap","options","normalizedOptions","baseURL","rest","createDefaultResolver","requestHeaders","requestTimeout","reload","isUpdateDownloaded","getSnapshot","getAppVersion","getBundleId","getMinBundleId","getChannel","checkForUpdate","config","mergedConfig","updateBundle","params","getFingerprintHash","getCrashHistory","clearCrashHistory","HotUpdater"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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AM,EAANA,cAAM;IAEN;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,kBAAkB,EAAEA,CAAA,KAAMvB,sBAAe,CAACwB,WAAW,CAAC,CAAC,CAACD,kBAAkB;IAE1E;AACJ;AACA;IACIE,aAAa,EAAbA,qBAAa;IAEb;AACJ;AACA;IACIC,WAAW,EAAXA,mBAAW;IAEX;AACJ;AACA;IACIC,cAAc,EAAdA,sBAAc;IAEd;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,UAAU,EAAVA,kBAAU;IAEV;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACI9B,WAAW,EAAXA,mBAAW;IAEX;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACI+B,cAAc,EAAGC,MAA6B,IAAK;MACjD,MAAMpB,QAAQ,GAAGC,oBAAoB,CAAC,gBAAgB,CAAC;MAEvD,MAAMoB,YAA2C,GAAG;QAClD,GAAGD,MAAM;QACTpB,QAAQ;QACRU,cAAc,EAAE;UACd,GAAGX,YAAY,CAACW,cAAc;UAC9B,GAAGU,MAAM,CAACV;QACZ,CAAC;QACDC,cAAc,EAAES,MAAM,CAACT,cAAc,IAAIZ,YAAY,CAACY;MACxD,CAAC;MAED,OAAO,IAAAQ,8BAAc,EAACE,YAAY,CAAC;IACrC,CAAC;IAED;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,YAAY,EAAGC,MAAoB,IAAK;MACtCtB,oBAAoB,CAAC,cAAc,CAAC;MACpC,OAAO,IAAAqB,oBAAY,EAACC,MAAM,CAAC;IAC7B,CAAC;IAED;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,kBAAkB,EAAlBA,0BAAkB;IAElB;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,eAAe,EAAfA,uBAAe;IAEf;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACIC,iBAAiB,EAAjBA;EACF,CAAC;AACH;AAEO,MAAMC,UAAU,GAAA7C,OAAA,CAAA6C,UAAA,GAAG7B,sBAAsB,CAAC,CAAC","ignoreList":[]}
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "HotUpdaterErrorCode", {
10
10
  return _error.HotUpdaterErrorCode;
11
11
  }
12
12
  });
13
- exports.getMinBundleId = exports.getFingerprintHash = exports.getCrashHistory = exports.getChannel = exports.getBundleId = exports.getAppVersion = exports.clearCrashHistory = exports.addListener = void 0;
13
+ exports.getMinBundleId = exports.getFingerprintHash = exports.getCrashHistory = exports.getChannel = exports.getBundleId = exports.getBaseURL = exports.getAppVersion = exports.clearCrashHistory = exports.addListener = void 0;
14
14
  Object.defineProperty(exports, "isHotUpdaterError", {
15
15
  enumerable: true,
16
16
  get: function () {
@@ -235,5 +235,21 @@ exports.getCrashHistory = getCrashHistory;
235
235
  const clearCrashHistory = () => {
236
236
  return _NativeHotUpdater.default.clearCrashHistory();
237
237
  };
238
+
239
+ /**
240
+ * Gets the base URL for the current active bundle directory.
241
+ * Returns the file:// URL to the bundle directory without trailing slash.
242
+ * This is used for Expo DOM components to construct full asset paths.
243
+ *
244
+ * @returns {string | null} Base URL string (e.g., "file:///data/.../bundle-store/abc123") or null if not available
245
+ */
238
246
  exports.clearCrashHistory = clearCrashHistory;
247
+ const getBaseURL = () => {
248
+ const result = _NativeHotUpdater.default.getBaseURL();
249
+ if (typeof result === "string" && result !== "") {
250
+ return result;
251
+ }
252
+ return null;
253
+ };
254
+ exports.getBaseURL = getBaseURL;
239
255
  //# sourceMappingURL=native.js.map