@hot-updater/react-native 1.1.2 → 1.1.4
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.
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +105 -83
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +21 -2
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/package.json +4 -4
- package/src/sdkVersion.ts +1 -1
|
@@ -1183,6 +1183,14 @@ class BundleFileStorageService(
|
|
|
1183
1183
|
private fun rollbackPendingBundle(
|
|
1184
1184
|
stagingBundleId: String,
|
|
1185
1185
|
unfinishedLaunch: Boolean = false,
|
|
1186
|
+
): Boolean =
|
|
1187
|
+
synchronized(releaseStateLock) {
|
|
1188
|
+
rollbackPendingBundleLocked(stagingBundleId, unfinishedLaunch)
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private fun rollbackPendingBundleLocked(
|
|
1192
|
+
stagingBundleId: String,
|
|
1193
|
+
unfinishedLaunch: Boolean,
|
|
1186
1194
|
): Boolean {
|
|
1187
1195
|
val metadata = loadMetadataOrNull() ?: return false
|
|
1188
1196
|
if (metadata.stagingBundleId != stagingBundleId) {
|
|
@@ -1231,18 +1239,22 @@ class BundleFileStorageService(
|
|
|
1231
1239
|
}
|
|
1232
1240
|
|
|
1233
1241
|
private fun applyPendingRecoveryIfNeeded(pendingRecovery: PendingCrashRecovery?) {
|
|
1234
|
-
|
|
1235
|
-
|
|
1242
|
+
synchronized(releaseStateLock) {
|
|
1243
|
+
val metadata = loadMetadataOrNull() ?: return
|
|
1244
|
+
val stagingBundleId = metadata.stagingBundleId ?: return
|
|
1236
1245
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1246
|
+
if (pendingRecovery?.shouldRollback == true &&
|
|
1247
|
+
pendingRecovery.launchedBundleId == stagingBundleId &&
|
|
1248
|
+
isVerificationPending(metadata)
|
|
1249
|
+
) {
|
|
1250
|
+
rollbackPendingBundle(stagingBundleId)
|
|
1251
|
+
}
|
|
1242
1252
|
}
|
|
1243
1253
|
}
|
|
1244
1254
|
|
|
1245
|
-
private fun selectLaunch(): LaunchSelection {
|
|
1255
|
+
private fun selectLaunch(): LaunchSelection = synchronized(releaseStateLock) { selectLaunchLocked() }
|
|
1256
|
+
|
|
1257
|
+
private fun selectLaunchLocked(): LaunchSelection {
|
|
1246
1258
|
val metadata = loadMetadataOrNull()
|
|
1247
1259
|
if (metadata == null) {
|
|
1248
1260
|
return LaunchSelection(
|
|
@@ -1263,7 +1275,7 @@ class BundleFileStorageService(
|
|
|
1263
1275
|
}
|
|
1264
1276
|
|
|
1265
1277
|
if (metadata.verificationPending && rollbackPendingBundle(stagingBundleId)) {
|
|
1266
|
-
return
|
|
1278
|
+
return selectLaunchLocked()
|
|
1267
1279
|
}
|
|
1268
1280
|
}
|
|
1269
1281
|
|
|
@@ -1583,35 +1595,39 @@ class BundleFileStorageService(
|
|
|
1583
1595
|
|
|
1584
1596
|
override fun markLaunchCompleted(currentBundleId: String?) {
|
|
1585
1597
|
readyInterruptedLaunchRetry(currentBundleId)
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
return
|
|
1590
|
-
|
|
1598
|
+
// A Release selection can commit at the same time, from a native update
|
|
1599
|
+
// check or JS; neither may save over the other's read.
|
|
1600
|
+
synchronized(releaseStateLock) {
|
|
1601
|
+
val metadata = loadMetadataOrNull() ?: return
|
|
1602
|
+
val stagingBundleId = metadata.stagingBundleId ?: return
|
|
1603
|
+
if (!metadata.verificationPending || stagingBundleId != currentBundleId) {
|
|
1604
|
+
return
|
|
1605
|
+
}
|
|
1591
1606
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1607
|
+
val transition = metadata.pendingTransition
|
|
1608
|
+
val fromBundleId = transition?.fromBundleId ?: getKnownLaunchBundleId(metadata)
|
|
1609
|
+
saveMetadata(
|
|
1610
|
+
metadata.copy(
|
|
1611
|
+
stableBundleId = null,
|
|
1612
|
+
stableSelection = null,
|
|
1613
|
+
pendingUpdateStrategy = null,
|
|
1614
|
+
pendingTransition = null,
|
|
1615
|
+
verificationPending = false,
|
|
1616
|
+
launchInProgress = false,
|
|
1617
|
+
updatedAt = System.currentTimeMillis(),
|
|
1618
|
+
),
|
|
1619
|
+
)
|
|
1620
|
+
saveLaunchReport(
|
|
1621
|
+
LaunchReport(
|
|
1622
|
+
status = "UPDATE_APPLIED",
|
|
1623
|
+
fromReleaseId = transition?.fromReleaseId,
|
|
1624
|
+
fromBundleId = fromBundleId,
|
|
1625
|
+
toReleaseId = transition?.toReleaseId,
|
|
1626
|
+
toBundleId = stagingBundleId,
|
|
1627
|
+
updateStrategy = metadata.pendingUpdateStrategy ?: resolveUpdateStrategy(),
|
|
1628
|
+
),
|
|
1629
|
+
)
|
|
1630
|
+
}
|
|
1615
1631
|
}
|
|
1616
1632
|
|
|
1617
1633
|
// MARK: - notifyAppReady
|
|
@@ -1688,17 +1704,19 @@ class BundleFileStorageService(
|
|
|
1688
1704
|
// Cleared before the rollbacks below, so the launch reports their RECOVERED.
|
|
1689
1705
|
saveLaunchReport(null)
|
|
1690
1706
|
applyPendingRecoveryIfNeeded(pendingRecovery())
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
metadata
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1707
|
+
synchronized(releaseStateLock) {
|
|
1708
|
+
// Only a new storage instance consumes an unfinished launch. Repeated bundle
|
|
1709
|
+
// lookups or bridge reloads in this process must not reject its own launch.
|
|
1710
|
+
val metadata = loadMetadataOrNull()
|
|
1711
|
+
if (metadata?.verificationPending == true && metadata.launchInProgress) {
|
|
1712
|
+
metadata.stagingBundleId?.let { rollbackPendingBundle(it, unfinishedLaunch = true) }
|
|
1713
|
+
}
|
|
1714
|
+
// A pending bundle whose file is gone rolls back, as selectLaunch does.
|
|
1715
|
+
val settled = loadMetadataOrNull()
|
|
1716
|
+
settled?.stagingBundleId?.let { stagingBundleId ->
|
|
1717
|
+
if (settled.verificationPending && getBundleUrlForId(stagingBundleId) == null) {
|
|
1718
|
+
rollbackPendingBundle(stagingBundleId)
|
|
1719
|
+
}
|
|
1702
1720
|
}
|
|
1703
1721
|
}
|
|
1704
1722
|
}
|
|
@@ -2708,47 +2726,51 @@ class BundleFileStorageService(
|
|
|
2708
2726
|
|
|
2709
2727
|
override suspend fun resetChannel(): Boolean =
|
|
2710
2728
|
withContext(Dispatchers.IO) {
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2729
|
+
synchronized(releaseStateLock) { resetChannelLocked() }
|
|
2730
|
+
}
|
|
2714
2731
|
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
stagingBundleId = null,
|
|
2720
|
-
stableSelection = null,
|
|
2721
|
-
stagingSelection = null,
|
|
2722
|
-
pendingUpdateStrategy = null,
|
|
2723
|
-
pendingTransition = null,
|
|
2724
|
-
verificationPending = false,
|
|
2725
|
-
launchInProgress = false,
|
|
2726
|
-
// Selections made before the reset no longer stage; the
|
|
2727
|
-
// high-water marks stay, so an older catalog is still refused.
|
|
2728
|
-
currentSelectionContexts = emptyMap(),
|
|
2729
|
-
updatedAt = System.currentTimeMillis(),
|
|
2730
|
-
)
|
|
2732
|
+
private fun resetChannelLocked(): Boolean {
|
|
2733
|
+
if (!setBundleURL(null)) {
|
|
2734
|
+
return false
|
|
2735
|
+
}
|
|
2731
2736
|
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2737
|
+
val clearedMetadata =
|
|
2738
|
+
(loadMetadataOrNull() ?: BundleMetadata()).copy(
|
|
2739
|
+
isolationKey = isolationKey,
|
|
2740
|
+
stableBundleId = null,
|
|
2741
|
+
stagingBundleId = null,
|
|
2742
|
+
stableSelection = null,
|
|
2743
|
+
stagingSelection = null,
|
|
2744
|
+
pendingUpdateStrategy = null,
|
|
2745
|
+
pendingTransition = null,
|
|
2746
|
+
verificationPending = false,
|
|
2747
|
+
launchInProgress = false,
|
|
2748
|
+
// Selections made before the reset no longer stage; the
|
|
2749
|
+
// high-water marks stay, so an older catalog is still refused.
|
|
2750
|
+
currentSelectionContexts = emptyMap(),
|
|
2751
|
+
updatedAt = System.currentTimeMillis(),
|
|
2752
|
+
)
|
|
2735
2753
|
|
|
2736
|
-
|
|
2754
|
+
if (!saveMetadata(clearedMetadata)) {
|
|
2755
|
+
return false
|
|
2756
|
+
}
|
|
2737
2757
|
|
|
2738
|
-
|
|
2739
|
-
if (
|
|
2740
|
-
file.name == BundleMetadata.METADATA_FILENAME ||
|
|
2741
|
-
file.name == CrashedHistory.CRASHED_HISTORY_FILENAME ||
|
|
2742
|
-
file.name == LaunchReport.LAUNCH_REPORT_FILENAME
|
|
2743
|
-
) {
|
|
2744
|
-
return@forEach
|
|
2745
|
-
}
|
|
2758
|
+
saveLaunchReport(null)
|
|
2746
2759
|
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2760
|
+
getBundleStoreDir().listFiles()?.forEach { file ->
|
|
2761
|
+
if (
|
|
2762
|
+
file.name == BundleMetadata.METADATA_FILENAME ||
|
|
2763
|
+
file.name == CrashedHistory.CRASHED_HISTORY_FILENAME ||
|
|
2764
|
+
file.name == LaunchReport.LAUNCH_REPORT_FILENAME
|
|
2765
|
+
) {
|
|
2766
|
+
return@forEach
|
|
2750
2767
|
}
|
|
2751
2768
|
|
|
2752
|
-
|
|
2769
|
+
if (file.isDirectory) {
|
|
2770
|
+
file.deleteRecursively()
|
|
2771
|
+
}
|
|
2753
2772
|
}
|
|
2773
|
+
|
|
2774
|
+
return true
|
|
2775
|
+
}
|
|
2754
2776
|
}
|
|
@@ -421,6 +421,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
421
421
|
private let builtInBundleIdProvider: () -> String
|
|
422
422
|
private let builtInAssetResolver: BuiltInAssetResolver
|
|
423
423
|
private let updateStrategyProvider: () -> UpdateStrategy
|
|
424
|
+
private let metadataWriter: (BundleMetadata, URL) -> Bool
|
|
424
425
|
private let releaseStateLock = NSRecursiveLock()
|
|
425
426
|
|
|
426
427
|
/// App-wide: every instance's storage reads the same install identity
|
|
@@ -787,7 +788,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
787
788
|
builtInBundleIdProvider: @escaping () -> String = { "" },
|
|
788
789
|
builtInAssetResolver: BuiltInAssetResolver? = nil,
|
|
789
790
|
updateStrategyProvider: (() -> UpdateStrategy)? = nil,
|
|
790
|
-
downloadRetryDelay: TimeInterval = 1
|
|
791
|
+
downloadRetryDelay: TimeInterval = 1,
|
|
792
|
+
metadataWriter: @escaping (BundleMetadata, URL) -> Bool = { metadata, file in
|
|
793
|
+
metadata.save(to: file)
|
|
794
|
+
}) {
|
|
791
795
|
|
|
792
796
|
self.fileSystem = fileSystem
|
|
793
797
|
self.downloadRetryDelay = downloadRetryDelay
|
|
@@ -802,6 +806,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
802
806
|
defaultBundle: { instance.bundle }
|
|
803
807
|
)
|
|
804
808
|
self.updateStrategyProvider = updateStrategyProvider ?? { instance.updateStrategy() }
|
|
809
|
+
self.metadataWriter = metadataWriter
|
|
805
810
|
|
|
806
811
|
// Create queue for file operations
|
|
807
812
|
self.fileOperationQueue = DispatchQueue(label: "com.hotupdater.fileoperations",
|
|
@@ -859,7 +864,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
859
864
|
}
|
|
860
865
|
var updatedMetadata = metadata
|
|
861
866
|
updatedMetadata.isolationKey = isolationKey
|
|
862
|
-
return updatedMetadata
|
|
867
|
+
return metadataWriter(updatedMetadata, file)
|
|
863
868
|
}
|
|
864
869
|
|
|
865
870
|
private func loadLaunchReport() -> LaunchReport? {
|
|
@@ -1368,6 +1373,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1368
1373
|
/// ended before first content without a crash marker.
|
|
1369
1374
|
@discardableResult
|
|
1370
1375
|
private func rollbackPendingBundle(_ stagingId: String, unfinishedLaunch: Bool = false) -> Bool {
|
|
1376
|
+
releaseStateLock.lock()
|
|
1377
|
+
defer { releaseStateLock.unlock() }
|
|
1371
1378
|
guard var metadata = loadMetadataOrNull(), metadata.stagingBundleId == stagingId else {
|
|
1372
1379
|
return false
|
|
1373
1380
|
}
|
|
@@ -1443,6 +1450,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1443
1450
|
}
|
|
1444
1451
|
|
|
1445
1452
|
private func applyPendingRecoveryIfNeeded(_ pendingRecovery: PendingCrashRecovery?) {
|
|
1453
|
+
releaseStateLock.lock()
|
|
1454
|
+
defer { releaseStateLock.unlock() }
|
|
1446
1455
|
guard let metadata = loadMetadataOrNull(),
|
|
1447
1456
|
let stagingBundleId = metadata.stagingBundleId,
|
|
1448
1457
|
pendingRecovery?.shouldRollback == true,
|
|
@@ -2001,6 +2010,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2001
2010
|
}
|
|
2002
2011
|
|
|
2003
2012
|
private func selectLaunch(bundle: Bundle) -> LaunchSelection {
|
|
2013
|
+
releaseStateLock.lock()
|
|
2014
|
+
defer { releaseStateLock.unlock() }
|
|
2004
2015
|
guard let metadata = loadMetadataOrNull() else {
|
|
2005
2016
|
return LaunchSelection(
|
|
2006
2017
|
bundleURL: getFallbackBundleURL(bundle: bundle),
|
|
@@ -2081,6 +2092,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2081
2092
|
// Cleared before the rollbacks below, so the launch reports their RECOVERED.
|
|
2082
2093
|
saveLaunchReport(nil)
|
|
2083
2094
|
applyPendingRecoveryIfNeeded(pendingRecovery())
|
|
2095
|
+
releaseStateLock.lock()
|
|
2096
|
+
defer { releaseStateLock.unlock() }
|
|
2084
2097
|
// Only a new storage instance consumes an unfinished launch. Repeated bundle
|
|
2085
2098
|
// lookups or bridge reloads in this process must not reject its own launch.
|
|
2086
2099
|
if let metadata = loadMetadataOrNull(),
|
|
@@ -3019,6 +3032,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
3019
3032
|
func markLaunchCompleted(bundleId: String?) {
|
|
3020
3033
|
// Before the guard: content from the built-in bundle (nil) also counts.
|
|
3021
3034
|
readyInterruptedLaunchRetry(bundleId)
|
|
3035
|
+
// A Release selection can commit at the same time, from a native update
|
|
3036
|
+
// check or JavaScript; neither may save over the other's read.
|
|
3037
|
+
releaseStateLock.lock()
|
|
3038
|
+
defer { releaseStateLock.unlock() }
|
|
3022
3039
|
guard let bundleId,
|
|
3023
3040
|
var metadata = loadMetadataOrNull(),
|
|
3024
3041
|
metadata.verificationPending,
|
|
@@ -3200,6 +3217,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
3200
3217
|
}
|
|
3201
3218
|
|
|
3202
3219
|
func resetChannel() -> Result<Bool, Error> {
|
|
3220
|
+
releaseStateLock.lock()
|
|
3221
|
+
defer { releaseStateLock.unlock() }
|
|
3203
3222
|
guard case .success = setBundleURL(localPath: nil) else {
|
|
3204
3223
|
return .failure(BundleStorageError.unknown(nil))
|
|
3205
3224
|
}
|
|
@@ -4,5 +4,5 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.HOT_UPDATER_SDK_VERSION = void 0;
|
|
7
|
-
const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "1.1.
|
|
7
|
+
const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "1.1.4";
|
|
8
8
|
//# sourceMappingURL=sdkVersion.js.map
|
package/lib/module/sdkVersion.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HOT_UPDATER_SDK_VERSION = "1.1.
|
|
1
|
+
export declare const HOT_UPDATER_SDK_VERSION = "1.1.4";
|
|
2
2
|
//# sourceMappingURL=sdkVersion.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HOT_UPDATER_SDK_VERSION = "1.1.
|
|
1
|
+
export declare const HOT_UPDATER_SDK_VERSION = "1.1.4";
|
|
2
2
|
//# sourceMappingURL=sdkVersion.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/commonjs/index.js",
|
|
@@ -124,9 +124,9 @@
|
|
|
124
124
|
},
|
|
125
125
|
"dependencies": {
|
|
126
126
|
"use-sync-external-store": "1.5.0",
|
|
127
|
-
"@hot-updater/
|
|
128
|
-
"@hot-updater/plugin-remote-config": "1.1.
|
|
129
|
-
"@hot-updater/
|
|
127
|
+
"@hot-updater/plugin-insights": "1.1.4",
|
|
128
|
+
"@hot-updater/plugin-remote-config": "1.1.4",
|
|
129
|
+
"@hot-updater/protocol": "1.1.4"
|
|
130
130
|
},
|
|
131
131
|
"scripts": {
|
|
132
132
|
"build": "node ./scripts/sync-sdk-version.mjs run -- bob build",
|
package/src/sdkVersion.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HOT_UPDATER_SDK_VERSION = "1.1.
|
|
1
|
+
export const HOT_UPDATER_SDK_VERSION = "1.1.4";
|