@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
|
@@ -3,56 +3,140 @@ package com.hotupdater
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.app.Application
|
|
5
5
|
import android.os.Bundle
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.view.ViewTreeObserver
|
|
6
9
|
import androidx.lifecycle.Lifecycle
|
|
10
|
+
import androidx.lifecycle.LifecycleOwner
|
|
7
11
|
import androidx.lifecycle.ProcessLifecycleOwner
|
|
12
|
+
import com.facebook.react.ReactRootView
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
* Runs an action once this process
|
|
15
|
+
* Runs an action once a started activity of this process shows a React Native
|
|
16
|
+
* view.
|
|
11
17
|
*
|
|
12
18
|
* A headless JS task (HeadlessJsTaskService, which background push libraries
|
|
13
|
-
* start) loads the bundle with no activity and never renders.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
19
|
+
* start) loads the bundle with no activity and never renders. A brownfield app
|
|
20
|
+
* loads the bundle with its first native screen, and the user may never open a
|
|
21
|
+
* React Native screen in that process. Waiting for a React Native view keeps
|
|
22
|
+
* both launches from counting as one that ended before its first render.
|
|
16
23
|
*
|
|
17
24
|
* Android does not replay lifecycle callbacks to a late registration. An
|
|
18
25
|
* activity that started before this gate existed, as when a brownfield app adds
|
|
19
26
|
* React Native to an activity already on screen, is read from
|
|
20
|
-
* [processHasStartedActivity] instead.
|
|
27
|
+
* [processHasStartedActivity] instead, and counts at once. A gate that knows
|
|
28
|
+
* those activities ([activitiesStartedBefore]) checks them for a React Native
|
|
29
|
+
* view instead, as a named instance's gate must: a started activity says
|
|
30
|
+
* nothing about whether that instance's view is shown.
|
|
21
31
|
*/
|
|
22
32
|
internal class ActivityStartGate(
|
|
23
33
|
private val application: Application?,
|
|
24
34
|
private val processHasStartedActivity: () -> Boolean = ::hasStartedActivity,
|
|
35
|
+
private val showsReactView: (Activity) -> Boolean = ::hasReactRootView,
|
|
36
|
+
private val watchLayout: (Activity, () -> Unit) -> () -> Unit = ::watchGlobalLayout,
|
|
37
|
+
private val activitiesStartedBefore: (() -> Collection<Activity>)? = null,
|
|
38
|
+
private val isStarted: (Activity) -> Boolean = { isActivityStarted(it) },
|
|
39
|
+
private val mainThread: HotUpdaterMainThread = HotUpdaterMainThread.android,
|
|
40
|
+
/**
|
|
41
|
+
* Runs on the main thread whenever this gate is about to look at an
|
|
42
|
+
* activity's views, before its action may run: a view now found there
|
|
43
|
+
* may be one whose first content appeared before it could be found, which
|
|
44
|
+
* verifies its launch instead of arming it.
|
|
45
|
+
*/
|
|
46
|
+
private val beforeCheckingViews: () -> Unit = {},
|
|
25
47
|
) : Application.ActivityLifecycleCallbacks {
|
|
26
|
-
private
|
|
48
|
+
private val startedActivities = mutableSetOf<Activity>()
|
|
49
|
+
private val layoutWatches = mutableMapOf<Activity, () -> Unit>()
|
|
27
50
|
private var pendingAction: (() -> Unit)? = null
|
|
28
51
|
|
|
52
|
+
// Counts the requests in the order they were made. A request made off the
|
|
53
|
+
// main thread runs later, and must not replace a newer one made there.
|
|
54
|
+
private var requests = 0L
|
|
55
|
+
|
|
29
56
|
init {
|
|
30
57
|
application?.registerActivityLifecycleCallbacks(this)
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
/**
|
|
34
|
-
* Runs [action] now when
|
|
35
|
-
*
|
|
36
|
-
* Without an application to observe, it runs [action] now.
|
|
61
|
+
* Runs [action] now when a started activity shows a React Native view,
|
|
62
|
+
* otherwise when one does. It replaces an action still waiting; null
|
|
63
|
+
* cancels it. Without an application to observe, it runs [action] now.
|
|
37
64
|
*/
|
|
38
65
|
fun runWhenActivityStarted(action: (() -> Unit)?) {
|
|
66
|
+
val request = synchronized(this) { ++requests }
|
|
67
|
+
// Views and their layout listeners belong to the main thread.
|
|
68
|
+
if (!mainThread.isCurrent()) {
|
|
69
|
+
mainThread.post { runWhenActivityStarted(request, action) }
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
runWhenActivityStarted(request, action)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun runWhenActivityStarted(
|
|
76
|
+
request: Long,
|
|
77
|
+
action: (() -> Unit)?,
|
|
78
|
+
) {
|
|
79
|
+
beforeCheckingViews()
|
|
39
80
|
val runNow =
|
|
40
81
|
synchronized(this) {
|
|
82
|
+
if (request != requests) return
|
|
83
|
+
stopWatching()
|
|
84
|
+
pendingAction = null
|
|
85
|
+
if (application != null) activitiesStartedBefore?.let { startedActivities += it() }
|
|
41
86
|
val waits =
|
|
42
87
|
application != null &&
|
|
43
|
-
startedActivities
|
|
44
|
-
!processHasStartedActivity()
|
|
45
|
-
|
|
88
|
+
startedActivities.none(showsReactView) &&
|
|
89
|
+
(startedActivities.isNotEmpty() || activitiesStartedBefore != null || !processHasStartedActivity())
|
|
90
|
+
if (waits && action != null) {
|
|
91
|
+
pendingAction = action
|
|
92
|
+
startedActivities.forEach(::watch)
|
|
93
|
+
}
|
|
46
94
|
action.takeUnless { waits }
|
|
47
95
|
}
|
|
48
96
|
runNow?.invoke()
|
|
49
97
|
}
|
|
50
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Counts [activity], which this gate may never have seen start, as
|
|
101
|
+
* started when it is, and while an action waits, checks it for a React
|
|
102
|
+
* Native view and watches its layout for one. A named instance's runtime
|
|
103
|
+
* hands over its activity when its module is created.
|
|
104
|
+
*/
|
|
105
|
+
fun watchActivity(activity: Activity) {
|
|
106
|
+
if (!mainThread.isCurrent()) {
|
|
107
|
+
mainThread.post { watchActivity(activity) }
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
beforeCheckingViews()
|
|
111
|
+
val action =
|
|
112
|
+
synchronized(this) {
|
|
113
|
+
// A runtime keeps its activity after it stops, as when the app
|
|
114
|
+
// goes to the background while the bundle loads. One that
|
|
115
|
+
// isn't started now counts when this gate sees it start.
|
|
116
|
+
if (application == null || !isStarted(activity)) return
|
|
117
|
+
// Counted until it stops, as one this gate saw start.
|
|
118
|
+
startedActivities += activity
|
|
119
|
+
if (pendingAction == null) return
|
|
120
|
+
if (!showsReactView(activity)) {
|
|
121
|
+
watch(activity)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
takePendingAction()
|
|
125
|
+
}
|
|
126
|
+
action?.invoke()
|
|
127
|
+
}
|
|
128
|
+
|
|
51
129
|
override fun onActivityStarted(activity: Activity) {
|
|
130
|
+
beforeCheckingViews()
|
|
52
131
|
val action =
|
|
53
132
|
synchronized(this) {
|
|
54
|
-
startedActivities +=
|
|
55
|
-
|
|
133
|
+
startedActivities += activity
|
|
134
|
+
if (pendingAction == null) return
|
|
135
|
+
if (!showsReactView(activity)) {
|
|
136
|
+
watch(activity)
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
takePendingAction()
|
|
56
140
|
}
|
|
57
141
|
action?.invoke()
|
|
58
142
|
}
|
|
@@ -60,10 +144,41 @@ internal class ActivityStartGate(
|
|
|
60
144
|
override fun onActivityStopped(activity: Activity) {
|
|
61
145
|
synchronized(this) {
|
|
62
146
|
// An activity started before this gate existed was never counted.
|
|
63
|
-
|
|
147
|
+
startedActivities -= activity
|
|
148
|
+
layoutWatches.remove(activity)?.invoke()
|
|
64
149
|
}
|
|
65
150
|
}
|
|
66
151
|
|
|
152
|
+
private fun watch(activity: Activity) {
|
|
153
|
+
if (activity in layoutWatches) return
|
|
154
|
+
layoutWatches[activity] = watchLayout(activity) { onLayout(activity) }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private fun onLayout(activity: Activity) {
|
|
158
|
+
beforeCheckingViews()
|
|
159
|
+
val action =
|
|
160
|
+
synchronized(this) {
|
|
161
|
+
if (pendingAction == null ||
|
|
162
|
+
activity !in startedActivities ||
|
|
163
|
+
!showsReactView(activity)
|
|
164
|
+
) {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
takePendingAction()
|
|
168
|
+
}
|
|
169
|
+
action?.invoke()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun takePendingAction(): (() -> Unit)? {
|
|
173
|
+
stopWatching()
|
|
174
|
+
return pendingAction.also { pendingAction = null }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun stopWatching() {
|
|
178
|
+
layoutWatches.values.forEach { it() }
|
|
179
|
+
layoutWatches.clear()
|
|
180
|
+
}
|
|
181
|
+
|
|
67
182
|
override fun onActivityCreated(
|
|
68
183
|
activity: Activity,
|
|
69
184
|
savedInstanceState: Bundle?,
|
|
@@ -92,3 +207,49 @@ private fun hasStartedActivity(): Boolean =
|
|
|
92
207
|
.lifecycle.currentState
|
|
93
208
|
.isAtLeast(Lifecycle.State.STARTED)
|
|
94
209
|
}.getOrDefault(false)
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Whether [activity] is started now. An activity that reports its lifecycle
|
|
213
|
+
* (ComponentActivity, which ReactActivity and AppCompatActivity are) says so.
|
|
214
|
+
* Any other, such as a plain Activity hosting a ReactRootView itself, is
|
|
215
|
+
* started while its window is shown: the framework hides a stopped
|
|
216
|
+
* activity's window.
|
|
217
|
+
*/
|
|
218
|
+
internal fun isActivityStarted(
|
|
219
|
+
activity: Activity,
|
|
220
|
+
windowShown: (Activity) -> Boolean = ::isWindowShown,
|
|
221
|
+
): Boolean {
|
|
222
|
+
if (activity.isFinishing || activity.isDestroyed) return false
|
|
223
|
+
val owner = activity as? LifecycleOwner ?: return windowShown(activity)
|
|
224
|
+
return runCatching { owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) }.getOrDefault(false)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private fun isWindowShown(activity: Activity): Boolean {
|
|
228
|
+
val decorView = activity.window?.peekDecorView() ?: return false
|
|
229
|
+
return decorView.isAttachedToWindow && decorView.windowVisibility == View.VISIBLE
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private fun hasReactRootView(activity: Activity): Boolean = activity.window?.peekDecorView()?.let(::containsReactRootView) ?: false
|
|
233
|
+
|
|
234
|
+
private fun containsReactRootView(view: View): Boolean =
|
|
235
|
+
view is ReactRootView ||
|
|
236
|
+
(view is ViewGroup && (0 until view.childCount).any { containsReactRootView(view.getChildAt(it)) })
|
|
237
|
+
|
|
238
|
+
// A React Native screen added later, such as a fragment of a native activity,
|
|
239
|
+
// changes the activity's layout.
|
|
240
|
+
// A dialog or presentation shows its views in a window of its own, whose
|
|
241
|
+
// layout this activity's never sees; the activity's window loses focus to it.
|
|
242
|
+
private fun watchGlobalLayout(
|
|
243
|
+
activity: Activity,
|
|
244
|
+
onLayout: () -> Unit,
|
|
245
|
+
): () -> Unit {
|
|
246
|
+
val decorView = activity.window?.decorView ?: return {}
|
|
247
|
+
val listener = ViewTreeObserver.OnGlobalLayoutListener(onLayout)
|
|
248
|
+
val focusListener = ViewTreeObserver.OnWindowFocusChangeListener { onLayout() }
|
|
249
|
+
decorView.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
|
250
|
+
decorView.viewTreeObserver.addOnWindowFocusChangeListener(focusListener)
|
|
251
|
+
return {
|
|
252
|
+
runCatching { decorView.viewTreeObserver.removeOnGlobalLayoutListener(listener) }
|
|
253
|
+
runCatching { decorView.viewTreeObserver.removeOnWindowFocusChangeListener(focusListener) }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -32,12 +32,14 @@ interface ArtifactFileResolver {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* POSTs `{ paths }` to the files URL the SDK composed, with the update check's
|
|
35
|
-
* headers and request timeout.
|
|
35
|
+
* headers and request timeout. [onResponse] receives each response's status
|
|
36
|
+
* and body, or a null body when it could not be read.
|
|
36
37
|
*/
|
|
37
38
|
class HttpArtifactFileResolver(
|
|
38
39
|
private val filesUrl: String,
|
|
39
40
|
private val headers: Map<String, String>,
|
|
40
41
|
private val timeoutMs: Long = DEFAULT_TIMEOUT_MS,
|
|
42
|
+
private val onResponse: ((status: Int, body: String?) -> Unit)? = null,
|
|
41
43
|
) : ArtifactFileResolver {
|
|
42
44
|
override suspend fun resolve(paths: List<String>): Map<String, ResolvedArtifactFile> =
|
|
43
45
|
withContext(Dispatchers.IO) {
|
|
@@ -57,20 +59,30 @@ class HttpArtifactFileResolver(
|
|
|
57
59
|
.newCall(request)
|
|
58
60
|
.execute()
|
|
59
61
|
.use { response ->
|
|
62
|
+
val body =
|
|
63
|
+
try {
|
|
64
|
+
response.body?.string()
|
|
65
|
+
} catch (error: java.io.IOException) {
|
|
66
|
+
onResponse?.invoke(response.code, null)
|
|
67
|
+
throw error
|
|
68
|
+
}
|
|
69
|
+
onResponse?.invoke(response.code, body)
|
|
60
70
|
if (!response.isSuccessful) {
|
|
61
71
|
throw HttpStatusException(response.code, response.message)
|
|
62
72
|
}
|
|
73
|
+
// As JSON.parse reads it, which org.json's parser does not.
|
|
63
74
|
val files =
|
|
64
|
-
|
|
65
|
-
?: throw
|
|
75
|
+
(StrictJson.parse(body ?: "") as? JSONObject)?.opt("files") as? JSONObject
|
|
76
|
+
?: throw InvalidUpdateResponseException("Artifact files response has no files")
|
|
66
77
|
paths.associateWith { path ->
|
|
67
78
|
val file =
|
|
68
|
-
files.
|
|
69
|
-
?: throw
|
|
79
|
+
files.opt(path) as? JSONObject
|
|
80
|
+
?: throw InvalidUpdateResponseException("Artifact files response omits $path")
|
|
70
81
|
ResolvedArtifactFile(
|
|
71
|
-
url =
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
url =
|
|
83
|
+
file.opt("url") as? String
|
|
84
|
+
?: throw InvalidUpdateResponseException("Artifact files response has no URL for $path"),
|
|
85
|
+
compression = file.opt("compression") as? String,
|
|
74
86
|
)
|
|
75
87
|
}
|
|
76
88
|
}
|