@capgo/capacitor-updater 8.51.2 → 8.51.3

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.
@@ -146,7 +146,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
146
146
  static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
147
147
  static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
148
148
 
149
- private final String pluginVersion = "8.51.2";
149
+ private final String pluginVersion = "8.51.3";
150
150
  private static final String DELAY_CONDITION_PREFERENCES = "";
151
151
 
152
152
  private SharedPreferences.Editor editor;
@@ -92,7 +92,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
92
92
  CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
93
93
  ]
94
94
  public var implementation = CapgoUpdater()
95
- private let pluginVersion: String = "8.51.2"
95
+ private let pluginVersion: String = "8.51.3"
96
96
  private let launchStartedAtMs = Int64(Date().timeIntervalSince1970 * 1000)
97
97
  static let updateUrlDefault = "https://plugin.capgo.app/updates"
98
98
  static let statsUrlDefault = "https://plugin.capgo.app/stats"
@@ -707,13 +707,42 @@ import UIKit
707
707
  }
708
708
  }
709
709
 
710
- private func populateDeltaCacheAsync(for id: String) {
710
+ private func populateDeltaCacheAsync(for id: String, manifest: [ManifestEntry]? = nil, sessionKey: String = "") {
711
711
  DispatchQueue.global(qos: .utility).async { [weak self] in
712
- self?.populateDeltaCache(for: id)
712
+ self?.populateDeltaCache(for: id, manifest: manifest, sessionKey: sessionKey)
713
713
  }
714
714
  }
715
715
 
716
- private func populateDeltaCache(for id: String) {
716
+ struct ManifestLookupEntry {
717
+ let hash: String
718
+ /// The manifest's own file name, `.br` suffix included when present. The
719
+ /// built-in bundle stores files under this exact name (see
720
+ /// isManifestEntryAvailableLocally), unlike the extracted/cached copy which
721
+ /// is always named without the suffix.
722
+ let originalFileName: String
723
+ }
724
+
725
+ /// Keys are stripped of the `.br` suffix so they match the extracted file names on
726
+ /// disk, not the manifest's (possibly brotli-compressed) original file names.
727
+ func manifestHashLookup(manifest: [ManifestEntry]?, sessionKey: String) -> [String: ManifestLookupEntry] {
728
+ guard let manifest else {
729
+ return [:]
730
+ }
731
+ var lookup: [String: ManifestLookupEntry] = [:]
732
+ for entry in manifest {
733
+ guard let fileName = entry.file_name,
734
+ let hash = resolveManifestFileHash(entry: entry, sessionKey: sessionKey) else {
735
+ continue
736
+ }
737
+ let destFileName = fileName.hasSuffix(".br") ? String(fileName.dropLast(3)) : fileName
738
+ lookup[destFileName] = ManifestLookupEntry(hash: hash, originalFileName: fileName)
739
+ }
740
+ return lookup
741
+ }
742
+
743
+ /// `manifest` must only contain entries the caller already checksum-verified
744
+ /// (as `downloadManifest` does) — the hashes are trusted as-is, not re-checked.
745
+ func populateDeltaCache(for id: String, manifest: [ManifestEntry]? = nil, sessionKey: String = "") {
717
746
  let bundleDir = self.getBundleDirectory(id: id)
718
747
  let fileManager = FileManager.default
719
748
 
@@ -733,17 +762,33 @@ import UIKit
733
762
  return
734
763
  }
735
764
 
765
+ let knownEntries = manifestHashLookup(manifest: manifest, sessionKey: sessionKey)
766
+ let builtinFolder = self.builtinFolderURL()
767
+
736
768
  for case let fileURL as URL in enumerator {
737
769
  let resourceValues = try? fileURL.resourceValues(forKeys: [.isDirectoryKey])
738
770
  if resourceValues?.isDirectory == true {
739
771
  continue
740
772
  }
741
773
 
742
- let checksum = CryptoCipher.calcChecksum(filePath: fileURL)
774
+ let relativePath = String(fileURL.path.dropFirst(bundleDir.path.count + 1))
775
+ let knownEntry = knownEntries[relativePath]
776
+
777
+ let checksum = knownEntry?.hash ?? CryptoCipher.calcChecksum(filePath: fileURL)
743
778
  if checksum.isEmpty {
744
779
  continue
745
780
  }
746
781
 
782
+ // Builtin is already a permanent reuse source (see isManifestEntryAvailableLocally),
783
+ // so there's no need to also duplicate this file into the delta cache
784
+ let builtinRelativePath = knownEntry?.originalFileName ?? relativePath
785
+ let builtinFilePath = builtinFolder.appendingPathComponent(builtinRelativePath)
786
+ let isBuiltinOrigin = fileManager.fileExists(atPath: builtinFilePath.path) &&
787
+ verifyChecksum(file: builtinFilePath, expectedHash: checksum)
788
+ if isBuiltinOrigin {
789
+ continue
790
+ }
791
+
747
792
  let cacheFile = cacheFolder.appendingPathComponent("\(checksum)_\(fileURL.lastPathComponent)")
748
793
  if fileManager.fileExists(atPath: cacheFile.path) {
749
794
  continue
@@ -931,6 +976,12 @@ import UIKit
931
976
  return actualHash == expectedHash
932
977
  }
933
978
 
979
+ /// Overridable so tests can point it at a writable directory instead of the
980
+ /// real (read-only) app bundle.
981
+ func builtinFolderURL() -> URL {
982
+ Bundle.main.bundleURL.appendingPathComponent("public")
983
+ }
984
+
934
985
  private func resolveManifestFileHash(entry: ManifestEntry, sessionKey: String) -> String? {
935
986
  guard var fileHash = entry.file_hash, !fileHash.isEmpty else {
936
987
  return nil
@@ -953,7 +1004,7 @@ import UIKit
953
1004
  return false
954
1005
  }
955
1006
 
956
- let builtinFolder = Bundle.main.bundleURL.appendingPathComponent("public")
1007
+ let builtinFolder = self.builtinFolderURL()
957
1008
  let builtinFilePath = builtinFolder.appendingPathComponent(fileName)
958
1009
  if FileManager.default.fileExists(atPath: builtinFilePath.path) && verifyChecksum(file: builtinFilePath, expectedHash: fileHash) {
959
1010
  return true
@@ -1066,7 +1117,7 @@ import UIKit
1066
1117
  let id = self.randomString(length: 10)
1067
1118
  logger.info("downloadManifest start \(id)")
1068
1119
  let destFolder = self.getBundleDirectory(id: id)
1069
- let builtinFolder = Bundle.main.bundleURL.appendingPathComponent("public")
1120
+ let builtinFolder = self.builtinFolderURL()
1070
1121
 
1071
1122
  // Check disk space before starting manifest download (estimate 100KB per file, minimum 50MB)
1072
1123
  let estimatedSize = Int64(max(manifest.count * 100 * 1024, 50 * 1024 * 1024))
@@ -1253,6 +1304,8 @@ import UIKit
1253
1304
  // Send stats for manifest download complete
1254
1305
  self.sendStats(action: "download_manifest_complete", versionName: version)
1255
1306
 
1307
+ self.populateDeltaCacheAsync(for: id, manifest: manifest, sessionKey: sessionKey)
1308
+
1256
1309
  self.notifyDownload(id: id, percent: 100, bundle: updatedBundle)
1257
1310
  logger.info("downloadManifest done \(id)")
1258
1311
  return updatedBundle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.51.2",
3
+ "version": "8.51.3",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",