@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.41
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/build.gradle +13 -2
- package/android/proguard-rules.pro +8 -2
- package/android/src/bridge/BridgeReactInstance.kt +38 -0
- package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
- package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -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 +46 -30
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
- 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/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/newarch/JavaScriptExceptionHooks.kt +48 -0
- package/android/src/newarch/ReactIntegrationManager.kt +6 -32
- 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/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
- package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
- 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 +32 -67
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -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 +96 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +121 -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 +5 -14
- 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 +60 -0
- package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
- package/lib/commonjs/httpClient.js +64 -95
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +355 -324
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/init.types.js +6 -0
- package/lib/commonjs/init.types.js.map +1 -0
- package/lib/commonjs/native.js +77 -63
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +41 -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 +145 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +46 -38
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +90 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +114 -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 +5 -14
- 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 +55 -0
- package/lib/module/fetchUpdateResponse.js.map +1 -0
- package/lib/module/httpClient.js +63 -94
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +335 -327
- package/lib/module/index.js.map +1 -1
- package/lib/module/init.types.js +4 -0
- package/lib/module/init.types.js.map +1 -0
- package/lib/module/native.js +70 -56
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +36 -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 +139 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +47 -38
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +30 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +20 -6
- 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 +5 -14
- 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 +5 -34
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +124 -116
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/init.types.d.ts +21 -0
- package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +32 -25
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +8 -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 +35 -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 +2 -3
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +13 -41
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +30 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +20 -6
- 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 +5 -14
- 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 +5 -34
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +124 -116
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/init.types.d.ts +21 -0
- package/lib/typescript/module/init.types.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +32 -25
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +8 -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 +35 -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 +2 -3
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +13 -41
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/appReady.ts +147 -0
- package/src/checkForUpdate.ts +156 -89
- package/src/clientPlugin.ts +21 -0
- package/src/error.ts +5 -15
- package/src/fetchJSON.ts +14 -30
- package/src/fetchUpdateResponse.ts +60 -0
- package/src/httpClient.ts +89 -160
- package/src/index.ts +413 -425
- package/src/init.types.ts +24 -0
- package/src/native.ts +101 -105
- package/src/pluginHost.ts +52 -0
- package/src/releaseCatalogCache.ts +43 -172
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +167 -0
- package/src/wrap.tsx +60 -43
- package/src/wrap.types.ts +18 -46
- 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 -616
- 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 -615
- 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 -612
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import React
|
|
3
|
+
import UIKit
|
|
3
4
|
|
|
4
5
|
private func hotUpdaterUncaughtExceptionHandler(_ exception: NSException) {
|
|
5
6
|
HotUpdaterRecoveryManager.shared.handleUncaughtException(exception)
|
|
@@ -17,6 +18,18 @@ private func hotUpdaterUpdateSignalLaunchStateSymbol(
|
|
|
17
18
|
@_silgen_name("HotUpdaterPerformRecoveryReload")
|
|
18
19
|
private func hotUpdaterPerformRecoveryReloadSymbol() -> ObjCBool
|
|
19
20
|
|
|
21
|
+
@_silgen_name("HotUpdaterMarkFailedRuntime")
|
|
22
|
+
private func hotUpdaterMarkFailedRuntime()
|
|
23
|
+
|
|
24
|
+
@_silgen_name("HotUpdaterIsFailedRuntime")
|
|
25
|
+
private func hotUpdaterIsFailedRuntime() -> ObjCBool
|
|
26
|
+
|
|
27
|
+
@_silgen_name("HotUpdaterInstallRecoveryHooks")
|
|
28
|
+
private func hotUpdaterInstallRecoveryHooks()
|
|
29
|
+
|
|
30
|
+
@_silgen_name("HotUpdaterAfterSurfaceStarts")
|
|
31
|
+
private func hotUpdaterAfterSurfaceStarts(_ action: @convention(block) @escaping () -> Void)
|
|
32
|
+
|
|
20
33
|
@_silgen_name("HotUpdaterCopyMinBundleId")
|
|
21
34
|
private func hotUpdaterCopyMinBundleIdSymbol() -> UnsafeMutableRawPointer
|
|
22
35
|
|
|
@@ -44,11 +57,28 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
44
57
|
private let cohortService: CohortService
|
|
45
58
|
private let recoveryManager: HotUpdaterRecoveryManager
|
|
46
59
|
private let releaseCatalogCache: ReleaseCatalogCacheService
|
|
60
|
+
private let keyValueStorage: KeyValueStorageService
|
|
47
61
|
private var currentLaunchSelection: LaunchSelection?
|
|
62
|
+
private var launchStartObservers: [NSObjectProtocol] = []
|
|
48
63
|
|
|
49
64
|
private static let DEFAULT_CHANNEL = "production"
|
|
50
65
|
private static let CHANNEL_STORAGE_KEY = "HotUpdaterChannel"
|
|
51
66
|
|
|
67
|
+
private static func parseSafeByteSize(_ value: Any?) -> Int64? {
|
|
68
|
+
guard let number = value as? NSNumber,
|
|
69
|
+
String(cString: number.objCType) != "c" else {
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
let doubleValue = number.doubleValue
|
|
73
|
+
guard doubleValue.isFinite,
|
|
74
|
+
doubleValue >= 0,
|
|
75
|
+
doubleValue <= 9_007_199_254_740_991,
|
|
76
|
+
doubleValue.rounded(.towardZero) == doubleValue else {
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
return Int64(doubleValue)
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
// MARK: - Initialization
|
|
53
83
|
|
|
54
84
|
/**
|
|
@@ -59,12 +89,9 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
59
89
|
let isolationKey = HotUpdaterImpl.getIsolationKey()
|
|
60
90
|
let preferences = VersionedPreferencesService()
|
|
61
91
|
let downloadService = URLSessionDownloadService()
|
|
62
|
-
let decompressService = DecompressService()
|
|
63
|
-
|
|
64
92
|
let bundleStorage = BundleFileStorageService(
|
|
65
93
|
fileSystem: fileSystem,
|
|
66
94
|
downloadService: downloadService,
|
|
67
|
-
decompressService: decompressService,
|
|
68
95
|
preferences: preferences,
|
|
69
96
|
isolationKey: isolationKey,
|
|
70
97
|
builtInBundleIdProvider: { HotUpdaterImpl.minBundleId() }
|
|
@@ -75,7 +102,10 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
75
102
|
bundleStorage: bundleStorage,
|
|
76
103
|
preferences: preferences,
|
|
77
104
|
recoveryManager: recoveryManager,
|
|
78
|
-
releaseCatalogCache: ReleaseCatalogCacheService()
|
|
105
|
+
releaseCatalogCache: ReleaseCatalogCacheService(),
|
|
106
|
+
keyValueStorage: KeyValueStorageService(
|
|
107
|
+
directory: NoBackupStorage.directory(in: fileSystem)
|
|
108
|
+
)
|
|
79
109
|
)
|
|
80
110
|
}
|
|
81
111
|
|
|
@@ -83,18 +113,23 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
83
113
|
* Primary initializer with dependency injection.
|
|
84
114
|
* @param bundleStorage Service for bundle storage operations
|
|
85
115
|
* @param preferences Service for preference storage
|
|
116
|
+
* @param keyValueStorage Persistent key-value store for client plugins
|
|
86
117
|
*/
|
|
87
118
|
internal init(
|
|
88
119
|
bundleStorage: BundleStorageService,
|
|
89
120
|
preferences: PreferencesService,
|
|
90
121
|
recoveryManager: HotUpdaterRecoveryManager,
|
|
91
|
-
releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService()
|
|
122
|
+
releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService(),
|
|
123
|
+
keyValueStorage: KeyValueStorageService = KeyValueStorageService(
|
|
124
|
+
directory: NoBackupStorage.directory(in: FileManagerService())
|
|
125
|
+
)
|
|
92
126
|
) {
|
|
93
127
|
self.bundleStorage = bundleStorage
|
|
94
128
|
self.preferences = preferences
|
|
95
129
|
self.cohortService = CohortService()
|
|
96
130
|
self.recoveryManager = recoveryManager
|
|
97
131
|
self.releaseCatalogCache = releaseCatalogCache
|
|
132
|
+
self.keyValueStorage = keyValueStorage
|
|
98
133
|
super.init()
|
|
99
134
|
|
|
100
135
|
// Configure preferences with isolation key
|
|
@@ -219,9 +254,11 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
219
254
|
/**
|
|
220
255
|
* Updates the bundle from JavaScript bridge.
|
|
221
256
|
* 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
|
-
*
|
|
257
|
+
* @param params Dictionary with bundleId, manifest, and asset parameters
|
|
258
|
+
* @param resolve Promise resolve callback, called with the staged bundle's
|
|
259
|
+
* `{ delivery, patchFallback }`
|
|
260
|
+
* @param reject Promise reject callback; download and install failures
|
|
261
|
+
* carry `stage`, `reason`, and their details in the error's userInfo
|
|
225
262
|
*/
|
|
226
263
|
public func updateBundle(_ params: NSDictionary?,
|
|
227
264
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -236,43 +273,49 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
236
273
|
return
|
|
237
274
|
}
|
|
238
275
|
|
|
276
|
+
// Invalid update parameters are an invalid update response.
|
|
239
277
|
guard let bundleId = data["bundleId"] as? String, !bundleId.isEmpty else {
|
|
240
|
-
let error =
|
|
241
|
-
userInfo: [NSLocalizedDescriptionKey: "Missing or empty 'bundleId'"])
|
|
278
|
+
let error = UpdateFailure.invalidResponse.error(description: "Missing or empty 'bundleId'")
|
|
242
279
|
reject("MISSING_BUNDLE_ID", error.localizedDescription, error)
|
|
243
280
|
return
|
|
244
281
|
}
|
|
245
282
|
|
|
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
|
|
283
|
+
guard let manifestFileHash = data["manifestFileHash"] as? String,
|
|
284
|
+
!manifestFileHash.isEmpty else {
|
|
285
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest)
|
|
286
|
+
.error(description: "Missing manifest file hash")
|
|
287
|
+
reject("INVALID_MANIFEST", error.localizedDescription, error)
|
|
288
|
+
return
|
|
257
289
|
}
|
|
258
|
-
|
|
259
|
-
// Extract fileHash if provided
|
|
260
|
-
let fileHash = data["fileHash"] as? String
|
|
261
|
-
let manifestFileHash = data["manifestFileHash"] as? String
|
|
262
290
|
let channel = data["channel"] as? String
|
|
263
291
|
let manifestUrlString = data["manifestUrl"] as? String ?? ""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
292
|
+
guard let manifestUrl = URL(string: manifestUrlString) else {
|
|
293
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest).error(
|
|
294
|
+
description: "Invalid 'manifestUrl' provided: \(manifestUrlString)"
|
|
295
|
+
)
|
|
296
|
+
reject("INVALID_FILE_URL", error.localizedDescription, error)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
let archiveUrl: URL?
|
|
300
|
+
if let archiveUrlString = data["archiveUrl"] as? String {
|
|
301
|
+
guard let parsedArchiveUrl = URL(string: archiveUrlString) else {
|
|
302
|
+
let error = UpdateFailure.invalidResponse.with(resource: .archive).error(
|
|
303
|
+
description: "Invalid 'archiveUrl' provided: \(archiveUrlString)"
|
|
304
|
+
)
|
|
269
305
|
reject("INVALID_FILE_URL", error.localizedDescription, error)
|
|
270
306
|
return
|
|
271
307
|
}
|
|
272
|
-
|
|
308
|
+
archiveUrl = parsedArchiveUrl
|
|
309
|
+
} else {
|
|
310
|
+
archiveUrl = nil
|
|
311
|
+
}
|
|
312
|
+
guard let assetsPayload = data["assets"] as? [String: [String: Any]] else {
|
|
313
|
+
let error = UpdateFailure.invalidResponse.with(resource: .manifest)
|
|
314
|
+
.error(description: "Missing manifest assets")
|
|
315
|
+
reject("INVALID_MANIFEST", error.localizedDescription, error)
|
|
316
|
+
return
|
|
273
317
|
}
|
|
274
|
-
let
|
|
275
|
-
let changedAssets = changedAssetsPayload?.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
|
|
318
|
+
let assets = assetsPayload.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
|
|
276
319
|
guard let fileHash = entry.value["fileHash"] as? String,
|
|
277
320
|
!fileHash.isEmpty
|
|
278
321
|
else {
|
|
@@ -296,7 +339,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
296
339
|
baseBundleId: baseBundleId,
|
|
297
340
|
baseFileHash: baseFileHash,
|
|
298
341
|
patchFileHash: patchFileHash,
|
|
299
|
-
patchUrl: patchUrl
|
|
342
|
+
patchUrl: patchUrl,
|
|
343
|
+
byteSize: Self.parseSafeByteSize(payload["byteSize"])
|
|
300
344
|
)
|
|
301
345
|
}
|
|
302
346
|
let filePayload = entry.value["file"] as? [String: Any]
|
|
@@ -315,11 +359,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
315
359
|
}
|
|
316
360
|
let selection = (data["selection"] as? [String: Any]).flatMap(Self.parseSelection)
|
|
317
361
|
if let selection, !bundleStorage.stageReleaseSelection(selection) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
code: 0,
|
|
321
|
-
userInfo: [NSLocalizedDescriptionKey: "Release catalog selection is stale"]
|
|
322
|
-
)
|
|
362
|
+
// Not an update failure, so the rejection carries no stage or reason.
|
|
363
|
+
let error = StaleReleaseSelectionError() as NSError
|
|
323
364
|
reject("UNKNOWN_ERROR", error.localizedDescription, error)
|
|
324
365
|
return
|
|
325
366
|
}
|
|
@@ -327,10 +368,10 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
327
368
|
// Extract progress callback if provided
|
|
328
369
|
let progressCallback = data["progressCallback"] as? RCTResponseSenderBlock
|
|
329
370
|
|
|
330
|
-
NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId),
|
|
371
|
+
NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), manifestUrl: \(manifestUrl.absoluteString)")
|
|
331
372
|
|
|
332
373
|
// Heavy work is delegated to bundle storage service with safe error handling
|
|
333
|
-
bundleStorage.updateBundle(bundleId: bundleId,
|
|
374
|
+
bundleStorage.updateBundle(bundleId: bundleId, manifestUrl: manifestUrl, manifestFileHash: manifestFileHash, archiveUrl: archiveUrl, assets: assets, progressHandler: { payload in
|
|
334
375
|
DispatchQueue.main.async {
|
|
335
376
|
NotificationCenter.default.post(
|
|
336
377
|
name: .updateProgressDidChange,
|
|
@@ -354,7 +395,7 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
354
395
|
// Return results on main thread for React Native bridge
|
|
355
396
|
DispatchQueue.main.async {
|
|
356
397
|
switch result {
|
|
357
|
-
case .success:
|
|
398
|
+
case .success(let delivery):
|
|
358
399
|
NSLog("[HotUpdaterImpl] Update successful for \(bundleId). Resolving promise.")
|
|
359
400
|
if let channel, !channel.isEmpty {
|
|
360
401
|
do {
|
|
@@ -367,12 +408,14 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
367
408
|
NSLog("[HotUpdaterImpl] Failed to persist channel override: \(error)")
|
|
368
409
|
}
|
|
369
410
|
}
|
|
370
|
-
|
|
411
|
+
// { delivery: "patch" | "manifest" | "archive", patchFallback }
|
|
412
|
+
resolve(delivery.dictionary)
|
|
371
413
|
case .failure(let error):
|
|
372
414
|
NSLog("[HotUpdaterImpl] Update failed for \(bundleId) - Error: \(error)")
|
|
373
415
|
|
|
374
416
|
let normalizedCode = HotUpdaterImpl.normalizeErrorCode(from: error)
|
|
375
|
-
|
|
417
|
+
// A classified failure adds stage, reason, and its details to userInfo.
|
|
418
|
+
let nsError = UpdateFailureError.rejectionError(for: error)
|
|
376
419
|
reject(normalizedCode, nsError.localizedDescription, nsError)
|
|
377
420
|
}
|
|
378
421
|
}
|
|
@@ -507,6 +550,8 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
507
550
|
* Rare or platform-specific codes are collapsed to UNKNOWN_ERROR to reduce surface area.
|
|
508
551
|
*/
|
|
509
552
|
private static func normalizeErrorCode(from error: Error) -> String {
|
|
553
|
+
// Classification wraps an error without changing its code.
|
|
554
|
+
let error = UpdateFailureError.unwrap(error)
|
|
510
555
|
let baseCode: String
|
|
511
556
|
|
|
512
557
|
if let storageError = error as? BundleStorageError {
|
|
@@ -532,7 +577,6 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
532
577
|
"DIRECTORY_CREATION_FAILED",
|
|
533
578
|
"DOWNLOAD_FAILED",
|
|
534
579
|
"INCOMPLETE_DOWNLOAD",
|
|
535
|
-
"EXTRACTION_FORMAT_ERROR",
|
|
536
580
|
"INVALID_BUNDLE",
|
|
537
581
|
"INSUFFICIENT_DISK_SPACE",
|
|
538
582
|
"SIGNATURE_VERIFICATION_FAILED",
|
|
@@ -545,6 +589,11 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
545
589
|
// MARK: - Rollback Support
|
|
546
590
|
|
|
547
591
|
public func notifyAppReady() -> [String: Any] {
|
|
592
|
+
// A failed runtime can finish queued native calls while recovery starts.
|
|
593
|
+
// Keep its readiness loop from consuming the new launch's report.
|
|
594
|
+
if hotUpdaterIsFailedRuntime().boolValue {
|
|
595
|
+
return ["status": "PENDING"]
|
|
596
|
+
}
|
|
548
597
|
return bundleStorage.notifyAppReady()
|
|
549
598
|
}
|
|
550
599
|
|
|
@@ -568,16 +617,21 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
568
617
|
return bundleStorage.getInstallId()
|
|
569
618
|
}
|
|
570
619
|
|
|
571
|
-
|
|
572
|
-
return bundleStorage.getUserId()
|
|
573
|
-
}
|
|
620
|
+
// MARK: - Client Plugin Storage
|
|
574
621
|
|
|
575
|
-
|
|
576
|
-
|
|
622
|
+
/**
|
|
623
|
+
* Reads a value from the persistent key-value store for client plugins.
|
|
624
|
+
* @return The stored value or nil when the key has none
|
|
625
|
+
*/
|
|
626
|
+
public func getStorageItem(_ key: String) -> String? {
|
|
627
|
+
return keyValueStorage.getItem(key)
|
|
577
628
|
}
|
|
578
629
|
|
|
579
|
-
|
|
580
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Writes a value to the persistent key-value store; nil removes the key.
|
|
632
|
+
*/
|
|
633
|
+
public func setStorageItem(_ key: String, value: String?) {
|
|
634
|
+
keyValueStorage.setItem(key, value: value)
|
|
581
635
|
}
|
|
582
636
|
|
|
583
637
|
/**
|
|
@@ -597,7 +651,7 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
597
651
|
|
|
598
652
|
/**
|
|
599
653
|
* Gets the current active bundle ID from bundle storage.
|
|
600
|
-
* Reads
|
|
654
|
+
* Reads the required OTA manifest.json.
|
|
601
655
|
* Built-in bundle fallback is handled in JS.
|
|
602
656
|
*/
|
|
603
657
|
public func getBundleId() -> String? {
|
|
@@ -651,12 +705,49 @@ private func hotUpdaterGetMinBundleId() -> String {
|
|
|
651
705
|
|
|
652
706
|
let pendingRecovery = recoveryManager.consumePendingCrashRecovery()
|
|
653
707
|
let selection = bundleStorage.prepareLaunch(bundle: bundle, pendingRecovery: pendingRecovery)
|
|
708
|
+
markLaunchStartedInForeground(selection)
|
|
654
709
|
recoveryManager.startMonitoring(bundleId: selection.launchedBundleId, shouldRollback: selection.shouldRollbackOnCrash) { [weak self] launchedBundleId in
|
|
655
710
|
self?.bundleStorage.markLaunchCompleted(bundleId: launchedBundleId)
|
|
656
711
|
}
|
|
657
712
|
currentLaunchSelection = selection
|
|
658
713
|
return selection
|
|
659
714
|
}
|
|
715
|
+
|
|
716
|
+
// A background launch, such as a silent push, may never render. Only a
|
|
717
|
+
// launch in the foreground may leave an unfinished launch for the next
|
|
718
|
+
// process to roll back.
|
|
719
|
+
private func markLaunchStartedInForeground(_ selection: LaunchSelection) {
|
|
720
|
+
let bundleId = selection.shouldRollbackOnCrash ? selection.launchedBundleId : nil
|
|
721
|
+
let mark = { [weak self] in
|
|
722
|
+
guard let self else { return }
|
|
723
|
+
self.removeLaunchStartObservers()
|
|
724
|
+
guard let bundleId else { return }
|
|
725
|
+
guard UIApplication.shared.applicationState == .background else {
|
|
726
|
+
self.bundleStorage.markLaunchStarted(bundleId: bundleId)
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
self.launchStartObservers = [
|
|
730
|
+
UIApplication.willEnterForegroundNotification,
|
|
731
|
+
UIApplication.didBecomeActiveNotification,
|
|
732
|
+
].map { name in
|
|
733
|
+
NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
|
|
734
|
+
self?.removeLaunchStartObservers()
|
|
735
|
+
self?.bundleStorage.markLaunchStarted(bundleId: bundleId)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// UIApplication is read on the main thread.
|
|
740
|
+
if Thread.isMainThread {
|
|
741
|
+
mark()
|
|
742
|
+
} else {
|
|
743
|
+
DispatchQueue.main.async(execute: mark)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private func removeLaunchStartObservers() {
|
|
748
|
+
launchStartObservers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
749
|
+
launchStartObservers = []
|
|
750
|
+
}
|
|
660
751
|
}
|
|
661
752
|
|
|
662
753
|
@objcMembers
|
|
@@ -672,6 +763,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
672
763
|
private var handlersInstalled = false
|
|
673
764
|
private var isMonitoring = false
|
|
674
765
|
private var recoveryRequested = false
|
|
766
|
+
private var monitoringGeneration = 0
|
|
675
767
|
private var currentBundleId: String?
|
|
676
768
|
private var shouldRollbackOnCrash = false
|
|
677
769
|
private var contentAppearedCallback: ((String?) -> Void)?
|
|
@@ -709,6 +801,10 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
709
801
|
shouldRollback: Bool,
|
|
710
802
|
onContentAppeared: @escaping (String?) -> Void
|
|
711
803
|
) {
|
|
804
|
+
objc_sync_enter(self)
|
|
805
|
+
defer { objc_sync_exit(self) }
|
|
806
|
+
|
|
807
|
+
monitoringGeneration += 1
|
|
712
808
|
currentBundleId = bundleId
|
|
713
809
|
shouldRollbackOnCrash = shouldRollback
|
|
714
810
|
recoveryRequested = false
|
|
@@ -719,6 +815,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
719
815
|
stopMonitoringWorkItem = nil
|
|
720
816
|
|
|
721
817
|
installHandlersIfNeeded()
|
|
818
|
+
hotUpdaterInstallRecoveryHooks()
|
|
722
819
|
registerObservers()
|
|
723
820
|
installSignalHandlersIfNeeded()
|
|
724
821
|
hotUpdaterUpdateSignalLaunchState(bundleId, shouldRollback: shouldRollback)
|
|
@@ -726,9 +823,8 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
726
823
|
|
|
727
824
|
func handleUncaughtException(_ exception: NSException) {
|
|
728
825
|
writeCrashMarker()
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
826
|
+
// An uncaught native exception cannot safely continue in this process.
|
|
827
|
+
// Preserve the marker so the next launch can select the safe bundle.
|
|
732
828
|
previousUncaughtExceptionHandler?(exception)
|
|
733
829
|
}
|
|
734
830
|
|
|
@@ -742,16 +838,22 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
742
838
|
previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler()
|
|
743
839
|
|
|
744
840
|
RCTSetFatalHandler { [weak self] error in
|
|
745
|
-
|
|
746
|
-
if self?.requestRecoveryReloadIfNeeded(
|
|
747
|
-
self?.
|
|
841
|
+
guard !hotUpdaterIsFailedRuntime().boolValue else { return }
|
|
842
|
+
if self?.requestRecoveryReloadIfNeeded(onFailure: {
|
|
843
|
+
self?.forwardFatalError(error)
|
|
844
|
+
}) != true {
|
|
845
|
+
self?.writeCrashMarker()
|
|
846
|
+
self?.forwardFatalError(error)
|
|
748
847
|
}
|
|
749
848
|
}
|
|
750
849
|
|
|
751
850
|
RCTSetFatalExceptionHandler { [weak self] exception in
|
|
752
|
-
|
|
753
|
-
if self?.requestRecoveryReloadIfNeeded(
|
|
754
|
-
self?.
|
|
851
|
+
guard !hotUpdaterIsFailedRuntime().boolValue else { return }
|
|
852
|
+
if self?.requestRecoveryReloadIfNeeded(onFailure: {
|
|
853
|
+
self?.forwardFatalException(exception)
|
|
854
|
+
}) != true {
|
|
855
|
+
self?.writeCrashMarker()
|
|
856
|
+
self?.forwardFatalException(exception)
|
|
755
857
|
}
|
|
756
858
|
}
|
|
757
859
|
|
|
@@ -759,6 +861,33 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
759
861
|
handlersInstalled = true
|
|
760
862
|
}
|
|
761
863
|
|
|
864
|
+
private func forwardFatalError(_ error: Error?) {
|
|
865
|
+
if let previousFatalHandler {
|
|
866
|
+
previousFatalHandler(error)
|
|
867
|
+
} else {
|
|
868
|
+
let handler = RCTGetFatalHandler()
|
|
869
|
+
RCTSetFatalHandler(nil)
|
|
870
|
+
RCTFatal(error)
|
|
871
|
+
// React Native's DEBUG default handler can return.
|
|
872
|
+
if RCTGetFatalHandler() == nil {
|
|
873
|
+
RCTSetFatalHandler(handler)
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private func forwardFatalException(_ exception: NSException?) {
|
|
879
|
+
if let previousFatalExceptionHandler {
|
|
880
|
+
previousFatalExceptionHandler(exception)
|
|
881
|
+
} else {
|
|
882
|
+
let handler = RCTGetFatalExceptionHandler()
|
|
883
|
+
RCTSetFatalExceptionHandler(nil)
|
|
884
|
+
RCTFatalException(exception)
|
|
885
|
+
if RCTGetFatalExceptionHandler() == nil {
|
|
886
|
+
RCTSetFatalExceptionHandler(handler)
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
762
891
|
private func installSignalHandlersIfNeeded() {
|
|
763
892
|
guard !signalHandlersInstalled else {
|
|
764
893
|
return
|
|
@@ -777,7 +906,7 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
777
906
|
unregisterObservers()
|
|
778
907
|
NotificationCenter.default.addObserver(
|
|
779
908
|
self,
|
|
780
|
-
selector: #selector(handleJavaScriptDidFailToLoad),
|
|
909
|
+
selector: #selector(handleJavaScriptDidFailToLoad(_:)),
|
|
781
910
|
name: NSNotification.Name.RCTJavaScriptDidFailToLoad,
|
|
782
911
|
object: nil
|
|
783
912
|
)
|
|
@@ -802,15 +931,25 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
802
931
|
)
|
|
803
932
|
}
|
|
804
933
|
|
|
805
|
-
@objc private func handleJavaScriptDidFailToLoad() {
|
|
806
|
-
|
|
934
|
+
@objc private func handleJavaScriptDidFailToLoad(_ notification: Notification) {
|
|
935
|
+
let error = notification.userInfo?["error"] as? NSError ?? NSError(
|
|
936
|
+
domain: "HotUpdaterRecovery",
|
|
937
|
+
code: 1,
|
|
938
|
+
userInfo: [NSLocalizedDescriptionKey: "JavaScript bundle failed to load"]
|
|
939
|
+
)
|
|
940
|
+
if requestRecoveryReloadIfNeeded(onFailure: { [weak self] in
|
|
941
|
+
self?.forwardFatalError(error)
|
|
942
|
+
}) {
|
|
807
943
|
return
|
|
808
944
|
}
|
|
809
945
|
unregisterObservers()
|
|
810
946
|
}
|
|
811
947
|
|
|
812
948
|
@objc private func handleContentDidAppear() {
|
|
813
|
-
|
|
949
|
+
objc_sync_enter(self)
|
|
950
|
+
defer { objc_sync_exit(self) }
|
|
951
|
+
|
|
952
|
+
guard isMonitoring, !recoveryRequested else {
|
|
814
953
|
return
|
|
815
954
|
}
|
|
816
955
|
|
|
@@ -820,14 +959,19 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
820
959
|
hotUpdaterUpdateSignalLaunchState(currentBundleId, shouldRollback: false)
|
|
821
960
|
|
|
822
961
|
stopMonitoringWorkItem?.cancel()
|
|
962
|
+
let generation = monitoringGeneration
|
|
823
963
|
let workItem = DispatchWorkItem { [weak self] in
|
|
824
|
-
self?.finishMonitoring()
|
|
964
|
+
self?.finishMonitoring(generation: generation)
|
|
825
965
|
}
|
|
826
966
|
stopMonitoringWorkItem = workItem
|
|
827
967
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: workItem)
|
|
828
968
|
}
|
|
829
969
|
|
|
830
|
-
private func finishMonitoring() {
|
|
970
|
+
private func finishMonitoring(generation: Int) {
|
|
971
|
+
objc_sync_enter(self)
|
|
972
|
+
defer { objc_sync_exit(self) }
|
|
973
|
+
guard monitoringGeneration == generation else { return }
|
|
974
|
+
|
|
831
975
|
isMonitoring = false
|
|
832
976
|
recoveryRequested = false
|
|
833
977
|
stopMonitoringWorkItem = nil
|
|
@@ -837,31 +981,40 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
837
981
|
hotUpdaterUpdateSignalLaunchState(nil, shouldRollback: false)
|
|
838
982
|
}
|
|
839
983
|
|
|
840
|
-
private func requestRecoveryReloadIfNeeded() -> Bool {
|
|
984
|
+
private func requestRecoveryReloadIfNeeded(onFailure: @escaping () -> Void) -> Bool {
|
|
985
|
+
objc_sync_enter(self)
|
|
986
|
+
defer { objc_sync_exit(self) }
|
|
987
|
+
|
|
841
988
|
guard isMonitoring, shouldRollbackOnCrash else {
|
|
842
989
|
return false
|
|
843
990
|
}
|
|
844
991
|
|
|
845
|
-
|
|
992
|
+
hotUpdaterMarkFailedRuntime()
|
|
846
993
|
if recoveryRequested {
|
|
847
|
-
objc_sync_exit(self)
|
|
848
994
|
return true
|
|
849
995
|
}
|
|
850
996
|
recoveryRequested = true
|
|
851
|
-
|
|
997
|
+
writeCrashMarker()
|
|
852
998
|
|
|
853
999
|
let bundleId = currentBundleId
|
|
854
|
-
|
|
1000
|
+
let generation = monitoringGeneration
|
|
1001
|
+
hotUpdaterAfterSurfaceStarts { [weak self] in
|
|
855
1002
|
guard let self else {
|
|
856
1003
|
return
|
|
857
1004
|
}
|
|
858
1005
|
|
|
1006
|
+
objc_sync_enter(self)
|
|
1007
|
+
let isCurrentLaunch = self.monitoringGeneration == generation
|
|
1008
|
+
objc_sync_exit(self)
|
|
1009
|
+
guard isCurrentLaunch else { return }
|
|
1010
|
+
|
|
859
1011
|
let started = hotUpdaterPerformRecoveryReload()
|
|
860
1012
|
if !started {
|
|
861
1013
|
objc_sync_enter(self)
|
|
862
1014
|
self.recoveryRequested = false
|
|
863
1015
|
objc_sync_exit(self)
|
|
864
1016
|
NSLog("[HotUpdaterRecovery] Failed to trigger recovery reload")
|
|
1017
|
+
onFailure()
|
|
865
1018
|
} else {
|
|
866
1019
|
NSLog("[HotUpdaterRecovery] Triggered recovery reload for bundleId=\(bundleId ?? "nil")")
|
|
867
1020
|
}
|
|
@@ -871,6 +1024,9 @@ final class HotUpdaterRecoveryManager: NSObject {
|
|
|
871
1024
|
}
|
|
872
1025
|
|
|
873
1026
|
private func writeCrashMarker() {
|
|
1027
|
+
objc_sync_enter(self)
|
|
1028
|
+
defer { objc_sync_exit(self) }
|
|
1029
|
+
|
|
874
1030
|
guard isMonitoring else {
|
|
875
1031
|
return
|
|
876
1032
|
}
|
|
@@ -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
|
}
|