@hot-updater/react-native 1.0.7 → 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/ReactIntegrationManagerBase.kt +0 -34
- package/android/src/newarch/ReactIntegrationManager.kt +0 -185
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
|
@@ -1,47 +1,275 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
|
|
6
|
+
private const val CONFIGURE_DEPRECATION =
|
|
7
|
+
"Pass channel, fingerprintHash, and publicKey to HotUpdaterOptions. " +
|
|
8
|
+
"Fingerprint downloads now survive native releases that ship the same artifact."
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* enables brownfield/AAR setups where the RN module cannot rely on the host
|
|
8
|
-
* app's manifest for configuration.
|
|
11
|
+
* Main's configuration: what [HotUpdaterOptions] of the `main` instance and the
|
|
12
|
+
* deprecated `HotUpdater.configure` set. Set values take priority over
|
|
13
|
+
* manifest metadata. Named instances never read it.
|
|
9
14
|
*/
|
|
10
15
|
object HotUpdaterConfig {
|
|
16
|
+
// The values themselves. Main's options record theirs here directly;
|
|
17
|
+
// every legacy write goes through the setters below. Each write takes
|
|
18
|
+
// MainInstanceConfiguration.shared's lock, which main's impl reads its
|
|
19
|
+
// values under.
|
|
20
|
+
@Volatile
|
|
21
|
+
internal var recordedFingerprintHash: String? = null
|
|
22
|
+
|
|
11
23
|
@Volatile
|
|
12
|
-
var
|
|
24
|
+
internal var recordedPublicKey: String? = null
|
|
13
25
|
|
|
14
26
|
@Volatile
|
|
15
|
-
var
|
|
27
|
+
internal var recordedChannel: String? = null
|
|
16
28
|
|
|
17
29
|
@Volatile
|
|
18
|
-
var
|
|
30
|
+
internal var recordedIsolationKey: String? = null
|
|
19
31
|
|
|
20
32
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* bumps (e.g. key only by fingerprint + channel).
|
|
33
|
+
* The fingerprint this build embedded, written by the
|
|
34
|
+
* generateHotUpdaterFingerprintHash build task, or null for an app
|
|
35
|
+
* without one.
|
|
25
36
|
*/
|
|
26
37
|
@Volatile
|
|
27
|
-
var
|
|
38
|
+
internal var buildFingerprintHash: String? = HotUpdaterBuildFingerprintHash.get().takeIf { it.isNotEmpty() }
|
|
39
|
+
|
|
40
|
+
/** Warns when it replaces a value main's [HotUpdaterOptions] set, as [configure] does. */
|
|
41
|
+
var fingerprintHash: String?
|
|
42
|
+
get() = recordedFingerprintHash
|
|
43
|
+
|
|
44
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
45
|
+
set(value) {
|
|
46
|
+
legacyWrite("HotUpdaterConfig.fingerprintHash", mapOf("fingerprintHash" to value)) { recordedFingerprintHash = value }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Warns when it replaces a value main's [HotUpdaterOptions] set, as [configure] does. */
|
|
50
|
+
var publicKey: String?
|
|
51
|
+
get() = recordedPublicKey
|
|
52
|
+
|
|
53
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
54
|
+
set(value) {
|
|
55
|
+
legacyWrite("HotUpdaterConfig.publicKey", mapOf("publicKey" to value)) { recordedPublicKey = value }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Warns when it replaces a value main's [HotUpdaterOptions] set, as [configure] does. */
|
|
59
|
+
var channel: String?
|
|
60
|
+
get() = recordedChannel
|
|
61
|
+
|
|
62
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
63
|
+
set(value) {
|
|
64
|
+
legacyWrite("HotUpdaterConfig.channel", mapOf("channel" to value)) { recordedChannel = value }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* When set, main uses this value verbatim as its storage isolation key,
|
|
69
|
+
* bypassing the default `HotUpdaterPrefs_{fingerprint}_{minBundleId}_{channel}`
|
|
70
|
+
* (fingerprint strategy) or `HotUpdaterPrefs_{appVersion}_{channel}`
|
|
71
|
+
* (appVersion strategy) composition.
|
|
72
|
+
*/
|
|
73
|
+
var isolationKey: String?
|
|
74
|
+
get() = recordedIsolationKey
|
|
28
75
|
|
|
76
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
77
|
+
set(value) {
|
|
78
|
+
synchronized(MainInstanceConfiguration.shared) { recordedIsolationKey = value }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Replaces all four values; a null argument means no override. It still
|
|
83
|
+
* replaces values main's [HotUpdaterOptions] set, and warns when it does.
|
|
84
|
+
* Main's impl reads the values once, when it is created; a later call
|
|
85
|
+
* changes only main's static entry points.
|
|
86
|
+
*/
|
|
87
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
29
88
|
fun configure(
|
|
30
89
|
fingerprintHash: String? = null,
|
|
31
90
|
publicKey: String? = null,
|
|
32
91
|
channel: String? = null,
|
|
33
92
|
isolationKey: String? = null,
|
|
34
93
|
) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
94
|
+
// One write, so main's impl never reads half of it.
|
|
95
|
+
legacyWrite(
|
|
96
|
+
"HotUpdater.configure",
|
|
97
|
+
mapOf("channel" to channel, "fingerprintHash" to fingerprintHash, "publicKey" to publicKey),
|
|
98
|
+
) {
|
|
99
|
+
recordedFingerprintHash = fingerprintHash
|
|
100
|
+
recordedPublicKey = publicKey
|
|
101
|
+
recordedChannel = channel
|
|
102
|
+
recordedIsolationKey = isolationKey
|
|
103
|
+
}
|
|
39
104
|
}
|
|
40
105
|
|
|
106
|
+
@Deprecated(CONFIGURE_DEPRECATION)
|
|
107
|
+
@Suppress("DEPRECATION")
|
|
41
108
|
fun clear() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
109
|
+
configure()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A write of the deprecated API, of the [written] values by field, under
|
|
113
|
+
// the lock main's impl reads its values under, warning when it replaces
|
|
114
|
+
// a value main's options set.
|
|
115
|
+
private fun legacyWrite(
|
|
116
|
+
source: String,
|
|
117
|
+
written: Map<String, String?>,
|
|
118
|
+
write: () -> Unit,
|
|
119
|
+
) {
|
|
120
|
+
val configuration = MainInstanceConfiguration.shared
|
|
121
|
+
synchronized(configuration) {
|
|
122
|
+
configuration.warnIfLegacyWriteOverrides(source, written)
|
|
123
|
+
write()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Records the channel, fingerprint hash, and public key of the `main`
|
|
130
|
+
* instance's [HotUpdaterOptions] into [HotUpdaterConfig] before main's impl
|
|
131
|
+
* exists, and refuses values that would contradict what main already uses.
|
|
132
|
+
*/
|
|
133
|
+
internal class MainInstanceConfiguration {
|
|
134
|
+
companion object {
|
|
135
|
+
private const val TAG = "HotUpdaterConfig"
|
|
136
|
+
|
|
137
|
+
/** Main's: the one HotUpdater.init, HotUpdater.getInstance, and HotUpdaterConfig use. */
|
|
138
|
+
val shared = MainInstanceConfiguration()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Where the warning about `HotUpdater.configure` overriding main's options goes. */
|
|
142
|
+
@Volatile
|
|
143
|
+
internal var logWarning: (String) -> Unit = { message -> Log.w(TAG, message) }
|
|
144
|
+
|
|
145
|
+
// The values main's options set, by field, until HotUpdater.configure
|
|
146
|
+
// replaces them.
|
|
147
|
+
private var claimed = emptyMap<String, String>()
|
|
148
|
+
|
|
149
|
+
// The values main's impl was created with, once it was: its storage key
|
|
150
|
+
// and stored bundles are fixed to them, whatever HotUpdater.configure
|
|
151
|
+
// sets later.
|
|
152
|
+
@get:Synchronized
|
|
153
|
+
var createdWith: MainImplValues? = null
|
|
154
|
+
private set
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Creates main's impl from one snapshot of its values, which it reads
|
|
158
|
+
* from then on, and records the snapshot. Writes to HotUpdaterConfig wait
|
|
159
|
+
* for the snapshot and never reach the impl.
|
|
160
|
+
*/
|
|
161
|
+
fun createImpl(
|
|
162
|
+
context: Context,
|
|
163
|
+
create: (Context, HotUpdaterInstanceEnvironment) -> HotUpdaterImpl = { appContext, environment ->
|
|
164
|
+
HotUpdaterImpl(appContext, environment)
|
|
165
|
+
},
|
|
166
|
+
): HotUpdaterImpl {
|
|
167
|
+
val values =
|
|
168
|
+
synchronized(this) {
|
|
169
|
+
MainImplValues.read(context).also { createdWith = it }
|
|
170
|
+
}
|
|
171
|
+
return create(context, HotUpdaterInstanceEnvironment.main(values))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Before main's impl exists, records every value [options] set and
|
|
176
|
+
* main's config doesn't hold yet. Values left null keep what main holds.
|
|
177
|
+
* @throws IllegalArgumentException when [options] set a channel,
|
|
178
|
+
* fingerprint hash, or public key other than the one main's config
|
|
179
|
+
* already holds, from earlier options or `HotUpdater.configure`, or, once
|
|
180
|
+
* main's impl exists, other than the one it uses
|
|
181
|
+
*/
|
|
182
|
+
@Synchronized
|
|
183
|
+
fun claim(options: HotUpdaterOptions) = claim(options, createdWith)
|
|
184
|
+
|
|
185
|
+
/** [claim], with the values main's impl was created with, or null before it exists. */
|
|
186
|
+
@Synchronized
|
|
187
|
+
fun claim(
|
|
188
|
+
options: HotUpdaterOptions,
|
|
189
|
+
createdWith: MainImplValues?,
|
|
190
|
+
) {
|
|
191
|
+
val implCreated = createdWith != null
|
|
192
|
+
val fields =
|
|
193
|
+
listOf(
|
|
194
|
+
Field("channel", options.channel.nonEmpty(), HotUpdaterConfig.channel.nonEmpty(), createdWith?.channel),
|
|
195
|
+
Field(
|
|
196
|
+
"fingerprintHash",
|
|
197
|
+
options.fingerprintHash.nonEmpty(),
|
|
198
|
+
HotUpdaterConfig.fingerprintHash.nonEmpty(),
|
|
199
|
+
createdWith?.fingerprintHash,
|
|
200
|
+
),
|
|
201
|
+
Field("publicKey", options.publicKey.nonEmpty(), HotUpdaterConfig.publicKey.nonEmpty(), createdWith?.publicKey),
|
|
202
|
+
)
|
|
203
|
+
fields.forEach { field ->
|
|
204
|
+
val requested = field.requested ?: return@forEach
|
|
205
|
+
// Once the impl exists, a value it doesn't use, even where it uses
|
|
206
|
+
// none, would be ignored.
|
|
207
|
+
val conflicts = if (implCreated) field.inUse != requested else field.held != null && field.held != requested
|
|
208
|
+
require(!conflicts) {
|
|
209
|
+
"HotUpdater instance \"main\" was already created with another ${field.name}"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
claimed = claimed + fields.mapNotNull { field -> field.requested?.let { field.name to it } }
|
|
213
|
+
if (implCreated) return
|
|
214
|
+
options.channel.nonEmpty()?.let { HotUpdaterConfig.recordedChannel = it }
|
|
215
|
+
options.fingerprintHash.nonEmpty()?.let { HotUpdaterConfig.recordedFingerprintHash = it }
|
|
216
|
+
options.publicKey.nonEmpty()?.let { HotUpdaterConfig.recordedPublicKey = it }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Warns, once for each value main's options set, when [source], the
|
|
221
|
+
* deprecated `HotUpdater.configure` or a `HotUpdaterConfig` setter,
|
|
222
|
+
* replaces it with another of [written], by field. The write still
|
|
223
|
+
* replaces it, as it always has; the field is the legacy API's from then on.
|
|
224
|
+
*/
|
|
225
|
+
@Synchronized
|
|
226
|
+
fun warnIfLegacyWriteOverrides(
|
|
227
|
+
source: String,
|
|
228
|
+
written: Map<String, String?>,
|
|
229
|
+
) {
|
|
230
|
+
val replaced =
|
|
231
|
+
written.mapNotNull { (name, value) ->
|
|
232
|
+
val set = claimed[name] ?: return@mapNotNull null
|
|
233
|
+
if (set == value.nonEmpty()) return@mapNotNull null
|
|
234
|
+
// A PEM key is long; its name says enough.
|
|
235
|
+
if (name == "publicKey") "publicKey" else "$name \"$set\" with ${value.nonEmpty()?.let { "\"$it\"" } ?: "none"}"
|
|
236
|
+
}
|
|
237
|
+
claimed = claimed - written.keys
|
|
238
|
+
if (replaced.isEmpty()) return
|
|
239
|
+
logWarning(
|
|
240
|
+
"$source replaced the ${replaced.joinToString()} that HotUpdaterOptions of instance \"main\" set. " +
|
|
241
|
+
"It is deprecated: pass channel, fingerprintHash, and publicKey only through HotUpdaterOptions.",
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// An empty string sets nothing, as in HotUpdater.configure.
|
|
246
|
+
private fun String?.nonEmpty(): String? = this?.takeIf { it.isNotEmpty() }
|
|
247
|
+
|
|
248
|
+
private class Field(
|
|
249
|
+
val name: String,
|
|
250
|
+
val requested: String?,
|
|
251
|
+
val held: String?,
|
|
252
|
+
val inUse: String?,
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** The channel, fingerprint hash, public key, and isolation key override main's impl reads. */
|
|
257
|
+
internal class MainImplValues(
|
|
258
|
+
val channel: String,
|
|
259
|
+
val fingerprintHash: String?,
|
|
260
|
+
val publicKey: String?,
|
|
261
|
+
val isolationKey: String?,
|
|
262
|
+
) {
|
|
263
|
+
companion object {
|
|
264
|
+
/** HotUpdaterConfig's values, then the manifest's, as main's static entry points read them. */
|
|
265
|
+
fun read(context: Context): MainImplValues {
|
|
266
|
+
val main = HotUpdaterInstanceEnvironment.main
|
|
267
|
+
return MainImplValues(
|
|
268
|
+
main.defaultChannel(context),
|
|
269
|
+
main.fingerprintHash(context),
|
|
270
|
+
main.publicKey(context),
|
|
271
|
+
HotUpdaterConfig.isolationKey,
|
|
272
|
+
)
|
|
273
|
+
}
|
|
46
274
|
}
|
|
47
275
|
}
|
|
@@ -8,13 +8,15 @@ import java.net.UnknownHostException
|
|
|
8
8
|
import javax.net.ssl.SSLException
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Where an update failed: [
|
|
12
|
-
* update's files, [INSTALL]
|
|
11
|
+
* Where an update failed: [CHECK] covers the Release catalog request,
|
|
12
|
+
* [DOWNLOAD] transferring and verifying the update's files, [INSTALL]
|
|
13
|
+
* producing the bundle on disk.
|
|
13
14
|
* Values match `UpdateErrorStage` in packages/react-native/src/clientPlugin.ts.
|
|
14
15
|
*/
|
|
15
16
|
enum class UpdateFailureStage(
|
|
16
17
|
val value: String,
|
|
17
18
|
) {
|
|
19
|
+
CHECK("check"),
|
|
18
20
|
DOWNLOAD("download"),
|
|
19
21
|
INSTALL("install"),
|
|
20
22
|
}
|
|
@@ -41,6 +43,8 @@ enum class UpdateFailureReason(
|
|
|
41
43
|
enum class UpdateFailureResource(
|
|
42
44
|
val value: String,
|
|
43
45
|
) {
|
|
46
|
+
CATALOG("catalog"),
|
|
47
|
+
ARTIFACT("artifact"),
|
|
44
48
|
MANIFEST("manifest"),
|
|
45
49
|
FILE("file"),
|
|
46
50
|
PATCH("patch"),
|
|
@@ -120,7 +124,7 @@ data class UpdateFailure(
|
|
|
120
124
|
install(UpdateFailureReason.STORAGE)
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
is MalformedURLException -> {
|
|
127
|
+
is MalformedURLException, is InvalidUpdateResponseException -> {
|
|
124
128
|
download(UpdateFailureReason.INVALID_RESPONSE)
|
|
125
129
|
}
|
|
126
130
|
|
|
@@ -152,7 +156,7 @@ data class UpdateFailure(
|
|
|
152
156
|
* [SocketException] a refused, unreachable, or reset connection, and
|
|
153
157
|
* OkHttp reports a canceled call as an IOException "Canceled".
|
|
154
158
|
*/
|
|
155
|
-
|
|
159
|
+
internal fun transportOf(
|
|
156
160
|
error: IOException,
|
|
157
161
|
isOffline: () -> Boolean,
|
|
158
162
|
): UpdateFailureTransport? =
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* First content no instance could be told for yet. A named runtime may render
|
|
5
|
+
* before its HotUpdater module exists, in an activity nothing followed, or
|
|
6
|
+
* before its root view is attached to an activity, so its view can't be found
|
|
7
|
+
* until the module hands over its activity or the view is attached. Crediting
|
|
8
|
+
* that content to main would leave the named launch unfinished, and its next
|
|
9
|
+
* process would roll a working bundle back.
|
|
10
|
+
*
|
|
11
|
+
* Held content is resolved again whenever a named runtime registers and
|
|
12
|
+
* whenever a launch gate looks at an activity's views. After
|
|
13
|
+
* [HOLD_MS] still-unresolved content verifies main, as unattributed content
|
|
14
|
+
* always has, once; it stays held, so a named runtime that registers later
|
|
15
|
+
* still verifies its own launch with it.
|
|
16
|
+
*
|
|
17
|
+
* Each hold remembers every instance's launch generation then, so content
|
|
18
|
+
* never verifies a launch that started after it appeared.
|
|
19
|
+
*/
|
|
20
|
+
internal class HotUpdaterHeldContent(
|
|
21
|
+
private val mainThread: HotUpdaterMainThread,
|
|
22
|
+
private val clock: () -> Long,
|
|
23
|
+
/** The instance of the view with this root tag, with its runtime's launch, or null while it can't be found. Main thread only. */
|
|
24
|
+
private val resolve: (Int) -> HotUpdaterRootOwner?,
|
|
25
|
+
/** Verifies the launch of [generation] of the instance. */
|
|
26
|
+
private val credit: (instanceName: String, generation: Long) -> Unit,
|
|
27
|
+
) {
|
|
28
|
+
private class Held(
|
|
29
|
+
val rootTag: Int,
|
|
30
|
+
val heldAt: Long,
|
|
31
|
+
val generations: Map<String, Long>,
|
|
32
|
+
var creditedToMain: Boolean = false,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
private val held = ArrayList<Held>()
|
|
36
|
+
|
|
37
|
+
/** Holds the content of [rootTag], seen while the instances had these launch [generations]. */
|
|
38
|
+
fun hold(
|
|
39
|
+
rootTag: Int,
|
|
40
|
+
generations: Map<String, Long>,
|
|
41
|
+
) {
|
|
42
|
+
val evicted =
|
|
43
|
+
synchronized(this) {
|
|
44
|
+
// A root view's first content counts once.
|
|
45
|
+
if (held.any { it.rootTag == rootTag }) return
|
|
46
|
+
held += Held(rootTag, clock(), generations)
|
|
47
|
+
// Root tags are small and each is a root view that rendered,
|
|
48
|
+
// so every one is kept while a named launch may own it. Only
|
|
49
|
+
// past this bound does the oldest leave, and only after it
|
|
50
|
+
// verified main, as content no instance claims does.
|
|
51
|
+
if (held.size > MAX_HELD) held.removeAt(0).takeUnless { it.creditedToMain } else null
|
|
52
|
+
}
|
|
53
|
+
evicted?.generations?.get(HotUpdaterInstanceEnvironment.MAIN_NAME)?.let { credit(HotUpdaterInstanceEnvironment.MAIN_NAME, it) }
|
|
54
|
+
mainThread.postDelayed({ creditExpiredToMain() }, HOLD_MS)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A named runtime registered: its views may be found now. */
|
|
58
|
+
fun runtimeRegistered() {
|
|
59
|
+
if (mainThread.isCurrent()) resolveHeld() else mainThread.post { resolveHeld() }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Main thread: verifies the launches whose views can be told now. */
|
|
63
|
+
fun resolveHeld() {
|
|
64
|
+
val candidates = synchronized(this) { held.toList() }
|
|
65
|
+
if (candidates.isEmpty()) return
|
|
66
|
+
candidates.forEach { content ->
|
|
67
|
+
val owner = resolve(content.rootTag) ?: return@forEach
|
|
68
|
+
val instanceName = owner.instanceName
|
|
69
|
+
val generation = owner.boundTo(content.generations)[instanceName]
|
|
70
|
+
val alreadyCredited = instanceName == HotUpdaterInstanceEnvironment.MAIN_NAME && content.creditedToMain
|
|
71
|
+
synchronized(this) { held.remove(content) }
|
|
72
|
+
if (generation != null && !alreadyCredited) credit(instanceName, generation)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Content held [HOLD_MS] without an instance verifies main, once. */
|
|
77
|
+
fun creditExpiredToMain() {
|
|
78
|
+
val now = clock()
|
|
79
|
+
val expired =
|
|
80
|
+
synchronized(this) {
|
|
81
|
+
held.filter { !it.creditedToMain && now - it.heldAt >= HOLD_MS }.onEach { it.creditedToMain = true }
|
|
82
|
+
}
|
|
83
|
+
expired.forEach { content ->
|
|
84
|
+
content.generations[HotUpdaterInstanceEnvironment.MAIN_NAME]?.let { credit(HotUpdaterInstanceEnvironment.MAIN_NAME, it) }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
companion object {
|
|
89
|
+
const val HOLD_MS = 10_000L
|
|
90
|
+
private const val MAX_HELD = 1024
|
|
91
|
+
}
|
|
92
|
+
}
|