@hot-updater/react-native 1.0.0-rc.3 → 1.0.0-rc.30
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/proguard-rules.pro +17 -2
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
- package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
- package/android/src/newarch/HotUpdater.kt +7 -9
- package/android/src/newarch/HotUpdaterModule.kt +109 -84
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +98 -74
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
- package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +124 -69
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +13 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +16 -27
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/fetchUpdateResponse.js +54 -0
- package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
- package/lib/commonjs/httpClient.js +63 -94
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +71 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +78 -60
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +44 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +36 -87
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +3 -10
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -26
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +117 -64
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +4 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +14 -26
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/fetchUpdateResponse.js +49 -0
- package/lib/module/fetchUpdateResponse.js.map +1 -0
- package/lib/module/httpClient.js +62 -93
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +58 -20
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +71 -53
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +35 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +32 -82
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +3 -10
- package/lib/module/store.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +43 -26
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/error.d.ts +4 -13
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/commonjs/httpClient.d.ts +4 -33
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +31 -22
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +1 -3
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +16 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +2 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -13
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +5 -5
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/module/httpClient.d.ts +4 -33
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +31 -22
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +4 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +1 -3
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -7
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/appReady.ts +149 -0
- package/src/checkForUpdate.ts +151 -88
- package/src/clientPlugin.ts +18 -0
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +14 -30
- package/src/fetchUpdateResponse.ts +54 -0
- package/src/httpClient.ts +88 -159
- package/src/index.ts +90 -24
- package/src/native.ts +106 -102
- package/src/pluginHost.ts +45 -0
- package/src/releaseCatalogCache.ts +43 -172
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +41 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +153 -0
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -7
- package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
- package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
- package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
- package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
- package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
- package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
- package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
- package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
- package/lib/commonjs/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -615
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -102
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -302
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -883
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -133
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -76
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -316
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -329
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -614
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -102
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -301
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -882
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -128
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -245
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -195
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -77
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -315
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -330
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -611
- package/src/httpClient.spec.ts +0 -132
- package/src/index.spec.ts +0 -337
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -945
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
- package/src/notifyAppReadyInsights.spec.ts +0 -243
- package/src/notifyAppReadyInsights.test-utils.ts +0 -31
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
- package/src/notifyAppReadyInsights.ts +0 -194
- package/src/releaseCatalogCache.spec.ts +0 -282
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -92
- package/src/store.spec.ts +0 -372
- package/src/wrap.spec.ts +0 -406
|
@@ -17,6 +17,18 @@ private func hotUpdaterUpdateSignalLaunchStateSymbol(
|
|
|
17
17
|
@_silgen_name("HotUpdaterPerformRecoveryReload")
|
|
18
18
|
private func hotUpdaterPerformRecoveryReloadSymbol() -> ObjCBool
|
|
19
19
|
|
|
20
|
+
@_silgen_name("HotUpdaterMarkFailedRuntime")
|
|
21
|
+
private func hotUpdaterMarkFailedRuntime()
|
|
22
|
+
|
|
23
|
+
@_silgen_name("HotUpdaterIsFailedRuntime")
|
|
24
|
+
private func hotUpdaterIsFailedRuntime() -> ObjCBool
|
|
25
|
+
|
|
26
|
+
@_silgen_name("HotUpdaterInstallRecoveryHooks")
|
|
27
|
+
private func hotUpdaterInstallRecoveryHooks()
|
|
28
|
+
|
|
29
|
+
@_silgen_name("HotUpdaterAfterSurfaceStarts")
|
|
30
|
+
private func hotUpdaterAfterSurfaceStarts(_ action: @convention(block) @escaping () -> Void)
|
|
31
|
+
|
|
20
32
|
@_silgen_name("HotUpdaterCopyMinBundleId")
|
|
21
33
|
private func hotUpdaterCopyMinBundleIdSymbol() -> UnsafeMutableRawPointer
|
|
22
34
|
|
|
@@ -44,11 +56,27 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
44
56
|
private let cohortService: CohortService
|
|
45
57
|
private let recoveryManager: HotUpdaterRecoveryManager
|
|
46
58
|
private let releaseCatalogCache: ReleaseCatalogCacheService
|
|
59
|
+
private let keyValueStorage: KeyValueStorageService
|
|
47
60
|
private var currentLaunchSelection: LaunchSelection?
|
|
48
61
|
|
|
49
62
|
private static let DEFAULT_CHANNEL = "production"
|
|
50
63
|
private static let CHANNEL_STORAGE_KEY = "HotUpdaterChannel"
|
|
51
64
|
|
|
65
|
+
private static func parseSafeByteSize(_ value: Any?) -> Int64? {
|
|
66
|
+
guard let number = value as? NSNumber,
|
|
67
|
+
String(cString: number.objCType) != "c" else {
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
let doubleValue = number.doubleValue
|
|
71
|
+
guard doubleValue.isFinite,
|
|
72
|
+
doubleValue >= 0,
|
|
73
|
+
doubleValue <= 9_007_199_254_740_991,
|
|
74
|
+
doubleValue.rounded(.towardZero) == doubleValue else {
|
|
75
|
+
return nil
|
|
76
|
+
}
|
|
77
|
+
return Int64(doubleValue)
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
// MARK: - Initialization
|
|
53
81
|
|
|
54
82
|
/**
|
|
@@ -59,12 +87,9 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
59
87
|
let isolationKey = HotUpdaterImpl.getIsolationKey()
|
|
60
88
|
let preferences = VersionedPreferencesService()
|
|
61
89
|
let downloadService = URLSessionDownloadService()
|
|
62
|
-
let decompressService = DecompressService()
|
|
63
|
-
|
|
64
90
|
let bundleStorage = BundleFileStorageService(
|
|
65
91
|
fileSystem: fileSystem,
|
|
66
92
|
downloadService: downloadService,
|
|
67
|
-
decompressService: decompressService,
|
|
68
93
|
preferences: preferences,
|
|
69
94
|
isolationKey: isolationKey,
|
|
70
95
|
builtInBundleIdProvider: { HotUpdaterImpl.minBundleId() }
|
|
@@ -75,7 +100,10 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
75
100
|
bundleStorage: bundleStorage,
|
|
76
101
|
preferences: preferences,
|
|
77
102
|
recoveryManager: recoveryManager,
|
|
78
|
-
releaseCatalogCache: ReleaseCatalogCacheService()
|
|
103
|
+
releaseCatalogCache: ReleaseCatalogCacheService(),
|
|
104
|
+
keyValueStorage: KeyValueStorageService(
|
|
105
|
+
directory: NoBackupStorage.directory(in: fileSystem)
|
|
106
|
+
)
|
|
79
107
|
)
|
|
80
108
|
}
|
|
81
109
|
|
|
@@ -83,18 +111,23 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
83
111
|
* Primary initializer with dependency injection.
|
|
84
112
|
* @param bundleStorage Service for bundle storage operations
|
|
85
113
|
* @param preferences Service for preference storage
|
|
114
|
+
* @param keyValueStorage Persistent key-value store for client plugins
|
|
86
115
|
*/
|
|
87
116
|
internal init(
|
|
88
117
|
bundleStorage: BundleStorageService,
|
|
89
118
|
preferences: PreferencesService,
|
|
90
119
|
recoveryManager: HotUpdaterRecoveryManager,
|
|
91
|
-
releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService()
|
|
120
|
+
releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService(),
|
|
121
|
+
keyValueStorage: KeyValueStorageService = KeyValueStorageService(
|
|
122
|
+
directory: NoBackupStorage.directory(in: FileManagerService())
|
|
123
|
+
)
|
|
92
124
|
) {
|
|
93
125
|
self.bundleStorage = bundleStorage
|
|
94
126
|
self.preferences = preferences
|
|
95
127
|
self.cohortService = CohortService()
|
|
96
128
|
self.recoveryManager = recoveryManager
|
|
97
129
|
self.releaseCatalogCache = releaseCatalogCache
|
|
130
|
+
self.keyValueStorage = keyValueStorage
|
|
98
131
|
super.init()
|
|
99
132
|
|
|
100
133
|
// Configure preferences with isolation key
|
|
@@ -219,9 +252,11 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
219
252
|
/**
|
|
220
253
|
* Updates the bundle from JavaScript bridge.
|
|
221
254
|
* This method acts as the primary error boundary for all bundle operations.
|
|
222
|
-
* @param params Dictionary with bundleId and
|
|
223
|
-
* @param resolve Promise resolve callback
|
|
224
|
-
*
|
|
255
|
+
* @param params Dictionary with bundleId, manifest, and asset parameters
|
|
256
|
+
* @param resolve Promise resolve callback, called with the staged bundle's
|
|
257
|
+
* `{ delivery, patchFallback }`
|
|
258
|
+
* @param reject Promise reject callback; download and install failures
|
|
259
|
+
* carry `stage`, `reason`, and their details in the error's userInfo
|
|
225
260
|
*/
|
|
226
261
|
public func updateBundle(_ params: NSDictionary?,
|
|
227
262
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -236,43 +271,49 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
236
271
|
return
|
|
237
272
|
}
|
|
238
273
|
|
|
274
|
+
// Invalid update parameters are an invalid update response.
|
|
239
275
|
guard let bundleId = data["bundleId"] as? String, !bundleId.isEmpty else {
|
|
240
|
-
let error =
|
|
241
|
-
userInfo: [NSLocalizedDescriptionKey: "Missing or empty 'bundleId'"])
|
|
276
|
+
let error = UpdateFailure.invalidResponse.error(description: "Missing or empty 'bundleId'")
|
|
242
277
|
reject("MISSING_BUNDLE_ID", error.localizedDescription, error)
|
|
243
278
|
return
|
|
244
279
|
}
|
|
245
280
|
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
userInfo: [NSLocalizedDescriptionKey: "Invalid 'fileUrl' provided: \(fileUrlString)"])
|
|
253
|
-
reject("INVALID_FILE_URL", error.localizedDescription, error)
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
fileUrl = url
|
|
281
|
+
guard let manifestFileHash = data["manifestFileHash"] as? String,
|
|
282
|
+
!manifestFileHash.isEmpty else {
|
|
283
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest)
|
|
284
|
+
.error(description: "Missing manifest file hash")
|
|
285
|
+
reject("INVALID_MANIFEST", error.localizedDescription, error)
|
|
286
|
+
return
|
|
257
287
|
}
|
|
258
|
-
|
|
259
|
-
// Extract fileHash if provided
|
|
260
|
-
let fileHash = data["fileHash"] as? String
|
|
261
|
-
let manifestFileHash = data["manifestFileHash"] as? String
|
|
262
288
|
let channel = data["channel"] as? String
|
|
263
289
|
let manifestUrlString = data["manifestUrl"] as? String ?? ""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
290
|
+
guard let manifestUrl = URL(string: manifestUrlString) else {
|
|
291
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest).error(
|
|
292
|
+
description: "Invalid 'manifestUrl' provided: \(manifestUrlString)"
|
|
293
|
+
)
|
|
294
|
+
reject("INVALID_FILE_URL", error.localizedDescription, error)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
let archiveUrl: URL?
|
|
298
|
+
if let archiveUrlString = data["archiveUrl"] as? String {
|
|
299
|
+
guard let parsedArchiveUrl = URL(string: archiveUrlString) else {
|
|
300
|
+
let error = UpdateFailure.invalidResponse.with(resource: .archive).error(
|
|
301
|
+
description: "Invalid 'archiveUrl' provided: \(archiveUrlString)"
|
|
302
|
+
)
|
|
269
303
|
reject("INVALID_FILE_URL", error.localizedDescription, error)
|
|
270
304
|
return
|
|
271
305
|
}
|
|
272
|
-
|
|
306
|
+
archiveUrl = parsedArchiveUrl
|
|
307
|
+
} else {
|
|
308
|
+
archiveUrl = nil
|
|
309
|
+
}
|
|
310
|
+
guard let assetsPayload = data["assets"] as? [String: [String: Any]] else {
|
|
311
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest)
|
|
312
|
+
.error(description: "Missing manifest assets")
|
|
313
|
+
reject("INVALID_MANIFEST", error.localizedDescription, error)
|
|
314
|
+
return
|
|
273
315
|
}
|
|
274
|
-
let
|
|
275
|
-
let changedAssets = changedAssetsPayload?.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
|
|
316
|
+
let assets = assetsPayload.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
|
|
276
317
|
guard let fileHash = entry.value["fileHash"] as? String,
|
|
277
318
|
!fileHash.isEmpty
|
|
278
319
|
else {
|
|
@@ -296,7 +337,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
296
337
|
baseBundleId: baseBundleId,
|
|
297
338
|
baseFileHash: baseFileHash,
|
|
298
339
|
patchFileHash: patchFileHash,
|
|
299
|
-
patchUrl: patchUrl
|
|
340
|
+
patchUrl: patchUrl,
|
|
341
|
+
byteSize: Self.parseSafeByteSize(payload["byteSize"])
|
|
300
342
|
)
|
|
301
343
|
}
|
|
302
344
|
let filePayload = entry.value["file"] as? [String: Any]
|
|
@@ -315,11 +357,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
315
357
|
}
|
|
316
358
|
let selection = (data["selection"] as? [String: Any]).flatMap(Self.parseSelection)
|
|
317
359
|
if let selection, !bundleStorage.stageReleaseSelection(selection) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
code: 0,
|
|
321
|
-
userInfo: [NSLocalizedDescriptionKey: "Release catalog selection is stale"]
|
|
322
|
-
)
|
|
360
|
+
// Not an update failure, so the rejection carries no stage or reason.
|
|
361
|
+
let error = StaleReleaseSelectionError() as NSError
|
|
323
362
|
reject("UNKNOWN_ERROR", error.localizedDescription, error)
|
|
324
363
|
return
|
|
325
364
|
}
|
|
@@ -327,10 +366,10 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
327
366
|
// Extract progress callback if provided
|
|
328
367
|
let progressCallback = data["progressCallback"] as? RCTResponseSenderBlock
|
|
329
368
|
|
|
330
|
-
NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId),
|
|
369
|
+
NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), manifestUrl: \(manifestUrl.absoluteString)")
|
|
331
370
|
|
|
332
371
|
// Heavy work is delegated to bundle storage service with safe error handling
|
|
333
|
-
bundleStorage.updateBundle(bundleId: bundleId,
|
|
372
|
+
bundleStorage.updateBundle(bundleId: bundleId, manifestUrl: manifestUrl, manifestFileHash: manifestFileHash, archiveUrl: archiveUrl, assets: assets, progressHandler: { payload in
|
|
334
373
|
DispatchQueue.main.async {
|
|
335
374
|
NotificationCenter.default.post(
|
|
336
375
|
name: .updateProgressDidChange,
|
|
@@ -354,7 +393,7 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
354
393
|
// Return results on main thread for React Native bridge
|
|
355
394
|
DispatchQueue.main.async {
|
|
356
395
|
switch result {
|
|
357
|
-
case .success:
|
|
396
|
+
case .success(let delivery):
|
|
358
397
|
NSLog("[HotUpdaterImpl] Update successful for \(bundleId). Resolving promise.")
|
|
359
398
|
if let channel, !channel.isEmpty {
|
|
360
399
|
do {
|
|
@@ -367,12 +406,14 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
367
406
|
NSLog("[HotUpdaterImpl] Failed to persist channel override: \(error)")
|
|
368
407
|
}
|
|
369
408
|
}
|
|
370
|
-
|
|
409
|
+
// { delivery: "patch" | "manifest" | "archive", patchFallback }
|
|
410
|
+
resolve(delivery.dictionary)
|
|
371
411
|
case .failure(let error):
|
|
372
412
|
NSLog("[HotUpdaterImpl] Update failed for \(bundleId) - Error: \(error)")
|
|
373
413
|
|
|
374
414
|
let normalizedCode = HotUpdaterImpl.normalizeErrorCode(from: error)
|
|
375
|
-
|
|
415
|
+
// A classified failure adds stage, reason, and its details to userInfo.
|
|
416
|
+
let nsError = UpdateFailureError.rejectionError(for: error)
|
|
376
417
|
reject(normalizedCode, nsError.localizedDescription, nsError)
|
|
377
418
|
}
|
|
378
419
|
}
|
|
@@ -507,6 +548,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
507
548
|
* Rare or platform-specific codes are collapsed to UNKNOWN_ERROR to reduce surface area.
|
|
508
549
|
*/
|
|
509
550
|
private static func normalizeErrorCode(from error: Error) -> String {
|
|
551
|
+
// Classification wraps an error without changing its code.
|
|
552
|
+
let error = UpdateFailureError.unwrap(error)
|
|
510
553
|
let baseCode: String
|
|
511
554
|
|
|
512
555
|
if let storageError = error as? BundleStorageError {
|
|
@@ -532,7 +575,6 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
532
575
|
"DIRECTORY_CREATION_FAILED",
|
|
533
576
|
"DOWNLOAD_FAILED",
|
|
534
577
|
"INCOMPLETE_DOWNLOAD",
|
|
535
|
-
"EXTRACTION_FORMAT_ERROR",
|
|
536
578
|
"INVALID_BUNDLE",
|
|
537
579
|
"INSUFFICIENT_DISK_SPACE",
|
|
538
580
|
"SIGNATURE_VERIFICATION_FAILED",
|
|
@@ -545,6 +587,11 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
545
587
|
// MARK: - Rollback Support
|
|
546
588
|
|
|
547
589
|
public func notifyAppReady() -> [String: Any] {
|
|
590
|
+
// A failed runtime can finish queued native calls while recovery starts.
|
|
591
|
+
// Keep its readiness loop from consuming the new launch's report.
|
|
592
|
+
if hotUpdaterIsFailedRuntime().boolValue {
|
|
593
|
+
return ["status": "PENDING"]
|
|
594
|
+
}
|
|
548
595
|
return bundleStorage.notifyAppReady()
|
|
549
596
|
}
|
|
550
597
|
|
|
@@ -568,16 +615,21 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
568
615
|
return bundleStorage.getInstallId()
|
|
569
616
|
}
|
|
570
617
|
|
|
571
|
-
|
|
572
|
-
return bundleStorage.getUserId()
|
|
573
|
-
}
|
|
618
|
+
// MARK: - Client Plugin Storage
|
|
574
619
|
|
|
575
|
-
|
|
576
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Reads a value from the persistent key-value store for client plugins.
|
|
622
|
+
* @return The stored value or nil when the key has none
|
|
623
|
+
*/
|
|
624
|
+
public func getStorageItem(_ key: String) -> String? {
|
|
625
|
+
return keyValueStorage.getItem(key)
|
|
577
626
|
}
|
|
578
627
|
|
|
579
|
-
|
|
580
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Writes a value to the persistent key-value store; nil removes the key.
|
|
630
|
+
*/
|
|
631
|
+
public func setStorageItem(_ key: String, value: String?) {
|
|
632
|
+
keyValueStorage.setItem(key, value: value)
|
|
581
633
|
}
|
|
582
634
|
|
|
583
635
|
/**
|
|
@@ -597,7 +649,7 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
597
649
|
|
|
598
650
|
/**
|
|
599
651
|
* Gets the current active bundle ID from bundle storage.
|
|
600
|
-
* Reads
|
|
652
|
+
* Reads the required OTA manifest.json.
|
|
601
653
|
* Built-in bundle fallback is handled in JS.
|
|
602
654
|
*/
|
|
603
655
|
public func getBundleId() -> String? {
|
|
@@ -672,6 +724,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
672
724
|
private var handlersInstalled = false
|
|
673
725
|
private var isMonitoring = false
|
|
674
726
|
private var recoveryRequested = false
|
|
727
|
+
private var monitoringGeneration = 0
|
|
675
728
|
private var currentBundleId: String?
|
|
676
729
|
private var shouldRollbackOnCrash = false
|
|
677
730
|
private var contentAppearedCallback: ((String?) -> Void)?
|
|
@@ -709,6 +762,10 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
709
762
|
shouldRollback: Bool,
|
|
710
763
|
onContentAppeared: @escaping (String?) -> Void
|
|
711
764
|
) {
|
|
765
|
+
objc_sync_enter(self)
|
|
766
|
+
defer { objc_sync_exit(self) }
|
|
767
|
+
|
|
768
|
+
monitoringGeneration += 1
|
|
712
769
|
currentBundleId = bundleId
|
|
713
770
|
shouldRollbackOnCrash = shouldRollback
|
|
714
771
|
recoveryRequested = false
|
|
@@ -719,6 +776,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
719
776
|
stopMonitoringWorkItem = nil
|
|
720
777
|
|
|
721
778
|
installHandlersIfNeeded()
|
|
779
|
+
hotUpdaterInstallRecoveryHooks()
|
|
722
780
|
registerObservers()
|
|
723
781
|
installSignalHandlersIfNeeded()
|
|
724
782
|
hotUpdaterUpdateSignalLaunchState(bundleId, shouldRollback: shouldRollback)
|
|
@@ -726,9 +784,8 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
726
784
|
|
|
727
785
|
func handleUncaughtException(_ exception: NSException) {
|
|
728
786
|
writeCrashMarker()
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
787
|
+
// An uncaught native exception cannot safely continue in this process.
|
|
788
|
+
// Preserve the marker so the next launch can select the safe bundle.
|
|
732
789
|
previousUncaughtExceptionHandler?(exception)
|
|
733
790
|
}
|
|
734
791
|
|
|
@@ -742,16 +799,22 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
742
799
|
previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler()
|
|
743
800
|
|
|
744
801
|
RCTSetFatalHandler { [weak self] error in
|
|
745
|
-
|
|
746
|
-
if self?.requestRecoveryReloadIfNeeded(
|
|
747
|
-
self?.
|
|
802
|
+
guard !hotUpdaterIsFailedRuntime().boolValue else { return }
|
|
803
|
+
if self?.requestRecoveryReloadIfNeeded(onFailure: {
|
|
804
|
+
self?.forwardFatalError(error)
|
|
805
|
+
}) != true {
|
|
806
|
+
self?.writeCrashMarker()
|
|
807
|
+
self?.forwardFatalError(error)
|
|
748
808
|
}
|
|
749
809
|
}
|
|
750
810
|
|
|
751
811
|
RCTSetFatalExceptionHandler { [weak self] exception in
|
|
752
|
-
|
|
753
|
-
if self?.requestRecoveryReloadIfNeeded(
|
|
754
|
-
self?.
|
|
812
|
+
guard !hotUpdaterIsFailedRuntime().boolValue else { return }
|
|
813
|
+
if self?.requestRecoveryReloadIfNeeded(onFailure: {
|
|
814
|
+
self?.forwardFatalException(exception)
|
|
815
|
+
}) != true {
|
|
816
|
+
self?.writeCrashMarker()
|
|
817
|
+
self?.forwardFatalException(exception)
|
|
755
818
|
}
|
|
756
819
|
}
|
|
757
820
|
|
|
@@ -759,6 +822,33 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
759
822
|
handlersInstalled = true
|
|
760
823
|
}
|
|
761
824
|
|
|
825
|
+
private func forwardFatalError(_ error: Error?) {
|
|
826
|
+
if let previousFatalHandler {
|
|
827
|
+
previousFatalHandler(error)
|
|
828
|
+
} else {
|
|
829
|
+
let handler = RCTGetFatalHandler()
|
|
830
|
+
RCTSetFatalHandler(nil)
|
|
831
|
+
RCTFatal(error)
|
|
832
|
+
// React Native's DEBUG default handler can return.
|
|
833
|
+
if RCTGetFatalHandler() == nil {
|
|
834
|
+
RCTSetFatalHandler(handler)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
private func forwardFatalException(_ exception: NSException?) {
|
|
840
|
+
if let previousFatalExceptionHandler {
|
|
841
|
+
previousFatalExceptionHandler(exception)
|
|
842
|
+
} else {
|
|
843
|
+
let handler = RCTGetFatalExceptionHandler()
|
|
844
|
+
RCTSetFatalExceptionHandler(nil)
|
|
845
|
+
RCTFatalException(exception)
|
|
846
|
+
if RCTGetFatalExceptionHandler() == nil {
|
|
847
|
+
RCTSetFatalExceptionHandler(handler)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
762
852
|
private func installSignalHandlersIfNeeded() {
|
|
763
853
|
guard !signalHandlersInstalled else {
|
|
764
854
|
return
|
|
@@ -777,7 +867,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
777
867
|
unregisterObservers()
|
|
778
868
|
NotificationCenter.default.addObserver(
|
|
779
869
|
self,
|
|
780
|
-
selector: #selector(handleJavaScriptDidFailToLoad),
|
|
870
|
+
selector: #selector(handleJavaScriptDidFailToLoad(_:)),
|
|
781
871
|
name: NSNotification.Name.RCTJavaScriptDidFailToLoad,
|
|
782
872
|
object: nil
|
|
783
873
|
)
|
|
@@ -802,15 +892,25 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
802
892
|
)
|
|
803
893
|
}
|
|
804
894
|
|
|
805
|
-
@objc private func handleJavaScriptDidFailToLoad() {
|
|
806
|
-
|
|
895
|
+
@objc private func handleJavaScriptDidFailToLoad(_ notification: Notification) {
|
|
896
|
+
let error = notification.userInfo?["error"] as? NSError ?? NSError(
|
|
897
|
+
domain: "HotUpdaterRecovery",
|
|
898
|
+
code: 1,
|
|
899
|
+
userInfo: [NSLocalizedDescriptionKey: "JavaScript bundle failed to load"]
|
|
900
|
+
)
|
|
901
|
+
if requestRecoveryReloadIfNeeded(onFailure: { [weak self] in
|
|
902
|
+
self?.forwardFatalError(error)
|
|
903
|
+
}) {
|
|
807
904
|
return
|
|
808
905
|
}
|
|
809
906
|
unregisterObservers()
|
|
810
907
|
}
|
|
811
908
|
|
|
812
909
|
@objc private func handleContentDidAppear() {
|
|
813
|
-
|
|
910
|
+
objc_sync_enter(self)
|
|
911
|
+
defer { objc_sync_exit(self) }
|
|
912
|
+
|
|
913
|
+
guard isMonitoring, !recoveryRequested else {
|
|
814
914
|
return
|
|
815
915
|
}
|
|
816
916
|
|
|
@@ -820,14 +920,19 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
820
920
|
hotUpdaterUpdateSignalLaunchState(currentBundleId, shouldRollback: false)
|
|
821
921
|
|
|
822
922
|
stopMonitoringWorkItem?.cancel()
|
|
923
|
+
let generation = monitoringGeneration
|
|
823
924
|
let workItem = DispatchWorkItem { [weak self] in
|
|
824
|
-
self?.finishMonitoring()
|
|
925
|
+
self?.finishMonitoring(generation: generation)
|
|
825
926
|
}
|
|
826
927
|
stopMonitoringWorkItem = workItem
|
|
827
928
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: workItem)
|
|
828
929
|
}
|
|
829
930
|
|
|
830
|
-
private func finishMonitoring() {
|
|
931
|
+
private func finishMonitoring(generation: Int) {
|
|
932
|
+
objc_sync_enter(self)
|
|
933
|
+
defer { objc_sync_exit(self) }
|
|
934
|
+
guard monitoringGeneration == generation else { return }
|
|
935
|
+
|
|
831
936
|
isMonitoring = false
|
|
832
937
|
recoveryRequested = false
|
|
833
938
|
stopMonitoringWorkItem = nil
|
|
@@ -837,31 +942,40 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
837
942
|
hotUpdaterUpdateSignalLaunchState(nil, shouldRollback: false)
|
|
838
943
|
}
|
|
839
944
|
|
|
840
|
-
private func requestRecoveryReloadIfNeeded() -> Bool {
|
|
945
|
+
private func requestRecoveryReloadIfNeeded(onFailure: @escaping () -> Void) -> Bool {
|
|
946
|
+
objc_sync_enter(self)
|
|
947
|
+
defer { objc_sync_exit(self) }
|
|
948
|
+
|
|
841
949
|
guard isMonitoring, shouldRollbackOnCrash else {
|
|
842
950
|
return false
|
|
843
951
|
}
|
|
844
952
|
|
|
845
|
-
|
|
953
|
+
hotUpdaterMarkFailedRuntime()
|
|
846
954
|
if recoveryRequested {
|
|
847
|
-
objc_sync_exit(self)
|
|
848
955
|
return true
|
|
849
956
|
}
|
|
850
957
|
recoveryRequested = true
|
|
851
|
-
|
|
958
|
+
writeCrashMarker()
|
|
852
959
|
|
|
853
960
|
let bundleId = currentBundleId
|
|
854
|
-
|
|
961
|
+
let generation = monitoringGeneration
|
|
962
|
+
hotUpdaterAfterSurfaceStarts { [weak self] in
|
|
855
963
|
guard let self else {
|
|
856
964
|
return
|
|
857
965
|
}
|
|
858
966
|
|
|
967
|
+
objc_sync_enter(self)
|
|
968
|
+
let isCurrentLaunch = self.monitoringGeneration == generation
|
|
969
|
+
objc_sync_exit(self)
|
|
970
|
+
guard isCurrentLaunch else { return }
|
|
971
|
+
|
|
859
972
|
let started = hotUpdaterPerformRecoveryReload()
|
|
860
973
|
if !started {
|
|
861
974
|
objc_sync_enter(self)
|
|
862
975
|
self.recoveryRequested = false
|
|
863
976
|
objc_sync_exit(self)
|
|
864
977
|
NSLog("[HotUpdaterRecovery] Failed to trigger recovery reload")
|
|
978
|
+
onFailure()
|
|
865
979
|
} else {
|
|
866
980
|
NSLog("[HotUpdaterRecovery] Triggered recovery reload for bundleId=\(bundleId ?? "nil")")
|
|
867
981
|
}
|
|
@@ -871,6 +985,9 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
871
985
|
}
|
|
872
986
|
|
|
873
987
|
private func writeCrashMarker() {
|
|
988
|
+
objc_sync_enter(self)
|
|
989
|
+
defer { objc_sync_exit(self) }
|
|
990
|
+
|
|
874
991
|
guard isMonitoring else {
|
|
875
992
|
return
|
|
876
993
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Persistent string key-value store for client plugins, kept in
|
|
4
|
+
/// `Application Support/HotUpdater/storage.json` outside device backups.
|
|
5
|
+
///
|
|
6
|
+
/// Keys are opaque; JavaScript namespaces them per plugin. The file is read
|
|
7
|
+
/// into memory on first use, and every write replaces the whole file.
|
|
8
|
+
final class KeyValueStorageService {
|
|
9
|
+
static let storageFilename = "storage.json"
|
|
10
|
+
|
|
11
|
+
private let fileURL: URL
|
|
12
|
+
private let lock = NSLock()
|
|
13
|
+
private var items: [String: String]?
|
|
14
|
+
|
|
15
|
+
init(directory: URL) {
|
|
16
|
+
self.fileURL = directory.appendingPathComponent(Self.storageFilename)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func getItem(_ key: String) -> String? {
|
|
20
|
+
lock.lock()
|
|
21
|
+
defer { lock.unlock() }
|
|
22
|
+
return loadedItems()[key]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Stores `value` under `key`, or removes the key when `value` is nil.
|
|
26
|
+
/// Returns false when the file could not be written.
|
|
27
|
+
@discardableResult
|
|
28
|
+
func setItem(_ key: String, value: String?) -> Bool {
|
|
29
|
+
lock.lock()
|
|
30
|
+
defer { lock.unlock() }
|
|
31
|
+
|
|
32
|
+
var updatedItems = loadedItems()
|
|
33
|
+
updatedItems[key] = value
|
|
34
|
+
// Memory stays the source of truth for this process, so the next
|
|
35
|
+
// successful write also persists a value whose own write failed.
|
|
36
|
+
items = updatedItems
|
|
37
|
+
|
|
38
|
+
do {
|
|
39
|
+
let data = try JSONSerialization.data(
|
|
40
|
+
withJSONObject: updatedItems,
|
|
41
|
+
options: [.sortedKeys]
|
|
42
|
+
)
|
|
43
|
+
try NoBackupStorage.write(data, to: fileURL)
|
|
44
|
+
return true
|
|
45
|
+
} catch {
|
|
46
|
+
NSLog("[KeyValueStorage] Failed to write \(fileURL.path): \(error)")
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private func loadedItems() -> [String: String] {
|
|
52
|
+
if let items {
|
|
53
|
+
return items
|
|
54
|
+
}
|
|
55
|
+
let loadedItems = Self.readItems(from: fileURL)
|
|
56
|
+
items = loadedItems
|
|
57
|
+
return loadedItems
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// A missing, unreadable, or corrupt file reads as an empty store.
|
|
61
|
+
private static func readItems(from file: URL) -> [String: String] {
|
|
62
|
+
guard let data = try? Data(contentsOf: file) else {
|
|
63
|
+
return [:]
|
|
64
|
+
}
|
|
65
|
+
guard let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
66
|
+
NSLog("[KeyValueStorage] Ignoring unreadable store at \(file.path)")
|
|
67
|
+
return [:]
|
|
68
|
+
}
|
|
69
|
+
return object.compactMapValues { $0 as? String }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
extension Notification.Name {
|
|
4
|
-
static let downloadProgressUpdate = Notification.Name("HotUpdaterDownloadProgressUpdate")
|
|
5
|
-
static let downloadDidFinish = Notification.Name("HotUpdaterDownloadDidFinish")
|
|
6
4
|
static let updateProgressDidChange = Notification.Name("HotUpdaterUpdateProgressDidChange")
|
|
7
5
|
}
|