pulse-updates 1.0.5 → 1.0.7

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.
@@ -99,6 +99,10 @@ class PulseController private constructor() {
99
99
  private var embeddedManifest: EmbeddedManifest? = null
100
100
  private val executor = Executors.newSingleThreadExecutor()
101
101
 
102
+ // Cached manifest from last check (to avoid duplicate requests in fetch)
103
+ // Only valid for the immediate check->fetch sequence, cleared on use or new check
104
+ private var lastCheckManifest: ManifestModel? = null
105
+
102
106
  // Embedded asset hashes set for download optimization
103
107
  internal val embeddedAssetHashes: Set<String>
104
108
  get() = embeddedManifest?.assets
@@ -526,11 +530,26 @@ class PulseController private constructor() {
526
530
  }
527
531
 
528
532
  executor.execute {
529
- PulseRemoteLoader.checkForUpdate(cfg, launchedUpdate?.updateId, callback)
533
+ // Clear any previous cached manifest before new check
534
+ lastCheckManifest = null
535
+
536
+ PulseRemoteLoader.checkForUpdate(cfg, launchedUpdate?.updateId) { result ->
537
+ // Cache the manifest if update is available (for use by fetchUpdate)
538
+ result.fold(
539
+ onSuccess = { checkResult ->
540
+ if (checkResult.isAvailable && checkResult.manifest != null) {
541
+ lastCheckManifest = checkResult.manifest
542
+ pulseLog(TAG, "checkForUpdate: cached manifest for fetch")
543
+ }
544
+ },
545
+ onFailure = { }
546
+ )
547
+ callback(result)
548
+ }
530
549
  }
531
550
  }
532
551
 
