@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.
- package/LICENSE +12 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +144 -20
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +27 -2
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +17 -2
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +11 -38
- package/android/src/newarch/HotUpdaterModule.kt +5 -0
- package/android/src/oldarch/HotUpdaterModule.kt +6 -0
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +168 -34
- package/ios/HotUpdater/Internal/BundleMetadata.swift +17 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +14 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +20 -3
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +24 -40
- package/lib/commonjs/global.d.js +6 -0
- package/lib/commonjs/global.d.js.map +1 -0
- package/lib/commonjs/index.js +25 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +17 -1
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/wrap.js +1 -2
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/global.d.js +8 -0
- package/lib/module/global.d.js.map +1 -0
- package/lib/module/index.js +26 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +15 -0
- package/lib/module/native.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +8 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +8 -0
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/global.d.ts +23 -0
- package/src/index.ts +26 -0
- package/src/native.ts +15 -0
- 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
|
-
|
|
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]
|
|
804
|
+
NSLog("[BundleStorage] Starting download from \(fileUrl)")
|
|
805
|
+
|
|
806
|
+
// Download with integrated disk space check
|
|
807
|
+
var diskSpaceError: BundleStorageError? = nil
|
|
721
808
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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]
|
|
824
|
+
NSLog("[BundleStorage] Available: \(freeSize) bytes, Required: \(requiredSpace) bytes")
|
|
734
825
|
|
|
735
826
|
if freeSize < requiredSpace {
|
|
736
|
-
NSLog("[BundleStorage] Insufficient disk space")
|
|
737
|
-
|
|
738
|
-
|
|
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
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
//
|
|
75
|
-
let baseKey
|
|
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
|
|
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 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["global.d.ts"],"mappings":"","ignoreList":[]}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,eAAA,GAAAC,OAAA;AAKA,IAAAC,gBAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;
|
|
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":[]}
|
package/lib/commonjs/native.js
CHANGED
|
@@ -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
|