@capgo/capacitor-updater 8.49.4 → 8.49.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.
@@ -142,7 +142,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
142
142
  static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
143
143
  static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
144
144
 
145
- private final String pluginVersion = "8.49.4";
145
+ private final String pluginVersion = "8.49.5";
146
146
  private static final String DELAY_CONDITION_PREFERENCES = "";
147
147
 
148
148
  private SharedPreferences.Editor editor;
@@ -85,7 +85,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
85
85
  CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
86
86
  ]
87
87
  public var implementation = CapgoUpdater()
88
- private let pluginVersion: String = "8.49.4"
88
+ private let pluginVersion: String = "8.49.5"
89
89
  static let updateUrlDefault = "https://plugin.capgo.app/updates"
90
90
  static let statsUrlDefault = "https://plugin.capgo.app/stats"
91
91
  static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
@@ -1160,7 +1160,7 @@ import UIKit
1160
1160
  do {
1161
1161
  // Try builtin first
1162
1162
  if FileManager.default.fileExists(atPath: builtinFilePath.path) && self.verifyChecksum(file: builtinFilePath, expectedHash: finalFileHash) {
1163
- try FileManager.default.copyItem(at: builtinFilePath, to: destFilePath)
1163
+ try self.copyItemReplacing(from: builtinFilePath, to: destFilePath)
1164
1164
  self.logger.info("downloadManifest \(fileName) using builtin file \(id)")
1165
1165
  }
1166
1166
  // Try cache
@@ -1323,8 +1323,8 @@ import UIKit
1323
1323
  finalData = decompressedData
1324
1324
  }
1325
1325
 
1326
- // Write to destination
1327
- try finalData.write(to: destFilePath)
1326
+ // Write to destination (replace if leftover from a previous failed download)
1327
+ try writeDataAtomically(finalData, to: destFilePath)
1328
1328
 
1329
1329
  // Always verify checksum when file_hash is present
1330
1330
  let calculatedChecksum = CryptoCipher.calcChecksum(filePath: destFilePath)
@@ -1336,8 +1336,8 @@ import UIKit
1336
1336
  throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash)) for file \(fileName) at url \(downloadUrl)"])
1337
1337
  }
1338
1338
 
1339
- // Save to cache
1340
- try finalData.write(to: cacheFilePath)
1339
+ // Save to cache (replace stale cache entries from partial or concurrent downloads)
1340
+ try writeDataAtomically(finalData, to: cacheFilePath)
1341
1341
 
1342
1342
  self.logger.info("Manifest file downloaded and cached")
1343
1343
  self.logger.debug("Bundle: \(bundleId), File: \(fileName), Brotli: \(isBrotli), Encrypted: \(!self.publicKey.isEmpty && !sessionKey.isEmpty)")
@@ -1348,22 +1348,49 @@ import UIKit
1348
1348
  }
1349
1349
  }
1350
1350
 
1351
+ /// Atomically write data to a file, replacing any existing file at the destination.
1352
+ private func writeDataAtomically(_ data: Data, to destination: URL) throws {
1353
+ let fileManager = FileManager.default
1354
+ let tempURL = destination.deletingLastPathComponent().appendingPathComponent("\(destination.lastPathComponent).\(UUID().uuidString).tmp")
1355
+ defer {
1356
+ try? fileManager.removeItem(at: tempURL)
1357
+ }
1358
+
1359
+ try data.write(to: tempURL, options: .atomic)
1360
+ if fileManager.fileExists(atPath: destination.path) {
1361
+ try fileManager.removeItem(at: destination)
1362
+ }
1363
+ try fileManager.moveItem(at: tempURL, to: destination)
1364
+ }
1365
+
1366
+ /// Copy a file to the destination, replacing any existing file.
1367
+ private func copyItemReplacing(from source: URL, to destination: URL) throws {
1368
+ let fileManager = FileManager.default
1369
+ if fileManager.fileExists(atPath: destination.path) {
1370
+ try fileManager.removeItem(at: destination)
1371
+ }
1372
+ try fileManager.copyItem(at: source, to: destination)
1373
+ }
1374
+
1351
1375
  /// Atomically try to copy a file from cache - returns true if successful, false if file doesn't exist or copy failed
1352
1376
  /// This handles the race condition where OS can delete cache files between exists() check and copy
1353
1377
  private func tryCopyFromCache(from source: URL, to destination: URL, expectedHash: String) -> Bool {
1378
+ let fileManager = FileManager.default
1379
+
1354
1380
  // First quick check - if file doesn't exist, don't bother
1355
- guard FileManager.default.fileExists(atPath: source.path) else {
1381
+ guard fileManager.fileExists(atPath: source.path) else {
1356
1382
  return false
1357
1383
  }
1358
1384
 
1359
- // Verify checksum before copy
1385
+ // Verify checksum before copy; remove stale cache entries that would block re-download
1360
1386
  guard verifyChecksum(file: source, expectedHash: expectedHash) else {
1387
+ try? fileManager.removeItem(at: source)
1361
1388
  return false
1362
1389
  }
1363
1390
 
1364
1391
  // Try to copy - if it fails (file deleted by OS between check and copy), return false
1365
1392
  do {
1366
- try FileManager.default.copyItem(at: source, to: destination)
1393
+ try copyItemReplacing(from: source, to: destination)
1367
1394
  return true
1368
1395
  } catch {
1369
1396
  // File was deleted between check and copy, or other IO error - caller should download instead
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.49.4",
3
+ "version": "8.49.5",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",