533
- fun fetchUpdate(callback: (Result<FetchResult>) -> Unit) {
552
+ fun fetchUpdate(cachedManifest: ManifestModel? = null, callback: (Result<FetchResult>) -> Unit) {
534
553
  val cfg = config
535
554
  val ctx = context
536
555
  val dir = directory
@@ -545,8 +564,18 @@ class PulseController private constructor() {
545
564
  return
546
565
  }
547
566
 
567
+ // Use cached manifest from recent check if available
568
+ var manifestToUse = cachedManifest
569
+ if (manifestToUse == null && lastCheckManifest != null) {
570
+ pulseLog(TAG, "fetchUpdate: using cached manifest from recent check")
571
+ manifestToUse = lastCheckManifest
572
+ }
573
+
574
+ // Clear the cache after using it
575
+ lastCheckManifest = null
576
+
548
577
  executor.execute {
549
- PulseRemoteLoader.fetchUpdate(cfg, database, dir, callback)
578
+ PulseRemoteLoader.fetchUpdate(cfg, database, dir, manifestToUse, callback)
550
579
  }
551
580
  }
552
581
 
@@ -1141,8 +1170,22 @@ object PulseRemoteLoader {
1141
1170
  config: PulseUpdatesConfig,
1142
1171
  database: PulseDatabase?,
1143
1172
  directory: File,
1173
+ cachedManifest: ManifestModel? = null,
1144
1174
  callback: (Result<FetchResult>) -> Unit
1145
1175
  ) {
1176
+ // If we already have a manifest from a previous check, use it directly
1177
+ if (cachedManifest != null) {
1178
+ pulseLog(TAG, "fetchUpdate: using cached manifest, skipping check")
1179
+ downloadUpdate(cachedManifest, config, database, directory) { downloadResult ->
1180
+ downloadResult.fold(
1181
+ onSuccess = { callback(Result.success(FetchResult(isNew = true, manifest = cachedManifest))) },
1182
+ onFailure = { callback(Result.failure(it)) }
1183
+ )
1184
+ }
1185
+ return
1186
+ }
1187
+
1188
+ // No cached manifest, need to check first
1146
1189
  checkForUpdate(config, PulseController.getInstance().launchedUpdate?.updateId) { result ->
1147
1190
  result.fold(
1148
1191
  onSuccess = { checkResult ->
@@ -1265,6 +1308,10 @@ object PulseRemoteLoader {
1265
1308
 
1266
1309
  // Download assets and link them to the update
1267
1310
  val embeddedHashes = PulseController.getInstance().embeddedAssetHashes
1311
+ pulseLog(TAG, "downloadUpdate: embeddedHashes count=${embeddedHashes.size}")
1312
+ if (embeddedHashes.isNotEmpty()) {
1313
+ pulseLog(TAG, "downloadUpdate: first few embedded hashes: ${embeddedHashes.take(3).map { it.take(16) }}")
1314
+ }
1268
1315
 
1269
1316
  for (asset in manifest.assets) {
1270
1317
  val assetHash = asset.hash.lowercase()
@@ -473,8 +473,8 @@ struct PulseEmbeddedAsset {
473
473
  self.hash = hash
474
474
  self.key = json["key"] as? String
475
475
  self.type = json["type"] as? String ?? json["contentType"] as? String
476
- self.nsBundleDir = json["nsBundleDir"] as? String
477
- self.nsBundleFilename = json["nsBundleFilename"] as? String
476
+ self.nsBundleDir = json["mainBundleDir"] as? String ?? json["nsBundleDir"] as? String
477
+ self.nsBundleFilename = json["mainBundleFilename"] as? String ?? json["nsBundleFilename"] as? String
478
478
  }
479
479
  }
480
480
 
@@ -66,6 +66,10 @@ public final class PulseController {
66
66
  internal var embeddedAssetHashes: [String: URL] = [:]
67
67
  private var embeddedBundleHash: String?
68
68
 
69
+ // Cached manifest from last check (to avoid duplicate requests in fetch)
70
+ // Only valid for the immediate check->fetch sequence, cleared on use or new check
71
+ private var lastCheckManifest: PulseManifestModel?
72
+
69
73
  // MARK: - Native Config (from Info.plist, like expo-updates)
70
74
 
71
75
  /// Load config from Info.plist (called before JS starts)
@@ -404,12 +408,12 @@ public final class PulseController {
404
408
  private func performBackgroundUpdateCheck() {
405
409
  pulseLog("Performing background update check")
406
410
 
407
- checkForUpdate { [weak self] result in
411
+ checkForUpdate { [weak self] (result: Result<PulseCheckResult, Error>) in
408
412
  switch result {
409
413
  case .success(let checkResult):
410
414
  if checkResult.isAvailable {
411
415
  pulseLog("Background check: update available, fetching...")
412
- self?.fetchUpdate { fetchResult in
416
+ self?.fetchUpdate { (fetchResult: Result<PulseFetchResult, Error>) in
413
417
  switch fetchResult {
414
418
  case .success(let fetch):
415
419
  if fetch.isNew {
@@ -475,15 +479,24 @@ public final class PulseController {
475
479
  return
476
480
  }
477
481
 
482
+ // Clear any previous cached manifest before new check
483
+ lastCheckManifest = nil
484
+
478
485
  PulseRemoteLoader.checkForUpdate(
479
486
  config: config,
480
- currentUpdateId: launchedUpdate?.updateId,
481
- completion: completion
482
- )
487
+ currentUpdateId: launchedUpdate?.updateId
488
+ ) { [weak self] (result: Result<PulseCheckResult, Error>) in
489
+ // Cache the manifest for subsequent fetchUpdate call
490
+ if case .success(let checkResult) = result, checkResult.isAvailable {
491
+ self?.lastCheckManifest = checkResult.manifest
492
+ }
493
+ completion(result)
494
+ }
483
495
  }
484
496
 
485
497
  /// Fetch and download an update
486
- public func fetchUpdate(completion: @escaping (Result<PulseFetchResult, Error>) -> Void) {
498
+ /// - Parameter cachedManifest: Optional manifest from a previous checkForUpdate call to avoid duplicate request
499
+ public func fetchUpdate(cachedManifest: PulseManifestModel? = nil, completion: @escaping (Result<PulseFetchResult, Error>) -> Void) {
487
500
  guard let config = config else {
488
501
  completion(.failure(PulseUpdatesError.notConfigured))
489
502
  return
@@ -494,10 +507,21 @@ public final class PulseController {
494
507
  return
495
508
  }
496
509
 
510
+ // Use cached manifest from recent checkForUpdate if available
511
+ var manifestToUse = cachedManifest
512
+ if manifestToUse == nil, let cached = lastCheckManifest {
513
+ pulseLog("fetchUpdate: using cached manifest from recent check")
514
+ manifestToUse = cached
515
+ }
516
+
517
+ // Clear cache after use
518
+ lastCheckManifest = nil
519
+
497
520
  PulseRemoteLoader.fetchUpdate(
498
521
  config: config,
499
522
  database: database,
500
523
  directory: directory,
524
+ cachedManifest: manifestToUse,
501
525
  completion: completion
502
526
  )
503
527
  }
@@ -594,7 +618,7 @@ public final class PulseController {
594
618
  }
595
619
  }
596
620
 
597
- pulseLog("Loaded embedded manifest: \(embeddedManifest?.updateId ?? "unknown")")
621
+ pulseLog("Loaded embedded manifest: \(embeddedManifest?.updateId ?? "unknown"), embeddedAssetHashes count: \(embeddedAssetHashes.count)")
598
622
  }
599
623
 
600
624
  private func seedEmbeddedUpdateIfNeeded() {
@@ -661,7 +685,7 @@ public final class PulseController {
661
685
 
662
686
  self.launcher = launcher
663
687
 
664
- launcher.launch { [weak self] success, error in
688
+ launcher.launch { [weak self] (success: Bool, error: Error?) in
665
689
  guard let self = self else {
666
690
  completion(false)
667
691
  return
@@ -931,10 +955,25 @@ final class PulseRemoteLoader {
931
955
  config: PulseUpdatesConfig,
932
956
  database: PulseDatabase?,
933
957
  directory: URL,
958
+ cachedManifest: PulseManifestModel? = nil,
934
959
  completion: @escaping (Result<PulseFetchResult, Error>) -> Void
935
960
  ) {
936
- // First check for update
937
- checkForUpdate(config: config, currentUpdateId: PulseController.shared.launchedUpdate?.updateId) { result in
961
+ // If we already have a manifest from a previous check, use it directly
962
+ if let manifest = cachedManifest {
963
+ pulseLog("fetchUpdate: using cached manifest, skipping check")
964
+ downloadUpdate(manifest: manifest, config: config, database: database, directory: directory) { (downloadResult: Result<Void, Error>) in
965
+ switch downloadResult {
966
+ case .failure(let error):
967
+ completion(.failure(error))
968
+ case .success:
969
+ completion(.success(PulseFetchResult(isNew: true, manifest: manifest)))
970
+ }
971
+ }
972
+ return
973
+ }
974
+
975
+ // No cached manifest, need to check first
976
+ checkForUpdate(config: config, currentUpdateId: PulseController.shared.launchedUpdate?.updateId) { (result: Result<PulseCheckResult, Error>) in
938
977
  switch result {
939
978
  case .failure(let error):
940
979
  completion(.failure(error))
@@ -946,7 +985,7 @@ final class PulseRemoteLoader {
946
985
  }
947
986
 
948
987
  // Download the update
949
- downloadUpdate(manifest: manifest, config: config, database: database, directory: directory) { downloadResult in
988
+ downloadUpdate(manifest: manifest, config: config, database: database, directory: directory) { (downloadResult: Result<Void, Error>) in
950
989
  switch downloadResult {
951
990
  case .failure(let error):
952
991
  completion(.failure(error))
@@ -1019,7 +1058,7 @@ final class PulseRemoteLoader {
1019
1058
  try? database?.addUpdate(update)
1020
1059
 
1021
1060
  // Download bundle
1022
- downloadBundle(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { bundleResult in
1061
+ downloadBundle(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { (bundleResult: Result<Void, Error>) in
1023
1062
  switch bundleResult {
1024
1063
  case .failure(let error):
1025
1064
  try? database?.setStatus(.failed, forUpdateId: updateId)
@@ -1028,7 +1067,7 @@ final class PulseRemoteLoader {
1028
1067
 
1029
1068
  case .success:
1030
1069
  // Download assets
1031
- downloadAssets(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { assetsResult in
1070
+ downloadAssets(manifest: manifest, directory: directory, stagingDir: stagingDir, database: database, updateId: updateId) { (assetsResult: Result<Void, Error>) in
1032
1071
  switch assetsResult {
1033
1072
  case .failure(let error):
1034
1073
  try? database?.setStatus(.failed, forUpdateId: updateId)
@@ -1082,7 +1121,7 @@ final class PulseRemoteLoader {
1082
1121
 
1083
1122
  let tempPath = stagingDir.appendingPathComponent("bundle.tmp")
1084
1123
 
1085
- downloadFile(from: url, to: tempPath) { result in
1124
+ downloadFile(from: url, to: tempPath) { (result: Result<Void, Error>) in
1086
1125
  switch result {
1087
1126
  case .failure(let error):
1088
1127
  completion(.failure(error))
@@ -1123,6 +1162,12 @@ final class PulseRemoteLoader {
1123
1162
  let group = DispatchGroup()
1124
1163
  var downloadError: Error?
1125
1164
 
1165
+ let embeddedHashes = PulseController.shared.embeddedAssetHashes
1166
+ pulseLog("downloadAssets: embeddedAssetHashes count=\(embeddedHashes.count)")
1167
+ if !embeddedHashes.isEmpty {
1168
+ pulseLog("downloadAssets: first few embedded hashes: \(Array(embeddedHashes.keys.prefix(3)).map { String($0.prefix(16)) })")
1169
+ }
1170
+
1126
1171
  for asset in assets {
1127
1172
  group.enter()
1128
1173
 
@@ -1152,7 +1197,7 @@ final class PulseRemoteLoader {
1152
1197
 
1153
1198
  let tempPath = stagingDir.appendingPathComponent("asset-\(assetHash).tmp")
1154
1199
 
1155
- downloadFile(from: url, to: tempPath) { result in
1200
+ downloadFile(from: url, to: tempPath) { (result: Result<Void, Error>) in
1156
1201
  switch result {
1157
1202
  case .failure(let error):
1158
1203
  downloadError = error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "OTA updates for React Native - lightweight alternative to expo-updates",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",