@hot-updater/react-native 1.0.8 → 1.1.0
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/HotUpdater.podspec +14 -2
- package/Package.swift +24 -3
- package/android/build.gradle +2 -6
- package/android/hot-updater-fingerprint.gradle +135 -0
- package/android/src/main/cpp/HotUpdaterRecovery.cpp +291 -21
- package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +176 -15
- package/android/src/main/java/com/hotupdater/ArtifactFileResolver.kt +20 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +145 -31
- package/android/src/main/java/com/hotupdater/CohortService.kt +10 -2
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +3 -1
- package/android/src/main/java/com/hotupdater/HotUpdaterConfig.kt +249 -21
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +8 -4
- package/android/src/main/java/com/hotupdater/HotUpdaterHeldContent.kt +92 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +319 -113
- package/android/src/main/java/com/hotupdater/HotUpdaterInstance.kt +87 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterInstanceEnvironment.kt +323 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterMainThread.kt +46 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterProcess.kt +30 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryAttribution.kt +492 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +1541 -159
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoverySlots.kt +137 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +4 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRuntimeInstance.kt +53 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterUpdateCheck.kt +149 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +12 -2
- package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +5 -6
- package/android/src/main/java/com/hotupdater/NativeUpdateCheck.kt +172 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateChecker.kt +1007 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateEventQueue.kt +111 -0
- package/android/src/main/java/com/hotupdater/OkHttpUpdateCheckTransport.kt +88 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalog.kt +491 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalogClient.kt +405 -0
- package/android/src/main/java/com/hotupdater/ReleaseSelection.kt +474 -0
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +32 -10
- package/android/src/main/java/com/hotupdater/StrictJson.kt +392 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +24 -9
- package/android/src/main/java/com/hotupdater/WeakIdentitySet.kt +76 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BitReader.java +314 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliError.java +47 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliInputStream.java +174 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliRuntimeException.java +21 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Context.java +58 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decode.java +1574 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decoder.java +72 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Dictionary.java +103 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/DictionaryData.java +82 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Huffman.java +143 -0
- package/{LICENSE → android/src/main/java/com/hotupdater/vendor/brotli/dec/LICENSE} +6 -20
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/README.md +39 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/State.java +100 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Transform.java +240 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Utils.java +163 -0
- package/android/src/main/resources/META-INF/LICENSE-brotli +19 -0
- package/android/src/newarch/HotUpdater.kt +53 -15
- package/android/src/newarch/HotUpdaterModule.kt +42 -17
- package/android/src/newarch/JavaScriptExceptionHooks.kt +45 -8
- package/android/src/oldarch/HotUpdater.kt +51 -12
- package/android/src/oldarch/HotUpdaterModule.kt +43 -17
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
- package/android/src/oldarch/JavaScriptExceptionHooks.kt +25 -16
- package/ios/HotUpdater/Interface/HotUpdaterInterface.h +13 -0
- package/ios/HotUpdater/Internal/ArtifactFileResolver.swift +38 -7
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +10 -4
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +186 -48
- package/ios/HotUpdater/Internal/CohortService.swift +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +821 -71
- package/ios/HotUpdater/Internal/HotUpdaterAsync.swift +7 -0
- package/ios/HotUpdater/Internal/HotUpdaterConfig.swift +208 -25
- package/ios/HotUpdater/Internal/HotUpdaterFingerprintHash.m +10 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +940 -261
- package/ios/HotUpdater/Internal/HotUpdaterInstanceEnvironment.swift +492 -0
- package/ios/HotUpdater/Internal/HotUpdaterInstanceRouter.swift +83 -0
- package/ios/HotUpdater/Internal/HotUpdaterInterface.swift +28 -1
- package/ios/HotUpdater/Internal/HotUpdaterLaunchRecovery.swift +1553 -0
- package/ios/HotUpdater/Internal/HotUpdaterNativeUpdates.swift +353 -0
- package/ios/HotUpdater/Internal/LaunchPreparationLock.swift +54 -0
- package/ios/HotUpdater/Internal/NativeUpdateCheck.swift +2051 -0
- package/ios/HotUpdater/Internal/ReleaseCatalog.swift +634 -0
- package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +1 -1
- package/ios/HotUpdater/Internal/ReleaseSelection.swift +482 -0
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +27 -10
- package/ios/HotUpdater/Internal/StrictJson.swift +363 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +4 -2
- package/ios/HotUpdater/Internal/UpdateFailure.swift +2 -0
- package/ios/HotUpdater/Package.swift +11 -0
- package/ios/HotUpdater/Public/HotUpdater/HotUpdater.h +4 -0
- package/ios/HotUpdater/Public/HotUpdater.h +21 -2
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +66 -0
- package/ios/Plugins/HotUpdaterFingerprintPlugin/HotUpdaterFingerprintPlugin.swift +45 -0
- package/ios/scripts/generate-fingerprint-hash.zsh +85 -0
- package/lib/commonjs/checkForUpdate.js +3 -1
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +9 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +24 -79
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/nativeUpdateEvents.js +66 -0
- package/lib/commonjs/nativeUpdateEvents.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/checkForUpdate.js +3 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +10 -13
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +19 -73
- package/lib/module/native.js.map +1 -1
- package/lib/module/nativeUpdateEvents.js +61 -0
- package/lib/module/nativeUpdateEvents.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -14
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +9 -46
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -14
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +9 -46
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/module/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/checkForUpdate.ts +7 -1
- package/src/index.ts +9 -17
- package/src/native.ts +19 -91
- package/src/nativeUpdateEvents.ts +77 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +20 -3
- package/android/libs/README.md +0 -50
- package/android/libs/hot-updater-brotli-dec-1.2.0.jar +0 -0
- package/android/src/bridge/BridgeReactInstance.kt +0 -38
- package/android/src/bridgeless/BridgeReactInstance.kt +0 -24
- package/android/src/main/java/com/hotupdater/ContentAppearedLog.kt +0 -31
- package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -34
- package/android/src/newarch/ReactIntegrationManager.kt +0 -212
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
3
4
|
import android.app.ActivityOptions
|
|
4
5
|
import android.app.Application
|
|
5
6
|
import android.content.Context
|
|
@@ -7,23 +8,56 @@ import android.content.Intent
|
|
|
7
8
|
import android.os.Process
|
|
8
9
|
import android.util.Log
|
|
9
10
|
import kotlinx.coroutines.Dispatchers
|
|
11
|
+
import kotlinx.coroutines.runBlocking
|
|
10
12
|
import kotlinx.coroutines.withContext
|
|
11
|
-
import java.
|
|
13
|
+
import java.util.concurrent.atomic.AtomicLong
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Core implementation class for HotUpdater functionality
|
|
15
17
|
*/
|
|
16
18
|
class HotUpdaterImpl {
|
|
17
19
|
private val context: Context
|
|
20
|
+
private val environment: HotUpdaterInstanceEnvironment
|
|
18
21
|
private val bundleStorage: BundleStorageService
|
|
19
22
|
private val preferences: PreferencesService
|
|
23
|
+
|
|
24
|
+
// Each instance watches its own launch: its crash marker, and the launch
|
|
25
|
+
// gate that waits for one of its own views.
|
|
20
26
|
private val recoveryManager: HotUpdaterRecoveryManager
|
|
27
|
+
private val activityStartGate: ActivityStartGate
|
|
21
28
|
private val releaseCatalogCache: ReleaseCatalogCacheService
|
|
22
29
|
private val installIdentity: InstallIdentityService
|
|
23
30
|
private val keyValueStorage: KeyValueStorageService
|
|
24
|
-
|
|
31
|
+
|
|
32
|
+
// Read by native update checks on other threads while React Native sets it.
|
|
33
|
+
@Volatile
|
|
25
34
|
private var currentLaunchSelection: LaunchSelection? = null
|
|
26
35
|
|
|
36
|
+
// The selection React Native runs in this process. A channel reset clears
|
|
37
|
+
// currentLaunchSelection, so React Native's next read picks the default
|
|
38
|
+
// channel's bundle, but the running bundle stays until a reload.
|
|
39
|
+
@Volatile
|
|
40
|
+
private var runningLaunchSelection: LaunchSelection? = null
|
|
41
|
+
|
|
42
|
+
// Counts the crash markers written for this instance's launches. A
|
|
43
|
+
// prepared launch keeps the count it started from: one written since,
|
|
44
|
+
// even while it was being prepared, makes the next start prepare again,
|
|
45
|
+
// which reads the marker. Crash recovery bumps it under its own lock, so
|
|
46
|
+
// it never waits for the launch lock.
|
|
47
|
+
private val launchInvalidations = AtomicLong()
|
|
48
|
+
|
|
49
|
+
@Volatile
|
|
50
|
+
private var currentLaunchInvalidations = 0L
|
|
51
|
+
|
|
52
|
+
// The selection a named instance's monitored launch runs, with that
|
|
53
|
+
// launch, under the launch lock.
|
|
54
|
+
private var monitoredLaunch: Pair<LaunchSelection, Long>? = null
|
|
55
|
+
|
|
56
|
+
// Serializes the launch's bundle selection with the update check's read
|
|
57
|
+
// of it, so a check never reads metadata the launch is still selecting from,
|
|
58
|
+
// and with a channel reset or built-in commit, which delete downloaded bundles.
|
|
59
|
+
private val launchPreparationLock = Any()
|
|
60
|
+
|
|
27
61
|
/**
|
|
28
62
|
* Primary constructor with dependency injection (for testing)
|
|
29
63
|
*/
|
|
@@ -31,27 +65,50 @@ class HotUpdaterImpl {
|
|
|
31
65
|
context: Context,
|
|
32
66
|
bundleStorage: BundleStorageService,
|
|
33
67
|
preferences: PreferencesService,
|
|
68
|
+
) : this(context, bundleStorage, preferences, HotUpdaterInstanceEnvironment.main)
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param activityStartGate the gate that arms this instance's launch;
|
|
72
|
+
* by default one that waits for a started activity showing one of its views
|
|
73
|
+
*/
|
|
74
|
+
internal constructor(
|
|
75
|
+
context: Context,
|
|
76
|
+
bundleStorage: BundleStorageService,
|
|
77
|
+
preferences: PreferencesService,
|
|
78
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
79
|
+
activityStartGate: ActivityStartGate? = null,
|
|
34
80
|
) {
|
|
35
81
|
this.context = context.applicationContext
|
|
82
|
+
// An activity started before this process followed activities is
|
|
83
|
+
// known only from the context the app hands over.
|
|
84
|
+
(context as? Activity)?.let(HotUpdaterRecoveryAttribution.shared::noteActivity)
|
|
85
|
+
this.environment = environment
|
|
36
86
|
this.bundleStorage = bundleStorage
|
|
37
87
|
this.preferences = preferences
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
88
|
+
this.recoveryManager = HotUpdaterRecoveryManager(this.context, environment)
|
|
89
|
+
this.activityStartGate = activityStartGate ?: createActivityStartGate(this.context, environment)
|
|
40
90
|
this.releaseCatalogCache =
|
|
41
|
-
ReleaseCatalogCacheService(
|
|
42
|
-
File(this.context.noBackupFilesDir, "hot-updater-release-catalog-cache"),
|
|
43
|
-
)
|
|
91
|
+
ReleaseCatalogCacheService(environment.releaseCatalogCacheDirectory(this.context))
|
|
44
92
|
this.installIdentity = InstallIdentityService.create(this.context)
|
|
45
|
-
this.keyValueStorage = KeyValueStorageService.
|
|
93
|
+
this.keyValueStorage = KeyValueStorageService(environment.keyValueStorageFile(this.context))
|
|
46
94
|
}
|
|
47
95
|
|
|
48
96
|
/**
|
|
49
97
|
* Convenience constructor for simple usage
|
|
50
98
|
*/
|
|
51
|
-
constructor(context: Context) : this(
|
|
99
|
+
constructor(context: Context) : this(context, HotUpdaterInstanceEnvironment.main)
|
|
100
|
+
|
|
101
|
+
/** The impl of a named instance, or of main, with its own storage and configuration. */
|
|
102
|
+
internal constructor(
|
|
103
|
+
context: Context,
|
|
104
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
105
|
+
activityStartGate: ActivityStartGate? = null,
|
|
106
|
+
) : this(
|
|
52
107
|
context = context,
|
|
53
|
-
bundleStorage = createBundleStorage(context),
|
|
54
|
-
preferences = createPreferences(context),
|
|
108
|
+
bundleStorage = createBundleStorage(context, environment),
|
|
109
|
+
preferences = createPreferences(context, environment),
|
|
110
|
+
environment = environment,
|
|
111
|
+
activityStartGate = activityStartGate,
|
|
55
112
|
)
|
|
56
113
|
|
|
57
114
|
/**
|
|
@@ -79,18 +136,25 @@ class HotUpdaterImpl {
|
|
|
79
136
|
|
|
80
137
|
companion object {
|
|
81
138
|
private const val TAG = "HotUpdaterImpl"
|
|
82
|
-
|
|
139
|
+
internal const val DEFAULT_CHANNEL = "production"
|
|
83
140
|
private const val CHANNEL_STORAGE_KEY = "HotUpdaterChannel"
|
|
84
141
|
|
|
142
|
+
// How many times one start prepares its launch while crashes of the
|
|
143
|
+
// running launch keep writing markers.
|
|
144
|
+
private const val MAX_LAUNCH_PREPARATIONS = 3
|
|
145
|
+
|
|
85
146
|
/**
|
|
86
147
|
* Create BundleStorageService with all dependencies
|
|
87
148
|
*/
|
|
88
|
-
private fun createBundleStorage(
|
|
149
|
+
private fun createBundleStorage(
|
|
150
|
+
context: Context,
|
|
151
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
152
|
+
): BundleStorageService {
|
|
89
153
|
val appContext = context.applicationContext
|
|
90
|
-
val fileSystem = FileManagerService(appContext)
|
|
91
|
-
val preferences = createPreferences(appContext)
|
|
154
|
+
val fileSystem = FileManagerService(appContext, environment.filesRoot(appContext))
|
|
155
|
+
val preferences = createPreferences(appContext, environment)
|
|
92
156
|
val downloadService = OkHttpDownloadService()
|
|
93
|
-
val isolationKey =
|
|
157
|
+
val isolationKey = environment.isolationKey(appContext)
|
|
94
158
|
|
|
95
159
|
return BundleFileStorageService(
|
|
96
160
|
appContext,
|
|
@@ -98,43 +162,54 @@ class HotUpdaterImpl {
|
|
|
98
162
|
downloadService,
|
|
99
163
|
preferences,
|
|
100
164
|
isolationKey,
|
|
165
|
+
defaultChannelProvider = { environment.defaultChannel(appContext) },
|
|
166
|
+
fingerprintHashProvider = { environment.fingerprintHash(appContext) },
|
|
167
|
+
publicKeyProvider = { environment.publicKey(appContext) },
|
|
168
|
+
fallbackBundleURL = environment.fallbackBundleURL,
|
|
101
169
|
)
|
|
102
170
|
}
|
|
103
171
|
|
|
104
172
|
/**
|
|
105
|
-
*
|
|
173
|
+
* A launch counts once a started activity shows one of the instance's
|
|
174
|
+
* views. Main takes every view no named instance claims, so an app
|
|
175
|
+
* with only main waits for any React Native view, as it always has,
|
|
176
|
+
* and counts an activity started before the gate at once. A named
|
|
177
|
+
* instance is often prepared while the app already shows other
|
|
178
|
+
* screens, so it checks the activities already started for its view.
|
|
106
179
|
*/
|
|
107
|
-
private fun
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
180
|
+
private fun createActivityStartGate(
|
|
181
|
+
context: Context,
|
|
182
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
183
|
+
): ActivityStartGate {
|
|
184
|
+
val attribution = HotUpdaterRecoveryAttribution.shared
|
|
185
|
+
return ActivityStartGate(
|
|
186
|
+
context as? Application,
|
|
187
|
+
showsReactView = { activity -> attribution.showsViewOf(activity, environment.name) },
|
|
188
|
+
activitiesStartedBefore = activitiesStartedBefore(environment, attribution),
|
|
189
|
+
beforeCheckingViews = HotUpdaterRecoveryManager::resolveHeldContent,
|
|
190
|
+
)
|
|
111
191
|
}
|
|
112
192
|
|
|
113
193
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
194
|
+
* The activities already started that a gate created now checks for
|
|
195
|
+
* the instance's view: a named instance's, while main counts any
|
|
196
|
+
* started activity at once, as it always has (null).
|
|
117
197
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
198
|
+
internal fun activitiesStartedBefore(
|
|
199
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
200
|
+
attribution: HotUpdaterRecoveryAttribution = HotUpdaterRecoveryAttribution.shared,
|
|
201
|
+
): (() -> Collection<Activity>)? = if (environment.isMain) null else attribution::startedActivities
|
|
122
202
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
val
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
} else {
|
|
134
|
-
appVersion
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return "HotUpdaterPrefs_${baseKey}_$appChannel"
|
|
203
|
+
/**
|
|
204
|
+
* Create PreferencesService with isolation key
|
|
205
|
+
*/
|
|
206
|
+
private fun createPreferences(
|
|
207
|
+
context: Context,
|
|
208
|
+
environment: HotUpdaterInstanceEnvironment,
|
|
209
|
+
): PreferencesService {
|
|
210
|
+
val appContext = context.applicationContext
|
|
211
|
+
val isolationKey = environment.isolationKey(appContext)
|
|
212
|
+
return VersionedPreferencesService(appContext, isolationKey, environment.preferencesPrefix)
|
|
138
213
|
}
|
|
139
214
|
|
|
140
215
|
fun getAppVersion(context: Context): String? =
|
|
@@ -177,22 +252,25 @@ class HotUpdaterImpl {
|
|
|
177
252
|
* @param context Application context
|
|
178
253
|
* @return The fingerprint hash or null if not set
|
|
179
254
|
*/
|
|
180
|
-
fun getFingerprintHash(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
255
|
+
fun getFingerprintHash(
|
|
256
|
+
@Suppress("UNUSED_PARAMETER") context: Context,
|
|
257
|
+
): String? = resolveFingerprintHash()
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The programmatic override (brownfield/AAR setups, see HotUpdaterConfig),
|
|
261
|
+
* else the fingerprint the build embedded, written by the
|
|
262
|
+
* generateHotUpdaterFingerprintHash build task.
|
|
263
|
+
*/
|
|
264
|
+
private fun resolveFingerprintHash(): String? =
|
|
265
|
+
HotUpdaterConfig.fingerprintHash?.takeIf { it.isNotEmpty() }
|
|
266
|
+
?: HotUpdaterConfig.buildFingerprintHash
|
|
185
267
|
}
|
|
186
268
|
|
|
187
269
|
/**
|
|
188
270
|
* Gets the current fingerprint hash
|
|
189
271
|
* @return The fingerprint hash or null if not set
|
|
190
272
|
*/
|
|
191
|
-
fun getFingerprintHash(): String? =
|
|
192
|
-
NativeConfigUtils.getString(
|
|
193
|
-
context,
|
|
194
|
-
NativeConfigUtils.FINGERPRINT_HASH_META_DATA_KEY,
|
|
195
|
-
)
|
|
273
|
+
fun getFingerprintHash(): String? = environment.fingerprintHash(context)
|
|
196
274
|
|
|
197
275
|
/**
|
|
198
276
|
* Gets the current update channel
|
|
@@ -209,19 +287,24 @@ class HotUpdaterImpl {
|
|
|
209
287
|
return overriddenChannel
|
|
210
288
|
}
|
|
211
289
|
|
|
212
|
-
return
|
|
213
|
-
context,
|
|
214
|
-
NativeConfigUtils.CHANNEL_META_DATA_KEY,
|
|
215
|
-
) ?: DEFAULT_CHANNEL
|
|
290
|
+
return environment.defaultChannel(context)
|
|
216
291
|
}
|
|
217
292
|
|
|
218
|
-
fun getDefaultChannel(): String =
|
|
293
|
+
fun getDefaultChannel(): String = environment.defaultChannel(context)
|
|
219
294
|
|
|
220
295
|
/**
|
|
221
296
|
* Gets the path to the bundle file
|
|
222
297
|
* @return The path to the bundle file
|
|
223
298
|
*/
|
|
224
|
-
fun getJSBundleFile(): String
|
|
299
|
+
fun getJSBundleFile(): String {
|
|
300
|
+
// Hot Updater's restart process only relaunches the app; preparing a
|
|
301
|
+
// launch there would consume the crash marker and launch state the
|
|
302
|
+
// relaunched process needs.
|
|
303
|
+
if (HotUpdaterProcess.isRestartProcess(context)) {
|
|
304
|
+
return bundleStorage.getFallbackBundleURL()
|
|
305
|
+
}
|
|
306
|
+
return prepareLaunchIfNeeded().bundleUrl
|
|
307
|
+
}
|
|
225
308
|
|
|
226
309
|
/**
|
|
227
310
|
* Updates the bundle from its manifest and complete asset descriptors.
|
|
@@ -244,18 +327,16 @@ class HotUpdaterImpl {
|
|
|
244
327
|
fileResolver: ArtifactFileResolver? = null,
|
|
245
328
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
246
329
|
): UpdateBundleResult {
|
|
247
|
-
if (selection != null && !bundleStorage.stageReleaseSelection(selection)) {
|
|
248
|
-
throw StaleReleaseSelectionException()
|
|
249
|
-
}
|
|
250
330
|
val result =
|
|
251
331
|
bundleStorage.updateBundle(
|
|
252
|
-
bundleId,
|
|
253
|
-
manifestUrl,
|
|
254
|
-
manifestFileHash,
|
|
255
|
-
assets,
|
|
256
|
-
archiveUrl,
|
|
257
|
-
fileResolver,
|
|
258
|
-
|
|
332
|
+
bundleId = bundleId,
|
|
333
|
+
manifestUrl = manifestUrl,
|
|
334
|
+
manifestFileHash = manifestFileHash,
|
|
335
|
+
assets = assets,
|
|
336
|
+
archiveUrl = archiveUrl,
|
|
337
|
+
fileResolver = fileResolver,
|
|
338
|
+
selection = selection,
|
|
339
|
+
progressCallback = progressCallback,
|
|
259
340
|
)
|
|
260
341
|
|
|
261
342
|
if (!channel.isNullOrEmpty()) {
|
|
@@ -269,43 +350,25 @@ class HotUpdaterImpl {
|
|
|
269
350
|
}
|
|
270
351
|
|
|
271
352
|
/**
|
|
272
|
-
*
|
|
353
|
+
* Restarts the app process so the next launch loads the current bundle.
|
|
273
354
|
* @param reactContext The original context (preferably ReactApplicationContext to get current activity)
|
|
274
355
|
*/
|
|
275
356
|
suspend fun reload(reactContext: Context) {
|
|
276
357
|
try {
|
|
277
|
-
currentLaunchSelection = null
|
|
278
|
-
withContext(Dispatchers.Main) {
|
|
279
|
-
performReactReload(reactContext)
|
|
280
|
-
}
|
|
281
|
-
} catch (e: Exception) {
|
|
282
|
-
Log.e("HotUpdaterImpl", "Failed to reload application", e)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
suspend fun reloadProcess(reactContext: Context) {
|
|
287
|
-
try {
|
|
288
|
-
currentLaunchSelection = null
|
|
289
358
|
withContext(Dispatchers.Main) {
|
|
290
359
|
if (!restartApplication(reactContext)) {
|
|
291
|
-
|
|
292
|
-
performReactReload(reactContext)
|
|
360
|
+
throw IllegalStateException("Process restart could not be started")
|
|
293
361
|
}
|
|
294
362
|
}
|
|
363
|
+
currentLaunchSelection = null
|
|
364
|
+
runningLaunchSelection = null
|
|
295
365
|
} catch (e: Exception) {
|
|
296
366
|
Log.e("HotUpdaterImpl", "Failed to restart application process", e)
|
|
297
367
|
throw e
|
|
298
368
|
}
|
|
299
369
|
}
|
|
300
370
|
|
|
301
|
-
|
|
302
|
-
val reactIntegrationManager = ReactIntegrationManager(reactContext)
|
|
303
|
-
val bundleURL = getJSBundleFile()
|
|
304
|
-
reactIntegrationManager.setJSBundle(bundleURL)
|
|
305
|
-
reactIntegrationManager.reload()
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Use a cold restart in release builds so bundle application does not depend on RN reload timing.
|
|
371
|
+
// A cold restart, so applying a bundle does not depend on React Native reload timing.
|
|
309
372
|
private fun restartApplication(reactContext: Context): Boolean {
|
|
310
373
|
val applicationContext = reactContext.applicationContext
|
|
311
374
|
val currentActivity =
|
|
@@ -381,7 +444,24 @@ class HotUpdaterImpl {
|
|
|
381
444
|
selectionContextHash,
|
|
382
445
|
)
|
|
383
446
|
|
|
384
|
-
suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean
|
|
447
|
+
suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean {
|
|
448
|
+
if (selection.kind == "BUNDLE") return bundleStorage.commitReleaseSelection(selection)
|
|
449
|
+
|
|
450
|
+
// Committing the built-in bundle deletes every downloaded bundle, so it
|
|
451
|
+
// runs under the launch lock as a channel reset does: React Native never
|
|
452
|
+
// selects a bundle the commit is about to delete, and its next read picks
|
|
453
|
+
// the built-in bundle. The storage commit runs on another IO thread, so
|
|
454
|
+
// blocking this one cannot deadlock.
|
|
455
|
+
return withContext(Dispatchers.IO) {
|
|
456
|
+
synchronized(launchPreparationLock) {
|
|
457
|
+
val committed = runBlocking { bundleStorage.commitReleaseSelection(selection) }
|
|
458
|
+
if (committed) {
|
|
459
|
+
currentLaunchSelection = null
|
|
460
|
+
}
|
|
461
|
+
committed
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
385
465
|
|
|
386
466
|
fun getInstallId(): String = installIdentity.getInstallId()
|
|
387
467
|
|
|
@@ -438,6 +518,38 @@ class HotUpdaterImpl {
|
|
|
438
518
|
return null
|
|
439
519
|
}
|
|
440
520
|
|
|
521
|
+
/**
|
|
522
|
+
* The bundle this process runs. A channel reset clears [currentLaunchSelection]
|
|
523
|
+
* for the next launch, but the running bundle stays until React Native reloads.
|
|
524
|
+
*/
|
|
525
|
+
fun getRunningBundleId(): String? = (currentLaunchSelection ?: runningLaunchSelection)?.launchedBundleId
|
|
526
|
+
|
|
527
|
+
/** Takes the queued native update events for JavaScript's `takeNativeUpdateEvents`. */
|
|
528
|
+
fun takeNativeUpdateEvents(): String? =
|
|
529
|
+
NativeUpdateEventQueue(::getStorageItem, ::setStorageItem, build = {
|
|
530
|
+
NativeUpdateEventQueue.buildOf(getAppVersion(), getMinBundleId(), CohortService(context).getCohort())
|
|
531
|
+
}).take()
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* The bundle the native update check counts as running: the launched one,
|
|
535
|
+
* or before React Native starts, the one its launch would pick. Before
|
|
536
|
+
* React Native starts, what the previous process left is settled first, so
|
|
537
|
+
* the check reads the selection, launch state, and crash history the
|
|
538
|
+
* launch will use. Preparing the launch here would arm crash monitoring and
|
|
539
|
+
* keep a forced update staged before React Native starts from applying in
|
|
540
|
+
* this process.
|
|
541
|
+
*/
|
|
542
|
+
fun getBundleIdForUpdateCheck(): String? =
|
|
543
|
+
synchronized(launchPreparationLock) {
|
|
544
|
+
val launchSelection = currentLaunchSelection ?: runningLaunchSelection
|
|
545
|
+
if (launchSelection != null) {
|
|
546
|
+
return@synchronized launchSelection.launchedBundleId
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
bundleStorage.settlePreviousLaunch { recoveryManager.consumePendingCrashRecovery() }
|
|
550
|
+
bundleStorage.peekLaunchBundleId()
|
|
551
|
+
}
|
|
552
|
+
|
|
441
553
|
/**
|
|
442
554
|
* Gets the current manifest from bundle storage.
|
|
443
555
|
*/
|
|
@@ -450,40 +562,134 @@ class HotUpdaterImpl {
|
|
|
450
562
|
return emptyMap()
|
|
451
563
|
}
|
|
452
564
|
|
|
453
|
-
suspend fun resetChannel(): Boolean
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
565
|
+
suspend fun resetChannel(): Boolean =
|
|
566
|
+
withContext(Dispatchers.IO) {
|
|
567
|
+
// Under the launch lock, so React Native never selects a bundle the
|
|
568
|
+
// reset is about to delete. The storage reset runs on another IO
|
|
569
|
+
// thread, so blocking this one cannot deadlock.
|
|
570
|
+
synchronized(launchPreparationLock) {
|
|
571
|
+
val success = runBlocking { bundleStorage.resetChannel() }
|
|
572
|
+
if (success) {
|
|
573
|
+
preferences.setItem(CHANNEL_STORAGE_KEY, null)
|
|
574
|
+
currentLaunchSelection = null
|
|
575
|
+
}
|
|
576
|
+
success
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private fun prepareLaunchIfNeeded(): LaunchSelection =
|
|
581
|
+
synchronized(launchPreparationLock) {
|
|
582
|
+
prepareLaunchLocked()
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private fun prepareLaunchLocked(): LaunchSelection {
|
|
586
|
+
var invalidations = launchInvalidations.get()
|
|
587
|
+
currentLaunchSelection?.takeIf { currentLaunchInvalidations == invalidations }?.let { return it }
|
|
588
|
+
|
|
589
|
+
// The running launch's crash may write its marker while this one is
|
|
590
|
+
// prepared, after the marker was read: the selection would launch
|
|
591
|
+
// the failing bundle again. Its monitoring is replaced only once the
|
|
592
|
+
// new launch's starts, so the count is checked after that, and the
|
|
593
|
+
// launch prepared again, reading the marker, while it changed. A
|
|
594
|
+
// crash can't recur faster than a launch is prepared; should it, the
|
|
595
|
+
// latest selection goes, and the count it started from has the next
|
|
596
|
+
// start prepare again.
|
|
597
|
+
repeat(MAX_LAUNCH_PREPARATIONS - 1) {
|
|
598
|
+
val prepared = selectAndMonitorLaunchLocked()
|
|
599
|
+
val now = launchInvalidations.get()
|
|
600
|
+
if (now == invalidations) return publishLaunchLocked(prepared, invalidations)
|
|
601
|
+
invalidations = now
|
|
458
602
|
}
|
|
459
|
-
return
|
|
603
|
+
return publishLaunchLocked(selectAndMonitorLaunchLocked(), invalidations)
|
|
460
604
|
}
|
|
461
605
|
|
|
462
|
-
private
|
|
463
|
-
|
|
606
|
+
private class PreparedLaunch(
|
|
607
|
+
val selection: LaunchSelection,
|
|
608
|
+
val launch: Long,
|
|
609
|
+
/** False when the selection continues the launch already monitored. */
|
|
610
|
+
val started: Boolean,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Whether [next] continues the named instance's launch of [current]
|
|
615
|
+
* rather than starting another: the same bundle, from the same URL,
|
|
616
|
+
* still unverified, or verified since. A launch's state is its bundle's
|
|
617
|
+
* persisted verification, so the same bundle selected again in this
|
|
618
|
+
* process, as after a reset that keeps it or a reload before the
|
|
619
|
+
* process restarts, is the same launch; another one would make its
|
|
620
|
+
* runtimes, which load the same URL, look like a replacement's.
|
|
621
|
+
*/
|
|
622
|
+
private fun continuesLaunch(
|
|
623
|
+
current: LaunchSelection,
|
|
624
|
+
next: LaunchSelection,
|
|
625
|
+
): Boolean =
|
|
626
|
+
current.launchedBundleId == next.launchedBundleId &&
|
|
627
|
+
current.bundleUrl == next.bundleUrl &&
|
|
628
|
+
(current.shouldRollbackOnCrash || !next.shouldRollbackOnCrash)
|
|
464
629
|
|
|
630
|
+
private fun selectAndMonitorLaunchLocked(): PreparedLaunch {
|
|
465
631
|
val pendingRecovery = recoveryManager.consumePendingCrashRecovery()
|
|
466
632
|
val selection = bundleStorage.prepareLaunch(pendingRecovery)
|
|
633
|
+
monitoredLaunch?.let { (current, launch) ->
|
|
634
|
+
if (!environment.isMain && continuesLaunch(current, selection) && recoveryManager.monitorsLaunch(launch)) {
|
|
635
|
+
return PreparedLaunch(selection, launch, started = false)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
val launch =
|
|
639
|
+
recoveryManager.startMonitoring(
|
|
640
|
+
bundleId = selection.launchedBundleId,
|
|
641
|
+
shouldRollback = selection.shouldRollbackOnCrash,
|
|
642
|
+
onContentAppeared = { launchedBundleId ->
|
|
643
|
+
bundleStorage.markLaunchCompleted(launchedBundleId)
|
|
644
|
+
},
|
|
645
|
+
onCrashOfAnotherInstance = { launchedBundleId ->
|
|
646
|
+
bundleStorage.clearLaunchStarted(launchedBundleId)
|
|
647
|
+
},
|
|
648
|
+
// Restarting the app restarts main's host. A named instance's
|
|
649
|
+
// crash leaves its marker for its next launch to roll back.
|
|
650
|
+
onRecoveryRestartRequested =
|
|
651
|
+
if (environment.isMain) {
|
|
652
|
+
{ restartApplication(context) }
|
|
653
|
+
} else {
|
|
654
|
+
null
|
|
655
|
+
},
|
|
656
|
+
onRuntimeActivity = activityStartGate::watchActivity,
|
|
657
|
+
launchURL = selection.bundleUrl,
|
|
658
|
+
onCharged = { launchInvalidations.incrementAndGet() },
|
|
659
|
+
)
|
|
660
|
+
return PreparedLaunch(selection, launch, started = true)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private fun publishLaunchLocked(
|
|
664
|
+
prepared: PreparedLaunch,
|
|
665
|
+
invalidations: Long,
|
|
666
|
+
): LaunchSelection {
|
|
667
|
+
val selection = prepared.selection
|
|
668
|
+
val launch = prepared.launch
|
|
669
|
+
monitoredLaunch = selection to launch
|
|
670
|
+
// A launch continued keeps its gate, which may have armed it already.
|
|
671
|
+
if (!prepared.started) {
|
|
672
|
+
runningLaunchSelection = selection
|
|
673
|
+
currentLaunchInvalidations = invalidations
|
|
674
|
+
currentLaunchSelection = selection
|
|
675
|
+
return selection
|
|
676
|
+
}
|
|
467
677
|
// A headless JS task, such as a background push, loads the bundle without
|
|
468
678
|
// an activity and never renders. Only a launch with an activity may leave
|
|
469
|
-
// an unfinished launch for the next process to roll back.
|
|
679
|
+
// an unfinished launch for the next process to roll back. A named
|
|
680
|
+
// runtime may also be pre-warmed while the app shows other screens, so
|
|
681
|
+
// its launch waits for one of its own views.
|
|
470
682
|
activityStartGate.runWhenActivityStarted(
|
|
471
683
|
if (selection.shouldRollbackOnCrash) {
|
|
472
|
-
{
|
|
684
|
+
{
|
|
685
|
+
recoveryManager.armLaunch(launch) { bundleStorage.markLaunchStarted(selection.launchedBundleId) }
|
|
686
|
+
}
|
|
473
687
|
} else {
|
|
474
688
|
null
|
|
475
689
|
},
|
|
476
690
|
)
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
shouldRollback = selection.shouldRollbackOnCrash,
|
|
480
|
-
onContentAppeared = { launchedBundleId ->
|
|
481
|
-
bundleStorage.markLaunchCompleted(launchedBundleId)
|
|
482
|
-
},
|
|
483
|
-
onRecoveryRestartRequested = {
|
|
484
|
-
restartApplication(context)
|
|
485
|
-
},
|
|
486
|
-
)
|
|
691
|
+
runningLaunchSelection = selection
|
|
692
|
+
currentLaunchInvalidations = invalidations
|
|
487
693
|
currentLaunchSelection = selection
|
|
488
694
|
return selection
|
|
489
695
|
}
|