@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
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.ContextWrapper
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import com.facebook.react.ReactRootView
|
|
11
|
+
import com.facebook.react.bridge.ReactContext
|
|
12
|
+
import com.facebook.react.bridge.UIManager
|
|
13
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
14
|
+
import com.facebook.react.uimanager.common.UIManagerType
|
|
15
|
+
import java.lang.ref.WeakReference
|
|
16
|
+
import java.util.Collections
|
|
17
|
+
import java.util.WeakHashMap
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Which instance a React Native view, runtime, or JavaScript error belongs to:
|
|
21
|
+
* the instance whose bundle the runtime runs, by the URL React Native loaded
|
|
22
|
+
* it from ([HotUpdaterInstanceRegistry.resolveName]). With only main, all of
|
|
23
|
+
* it is main's.
|
|
24
|
+
*/
|
|
25
|
+
internal class HotUpdaterRecoveryAttribution(
|
|
26
|
+
private val resolveName: (Context, String) -> String = HotUpdaterInstanceRegistry.shared::resolveName,
|
|
27
|
+
private val isStarted: (Activity) -> Boolean = { isActivityStarted(it) },
|
|
28
|
+
runtimeRoots: HotUpdaterRuntimeRoots? = null,
|
|
29
|
+
decorRoots: HotUpdaterDecorRoots? = null,
|
|
30
|
+
/**
|
|
31
|
+
* Whether a runtime that loaded the URL runs the instance's current
|
|
32
|
+
* launch. A lambda, not a reference: the manager's companion reads
|
|
33
|
+
* [shared], so creating [shared] must not initialize the manager.
|
|
34
|
+
*/
|
|
35
|
+
private val runsCurrentLaunch: (instanceName: String, sourceURL: String?) -> Boolean = { name, url ->
|
|
36
|
+
HotUpdaterRecoveryManager.runsCurrentLaunch(name, url)
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Whether a runtime registered for the instance's current launch, by the
|
|
40
|
+
* launch it registered for: a replacement launch of the same bundle has
|
|
41
|
+
* the same URL. Null for a runtime that registered for no launch.
|
|
42
|
+
*/
|
|
43
|
+
private val runtimeRunsCurrentLaunch: (instanceName: String, runtime: Any?) -> Boolean? = { name, runtime ->
|
|
44
|
+
HotUpdaterRecoveryManager.runtimeRunsCurrentLaunch(name, runtime)
|
|
45
|
+
},
|
|
46
|
+
) {
|
|
47
|
+
/**
|
|
48
|
+
* Root views of the registered runtimes, found through their UI
|
|
49
|
+
* managers in whichever window shows them: a dialog or a presentation
|
|
50
|
+
* has its own window, which no activity's decor view contains.
|
|
51
|
+
*/
|
|
52
|
+
private val runtimeRoots: HotUpdaterRuntimeRoots = runtimeRoots ?: UIManagerRuntimeRoots()
|
|
53
|
+
|
|
54
|
+
/** Root views in activities' decor views, for a runtime not registered yet. */
|
|
55
|
+
private val decorRoots: HotUpdaterDecorRoots =
|
|
56
|
+
decorRoots ?: HotUpdaterDecorRoots { activity ->
|
|
57
|
+
val decorView = activity.window?.peekDecorView()
|
|
58
|
+
if (decorView == null) {
|
|
59
|
+
emptyList()
|
|
60
|
+
} else {
|
|
61
|
+
rootViewsIn(decorView).map { rootView ->
|
|
62
|
+
val runtime = runCatching { rootView.currentReactContext }.getOrNull()
|
|
63
|
+
HotUpdaterDecorRoots.Root(rootView.rootViewTag, runCatching { runtime?.sourceURL }.getOrNull(), runtime)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
companion object {
|
|
69
|
+
val shared = HotUpdaterRecoveryAttribution()
|
|
70
|
+
|
|
71
|
+
// Instance names have no "@", so this names no launch.
|
|
72
|
+
private const val EARLIER_LAUNCH_SUFFIX = "@earlier-launch"
|
|
73
|
+
|
|
74
|
+
// A JavaScript stack frame's source: an absolute path or a URL, such
|
|
75
|
+
// as Hermes' "at f (address at /data/.../index.android.bundle:1:2345)",
|
|
76
|
+
// "f@/data/.../index.android.bundle:1:2345", or "assets://widget.android.bundle:1:9".
|
|
77
|
+
private val FRAME_SOURCE = Regex("""((?:[A-Za-z][A-Za-z0-9+.-]*://|/)[^\s()]*?)(?::\d+){1,2}(?=[)\s,]|$)""")
|
|
78
|
+
|
|
79
|
+
/** The bundle locations the frames of [stack] run from. */
|
|
80
|
+
fun frameSources(stack: String): List<String> =
|
|
81
|
+
FRAME_SOURCE
|
|
82
|
+
.findAll(stack)
|
|
83
|
+
.map { it.groupValues[1] }
|
|
84
|
+
.distinct()
|
|
85
|
+
.toList()
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The named instance whose bundle every located frame of [stacks]
|
|
89
|
+
* runs from, or null when no frame is located or they name main, an
|
|
90
|
+
* unknown bundle, or more than one instance. A runtime runs one
|
|
91
|
+
* bundle, so its error's frames name its instance even before its
|
|
92
|
+
* HotUpdater module exists.
|
|
93
|
+
*/
|
|
94
|
+
fun namedInstanceOfStack(
|
|
95
|
+
stacks: List<String>,
|
|
96
|
+
resolve: (String) -> String,
|
|
97
|
+
): String? = instanceOfStack(stacks, resolve)?.takeIf { it != HotUpdaterInstanceEnvironment.MAIN_NAME }
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The instance, main included, whose bundle every located frame of
|
|
101
|
+
* [stacks] runs from; null when no frame is located or they name more
|
|
102
|
+
* than one instance. Main's frames are main's own bundle or any the
|
|
103
|
+
* named instances don't know, as [HotUpdaterInstanceRegistry.resolveName] has it.
|
|
104
|
+
*/
|
|
105
|
+
fun instanceOfStack(
|
|
106
|
+
stacks: List<String>,
|
|
107
|
+
resolve: (String) -> String,
|
|
108
|
+
): String? {
|
|
109
|
+
val sources = stacks.flatMap(::frameSources).distinct()
|
|
110
|
+
if (sources.isEmpty()) return null
|
|
111
|
+
return sources.map(resolve).toSet().singleOrNull()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether a view of [viewInstance] counts for [instanceName]. Main
|
|
116
|
+
* takes every view no named instance claims, so a view whose runtime
|
|
117
|
+
* isn't known yet counts for main, as every view always has.
|
|
118
|
+
*/
|
|
119
|
+
fun owns(
|
|
120
|
+
instanceName: String,
|
|
121
|
+
viewInstance: String?,
|
|
122
|
+
): Boolean =
|
|
123
|
+
if (instanceName == HotUpdaterInstanceEnvironment.MAIN_NAME) {
|
|
124
|
+
viewInstance == null || viewInstance == HotUpdaterInstanceEnvironment.MAIN_NAME
|
|
125
|
+
} else {
|
|
126
|
+
viewInstance == instanceName
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Volatile
|
|
131
|
+
private var context: Context? = null
|
|
132
|
+
private var trackedApplication: Application? = null
|
|
133
|
+
|
|
134
|
+
// Activities that may hold a React Native view whose first content appears.
|
|
135
|
+
private val activities = Collections.newSetFromMap(WeakHashMap<Activity, Boolean>())
|
|
136
|
+
private val startedActivities = Collections.newSetFromMap(WeakHashMap<Activity, Boolean>())
|
|
137
|
+
private val runtimes = mutableListOf<WeakReference<ReactContext>>()
|
|
138
|
+
|
|
139
|
+
private val activityCallbacks =
|
|
140
|
+
object : Application.ActivityLifecycleCallbacks {
|
|
141
|
+
override fun onActivityCreated(
|
|
142
|
+
activity: Activity,
|
|
143
|
+
savedInstanceState: Bundle?,
|
|
144
|
+
) {
|
|
145
|
+
synchronized(activities) { activities += activity }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override fun onActivityStarted(activity: Activity) {
|
|
149
|
+
synchronized(activities) {
|
|
150
|
+
activities += activity
|
|
151
|
+
startedActivities += activity
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
156
|
+
synchronized(activities) {
|
|
157
|
+
activities -= activity
|
|
158
|
+
startedActivities -= activity
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override fun onActivityResumed(activity: Activity) {}
|
|
163
|
+
|
|
164
|
+
override fun onActivityPaused(activity: Activity) {}
|
|
165
|
+
|
|
166
|
+
override fun onActivityStopped(activity: Activity) {
|
|
167
|
+
synchronized(activities) { startedActivities -= activity }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override fun onActivitySaveInstanceState(
|
|
171
|
+
activity: Activity,
|
|
172
|
+
outState: Bundle,
|
|
173
|
+
) {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Starts following the app's activities, once per process. */
|
|
177
|
+
fun track(context: Context) {
|
|
178
|
+
this.context = context.applicationContext
|
|
179
|
+
val application = context.applicationContext as? Application ?: return
|
|
180
|
+
synchronized(this) {
|
|
181
|
+
if (trackedApplication != null) return
|
|
182
|
+
trackedApplication = application
|
|
183
|
+
}
|
|
184
|
+
application.registerActivityLifecycleCallbacks(activityCallbacks)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Remembers [activity], which the app handed over as a context: it may
|
|
189
|
+
* start, or have started, before [track] registered for lifecycle
|
|
190
|
+
* callbacks. It counts as started only while it is.
|
|
191
|
+
*/
|
|
192
|
+
fun noteActivity(activity: Activity) {
|
|
193
|
+
synchronized(activities) { activities += activity }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Remembers a runtime whose HotUpdater module was created. Its current
|
|
198
|
+
* activity is searched for its views even when that activity started
|
|
199
|
+
* before [track], as when a brownfield app adds React Native to a screen
|
|
200
|
+
* already shown.
|
|
201
|
+
*/
|
|
202
|
+
fun registerRuntime(reactContext: ReactContext) {
|
|
203
|
+
if (context == null) context = reactContext.applicationContext
|
|
204
|
+
synchronized(runtimes) {
|
|
205
|
+
runtimes.removeAll { it.get() == null }
|
|
206
|
+
if (runtimes.none { it.get() === reactContext }) runtimes += WeakReference(reactContext)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Forgets [reactContext], whose runtime was torn down. */
|
|
211
|
+
fun unregisterRuntime(reactContext: ReactContext) {
|
|
212
|
+
synchronized(runtimes) { runtimes.removeAll { it.get().let { runtime -> runtime == null || runtime === reactContext } } }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The activities started now, as far as this process has seen: the ones
|
|
217
|
+
* seen starting since [track], and any other known activity, such as a
|
|
218
|
+
* registered runtime's current one, that reports it is started now. A
|
|
219
|
+
* runtime keeps its activity after it stops. A gate created after they
|
|
220
|
+
* started reads them here.
|
|
221
|
+
*/
|
|
222
|
+
fun startedActivities(): List<Activity> {
|
|
223
|
+
val (seenStarted, known) = synchronized(activities) { startedActivities.toList() to activities.toList() }
|
|
224
|
+
val runtimeActivities = synchronized(runtimes) { runtimes.mapNotNull { it.get()?.currentActivity } }
|
|
225
|
+
return (seenStarted + (known + runtimeActivities).filter(isStarted)).distinct()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** [namedInstanceOfStack] with this process's instances. */
|
|
229
|
+
fun namedInstanceOfStack(stacks: List<String>): String? {
|
|
230
|
+
val context = context ?: return null
|
|
231
|
+
return namedInstanceOfStack(stacks) { resolveName(context, it) }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** [instanceOfStack] with this process's instances. */
|
|
235
|
+
fun instanceOfStack(stacks: List<String>): String? {
|
|
236
|
+
val context = context ?: return null
|
|
237
|
+
return instanceOfStack(stacks) { resolveName(context, it) }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The instance whose bundle runs from [sourceURL], or null when it isn't known. */
|
|
241
|
+
fun instanceOf(sourceURL: String?): String? {
|
|
242
|
+
val url = sourceURL?.takeIf { it.isNotEmpty() } ?: return null
|
|
243
|
+
val context = context ?: return HotUpdaterInstanceEnvironment.MAIN_NAME
|
|
244
|
+
return resolveName(context, url)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The instance whose current launch a view of a runtime that loaded
|
|
249
|
+
* [sourceURL] belongs to. A named host may still show a view of its
|
|
250
|
+
* previous runtime while its next launch starts: that view belongs to an
|
|
251
|
+
* earlier launch, which neither arms nor verifies the current one. Main
|
|
252
|
+
* takes any view, as it always has. Every view that arms or verifies a
|
|
253
|
+
* launch is attributed here, null before the view joins a runtime.
|
|
254
|
+
*/
|
|
255
|
+
fun launchInstanceOf(
|
|
256
|
+
sourceURL: String?,
|
|
257
|
+
runtime: Any? = null,
|
|
258
|
+
): String? {
|
|
259
|
+
val instanceName = instanceOf(sourceURL) ?: return null
|
|
260
|
+
if (instanceName == HotUpdaterInstanceEnvironment.MAIN_NAME) return instanceName
|
|
261
|
+
// The launch [runtime] registered for, when it did; else its bundle's URL.
|
|
262
|
+
val current = runtimeRunsCurrentLaunch(instanceName, runtime) ?: runsCurrentLaunch(instanceName, sourceURL)
|
|
263
|
+
return if (current) instanceName else "$instanceName$EARLIER_LAUNCH_SUFFIX"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Whether [activity] shows a React Native view that counts for [instanceName]. */
|
|
267
|
+
fun showsViewOf(
|
|
268
|
+
activity: Activity,
|
|
269
|
+
instanceName: String,
|
|
270
|
+
): Boolean {
|
|
271
|
+
if (decorRoots.rootsIn(activity).any { owns(instanceName, launchInstanceOf(it.sourceURL, it.runtime)) }) return true
|
|
272
|
+
// Main takes the views no named instance claims, which a decor view shows.
|
|
273
|
+
if (instanceName == HotUpdaterInstanceEnvironment.MAIN_NAME) return false
|
|
274
|
+
return runtimeRoots.runtimesShownIn(activity).any { launchInstanceOf(it.sourceURL, it.runtime) == instanceName }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The instance of the root view whose first content appeared, which React
|
|
279
|
+
* Native names by its root tag; null when no known activity shows it or
|
|
280
|
+
* its runtime isn't known yet.
|
|
281
|
+
*/
|
|
282
|
+
fun instanceOfRootTag(rootTag: Int): String? = rootOf(rootTag)?.instanceName
|
|
283
|
+
|
|
284
|
+
/** A root view's instance, as [instanceOfRootTag] has it, and the runtime that holds it. */
|
|
285
|
+
data class RootOwner(
|
|
286
|
+
val instanceName: String,
|
|
287
|
+
val runtime: Any?,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
/** [instanceOfRootTag], with the runtime holding the root view, by which content is bound to that runtime's launch. */
|
|
291
|
+
fun rootOf(rootTag: Int): RootOwner? {
|
|
292
|
+
runtimeRoots.runtimeOfRootTag(rootTag)?.let { root ->
|
|
293
|
+
return launchInstanceOf(root.sourceURL, root.runtime)?.let { RootOwner(it, root.runtime) }
|
|
294
|
+
}
|
|
295
|
+
val candidates =
|
|
296
|
+
synchronized(activities) { activities.toList() } +
|
|
297
|
+
synchronized(runtimes) { runtimes.mapNotNull { it.get()?.currentActivity } }
|
|
298
|
+
candidates.distinct().forEach { activity ->
|
|
299
|
+
// A view whose runtime isn't known yet tells nothing either.
|
|
300
|
+
decorRoots.rootsIn(activity).firstOrNull { it.rootTag == rootTag }?.let { root ->
|
|
301
|
+
return launchInstanceOf(root.sourceURL, root.runtime)?.let { RootOwner(it, root.runtime) }
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private fun registeredRuntimes(): List<ReactContext> = synchronized(runtimes) { runtimes.mapNotNull { it.get() } }
|
|
308
|
+
|
|
309
|
+
/** Root views through the registered runtimes' UI managers. Main thread, as UI managers require. */
|
|
310
|
+
private inner class UIManagerRuntimeRoots : HotUpdaterRuntimeRoots {
|
|
311
|
+
private val liveRootTags = HotUpdaterLiveRootTags()
|
|
312
|
+
|
|
313
|
+
// The runtimes the root tags were looked at with.
|
|
314
|
+
private val scannedRuntimes = WeakIdentitySet<ReactContext>()
|
|
315
|
+
|
|
316
|
+
override fun runtimeOfRootTag(rootTag: Int): HotUpdaterRuntimeRoots.Runtime? {
|
|
317
|
+
registeredRuntimes().forEach { runtime ->
|
|
318
|
+
if (RootViews.rootViewOf(RootViews.uiManagersOf(runtime), rootTag) != null) {
|
|
319
|
+
return HotUpdaterRuntimeRoots.Runtime(runCatching { runtime.sourceURL }.getOrNull(), runtime)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
override fun runtimesShownIn(activity: Activity): List<HotUpdaterRuntimeRoots.Runtime> {
|
|
326
|
+
val runtimes = registeredRuntimes()
|
|
327
|
+
if (runtimes.isEmpty()) return emptyList()
|
|
328
|
+
val nextRootViewTag = RootViews.nextRootViewTag() ?: return emptyList()
|
|
329
|
+
val uiManagers = runtimes.associateWith { RootViews.uiManagersOf(it) }
|
|
330
|
+
// A runtime registered since: its roots may be older than the recent tags.
|
|
331
|
+
if (runtimes.map(scannedRuntimes::add).any { it }) liveRootTags.rescanAll()
|
|
332
|
+
val rootTags =
|
|
333
|
+
liveRootTags.liveTags(nextRootViewTag) { rootTag ->
|
|
334
|
+
uiManagers.values.any { RootViews.rootViewOf(it, rootTag) != null }
|
|
335
|
+
}
|
|
336
|
+
return runtimes
|
|
337
|
+
.filter { runtime ->
|
|
338
|
+
rootTags.any { rootTag ->
|
|
339
|
+
RootViews.rootViewOf(uiManagers.getValue(runtime), rootTag)?.let { isShownIn(it, activity) } ==
|
|
340
|
+
true
|
|
341
|
+
}
|
|
342
|
+
}.map { HotUpdaterRuntimeRoots.Runtime(runCatching { it.sourceURL }.getOrNull(), it) }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private fun isShownIn(
|
|
346
|
+
view: View,
|
|
347
|
+
activity: Activity,
|
|
348
|
+
): Boolean =
|
|
349
|
+
view.isAttachedToWindow &&
|
|
350
|
+
view.isShown &&
|
|
351
|
+
view.windowVisibility == View.VISIBLE &&
|
|
352
|
+
RootViews.ownerActivity(view.context) === activity &&
|
|
353
|
+
isStarted(activity)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
internal object RootViews {
|
|
357
|
+
// UIManagerType.LEGACY, named DEFAULT before React Native 0.80.
|
|
358
|
+
private const val LEGACY_UI_MANAGER = 1
|
|
359
|
+
|
|
360
|
+
/** [runtime]'s UI managers: Fabric's, and the legacy one of a runtime on the bridge. */
|
|
361
|
+
fun uiManagersOf(runtime: ReactContext): List<UIManager> =
|
|
362
|
+
listOf(UIManagerType.FABRIC, LEGACY_UI_MANAGER)
|
|
363
|
+
.mapNotNull { type -> runCatching { UIManagerHelper.getUIManager(runtime, type) }.getOrNull() }
|
|
364
|
+
.distinct()
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The view of [rootTag] one of [uiManagers] holds: Fabric's surfaces
|
|
368
|
+
* hold their root view under the surface id, which is the root tag,
|
|
369
|
+
* and the legacy UI manager its root views under theirs.
|
|
370
|
+
*/
|
|
371
|
+
fun rootViewOf(
|
|
372
|
+
uiManagers: List<UIManager>,
|
|
373
|
+
rootTag: Int,
|
|
374
|
+
): View? = uiManagers.firstNotNullOfOrNull { runCatching { it.resolveView(rootTag) }.getOrNull() }
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Every root tag React Native handed out so far: one counter serves
|
|
378
|
+
* every runtime and both renderers, from 1 in steps of 10. Null when
|
|
379
|
+
* the counter can't be read.
|
|
380
|
+
*/
|
|
381
|
+
fun nextRootViewTag(): Int? =
|
|
382
|
+
runCatching {
|
|
383
|
+
val generator = Class.forName("com.facebook.react.uimanager.ReactRootViewTagGenerator")
|
|
384
|
+
// A Kotlin object's field from React Native 0.80, a Java static before.
|
|
385
|
+
listOf("nextRootViewTag", "sNextRootViewTag")
|
|
386
|
+
.firstNotNullOfOrNull { name -> runCatching { generator.getDeclaredField(name) }.getOrNull() }
|
|
387
|
+
?.apply { isAccessible = true }
|
|
388
|
+
?.getInt(null)
|
|
389
|
+
}.getOrNull()
|
|
390
|
+
|
|
391
|
+
/** The activity a view's context belongs to, through wrappers such as a dialog's theme. */
|
|
392
|
+
fun ownerActivity(context: Context?): Activity? {
|
|
393
|
+
var current = context
|
|
394
|
+
repeat(MAX_CONTEXT_DEPTH) {
|
|
395
|
+
when (current) {
|
|
396
|
+
is Activity -> return current as Activity
|
|
397
|
+
is ReactContext -> return (current as ReactContext).currentActivity
|
|
398
|
+
is ContextWrapper -> current = (current as ContextWrapper).baseContext
|
|
399
|
+
else -> return null
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return null
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private const val MAX_CONTEXT_DEPTH = 16
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The root tags whose root view a registered runtime holds, however many
|
|
411
|
+
* React Native handed out before them. React Native hands out root tags from
|
|
412
|
+
* one counter for every runtime and both renderers, from 1 in steps of 10,
|
|
413
|
+
* and lists no live roots: each tag handed out is looked at once it is, and
|
|
414
|
+
* the ones a runtime holds are kept until their root goes, which no age
|
|
415
|
+
* bounds. Tags no runtime held yet, such as main's (whose runtime isn't
|
|
416
|
+
* registered), are looked at again only while they are among the latest
|
|
417
|
+
* [maxUnheld]: a dead tag is never live again. A runtime that registers may
|
|
418
|
+
* hold an older one, shown before its HotUpdater module was created, so
|
|
419
|
+
* [rescanAll] has every tag handed out looked at once more.
|
|
420
|
+
*/
|
|
421
|
+
internal class HotUpdaterLiveRootTags(
|
|
422
|
+
private val maxUnheld: Int = 64,
|
|
423
|
+
) {
|
|
424
|
+
private var nextUnseen = ROOT_TAG_FIRST
|
|
425
|
+
private val live = linkedSetOf<Int>()
|
|
426
|
+
private val unheld = ArrayDeque<Int>()
|
|
427
|
+
|
|
428
|
+
/** Looks at every tag handed out again on the next [liveTags], as after a runtime registered. Rare. */
|
|
429
|
+
@Synchronized
|
|
430
|
+
fun rescanAll() {
|
|
431
|
+
nextUnseen = ROOT_TAG_FIRST
|
|
432
|
+
unheld.clear()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** The live root tags, with [nextRootViewTag] React Native's counter; [isHeld] whether a runtime holds a tag's root. */
|
|
436
|
+
@Synchronized
|
|
437
|
+
fun liveTags(
|
|
438
|
+
nextRootViewTag: Int,
|
|
439
|
+
isHeld: (Int) -> Boolean,
|
|
440
|
+
): List<Int> {
|
|
441
|
+
while (nextUnseen < nextRootViewTag) {
|
|
442
|
+
if (nextUnseen !in live) unheld.addLast(nextUnseen)
|
|
443
|
+
nextUnseen += ROOT_TAG_STEP
|
|
444
|
+
}
|
|
445
|
+
live.retainAll(isHeld)
|
|
446
|
+
val held = unheld.filter(isHeld)
|
|
447
|
+
unheld.removeAll(held.toSet())
|
|
448
|
+
live += held
|
|
449
|
+
while (unheld.size > maxUnheld) unheld.removeFirst()
|
|
450
|
+
return live.toList()
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private companion object {
|
|
454
|
+
const val ROOT_TAG_FIRST = 1
|
|
455
|
+
const val ROOT_TAG_STEP = 10
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Root views React Native's UI managers know, in any window. Main thread. */
|
|
460
|
+
internal interface HotUpdaterRuntimeRoots {
|
|
461
|
+
/** A registered runtime, by the URL it loaded its bundle from. */
|
|
462
|
+
data class Runtime(
|
|
463
|
+
val sourceURL: String?,
|
|
464
|
+
/** The runtime itself, which content is bound to the launch of. */
|
|
465
|
+
val runtime: Any? = null,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
/** The registered runtime holding root view [rootTag], or null. */
|
|
469
|
+
fun runtimeOfRootTag(rootTag: Int): Runtime?
|
|
470
|
+
|
|
471
|
+
/** The registered runtimes whose root view a started [activity] shows in any of its windows. */
|
|
472
|
+
fun runtimesShownIn(activity: Activity): List<Runtime>
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** React Native root views an activity's decor view contains. Main thread. */
|
|
476
|
+
internal fun interface HotUpdaterDecorRoots {
|
|
477
|
+
/** A root view, by its root tag and the URL its runtime loaded, null before it joins one. */
|
|
478
|
+
data class Root(
|
|
479
|
+
val rootTag: Int,
|
|
480
|
+
val sourceURL: String?,
|
|
481
|
+
/** The runtime holding the view, which content is bound to the launch of. */
|
|
482
|
+
val runtime: Any? = null,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
fun rootsIn(activity: Activity): List<Root>
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
internal fun rootViewsIn(view: View): List<ReactRootView> {
|
|
489
|
+
if (view is ReactRootView) return listOf(view)
|
|
490
|
+
if (view !is ViewGroup) return emptyList()
|
|
491
|
+
return (0 until view.childCount).flatMap { rootViewsIn(view.getChildAt(it)) }
|
|
492
|
+
}
|