@capgo/capacitor-updater 8.49.3 → 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.
|
|
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;
|
|
@@ -216,7 +216,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
216
216
|
private volatile Thread backgroundDownloadTask;
|
|
217
217
|
private volatile Thread appReadyCheck;
|
|
218
218
|
private volatile long downloadStartTimeMs = 0;
|
|
219
|
-
private static final long DOWNLOAD_TIMEOUT_MS =
|
|
219
|
+
private static final long DOWNLOAD_TIMEOUT_MS = 600000; // 10 minute timeout
|
|
220
220
|
|
|
221
221
|
private final Phaser semaphoreReady = new Phaser(0) {
|
|
222
222
|
@Override
|
|
@@ -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.
|
|
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"
|
|
@@ -168,7 +168,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
168
168
|
private let onLaunchDirectUpdateStateLock = NSLock()
|
|
169
169
|
private var downloadInProgress = false
|
|
170
170
|
private var downloadStartTime: Date?
|
|
171
|
-
private let downloadTimeout: TimeInterval =
|
|
171
|
+
private let downloadTimeout: TimeInterval = 600 // 10 minute timeout
|
|
172
172
|
|
|
173
173
|
// Lock to ensure cleanup completes before downloads start
|
|
174
174
|
private let cleanupLock = NSLock()
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|
|
@@ -71,7 +71,10 @@
|
|
|
71
71
|
"clean": "rimraf ./dist",
|
|
72
72
|
"watch": "tsc --watch",
|
|
73
73
|
"prepublishOnly": "bun run build",
|
|
74
|
-
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
|
|
74
|
+
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
|
|
75
|
+
"example:install": "cd example-app && bun install --frozen-lockfile",
|
|
76
|
+
"example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
|
|
77
|
+
"example:capgo:deploy": "bun run example:build && bun scripts/deploy-example-capgo.mjs"
|
|
75
78
|
},
|
|
76
79
|
"devDependencies": {
|
|
77
80
|
"@capacitor/android": "^8.0.0",
|