@capgo/capacitor-updater 8.0.0-alpha → 8.0.0-alpha.2

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.
@@ -372,6 +372,12 @@ import UIKit
372
372
  if let manifest = response.value?.manifest {
373
373
  latest.manifest = manifest
374
374
  }
375
+ if let link = response.value?.link {
376
+ latest.link = link
377
+ }
378
+ if let comment = response.value?.comment {
379
+ latest.comment = comment
380
+ }
375
381
  case let .failure(error):
376
382
  self.logger.error("Error getting Latest \(response.value.debugDescription) \(error)")
377
383
  latest.message = "Error getting Latest \(String(describing: response.value))"
@@ -400,11 +406,11 @@ import UIKit
400
406
  private var tempData = Data()
401
407
 
402
408
  private func verifyChecksum(file: URL, expectedHash: String) -> Bool {
403
- let actualHash = CryptoCipher.calcChecksum(filePath: file)
409
+ let actualHash = CryptoCipher.calcChecksum(filePath: file)
404
410
  return actualHash == expectedHash
405
411
  }
406
412
 
407
- public func downloadManifest(manifest: [ManifestEntry], version: String, sessionKey: String) throws -> BundleInfo {
413
+ public func downloadManifest(manifest: [ManifestEntry], version: String, sessionKey: String, link: String? = nil, comment: String? = nil) throws -> BundleInfo {
408
414
  let id = self.randomString(length: 10)
409
415
  logger.info("downloadManifest start \(id)")
410
416
  let destFolder = self.getBundleDirectory(id: id)
@@ -414,7 +420,7 @@ import UIKit
414
420
  try FileManager.default.createDirectory(at: destFolder, withIntermediateDirectories: true, attributes: nil)
415
421
 
416
422
  // Create and save BundleInfo before starting the download process
417
- let bundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: "")
423
+ let bundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: "", link: link, comment: comment)
418
424
  self.saveBundleInfo(id: id, bundle: bundleInfo)
419
425
 
420
426
  // Send stats for manifest download start
@@ -445,10 +451,16 @@ import UIKit
445
451
  }
446
452
  }
447
453
 
454
+ // Check if file has .br extension for Brotli decompression
448
455
  let fileNameWithoutPath = (fileName as NSString).lastPathComponent
449
456
  let cacheFileName = "\(fileHash)_\(fileNameWithoutPath)"
450
457
  let cacheFilePath = cacheFolder.appendingPathComponent(cacheFileName)
451
- let destFilePath = destFolder.appendingPathComponent(fileName)
458
+
459
+ // Check if file is Brotli compressed and remove .br extension from destination
460
+ let isBrotli = fileName.hasSuffix(".br")
461
+ let destFileName = isBrotli ? String(fileName.dropLast(3)) : fileName
462
+
463
+ let destFilePath = destFolder.appendingPathComponent(destFileName)
452
464
  let builtinFilePath = builtinFolder.appendingPathComponent(fileName)
453
465
 
454
466
  // Create necessary subdirectories in the destination folder
@@ -457,16 +469,26 @@ import UIKit
457
469
  dispatchGroup.enter()
458
470
 
459
471
  if FileManager.default.fileExists(atPath: builtinFilePath.path) && verifyChecksum(file: builtinFilePath, expectedHash: fileHash) {
460
- try FileManager.default.copyItem(at: builtinFilePath, to: destFilePath)
461
- logger.info("downloadManifest \(fileName) using builtin file \(id)")
462
- completedFiles += 1
463
- self.notifyDownload(id: id, percent: self.calcTotalPercent(percent: Int((Double(completedFiles) / Double(totalFiles)) * 100), min: 10, max: 70))
472
+ do {
473
+ try FileManager.default.copyItem(at: builtinFilePath, to: destFilePath)
474
+ logger.info("downloadManifest \(fileName) using builtin file \(id)")
475
+ completedFiles += 1
476
+ self.notifyDownload(id: id, percent: self.calcTotalPercent(percent: Int((Double(completedFiles) / Double(totalFiles)) * 100), min: 10, max: 70))
477
+ } catch {
478
+ downloadError = error
479
+ logger.error("Failed to copy builtin file \(fileName): \(error.localizedDescription)")
480
+ }
464
481
  dispatchGroup.leave()
465
482
  } else if FileManager.default.fileExists(atPath: cacheFilePath.path) && verifyChecksum(file: cacheFilePath, expectedHash: fileHash) {
466
- try FileManager.default.copyItem(at: cacheFilePath, to: destFilePath)
467
- logger.info("downloadManifest \(fileName) copy from cache \(id)")
468
- completedFiles += 1
469
- self.notifyDownload(id: id, percent: self.calcTotalPercent(percent: Int((Double(completedFiles) / Double(totalFiles)) * 100), min: 10, max: 70))
483
+ do {
484
+ try FileManager.default.copyItem(at: cacheFilePath, to: destFilePath)
485
+ logger.info("downloadManifest \(fileName) copy from cache \(id)")
486
+ completedFiles += 1
487
+ self.notifyDownload(id: id, percent: self.calcTotalPercent(percent: Int((Double(completedFiles) / Double(totalFiles)) * 100), min: 10, max: 70))
488
+ } catch {
489
+ downloadError = error
490
+ logger.error("Failed to copy cached file \(fileName): \(error.localizedDescription)")
491
+ }
470
492
  dispatchGroup.leave()
471
493
  } else {
472
494
  // File not in cache, download, decompress, and save to both cache and destination
@@ -502,15 +524,11 @@ import UIKit
502
524
  try FileManager.default.removeItem(at: tempFile)
503
525
  }
