@hot-updater/react-native 0.19.3 → 0.19.5
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.
|
@@ -100,6 +100,15 @@ class BundleFileStorageService(
|
|
|
100
100
|
bundleStoreDir.mkdirs()
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
val currentBundleId =
|
|
104
|
+
getCachedBundleURL()?.let { cachedUrl ->
|
|
105
|
+
// Only consider cached bundles, not fallback bundles
|
|
106
|
+
if (!cachedUrl.startsWith("assets://")) {
|
|
107
|
+
File(cachedUrl).parentFile?.name
|
|
108
|
+
} else {
|
|
109
|
+
null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
103
112
|
val finalBundleDir = File(bundleStoreDir, bundleId)
|
|
104
113
|
if (finalBundleDir.exists()) {
|
|
105
114
|
Log.d("BundleStorage", "Bundle for bundleId $bundleId already exists. Using cached bundle.")
|
|
@@ -108,7 +117,7 @@ class BundleFileStorageService(
|
|
|
108
117
|
// Update last modified time and set the cached bundle URL
|
|
109
118
|
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
110
119
|
setBundleURL(existingIndexFile.absolutePath)
|
|
111
|
-
cleanupOldBundles(bundleStoreDir)
|
|
120
|
+
cleanupOldBundles(bundleStoreDir, currentBundleId, bundleId)
|
|
112
121
|
return true
|
|
113
122
|
} else {
|
|
114
123
|
// If index.android.bundle is missing, delete and re-download
|
|
@@ -203,7 +212,7 @@ class BundleFileStorageService(
|
|
|
203
212
|
tempDir.deleteRecursively()
|
|
204
213
|
|
|
205
214
|
// 10) Remove old bundles
|
|
206
|
-
cleanupOldBundles(bundleStoreDir)
|
|
215
|
+
cleanupOldBundles(bundleStoreDir, currentBundleId, bundleId)
|
|
207
216
|
|
|
208
217
|
Log.d("BundleStorage", "Downloaded and activated bundle successfully.")
|
|
209
218
|
return@withContext true
|
|
@@ -213,25 +222,59 @@ class BundleFileStorageService(
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
/**
|
|
216
|
-
* Removes
|
|
225
|
+
* Removes old bundles except for the specified bundle IDs, and any leftover .tmp directories
|
|
217
226
|
*/
|
|
218
|
-
private fun cleanupOldBundles(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
private fun cleanupOldBundles(
|
|
228
|
+
bundleStoreDir: File,
|
|
229
|
+
currentBundleId: String?,
|
|
230
|
+
bundleId: String,
|
|
231
|
+
) {
|
|
232
|
+
try {
|
|
233
|
+
// List only directories that are not .tmp
|
|
234
|
+
val bundles =
|
|
235
|
+
bundleStoreDir
|
|
236
|
+
.listFiles { file ->
|
|
237
|
+
file.isDirectory && !file.name.endsWith(".tmp")
|
|
238
|
+
}?.toList() ?: return
|
|
239
|
+
|
|
240
|
+
// Keep only the specified bundle IDs (filter out null values)
|
|
241
|
+
val bundleIdsToKeep = setOfNotNull(currentBundleId, bundleId).filter { it.isNotBlank() }
|
|
242
|
+
|
|
243
|
+
bundles.forEach { bundle ->
|
|
244
|
+
try {
|
|
245
|
+
if (bundle.name !in bundleIdsToKeep) {
|
|
246
|
+
Log.d("BundleStorage", "Removing old bundle: ${bundle.name}")
|
|
247
|
+
if (bundle.deleteRecursively()) {
|
|
248
|
+
Log.d("BundleStorage", "Successfully removed old bundle: ${bundle.name}")
|
|
249
|
+
} else {
|
|
250
|
+
Log.w("BundleStorage", "Failed to remove old bundle: ${bundle.name}")
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
Log.d("BundleStorage", "Keeping bundle: ${bundle.name}")
|
|
254
|
+
}
|
|
255
|
+
} catch (e: Exception) {
|
|
256
|
+
Log.e("BundleStorage", "Error removing bundle ${bundle.name}: ${e.message}")
|
|
257
|
+
}
|
|
228
258
|
}
|
|
229
|
-
}
|
|
230
259
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
// Remove any leftover .tmp directories
|
|
261
|
+
bundleStoreDir
|
|
262
|
+
.listFiles { file ->
|
|
263
|
+
file.isDirectory && file.name.endsWith(".tmp")
|
|
264
|
+
}?.forEach { staleTmp ->
|
|
265
|
+
try {
|
|
266
|
+
Log.d("BundleStorage", "Removing stale tmp directory: ${staleTmp.name}")
|
|
267
|
+
if (staleTmp.deleteRecursively()) {
|
|
268
|
+
Log.d("BundleStorage", "Successfully removed tmp directory: ${staleTmp.name}")
|
|
269
|
+
} else {
|
|
270
|
+
Log.w("BundleStorage", "Failed to remove tmp directory: ${staleTmp.name}")
|
|
271
|
+
}
|
|
272
|
+
} catch (e: Exception) {
|
|
273
|
+
Log.e("BundleStorage", "Error removing tmp directory ${staleTmp.name}: ${e.message}")
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch (e: Exception) {
|
|
277
|
+
Log.e("BundleStorage", "Error during cleanup: ${e.message}")
|
|
235
278
|
}
|
|
236
279
|
}
|
|
237
280
|
}
|
|
@@ -159,115 +159,67 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
159
159
|
return .failure(error)
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
* Cleans up old bundles, keeping only the current and new bundles.
|
|
165
|
+
* Executes synchronously on the calling thread.
|
|
166
|
+
* @param currentBundleId ID of the current active bundle (optional)
|
|
167
|
+
* @param bundleId ID of the new bundle to keep (optional)
|
|
168
|
+
* @return Result of operation
|
|
169
|
+
*/
|
|
170
|
+
func cleanupOldBundles(currentBundleId: String?, bundleId: String?) -> Result<Void, Error> {
|
|
170
171
|
let storeDirResult = bundleStoreDir()
|
|
171
172
|
|
|
172
173
|
guard case .success(let storeDir) = storeDirResult else {
|
|
173
174
|
return .failure(storeDirResult.failureError ?? BundleStorageError.unknown(nil))
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
// List only directories that are not .tmp
|
|
178
|
+
let contents: [String]
|
|
176
179
|
do {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
var latestBundlePath: String? = nil
|
|
194
|
-
var latestModDate: Date = .distantPast
|
|
180
|
+
contents = try self.fileSystem.contentsOfDirectory(atPath: storeDir)
|
|
181
|
+
} catch let error {
|
|
182
|
+
NSLog("[BundleStorage] Failed to list contents of bundle store directory: \(storeDir)")
|
|
183
|
+
return .failure(BundleStorageError.fileSystemError(error))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let bundles = contents.compactMap { item -> String? in
|
|
187
|
+
let fullPath = (storeDir as NSString).appendingPathComponent(item)
|
|
188
|
+
return (!item.hasSuffix(".tmp") && self.fileSystem.fileExists(atPath: fullPath)) ? fullPath : nil
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Keep only the specified bundle IDs
|
|
192
|
+
let bundleIdsToKeep = Set([currentBundleId, bundleId].compactMap { $0 })
|
|
193
|
+
|
|
194
|
+
bundles.forEach { bundlePath in
|
|
195
|
+
let bundleName = (bundlePath as NSString).lastPathComponent
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if let currentPath = currentBundlePath, fullPath == currentPath {
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if self.fileSystem.fileExists(atPath: fullPath) {
|
|
209
|
-
do {
|
|
210
|
-
let attributes = try self.fileSystem.attributesOfItem(atPath: fullPath)
|
|
211
|
-
if let modDate = attributes[FileAttributeKey.modificationDate] as? Date {
|
|
212
|
-
if modDate > latestModDate {
|
|
213
|
-
latestModDate = modDate
|
|
214
|
-
latestBundlePath = fullPath
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} catch {
|
|
218
|
-
NSLog("[BundleStorage] Warning: Could not get attributes for \(fullPath): \(error)")
|
|
219
|
-
}
|
|
197
|
+
if !bundleIdsToKeep.contains(bundleName) {
|
|
198
|
+
do {
|
|
199
|
+
try self.fileSystem.removeItem(atPath: bundlePath)
|
|
200
|
+
NSLog("[BundleStorage] Removing old bundle: \(bundleName)")
|
|
201
|
+
} catch {
|
|
202
|
+
NSLog("[BundleStorage] Failed to remove old bundle at \(bundlePath): \(error)")
|
|
220
203
|
}
|
|
204
|
+
} else {
|
|
205
|
+
NSLog("[BundleStorage] Keeping bundle: \(bundleName)")
|
|
221
206
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
NSLog("[BundleStorage] Keeping current bundle: \(currentBundleId!)")
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if let latestPath = latestBundlePath {
|
|
231
|
-
bundlesToKeep.insert(latestPath)
|
|
232
|
-
NSLog("[BundleStorage] Keeping latest bundle: \((latestPath as NSString).lastPathComponent)")
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
var removedCount = 0
|
|
236
|
-
for item in contents {
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Remove any leftover .tmp directories
|
|
210
|
+
contents.forEach { item in
|
|
211
|
+
if item.hasSuffix(".tmp") {
|
|
237
212
|
let fullPath = (storeDir as NSString).appendingPathComponent(item)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
NSLog("[BundleStorage] Removed stale tmp directory: \(fullPath)")
|
|
244
|
-
} catch {
|
|
245
|
-
NSLog("[BundleStorage] Failed to remove stale tmp directory \(fullPath): \(error)")
|
|
246
|
-
}
|
|
247
|
-
continue
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if !bundlesToKeep.contains(fullPath) {
|
|
251
|
-
do {
|
|
252
|
-
try self.fileSystem.removeItem(atPath: fullPath)
|
|
253
|
-
removedCount += 1
|
|
254
|
-
NSLog("[BundleStorage] Removed old bundle: \(item)")
|
|
255
|
-
} catch {
|
|
256
|
-
NSLog("[BundleStorage] Failed to remove old bundle at \(fullPath): \(error)")
|
|
257
|
-
}
|
|
213
|
+
do {
|
|
214
|
+
try self.fileSystem.removeItem(atPath: fullPath)
|
|
215
|
+
NSLog("[BundleStorage] Removing stale tmp directory: \(item)")
|
|
216
|
+
} catch {
|
|
217
|
+
NSLog("[BundleStorage] Failed to remove stale tmp directory \(fullPath): \(error)")
|
|
258
218
|
}
|
|
259
219
|
}
|
|
260
|
-
|
|
261
|
-
if removedCount == 0 {
|
|
262
|
-
NSLog("[BundleStorage] No old bundles to remove.")
|
|
263
|
-
} else {
|
|
264
|
-
NSLog("[BundleStorage] Removed \(removedCount) old bundle(s).")
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return .success(())
|
|
268
|
-
} catch let error {
|
|
269
|
-
return .failure(error)
|
|
270
220
|
}
|
|
221
|
+
|
|
222
|
+
return .success(())
|
|
271
223
|
}
|
|
272
224
|
|
|
273
225
|
/**
|
|
@@ -324,6 +276,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
324
276
|
* @param completion Callback with result of the operation
|
|
325
277
|
*/
|
|
326
278
|
func updateBundle(bundleId: String, fileUrl: URL?, completion: @escaping (Result<Bool, Error>) -> Void) {
|
|
279
|
+
// Get the current bundle ID from the cached bundle URL (exclude fallback bundles)
|
|
280
|
+
let currentBundleId = self.getCachedBundleURL()?.deletingLastPathComponent().lastPathComponent
|
|
281
|
+
|
|
327
282
|
guard let validFileUrl = fileUrl else {
|
|
328
283
|
NSLog("[BundleStorage] fileUrl is nil, resetting bundle URL.")
|
|
329
284
|
// Dispatch the sequence to the file operation queue to ensure completion is called asynchronously
|
|
@@ -332,7 +287,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
332
287
|
let setResult = self.setBundleURL(localPath: nil)
|
|
333
288
|
switch setResult {
|
|
334
289
|
case .success:
|
|
335
|
-
let cleanupResult = self.cleanupOldBundles(currentBundleId:
|
|
290
|
+
let cleanupResult = self.cleanupOldBundles(currentBundleId: currentBundleId, bundleId: bundleId)
|
|
336
291
|
switch cleanupResult {
|
|
337
292
|
case .success:
|
|
338
293
|
completion(.success(true))
|
|
@@ -350,6 +305,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
350
305
|
|
|
351
306
|
// Start the bundle update process on a background queue
|
|
352
307
|
fileOperationQueue.async {
|
|
308
|
+
|
|
353
309
|
let storeDirResult = self.bundleStoreDir()
|
|
354
310
|
guard case .success(let storeDir) = storeDirResult else {
|
|
355
311
|
completion(.failure(storeDirResult.failureError ?? BundleStorageError.unknown(nil)))
|
|
@@ -368,7 +324,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
368
324
|
let setResult = self.setBundleURL(localPath: bundlePath)
|
|
369
325
|
switch setResult {
|
|
370
326
|
case .success:
|
|
371
|
-
let cleanupResult = self.cleanupOldBundles(currentBundleId: bundleId)
|
|
327
|
+
let cleanupResult = self.cleanupOldBundles(currentBundleId: currentBundleId, bundleId: bundleId)
|
|
372
328
|
switch cleanupResult {
|
|
373
329
|
case .success:
|
|
374
330
|
completion(.success(true))
|
|
@@ -478,7 +434,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
478
434
|
}
|
|
479
435
|
|
|
480
436
|
/**
|
|
481
|
-
* Processes a downloaded bundle file using the
|
|
437
|
+
* Processes a downloaded bundle file using the "tmp" rename approach.
|
|
482
438
|
* This method is part of the asynchronous `updateBundle` flow and is expected to run on a background thread.
|
|
483
439
|
* @param location URL of the downloaded file
|
|
484
440
|
* @param tempZipFile Path to store the downloaded zip file
|
|
@@ -495,6 +451,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
495
451
|
tempDirectory: String,
|
|
496
452
|
completion: @escaping (Result<Bool, Error>) -> Void
|
|
497
453
|
) {
|
|
454
|
+
let currentBundleId = self.getCachedBundleURL()?.deletingLastPathComponent().lastPathComponent
|
|
498
455
|
NSLog("[BundleStorage] Processing downloaded file atPath: \(location.path)")
|
|
499
456
|
|
|
500
457
|
// 1) Ensure the ZIP file exists
|
|
@@ -556,7 +513,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
556
513
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
557
514
|
|
|
558
515
|
// 13) Clean up old bundles, preserving current and latest
|
|
559
|
-
let _ = self.cleanupOldBundles(currentBundleId: bundleId)
|
|
516
|
+
let _ = self.cleanupOldBundles(currentBundleId: currentBundleId, bundleId: bundleId)
|
|
560
517
|
|
|
561
518
|
// 14) Complete with success
|
|
562
519
|
completion(.success(true))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -119,12 +119,12 @@
|
|
|
119
119
|
"react-native": "0.79.1",
|
|
120
120
|
"react-native-builder-bob": "^0.40.10",
|
|
121
121
|
"typescript": "^5.8.3",
|
|
122
|
-
"hot-updater": "0.19.
|
|
122
|
+
"hot-updater": "0.19.5"
|
|
123
123
|
},
|
|
124
124
|
"dependencies": {
|
|
125
125
|
"use-sync-external-store": "1.5.0",
|
|
126
|
-
"@hot-updater/core": "0.19.
|
|
127
|
-
"@hot-updater/js": "0.19.
|
|
126
|
+
"@hot-updater/core": "0.19.5",
|
|
127
|
+
"@hot-updater/js": "0.19.5"
|
|
128
128
|
},
|
|
129
129
|
"scripts": {
|
|
130
130
|
"build": "bob build && tsc -p plugin/tsconfig.json",
|