@onekeyfe/react-native-bundle-update 3.0.63 → 3.0.65

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.
@@ -1013,16 +1013,26 @@ object BundleUpdateStoreAndroid {
1013
1013
  }
1014
1014
  }
1015
1015
 
1016
- private fun deleteDirectory(directory: File) {
1017
- if (directory.exists()) {
1018
- directory.listFiles()?.forEach { file ->
1019
- if (file.isDirectory) deleteDirectory(file) else file.delete()
1020
- }
1021
- directory.delete()
1016
+ /**
1017
+ * Recursively deletes [directory], returning true only when nothing is left
1018
+ * behind. An already-missing entry counts as success (tolerates concurrent
1019
+ * removal); a child that fails to delete makes the whole call return false
1020
+ * so callers never report a half-deleted tree as a clean delete.
1021
+ */
1022
+ private fun deleteDirectory(directory: File): Boolean {
1023
+ if (!directory.exists()) return true
1024
+ var allDeleted = true
1025
+ directory.listFiles()?.forEach { file ->
1026
+ val ok = if (file.isDirectory) deleteDirectory(file) else (!file.exists() || file.delete())
1027
+ if (!ok) allDeleted = false
1022
1028
  }
1029
+ // delete() on a non-empty dir returns false, so a child failure above
1030
+ // naturally propagates here too.
1031
+ val dirDeleted = !directory.exists() || directory.delete()
1032
+ return allDeleted && dirDeleted
1023
1033
  }
1024
1034
 
1025
- fun deleteDir(dir: File) = deleteDirectory(dir)
1035
+ fun deleteDir(dir: File): Boolean = deleteDirectory(dir)
1026
1036
 
1027
1037
  private const val MAX_UNZIPPED_SIZE = 512L * 1024 * 1024 // 512 MB limit
1028
1038
 
@@ -1757,6 +1767,148 @@ class ReactNativeBundleUpdate : HybridReactNativeBundleUpdateSpec() {
1757
1767
  }
1758
1768
  }
1759
1769
 
1770
+ /**
1771
+ * Prunes every artifact whose appVersion != the running native binary
1772
+ * version: stale onekey-bundle/<v> dirs, onekey-bundle-download/<v> stages
1773
+ * (.zip / .partial / .progress / .resume), orphan asc signatures, and
1774
+ * lingering fallback entries. Hard-refuses to delete the current
1775
+ * appVersion's artifacts and the active currentBundleVersion. Tolerates
1776
+ * already-missing files. Returns the count of deleted version directories.
1777
+ *
1778
+ * appVersion is parsed from the "{appVersion}-{bundleVersion}" stem using
1779
+ * the SAME last-dash split as listLocalBundles / the installBundle fallback
1780
+ * logic, so behavior matches the rest of the module.
1781
+ */
1782
+ override fun pruneStaleAppVersionBundles(): Promise<Double> {
1783
+ BundleUpdateStoreAndroid.invalidateValidatedBundleInfoCache()
1784
+ return Promise.async {
1785
+ val context = getContext()
1786
+ val currentAppV = BundleUpdateStoreAndroid.getAppVersion(context) ?: ""
1787
+ // Safety net: never delete the bundle backing the active pointer.
1788
+ val currentBundleVersion = BundleUpdateStoreAndroid.getCurrentBundleVersion(context)
1789
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: currentAppV=$currentAppV, currentBundleVersion=$currentBundleVersion")
1790
+
1791
+ // Without a known native version we cannot decide what is stale;
1792
+ // bail out rather than risk deleting the wrong artifacts.
1793
+ if (currentAppV.isEmpty()) {
1794
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: empty currentAppV, skipping")
1795
+ return@async 0.0
1796
+ }
1797
+
1798
+ // Parses an "{appVersion}-{bundleVersion}" stem into its appVersion
1799
+ // component using the same last-dash split as listLocalBundles.
1800
+ // Returns null for stems without a dash or with an empty appVersion.
1801
+ fun appVersionFromStem(stem: String): String? {
1802
+ val lastDash = stem.lastIndexOf('-')
1803
+ if (lastDash <= 0) return null
1804
+ val appV = stem.substring(0, lastDash)
1805
+ return if (appV.isEmpty()) null else appV
1806
+ }
1807
+
1808
+ // True when this entry stem must be kept: its appVersion matches the
1809
+ // running binary, it IS the active currentBundleVersion, or it is
1810
+ // unparseable (leave foreign names alone).
1811
+ fun shouldKeep(stem: String): Boolean {
1812
+ if (currentBundleVersion != null && stem == currentBundleVersion) return true
1813
+ val appV = appVersionFromStem(stem) ?: return true
1814
+ return appV == currentAppV
1815
+ }
1816
+
1817
+ var deletedDirCount = 0
1818
+
1819
+ // 1. onekey-bundle/* extracted dirs
1820
+ val bundleDir = File(BundleUpdateStoreAndroid.getBundleDir(context))
1821
+ if (bundleDir.exists() && bundleDir.isDirectory) {
1822
+ bundleDir.listFiles()?.forEach { child ->
1823
+ if (!child.isDirectory) return@forEach
1824
+ val name = child.name
1825
+ // Skip non-version entries (asc dir, fallback json, etc.)
1826
+ if (name == "asc" || name == "fallbackUpdateBundleData.json") return@forEach
1827
+ if (shouldKeep(name)) return@forEach
1828
+ try {
1829
+ if (BundleUpdateStoreAndroid.deleteDir(child)) {
1830
+ deletedDirCount++
1831
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted stale bundle dir $name")
1832
+ } else {
1833
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: incomplete delete of bundle dir $name (left behind)")
1834
+ }
1835
+ } catch (e: Exception) {
1836
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete bundle dir $name: ${e.message}")
1837
+ }
1838
+ }
1839
+ }
1840
+
1841
+ // 2. onekey-bundle-download/* stages (zip / partial / progress / resume)
1842
+ val downloadDir = File(BundleUpdateStoreAndroid.getDownloadBundleDir(context))
1843
+ if (downloadDir.exists() && downloadDir.isDirectory) {
1844
+ downloadDir.listFiles()?.forEach { file ->
1845
+ val name = file.name
1846
+ // Strip the trailing extension chain to recover the
1847
+ // "{appV}-{bV}" stem (e.g. "6.3.0-123.zip.partial").
1848
+ var stem = name
1849
+ for (suffix in listOf(".resume", ".progress", ".partial", ".zip")) {
1850
+ if (stem.endsWith(suffix)) {
1851
+ stem = stem.substring(0, stem.length - suffix.length)
1852
+ }
1853
+ }
1854
+ if (shouldKeep(stem)) return@forEach
1855
+ try {
1856
+ val deleted = if (file.isDirectory) {
1857
+ BundleUpdateStoreAndroid.deleteDir(file)
1858
+ } else {
1859
+ !file.exists() || file.delete()
1860
+ }
1861
+ if (deleted) {
1862
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted stale download $name")
1863
+ } else {
1864
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete download $name")
1865
+ }
1866
+ } catch (e: Exception) {
1867
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete download $name: ${e.message}")
1868
+ }
1869
+ }
1870
+ }
1871
+
1872
+ // 3. onekey-bundle/asc/*-signature.asc orphan signatures
1873
+ val ascDir = File(BundleUpdateStoreAndroid.getAscDir(context))
1874
+ if (ascDir.exists() && ascDir.isDirectory) {
1875
+ val suffix = "-signature.asc"
1876
+ ascDir.listFiles()?.forEach { file ->
1877
+ val name = file.name
1878
+ if (!name.endsWith(suffix)) return@forEach
1879
+ val stem = name.substring(0, name.length - suffix.length)
1880
+ if (shouldKeep(stem)) return@forEach
1881
+ try {
1882
+ file.delete()
1883
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted orphan asc $name")
1884
+ } catch (e: Exception) {
1885
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete asc $name: ${e.message}")
1886
+ }
1887
+ }
1888
+ }
1889
+
1890
+ // 4. Persisted fallback list: drop entries whose appVersion != currentAppV.
1891
+ // Fixes the latent leak where stale fallback entries linger after a
1892
+ // native upgrade. Reuses the existing read/write helpers.
1893
+ try {
1894
+ val fallbackData = BundleUpdateStoreAndroid.readFallbackUpdateBundleDataFile(context)
1895
+ val prunedFallback = fallbackData.filter { entry ->
1896
+ val appV = entry["appVersion"]
1897
+ appV.isNullOrEmpty() || appV == currentAppV
1898
+ }
1899
+ if (prunedFallback.size != fallbackData.size) {
1900
+ BundleUpdateStoreAndroid.writeFallbackUpdateBundleDataFile(prunedFallback, context)
1901
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: pruned fallback entries ${fallbackData.size} -> ${prunedFallback.size}")
1902
+ }
1903
+ } catch (e: Exception) {
1904
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: fallback prune error: ${e.message}")
1905
+ }
1906
+
1907
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: completed, deletedDirCount=$deletedDirCount")
1908
+ deletedDirCount.toDouble()
1909
+ }
1910
+ }
1911
+
1760
1912
  override fun resetToBuiltInBundle(): Promise<Unit> {
1761
1913
  BundleUpdateStoreAndroid.invalidateValidatedBundleInfoCache()
1762
1914
  return Promise.async {
@@ -1725,6 +1725,130 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
1725
1725
  }
1726
1726
  }
1727
1727
 
1728
+ /// Parses an "{appVersion}-{bundleVersion}" folder/file stem into its
1729
+ /// appVersion component using the SAME last-dash split as listLocalBundles
1730
+ /// and the installBundle fallback logic. Returns nil when the stem has no
1731
+ /// dash or an empty appVersion, so callers leave unrecognized entries
1732
+ /// untouched.
1733
+ private static func appVersionFromStem(_ stem: String) -> String? {
1734
+ guard let lastDash = stem.range(of: "-", options: .backwards),
1735
+ lastDash.lowerBound > stem.startIndex else { return nil }
1736
+ let appVersion = String(stem[stem.startIndex..<lastDash.lowerBound])
1737
+ return appVersion.isEmpty ? nil : appVersion
1738
+ }
1739
+
1740
+ /// Prunes every artifact whose appVersion != the running native binary
1741
+ /// version: stale onekey-bundle/* dirs, onekey-bundle-download/* stages
1742
+ /// (.zip / .partial / .progress / .resume), orphan asc signatures, and
1743
+ /// lingering fallback entries. Hard-refuses to delete the current
1744
+ /// appVersion's artifacts and the active currentBundleVersion. Tolerates
1745
+ /// already-missing files. Returns the count of deleted version directories.
1746
+ func pruneStaleAppVersionBundles() throws -> Promise<Double> {
1747
+ BundleUpdateStore.invalidateValidatedBundleInfoCache()
1748
+ return Promise.async {
1749
+ let fm = FileManager.default
1750
+ let currentAppV = BundleUpdateStore.getCurrentNativeVersion()
1751
+ // Safety net: never delete the bundle backing the active pointer.
1752
+ let currentBundleVersion = BundleUpdateStore.currentBundleVersion()
1753
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: currentAppV=\(currentAppV), currentBundleVersion=\(currentBundleVersion ?? "nil")")
1754
+
1755
+ // Without a known native version we cannot decide what is stale;
1756
+ // bail out rather than risk deleting the wrong artifacts.
1757
+ guard !currentAppV.isEmpty else {
1758
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: empty currentAppV, skipping")
1759
+ return 0
1760
+ }
1761
+
1762
+ /// True when this entry stem must be kept: its appVersion matches
1763
+ /// the running binary, it IS the active currentBundleVersion, or it
1764
+ /// is unparseable (leave foreign names alone).
1765
+ func shouldKeep(stem: String) -> Bool {
1766
+ if let active = currentBundleVersion, stem == active { return true }
1767
+ guard let appV = Self.appVersionFromStem(stem) else { return true }
1768
+ return appV == currentAppV
1769
+ }
1770
+
1771
+ var deletedDirCount = 0
1772
+
1773
+ // 1. onekey-bundle/* extracted dirs
1774
+ let bundleDir = BundleUpdateStore.bundleDir()
1775
+ if let contents = try? fm.contentsOfDirectory(atPath: bundleDir) {
1776
+ for name in contents {
1777
+ // Skip non-version entries (asc dir, fallback json, etc.)
1778
+ if name == "asc" || name == "fallbackUpdateBundleData.json" { continue }
1779
+ let fullPath = (bundleDir as NSString).appendingPathComponent(name)
1780
+ var isDir: ObjCBool = false
1781
+ guard fm.fileExists(atPath: fullPath, isDirectory: &isDir), isDir.boolValue else { continue }
1782
+ if shouldKeep(stem: name) { continue }
1783
+ do {
1784
+ try fm.removeItem(atPath: fullPath)
1785
+ deletedDirCount += 1
1786
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted stale bundle dir \(name)")
1787
+ } catch {
1788
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete bundle dir \(name): \(error)")
1789
+ }
1790
+ }
1791
+ }
1792
+
1793
+ // 2. onekey-bundle-download/* stages (zip / partial / progress / resume)
1794
+ let downloadDir = BundleUpdateStore.downloadBundleDir()
1795
+ if let contents = try? fm.contentsOfDirectory(atPath: downloadDir) {
1796
+ for name in contents {
1797
+ // Strip the trailing extension chain to recover the
1798
+ // "{appV}-{bV}" stem (e.g. "6.3.0-123.zip.partial").
1799
+ var stem = name
1800
+ for suffix in [".resume", ".progress", ".partial", ".zip"] {
1801
+ if stem.hasSuffix(suffix) {
1802
+ stem = String(stem.dropLast(suffix.count))
1803
+ }
1804
+ }
1805
+ if shouldKeep(stem: stem) { continue }
1806
+ let fullPath = (downloadDir as NSString).appendingPathComponent(name)
1807
+ do {
1808
+ try fm.removeItem(atPath: fullPath)
1809
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted stale download \(name)")
1810
+ } catch {
1811
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete download \(name): \(error)")
1812
+ }
1813
+ }
1814
+ }
1815
+
1816
+ // 3. onekey-bundle/asc/*-signature.asc orphan signatures
1817
+ let ascDir = BundleUpdateStore.ascDir()
1818
+ if let contents = try? fm.contentsOfDirectory(atPath: ascDir) {
1819
+ let suffix = "-signature.asc"
1820
+ for name in contents {
1821
+ guard name.hasSuffix(suffix) else { continue }
1822
+ let stem = String(name.dropLast(suffix.count))
1823
+ if shouldKeep(stem: stem) { continue }
1824
+ let fullPath = (ascDir as NSString).appendingPathComponent(name)
1825
+ do {
1826
+ try fm.removeItem(atPath: fullPath)
1827
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: deleted orphan asc \(name)")
1828
+ } catch {
1829
+ OneKeyLog.warn("BundleUpdate", "pruneStaleAppVersionBundles: failed to delete asc \(name): \(error)")
1830
+ }
1831
+ }
1832
+ }
1833
+
1834
+ // 4. Persisted fallback list: drop entries whose appVersion != currentAppV.
1835
+ // Fixes the latent leak where stale fallback entries linger after a
1836
+ // native upgrade. Reuses the existing read/write helpers.
1837
+ let fallbackData = BundleUpdateStore.readFallbackUpdateBundleDataFile()
1838
+ let prunedFallback = fallbackData.filter { entry in
1839
+ guard let appV = entry["appVersion"], !appV.isEmpty else { return true }
1840
+ return appV == currentAppV
1841
+ }
1842
+ if prunedFallback.count != fallbackData.count {
1843
+ BundleUpdateStore.writeFallbackUpdateBundleDataFile(prunedFallback)
1844
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: pruned fallback entries \(fallbackData.count) -> \(prunedFallback.count)")
1845
+ }
1846
+
1847
+ OneKeyLog.info("BundleUpdate", "pruneStaleAppVersionBundles: completed, deletedDirCount=\(deletedDirCount)")
1848
+ return Double(deletedDirCount)
1849
+ }
1850
+ }
1851
+
1728
1852
  func resetToBuiltInBundle() throws -> Promise<Void> {
1729
1853
  BundleUpdateStore.invalidateValidatedBundleInfoCache()
1730
1854
  return Promise.async {
@@ -81,6 +81,7 @@ export interface ReactNativeBundleUpdate extends HybridObject<{
81
81
  clearBundle(): Promise<void>;
82
82
  clearAllJSBundleData(): Promise<TestResult>;
83
83
  resetToBuiltInBundle(): Promise<void>;
84
+ pruneStaleAppVersionBundles(): Promise<number>;
84
85
  getFallbackUpdateBundleData(): Promise<FallbackBundleInfo[]>;
85
86
  setCurrentUpdateBundleData(params: BundleSwitchParams): Promise<void>;
86
87
  getWebEmbedPath(): string;
@@ -233,6 +233,22 @@ namespace margelo::nitro::reactnativebundleupdate {
233
233
  return __promise;
234
234
  }();
235
235
  }
236
+ std::shared_ptr<Promise<double>> JHybridReactNativeBundleUpdateSpec::pruneStaleAppVersionBundles() {
237
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("pruneStaleAppVersionBundles");
238
+ auto __result = method(_javaPart);
239
+ return [&]() {
240
+ auto __promise = Promise<double>::create();
241
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
242
+ auto __result = jni::static_ref_cast<jni::JDouble>(__boxedResult);
243
+ __promise->resolve(__result->value());
244
+ });
245
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
246
+ jni::JniException __jniError(__throwable);
247
+ __promise->reject(std::make_exception_ptr(__jniError));
248
+ });
249
+ return __promise;
250
+ }();
251
+ }
236
252
  std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>> JHybridReactNativeBundleUpdateSpec::getFallbackUpdateBundleData() {
237
253
  static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("getFallbackUpdateBundleData");
238
254
  auto __result = method(_javaPart);
@@ -63,6 +63,7 @@ namespace margelo::nitro::reactnativebundleupdate {
63
63
  std::shared_ptr<Promise<void>> clearBundle() override;
64
64
  std::shared_ptr<Promise<TestResult>> clearAllJSBundleData() override;
65
65
  std::shared_ptr<Promise<void>> resetToBuiltInBundle() override;
66
+ std::shared_ptr<Promise<double>> pruneStaleAppVersionBundles() override;
66
67
  std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>> getFallbackUpdateBundleData() override;
67
68
  std::shared_ptr<Promise<void>> setCurrentUpdateBundleData(const BundleSwitchParams& params) override;
68
69
  std::string getWebEmbedPath() override;
@@ -82,6 +82,10 @@ abstract class HybridReactNativeBundleUpdateSpec: HybridObject() {
82
82
  @Keep
83
83
  abstract fun resetToBuiltInBundle(): Promise<Unit>
84
84
 
85
+ @DoNotStrip
86
+ @Keep
87
+ abstract fun pruneStaleAppVersionBundles(): Promise<Double>
88
+
85
89
  @DoNotStrip
86
90
  @Keep
87
91
  abstract fun getFallbackUpdateBundleData(): Promise<Array<FallbackBundleInfo>>
@@ -46,6 +46,14 @@ namespace margelo::nitro::reactnativebundleupdate::bridge::swift {
46
46
  };
47
47
  }
48
48
 
49
+ // pragma MARK: std::function<void(double /* result */)>
50
+ Func_void_double create_Func_void_double(void* NON_NULL swiftClosureWrapper) noexcept {
51
+ auto swiftClosure = ReactNativeBundleUpdate::Func_void_double::fromUnsafe(swiftClosureWrapper);
52
+ return [swiftClosure = std::move(swiftClosure)](double result) mutable -> void {
53
+ swiftClosure.call(result);
54
+ };
55
+ }
56
+
49
57
  // pragma MARK: std::function<void(const std::vector<FallbackBundleInfo>& /* result */)>
50
58
  Func_void_std__vector_FallbackBundleInfo_ create_Func_void_std__vector_FallbackBundleInfo_(void* NON_NULL swiftClosureWrapper) noexcept {
51
59
  auto swiftClosure = ReactNativeBundleUpdate::Func_void_std__vector_FallbackBundleInfo_::fromUnsafe(swiftClosureWrapper);
@@ -174,6 +174,40 @@ namespace margelo::nitro::reactnativebundleupdate::bridge::swift {
174
174
  return Func_void_TestResult_Wrapper(std::move(value));
175
175
  }
176
176
 
177
+ // pragma MARK: std::shared_ptr<Promise<double>>
178
+ /**
179
+ * Specialized version of `std::shared_ptr<Promise<double>>`.
180
+ */
181
+ using std__shared_ptr_Promise_double__ = std::shared_ptr<Promise<double>>;
182
+ inline std::shared_ptr<Promise<double>> create_std__shared_ptr_Promise_double__() noexcept {
183
+ return Promise<double>::create();
184
+ }
185
+ inline PromiseHolder<double> wrap_std__shared_ptr_Promise_double__(std::shared_ptr<Promise<double>> promise) noexcept {
186
+ return PromiseHolder<double>(std::move(promise));
187
+ }
188
+
189
+ // pragma MARK: std::function<void(double /* result */)>
190
+ /**
191
+ * Specialized version of `std::function<void(double)>`.
192
+ */
193
+ using Func_void_double = std::function<void(double /* result */)>;
194
+ /**
195
+ * Wrapper class for a `std::function<void(double / * result * /)>`, this can be used from Swift.
196
+ */
197
+ class Func_void_double_Wrapper final {
198
+ public:
199
+ explicit Func_void_double_Wrapper(std::function<void(double /* result */)>&& func): _function(std::make_unique<std::function<void(double /* result */)>>(std::move(func))) {}
200
+ inline void call(double result) const noexcept {
201
+ _function->operator()(result);
202
+ }
203
+ private:
204
+ std::unique_ptr<std::function<void(double /* result */)>> _function;
205
+ } SWIFT_NONCOPYABLE;
206
+ Func_void_double create_Func_void_double(void* NON_NULL swiftClosureWrapper) noexcept;
207
+ inline Func_void_double_Wrapper wrap_Func_void_double(Func_void_double value) noexcept {
208
+ return Func_void_double_Wrapper(std::move(value));
209
+ }
210
+
177
211
  // pragma MARK: std::vector<FallbackBundleInfo>
178
212
  /**
179
213
  * Specialized version of `std::vector<FallbackBundleInfo>`.
@@ -438,6 +472,15 @@ namespace margelo::nitro::reactnativebundleupdate::bridge::swift {
438
472
  return Result<std::shared_ptr<Promise<TestResult>>>::withError(error);
439
473
  }
440
474
 
475
+ // pragma MARK: Result<std::shared_ptr<Promise<double>>>
476
+ using Result_std__shared_ptr_Promise_double___ = Result<std::shared_ptr<Promise<double>>>;
477
+ inline Result_std__shared_ptr_Promise_double___ create_Result_std__shared_ptr_Promise_double___(const std::shared_ptr<Promise<double>>& value) noexcept {
478
+ return Result<std::shared_ptr<Promise<double>>>::withValue(value);
479
+ }
480
+ inline Result_std__shared_ptr_Promise_double___ create_Result_std__shared_ptr_Promise_double___(const std::exception_ptr& error) noexcept {
481
+ return Result<std::shared_ptr<Promise<double>>>::withError(error);
482
+ }
483
+
441
484
  // pragma MARK: Result<std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>>>
442
485
  using Result_std__shared_ptr_Promise_std__vector_FallbackBundleInfo____ = Result<std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>>>;
443
486
  inline Result_std__shared_ptr_Promise_std__vector_FallbackBundleInfo____ create_Result_std__shared_ptr_Promise_std__vector_FallbackBundleInfo____(const std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>>& value) noexcept {
@@ -170,6 +170,14 @@ namespace margelo::nitro::reactnativebundleupdate {
170
170
  auto __value = std::move(__result.value());
171
171
  return __value;
172
172
  }
173
+ inline std::shared_ptr<Promise<double>> pruneStaleAppVersionBundles() override {
174
+ auto __result = _swiftPart.pruneStaleAppVersionBundles();
175
+ if (__result.hasError()) [[unlikely]] {
176
+ std::rethrow_exception(__result.error());
177
+ }
178
+ auto __value = std::move(__result.value());
179
+ return __value;
180
+ }
173
181
  inline std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>> getFallbackUpdateBundleData() override {
174
182
  auto __result = _swiftPart.getFallbackUpdateBundleData();
175
183
  if (__result.hasError()) [[unlikely]] {
@@ -0,0 +1,47 @@
1
+ ///
2
+ /// Func_void_double.swift
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2026 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ import Foundation
9
+ import NitroModules
10
+
11
+ /**
12
+ * Wraps a Swift `(_ value: Double) -> Void` as a class.
13
+ * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`.
14
+ */
15
+ public final class Func_void_double {
16
+ public typealias bridge = margelo.nitro.reactnativebundleupdate.bridge.swift
17
+
18
+ private let closure: (_ value: Double) -> Void
19
+
20
+ public init(_ closure: @escaping (_ value: Double) -> Void) {
21
+ self.closure = closure
22
+ }
23
+
24
+ @inline(__always)
25
+ public func call(value: Double) -> Void {
26
+ self.closure(value)
27
+ }
28
+
29
+ /**
30
+ * Casts this instance to a retained unsafe raw pointer.
31
+ * This acquires one additional strong reference on the object!
32
+ */
33
+ @inline(__always)
34
+ public func toUnsafe() -> UnsafeMutableRawPointer {
35
+ return Unmanaged.passRetained(self).toOpaque()
36
+ }
37
+
38
+ /**
39
+ * Casts an unsafe pointer to a `Func_void_double`.
40
+ * The pointer has to be a retained opaque `Unmanaged<Func_void_double>`.
41
+ * This removes one strong reference from the object!
42
+ */
43
+ @inline(__always)
44
+ public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_double {
45
+ return Unmanaged<Func_void_double>.fromOpaque(pointer).takeRetainedValue()
46
+ }
47
+ }
@@ -23,6 +23,7 @@ public protocol HybridReactNativeBundleUpdateSpec_protocol: HybridObject {
23
23
  func clearBundle() throws -> Promise<Void>
24
24
  func clearAllJSBundleData() throws -> Promise<TestResult>
25
25
  func resetToBuiltInBundle() throws -> Promise<Void>
26
+ func pruneStaleAppVersionBundles() throws -> Promise<Double>
26
27
  func getFallbackUpdateBundleData() throws -> Promise<[FallbackBundleInfo]>
27
28
  func setCurrentUpdateBundleData(params: BundleSwitchParams) throws -> Promise<Void>
28
29
  func getWebEmbedPath() throws -> String
@@ -288,6 +288,25 @@ open class HybridReactNativeBundleUpdateSpec_cxx {
288
288
  }
289
289
  }
290
290
 
291
+ @inline(__always)
292
+ public final func pruneStaleAppVersionBundles() -> bridge.Result_std__shared_ptr_Promise_double___ {
293
+ do {
294
+ let __result = try self.__implementation.pruneStaleAppVersionBundles()
295
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_double__ in
296
+ let __promise = bridge.create_std__shared_ptr_Promise_double__()
297
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_double__(__promise)
298
+ __result
299
+ .then({ __result in __promiseHolder.resolve(__result) })
300
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
301
+ return __promise
302
+ }()
303
+ return bridge.create_Result_std__shared_ptr_Promise_double___(__resultCpp)
304
+ } catch (let __error) {
305
+ let __exceptionPtr = __error.toCpp()
306
+ return bridge.create_Result_std__shared_ptr_Promise_double___(__exceptionPtr)
307
+ }
308
+ }
309
+
291
310
  @inline(__always)
292
311
  public final func getFallbackUpdateBundleData() -> bridge.Result_std__shared_ptr_Promise_std__vector_FallbackBundleInfo____ {
293
312
  do {
@@ -23,6 +23,7 @@ namespace margelo::nitro::reactnativebundleupdate {
23
23
  prototype.registerHybridMethod("clearBundle", &HybridReactNativeBundleUpdateSpec::clearBundle);
24
24
  prototype.registerHybridMethod("clearAllJSBundleData", &HybridReactNativeBundleUpdateSpec::clearAllJSBundleData);
25
25
  prototype.registerHybridMethod("resetToBuiltInBundle", &HybridReactNativeBundleUpdateSpec::resetToBuiltInBundle);
26
+ prototype.registerHybridMethod("pruneStaleAppVersionBundles", &HybridReactNativeBundleUpdateSpec::pruneStaleAppVersionBundles);
26
27
  prototype.registerHybridMethod("getFallbackUpdateBundleData", &HybridReactNativeBundleUpdateSpec::getFallbackUpdateBundleData);
27
28
  prototype.registerHybridMethod("setCurrentUpdateBundleData", &HybridReactNativeBundleUpdateSpec::setCurrentUpdateBundleData);
28
29
  prototype.registerHybridMethod("getWebEmbedPath", &HybridReactNativeBundleUpdateSpec::getWebEmbedPath);
@@ -95,6 +95,7 @@ namespace margelo::nitro::reactnativebundleupdate {
95
95
  virtual std::shared_ptr<Promise<void>> clearBundle() = 0;
96
96
  virtual std::shared_ptr<Promise<TestResult>> clearAllJSBundleData() = 0;
97
97
  virtual std::shared_ptr<Promise<void>> resetToBuiltInBundle() = 0;
98
+ virtual std::shared_ptr<Promise<double>> pruneStaleAppVersionBundles() = 0;
98
99
  virtual std::shared_ptr<Promise<std::vector<FallbackBundleInfo>>> getFallbackUpdateBundleData() = 0;
99
100
  virtual std::shared_ptr<Promise<void>> setCurrentUpdateBundleData(const BundleSwitchParams& params) = 0;
100
101
  virtual std::string getWebEmbedPath() = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-bundle-update",
3
- "version": "3.0.63",
3
+ "version": "3.0.65",
4
4
  "description": "react-native-bundle-update",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -99,6 +99,10 @@ export interface ReactNativeBundleUpdate
99
99
  clearBundle(): Promise<void>;
100
100
  clearAllJSBundleData(): Promise<TestResult>;
101
101
  resetToBuiltInBundle(): Promise<void>;
102
+ // Prune every artifact whose appVersion differs from the running native
103
+ // binary version (stale OTA dirs, download stages, orphan asc, lingering
104
+ // fallback entries). Returns the count of deleted version directories.
105
+ pruneStaleAppVersionBundles(): Promise<number>;
102
106
 
103
107
  // Bundle data
104
108
  getFallbackUpdateBundleData(): Promise<FallbackBundleInfo[]>;