504
526
 
505
- // Check if file has .br extension for Brotli decompression
506
- let isBrotli = fileName.hasSuffix(".br")
507
- let finalFileName = isBrotli ? String(fileName.dropLast(3)) : fileName
508
- let destFilePath = destFolder.appendingPathComponent(finalFileName)
509
-
527
+ // Use the isBrotli and destFilePath already computed above
510
528
  if isBrotli {
511
529
  // Decompress the Brotli data
512
530
  guard let decompressedData = self.decompressBrotli(data: finalData, fileName: fileName) else {
513
- self.sendStats(action: "download_manifest_brotli_fail", versionName: "\(version):\(finalFileName)")
531
+ self.sendStats(action: "download_manifest_brotli_fail", versionName: "\(version):\(destFileName)")
514
532
  throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data for file \(fileName) at url \(downloadUrl)"])
515
533
  }
516
534
  finalData = decompressedData
@@ -523,7 +541,9 @@ import UIKit
523
541
  CryptoCipher.logChecksumInfo(label: "Calculated checksum", hexChecksum: calculatedChecksum)
524
542
  CryptoCipher.logChecksumInfo(label: "Expected checksum", hexChecksum: fileHash)
525
543
  if calculatedChecksum != fileHash {
526
- self.sendStats(action: "download_manifest_checksum_fail", versionName: "\(version):\(finalFileName)")
544
+ // Delete the corrupt file before throwing error
545
+ try? FileManager.default.removeItem(at: destFilePath)
546
+ self.sendStats(action: "download_manifest_checksum_fail", versionName: "\(version):\(destFileName)")
527
547
  throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash)) for file \(fileName) at url \(downloadUrl)"])
528
548
  }
529
549
  }
@@ -681,7 +701,7 @@ import UIKit
681
701
  return status == COMPRESSION_STATUS_END ? decompressedData : nil
682
702
  }
683
703
 
684
- public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
704
+ public func download(url: URL, version: String, sessionKey: String, link: String? = nil, comment: String? = nil) throws -> BundleInfo {
685
705
  let id: String = self.randomString(length: 10)
686
706
  let semaphore = DispatchSemaphore(value: 0)
687
707
  if version != getLocalUpdateVersion() {
@@ -748,7 +768,7 @@ import UIKit
748
768
  semaphore.signal()
749
769
  }
750
770
  }
751
- self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
771
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum, link: link, comment: comment))
752
772
  let reachabilityManager = NetworkReachabilityManager()
753
773
  reachabilityManager?.startListening { status in
754
774
  switch status {
@@ -766,7 +786,7 @@ import UIKit
766
786
 
767
787
  if mainError != nil {
768
788
  logger.error("Failed to download: \(String(describing: mainError))")
769
- self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
789
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum, link: link, comment: comment))
770
790
  throw mainError!
771
791
  }
772
792
 
@@ -776,7 +796,7 @@ import UIKit
776
796
  try FileManager.default.moveItem(at: tempDataPath, to: finalPath)
777
797
  } catch {
778
798
  logger.error("Failed decrypt file : \(error)")
779
- self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
799
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum, link: link, comment: comment))
780
800
  cleanDownloadData()
781
801
  throw error
782
802
  }
@@ -789,7 +809,7 @@ import UIKit
789
809
 
790
810
  } catch {
791
811
  logger.error("Failed to unzip file: \(error)")
792
- self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
812
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum, link: link, comment: comment))
793
813
  // Best-effort cleanup of the decrypted zip file when unzip fails
794
814
  do {
795
815
  if FileManager.default.fileExists(atPath: finalPath.path) {
@@ -804,7 +824,7 @@ import UIKit
804
824
 
805
825
  self.notifyDownload(id: id, percent: 90)
806
826
  logger.info("Downloading: 90% (wrapping up)")
807
- let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
827
+ let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum, link: link, comment: comment)
808
828
  self.saveBundleInfo(id: id, bundle: info)
809
829
  self.cleanDownloadData()
810
830
 
@@ -1111,60 +1131,31 @@ import UIKit
1111
1131
  self.setBundleStatus(id: bundle.getId(), status: BundleStatus.ERROR)
1112
1132
  }
1113
1133
 
1114
- func unsetChannel() -> SetChannel {
1134
+ func unsetChannel(defaultChannelKey: String, configDefaultChannel: String) -> SetChannel {
1115
1135
  let setChannel: SetChannel = SetChannel()
1116
1136
 
1117
- // Check if rate limit was exceeded
1118
- if CapgoUpdater.rateLimitExceeded {
1119
- logger.debug("Skipping unsetChannel due to rate limit (429). Requests will resume after app restart.")
1120
- setChannel.message = "Rate limit exceeded"
1121
- setChannel.error = "rate_limit_exceeded"
1122
- return setChannel
1123
- }
1124
-
1125
- if (self.channelUrl ).isEmpty {
1126
- logger.error("Channel URL is not set")
1127
- setChannel.message = "Channel URL is not set"
1128
- setChannel.error = "missing_config"
1129
- return setChannel
1130
- }
1131
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
1132
- let parameters: InfoObject = self.createInfoObject()
1133
-
1134
- let request = alamofireSession.request(self.channelUrl, method: .delete, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
1135
-
1136
- request.validate().responseDecodable(of: SetChannelDec.self) { response in
1137
- // Check for 429 rate limit
1138
- if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
1139
- setChannel.message = "Rate limit exceeded"
1140
- setChannel.error = "rate_limit_exceeded"
1141
- semaphore.signal()
1142
- return
1143
- }
1137
+ // Clear persisted defaultChannel and revert to config value
1138
+ UserDefaults.standard.removeObject(forKey: defaultChannelKey)
1139
+ UserDefaults.standard.synchronize()
1140
+ self.defaultChannel = configDefaultChannel
1141
+ self.logger.info("Persisted defaultChannel cleared, reverted to config value: \(configDefaultChannel)")
1144
1142
 
1145
- switch response.result {
1146
- case .success:
1147
- if let responseValue = response.value {
1148
- if let error = responseValue.error {
1149
- setChannel.error = error
1150
- } else {
1151
- setChannel.status = responseValue.status ?? ""
1152
- setChannel.message = responseValue.message ?? ""
1153
- }
1154
- }
1155
- case let .failure(error):
1156
- self.logger.error("Error unset Channel \(error)")
1157
- setChannel.error = "Request failed: \(error.localizedDescription)"
1158
- }
1159
- semaphore.signal()
1160
- }
1161
- semaphore.wait()
1143
+ setChannel.status = "ok"
1144
+ setChannel.message = "Channel override removed"
1162
1145
  return setChannel
1163
1146
  }
1164
1147
 
1165
- func setChannel(channel: String) -> SetChannel {
1148
+ func setChannel(channel: String, defaultChannelKey: String, allowSetDefaultChannel: Bool) -> SetChannel {
1166
1149
  let setChannel: SetChannel = SetChannel()
1167
1150
 
1151
+ // Check if setting defaultChannel is allowed
1152
+ if !allowSetDefaultChannel {
1153
+ logger.error("setChannel is disabled by allowSetDefaultChannel config")
1154
+ setChannel.message = "setChannel is disabled by configuration"
1155
+ setChannel.error = "disabled_by_config"
1156
+ return setChannel
1157
+ }
1158
+
1168
1159
  // Check if rate limit was exceeded
1169
1160
  if CapgoUpdater.rateLimitExceeded {
1170
1161
  logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.")
@@ -1200,6 +1191,12 @@ import UIKit
1200
1191
  if let error = responseValue.error {
1201
1192
  setChannel.error = error
1202
1193
  } else {
1194
+ // Success - persist defaultChannel
1195
+ self.defaultChannel = channel
1196
+ UserDefaults.standard.set(channel, forKey: defaultChannelKey)
1197
+ UserDefaults.standard.synchronize()
1198
+ self.logger.info("defaultChannel persisted locally: \(channel)")
1199
+
1203
1200
  setChannel.status = responseValue.status ?? ""
1204
1201
  setChannel.message = responseValue.message ?? ""
1205
1202
  }
@@ -160,6 +160,8 @@ struct AppVersionDec: Decodable {
160
160
  let breaking: Bool?
161
161
  let data: [String: String]?
162
162
  let manifest: [ManifestEntry]?
163
+ let link: String?
164
+ let comment: String?
163
165
  // The HTTP status code is captured separately in CapgoUpdater; this struct only mirrors JSON.
164
166
  }
165
167
 
@@ -174,6 +176,8 @@ public class AppVersion: NSObject {
174
176
  var breaking: Bool?
175
177
  var data: [String: String]?
176
178
  var manifest: [ManifestEntry]?
179
+ var link: String?
180
+ var comment: String?
177
181
  var statusCode: Int = 0
178
182
  }
179
183
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.0.0-alpha",
3
+ "version": "8.0.0-alpha.2",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",
@@ -37,6 +37,7 @@
37
37
  "ionic",
38
38
  "appflow alternative",
39
39
  "capawesome alternative",
40
+ "@capawesome/capacitor-live-update",
40
41
  "native"
41
42
  ],
42
43
  "scripts": {