@hot-updater/react-native 0.36.7 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +357 -6
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +268 -34
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +75 -9
- package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +2 -16
- package/android/src/main/java/com/hotupdater/ReleaseCatalogCacheService.kt +166 -0
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +1 -2
- package/android/src/newarch/HotUpdater.kt +1 -1
- package/android/src/newarch/HotUpdaterModule.kt +116 -0
- package/android/src/oldarch/HotUpdater.kt +1 -1
- package/android/src/oldarch/HotUpdaterModule.kt +128 -0
- package/android/src/oldarch/HotUpdaterSpec.kt +38 -0
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +280 -2
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +57 -2
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +118 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +491 -30
- package/ios/HotUpdater/Internal/BundleMetadata.swift +229 -6
- package/ios/HotUpdater/Internal/HotUpdater.mm +122 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +176 -7
- package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +213 -0
- package/lib/commonjs/catalogCacheNative.js +44 -0
- package/lib/commonjs/catalogCacheNative.js.map +1 -0
- package/lib/commonjs/catalogCacheNative.spec.js +31 -0
- package/lib/commonjs/catalogCacheNative.spec.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +231 -60
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +604 -0
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -0
- package/lib/commonjs/error.js +8 -1
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/{fetchUpdateInfo.js → fetchJSON.js} +11 -14
- package/lib/commonjs/fetchJSON.js.map +1 -0
- package/lib/commonjs/httpClient.js +153 -0
- package/lib/commonjs/httpClient.js.map +1 -0
- package/lib/commonjs/httpClient.spec.js +102 -0
- package/lib/commonjs/httpClient.spec.js.map +1 -0
- package/lib/commonjs/index.js +47 -70
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +115 -159
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/iosInterop.spec.js +18 -0
- package/lib/commonjs/iosInterop.spec.js.map +1 -0
- package/lib/commonjs/native.js +222 -53
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +226 -12
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js +218 -0
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.js +133 -0
- package/lib/commonjs/notifyAppReadyAnalytics.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js +246 -0
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js +24 -0
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js +117 -0
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +179 -0
- package/lib/commonjs/releaseCatalogCache.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.spec.js +195 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -0
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +64 -0
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/types.js +0 -12
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/wrap.js +7 -82
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +203 -56
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/commonjs/wrap.types.js +6 -0
- package/lib/commonjs/wrap.types.js.map +1 -0
- package/lib/module/catalogCacheNative.js +36 -0
- package/lib/module/catalogCacheNative.js.map +1 -0
- package/lib/module/catalogCacheNative.spec.js +31 -0
- package/lib/module/catalogCacheNative.spec.js.map +1 -0
- package/lib/module/checkForUpdate.js +233 -62
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +603 -0
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -0
- package/lib/module/error.js +6 -0
- package/lib/module/error.js.map +1 -1
- package/lib/module/{fetchUpdateInfo.js → fetchJSON.js} +9 -12
- package/lib/module/fetchJSON.js.map +1 -0
- package/lib/module/httpClient.js +148 -0
- package/lib/module/httpClient.js.map +1 -0
- package/lib/module/httpClient.spec.js +102 -0
- package/lib/module/httpClient.spec.js.map +1 -0
- package/lib/module/index.js +48 -65
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +115 -159
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/iosInterop.spec.js +18 -0
- package/lib/module/iosInterop.spec.js.map +1 -0
- package/lib/module/native.js +206 -49
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +226 -12
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js +217 -0
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.js +128 -0
- package/lib/module/notifyAppReadyAnalytics.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.spec.js +245 -0
- package/lib/module/notifyAppReadyAnalytics.spec.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.test-utils.js +18 -0
- package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js +116 -0
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +171 -0
- package/lib/module/releaseCatalogCache.js.map +1 -0
- package/lib/module/releaseCatalogCache.spec.js +195 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -0
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +64 -0
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/types.js +0 -12
- package/lib/module/types.js.map +1 -1
- package/lib/module/wrap.js +6 -81
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +203 -56
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/module/wrap.types.js +4 -0
- package/lib/module/wrap.types.js.map +1 -0
- package/lib/typescript/commonjs/catalogCacheNative.d.ts +4 -0
- package/lib/typescript/commonjs/catalogCacheNative.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +14 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/error.d.ts +3 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +6 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -0
- package/lib/typescript/commonjs/httpClient.d.ts +56 -0
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +31 -40
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +84 -17
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +13 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +1 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +25 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +40 -5
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +0 -118
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +2 -191
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +65 -0
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -0
- package/lib/typescript/module/catalogCacheNative.d.ts +4 -0
- package/lib/typescript/module/catalogCacheNative.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +14 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/error.d.ts +3 -0
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +6 -0
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -0
- package/lib/typescript/module/httpClient.d.ts +56 -0
- package/lib/typescript/module/httpClient.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +31 -40
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +84 -17
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +13 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +1 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +25 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +40 -5
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +0 -118
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +2 -191
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +65 -0
- package/lib/typescript/module/wrap.types.d.ts.map +1 -0
- package/package.json +6 -24
- package/src/catalogCacheNative.spec.ts +63 -0
- package/src/catalogCacheNative.ts +74 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +596 -0
- package/src/checkForUpdate.ts +367 -96
- package/src/error.ts +7 -0
- package/src/{fetchUpdateInfo.ts → fetchJSON.ts} +8 -15
- package/src/httpClient.spec.ts +132 -0
- package/src/httpClient.ts +261 -0
- package/src/index.spec.ts +108 -201
- package/src/index.ts +68 -91
- package/src/iosInterop.spec.ts +30 -0
- package/src/native.spec.ts +226 -10
- package/src/native.ts +385 -81
- package/src/notifyAppReadyAnalytics.boundary.spec.ts +234 -0
- package/src/notifyAppReadyAnalytics.spec.ts +243 -0
- package/src/notifyAppReadyAnalytics.test-utils.ts +31 -0
- package/src/notifyAppReadyAnalytics.transport.spec.ts +130 -0
- package/src/notifyAppReadyAnalytics.ts +194 -0
- package/src/releaseCatalogCache.spec.ts +282 -0
- package/src/releaseCatalogCache.ts +341 -0
- package/src/releaseCatalogCacheNativeContract.spec.ts +80 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +51 -5
- package/src/types.ts +0 -140
- package/src/wrap.spec.ts +251 -56
- package/src/wrap.tsx +23 -318
- package/src/wrap.types.ts +78 -0
- package/android/src/main/java/com/hotupdater/StringResourceUtils.kt +0 -45
- package/app.plugin.js +0 -1
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +0 -7
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +0 -4
- package/lib/commonjs/DefaultResolver.js +0 -50
- package/lib/commonjs/DefaultResolver.js.map +0 -1
- package/lib/commonjs/DefaultResolver.spec.js +0 -144
- package/lib/commonjs/DefaultResolver.spec.js.map +0 -1
- package/lib/commonjs/fetchUpdateInfo.js.map +0 -1
- package/lib/module/DefaultResolver.js +0 -46
- package/lib/module/DefaultResolver.js.map +0 -1
- package/lib/module/DefaultResolver.spec.js +0 -144
- package/lib/module/DefaultResolver.spec.js.map +0 -1
- package/lib/module/fetchUpdateInfo.js.map +0 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts +0 -11
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +0 -1
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +0 -7
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +0 -1
- package/lib/typescript/module/DefaultResolver.d.ts +0 -11
- package/lib/typescript/module/DefaultResolver.d.ts.map +0 -1
- package/lib/typescript/module/fetchUpdateInfo.d.ts +0 -7
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +0 -1
- package/plugin/build/transformers.js +0 -314
- package/plugin/build/withHotUpdater.js +0 -228
- package/src/DefaultResolver.spec.ts +0 -193
- package/src/DefaultResolver.ts +0 -63
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import androidx.core.util.AtomicFile
|
|
4
5
|
import org.json.JSONArray
|
|
5
6
|
import org.json.JSONObject
|
|
6
7
|
import java.io.File
|
|
8
|
+
import java.io.FileNotFoundException
|
|
9
|
+
import java.io.FileOutputStream
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Bundle metadata for managing stable/staging bundles and verification state
|
|
@@ -13,38 +16,66 @@ data class BundleMetadata(
|
|
|
13
16
|
val isolationKey: String? = null,
|
|
14
17
|
val stableBundleId: String? = null,
|
|
15
18
|
val stagingBundleId: String? = null,
|
|
19
|
+
val stableSelection: PersistedSelection? = null,
|
|
20
|
+
val stagingSelection: PersistedSelection? = null,
|
|
21
|
+
val pendingUpdateStrategy: String? = null,
|
|
22
|
+
val pendingTransition: PendingSelectionTransition? = null,
|
|
16
23
|
val verificationPending: Boolean = false,
|
|
24
|
+
val highestSeenCatalogs: Map<String, CatalogHighWater> = emptyMap(),
|
|
25
|
+
val currentSelectionContexts: Map<String, String> = emptyMap(),
|
|
17
26
|
val updatedAt: Long = System.currentTimeMillis(),
|
|
18
27
|
) {
|
|
19
28
|
companion object {
|
|
20
29
|
private const val TAG = "BundleMetadata"
|
|
21
|
-
const val SCHEMA_VERSION = "metadata-
|
|
30
|
+
const val SCHEMA_VERSION = "metadata-v2"
|
|
22
31
|
const val METADATA_FILENAME = "metadata.json"
|
|
23
32
|
|
|
24
|
-
fun fromJson(json: JSONObject): BundleMetadata
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
fun fromJson(json: JSONObject): BundleMetadata {
|
|
34
|
+
val stableBundleId = json.optNullableString("stableBundleId")
|
|
35
|
+
val stagingBundleId = json.optNullableString("stagingBundleId")
|
|
36
|
+
val stableSelection =
|
|
37
|
+
json.optJSONObject("stableSelection")?.let(PersistedSelection::fromJson)
|
|
38
|
+
?: stableBundleId?.let(PersistedSelection::legacyBundle)
|
|
39
|
+
val stagingSelection =
|
|
40
|
+
json.optJSONObject("stagingSelection")?.let(PersistedSelection::fromJson)
|
|
41
|
+
?: stagingBundleId?.let(PersistedSelection::legacyBundle)
|
|
42
|
+
val highestSeenCatalogs = linkedMapOf<String, CatalogHighWater>()
|
|
43
|
+
json.optJSONObject("highestSeenCatalogs")?.let { highWaters ->
|
|
44
|
+
highWaters.keys().forEach { key ->
|
|
45
|
+
highWaters.optJSONObject(key)?.let { value ->
|
|
46
|
+
highestSeenCatalogs[key] = CatalogHighWater.fromJson(value)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
val currentSelectionContexts = linkedMapOf<String, String>()
|
|
51
|
+
json.optJSONObject("currentSelectionContexts")?.let { contexts ->
|
|
52
|
+
contexts.keys().forEach { key ->
|
|
53
|
+
contexts.optString(key).takeIf { it.isNotEmpty() }?.let { value ->
|
|
54
|
+
currentSelectionContexts[key] = value
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return BundleMetadata(
|
|
59
|
+
schema = SCHEMA_VERSION,
|
|
27
60
|
isolationKey =
|
|
28
61
|
if (json.has("isolationKey") && !json.isNull("isolationKey")) {
|
|
29
62
|
json.getString("isolationKey").takeIf { it.isNotEmpty() }
|
|
30
63
|
} else {
|
|
31
64
|
null
|
|
32
65
|
},
|
|
33
|
-
stableBundleId =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (json.has("stagingBundleId") && !json.isNull("stagingBundleId")) {
|
|
41
|
-
json.getString("stagingBundleId").takeIf { it.isNotEmpty() }
|
|
42
|
-
} else {
|
|
43
|
-
null
|
|
44
|
-
},
|
|
66
|
+
stableBundleId = stableSelection?.bundleId ?: stableBundleId,
|
|
67
|
+
stagingBundleId = stagingSelection?.bundleId ?: stagingBundleId,
|
|
68
|
+
stableSelection = stableSelection,
|
|
69
|
+
stagingSelection = stagingSelection,
|
|
70
|
+
pendingUpdateStrategy = json.optNullableString("pendingUpdateStrategy"),
|
|
71
|
+
pendingTransition =
|
|
72
|
+
json.optJSONObject("pendingTransition")?.let(PendingSelectionTransition::fromJson),
|
|
45
73
|
verificationPending = json.optBoolean("verificationPending", false),
|
|
74
|
+
highestSeenCatalogs = highestSeenCatalogs,
|
|
75
|
+
currentSelectionContexts = currentSelectionContexts,
|
|
46
76
|
updatedAt = json.optLong("updatedAt", System.currentTimeMillis()),
|
|
47
77
|
)
|
|
78
|
+
}
|
|
48
79
|
|
|
49
80
|
fun loadFromFile(
|
|
50
81
|
file: File,
|
|
@@ -85,19 +116,155 @@ data class BundleMetadata(
|
|
|
85
116
|
put("isolationKey", isolationKey ?: JSONObject.NULL)
|
|
86
117
|
put("stableBundleId", stableBundleId ?: JSONObject.NULL)
|
|
87
118
|
put("stagingBundleId", stagingBundleId ?: JSONObject.NULL)
|
|
119
|
+
put("stableSelection", stableSelection?.toJson() ?: JSONObject.NULL)
|
|
120
|
+
put("stagingSelection", stagingSelection?.toJson() ?: JSONObject.NULL)
|
|
121
|
+
put("pendingUpdateStrategy", pendingUpdateStrategy ?: JSONObject.NULL)
|
|
122
|
+
put("pendingTransition", pendingTransition?.toJson() ?: JSONObject.NULL)
|
|
88
123
|
put("verificationPending", verificationPending)
|
|
124
|
+
put(
|
|
125
|
+
"highestSeenCatalogs",
|
|
126
|
+
JSONObject().apply {
|
|
127
|
+
highestSeenCatalogs.toSortedMap().forEach { (key, value) ->
|
|
128
|
+
put(key, value.toJson())
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
put(
|
|
133
|
+
"currentSelectionContexts",
|
|
134
|
+
JSONObject().apply {
|
|
135
|
+
currentSelectionContexts.toSortedMap().forEach { (key, value) ->
|
|
136
|
+
put(key, value)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
)
|
|
89
140
|
put("updatedAt", updatedAt)
|
|
90
141
|
}
|
|
91
142
|
|
|
92
143
|
fun saveToFile(file: File): Boolean =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
AtomicFile(file).let { atomicFile ->
|
|
145
|
+
var output: FileOutputStream? = null
|
|
146
|
+
try {
|
|
147
|
+
file.parentFile?.mkdirs()
|
|
148
|
+
output = atomicFile.startWrite()
|
|
149
|
+
output.write(toJson().toString(2).toByteArray())
|
|
150
|
+
atomicFile.finishWrite(output)
|
|
151
|
+
Log.d(TAG, "Saved metadata to file: ${file.absolutePath}")
|
|
152
|
+
true
|
|
153
|
+
} catch (e: Exception) {
|
|
154
|
+
output?.let(atomicFile::failWrite)
|
|
155
|
+
Log.e(TAG, "Failed to save metadata to file", e)
|
|
156
|
+
false
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
data class CatalogHighWater(
|
|
162
|
+
val generation: Long,
|
|
163
|
+
val catalogHash: String,
|
|
164
|
+
) {
|
|
165
|
+
companion object {
|
|
166
|
+
fun fromJson(json: JSONObject): CatalogHighWater =
|
|
167
|
+
CatalogHighWater(
|
|
168
|
+
generation = json.getLong("generation"),
|
|
169
|
+
catalogHash = json.getString("catalogHash"),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fun toJson(): JSONObject =
|
|
174
|
+
JSONObject().apply {
|
|
175
|
+
put("generation", generation)
|
|
176
|
+
put("catalogHash", catalogHash)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
data class PersistedSelection(
|
|
181
|
+
val kind: String,
|
|
182
|
+
val releaseId: String?,
|
|
183
|
+
val bundleId: String,
|
|
184
|
+
val catalogId: String?,
|
|
185
|
+
val scopeKey: String?,
|
|
186
|
+
val generation: Long?,
|
|
187
|
+
val catalogHash: String?,
|
|
188
|
+
val channel: String,
|
|
189
|
+
val selectionContextHash: String?,
|
|
190
|
+
) {
|
|
191
|
+
companion object {
|
|
192
|
+
fun legacyBundle(bundleId: String): PersistedSelection =
|
|
193
|
+
PersistedSelection(
|
|
194
|
+
kind = "BUNDLE",
|
|
195
|
+
releaseId = null,
|
|
196
|
+
bundleId = bundleId,
|
|
197
|
+
catalogId = null,
|
|
198
|
+
scopeKey = null,
|
|
199
|
+
generation = null,
|
|
200
|
+
catalogHash = null,
|
|
201
|
+
channel = "",
|
|
202
|
+
selectionContextHash = null,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
fun fromJson(json: JSONObject): PersistedSelection =
|
|
206
|
+
PersistedSelection(
|
|
207
|
+
kind = json.getString("kind"),
|
|
208
|
+
releaseId = json.optNullableString("releaseId"),
|
|
209
|
+
bundleId = json.getString("bundleId"),
|
|
210
|
+
catalogId = json.optNullableString("catalogId"),
|
|
211
|
+
scopeKey = json.optNullableString("scopeKey"),
|
|
212
|
+
generation = if (json.has("generation") && !json.isNull("generation")) json.getLong("generation") else null,
|
|
213
|
+
catalogHash = json.optNullableString("catalogHash"),
|
|
214
|
+
channel = json.optString("channel", ""),
|
|
215
|
+
selectionContextHash = json.optNullableString("selectionContextHash"),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fun toJson(): JSONObject =
|
|
220
|
+
JSONObject().apply {
|
|
221
|
+
put("kind", kind)
|
|
222
|
+
put("releaseId", releaseId ?: JSONObject.NULL)
|
|
223
|
+
put("bundleId", bundleId)
|
|
224
|
+
put("catalogId", catalogId ?: JSONObject.NULL)
|
|
225
|
+
put("scopeKey", scopeKey ?: JSONObject.NULL)
|
|
226
|
+
put("generation", generation ?: JSONObject.NULL)
|
|
227
|
+
put("catalogHash", catalogHash ?: JSONObject.NULL)
|
|
228
|
+
put("channel", channel)
|
|
229
|
+
put("selectionContextHash", selectionContextHash ?: JSONObject.NULL)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fun toMap(): Map<String, Any?> =
|
|
233
|
+
mapOf(
|
|
234
|
+
"kind" to kind,
|
|
235
|
+
"releaseId" to releaseId,
|
|
236
|
+
"bundleId" to bundleId,
|
|
237
|
+
"catalogId" to catalogId,
|
|
238
|
+
"scopeKey" to scopeKey,
|
|
239
|
+
"generation" to generation?.toDouble(),
|
|
240
|
+
"catalogHash" to catalogHash,
|
|
241
|
+
"channel" to channel,
|
|
242
|
+
"selectionContextHash" to selectionContextHash,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
data class PendingSelectionTransition(
|
|
247
|
+
val fromReleaseId: String?,
|
|
248
|
+
val fromBundleId: String,
|
|
249
|
+
val toReleaseId: String?,
|
|
250
|
+
val toBundleId: String,
|
|
251
|
+
) {
|
|
252
|
+
companion object {
|
|
253
|
+
fun fromJson(json: JSONObject): PendingSelectionTransition =
|
|
254
|
+
PendingSelectionTransition(
|
|
255
|
+
fromReleaseId = json.optNullableString("fromReleaseId"),
|
|
256
|
+
fromBundleId = json.getString("fromBundleId"),
|
|
257
|
+
toReleaseId = json.optNullableString("toReleaseId"),
|
|
258
|
+
toBundleId = json.getString("toBundleId"),
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fun toJson(): JSONObject =
|
|
263
|
+
JSONObject().apply {
|
|
264
|
+
put("fromReleaseId", fromReleaseId ?: JSONObject.NULL)
|
|
265
|
+
put("fromBundleId", fromBundleId)
|
|
266
|
+
put("toReleaseId", toReleaseId ?: JSONObject.NULL)
|
|
267
|
+
put("toBundleId", toBundleId)
|
|
101
268
|
}
|
|
102
269
|
}
|
|
103
270
|
|
|
@@ -247,8 +414,12 @@ data class LaunchSelection(
|
|
|
247
414
|
)
|
|
248
415
|
|
|
249
416
|
data class LaunchReport(
|
|
250
|
-
val status: String = "
|
|
251
|
-
val
|
|
417
|
+
val status: String = "UNCHANGED",
|
|
418
|
+
val fromReleaseId: String? = null,
|
|
419
|
+
val fromBundleId: String? = null,
|
|
420
|
+
val toReleaseId: String? = null,
|
|
421
|
+
val toBundleId: String? = null,
|
|
422
|
+
val updateStrategy: String? = null,
|
|
252
423
|
) {
|
|
253
424
|
companion object {
|
|
254
425
|
private const val TAG = "LaunchReport"
|
|
@@ -256,13 +427,12 @@ data class LaunchReport(
|
|
|
256
427
|
|
|
257
428
|
fun fromJson(json: JSONObject): LaunchReport =
|
|
258
429
|
LaunchReport(
|
|
259
|
-
status = json.optString("status", "
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
},
|
|
430
|
+
status = json.optString("status", "UNCHANGED"),
|
|
431
|
+
fromReleaseId = json.optNullableString("fromReleaseId"),
|
|
432
|
+
fromBundleId = json.optNullableString("fromBundleId"),
|
|
433
|
+
toReleaseId = json.optNullableString("toReleaseId"),
|
|
434
|
+
toBundleId = json.optNullableString("toBundleId"),
|
|
435
|
+
updateStrategy = json.optNullableString("updateStrategy"),
|
|
266
436
|
)
|
|
267
437
|
|
|
268
438
|
fun loadFromFile(file: File): LaunchReport? =
|
|
@@ -281,7 +451,11 @@ data class LaunchReport(
|
|
|
281
451
|
fun toJson(): JSONObject =
|
|
282
452
|
JSONObject().apply {
|
|
283
453
|
put("status", status)
|
|
284
|
-
put("
|
|
454
|
+
put("fromReleaseId", fromReleaseId ?: JSONObject.NULL)
|
|
455
|
+
put("fromBundleId", fromBundleId ?: JSONObject.NULL)
|
|
456
|
+
put("toReleaseId", toReleaseId ?: JSONObject.NULL)
|
|
457
|
+
put("toBundleId", toBundleId ?: JSONObject.NULL)
|
|
458
|
+
put("updateStrategy", updateStrategy ?: JSONObject.NULL)
|
|
285
459
|
}
|
|
286
460
|
|
|
287
461
|
fun saveToFile(file: File): Boolean =
|
|
@@ -294,3 +468,63 @@ data class LaunchReport(
|
|
|
294
468
|
false
|
|
295
469
|
}
|
|
296
470
|
}
|
|
471
|
+
|
|
472
|
+
data class InstallationIdentity(
|
|
473
|
+
val installId: String,
|
|
474
|
+
val userId: String? = null,
|
|
475
|
+
val username: String? = null,
|
|
476
|
+
) {
|
|
477
|
+
companion object {
|
|
478
|
+
private const val TAG = "InstallationIdentity"
|
|
479
|
+
const val IDENTITY_FILENAME = "identity.json"
|
|
480
|
+
|
|
481
|
+
fun fromJson(json: JSONObject): InstallationIdentity =
|
|
482
|
+
InstallationIdentity(
|
|
483
|
+
installId = json.getString("installId"),
|
|
484
|
+
userId = json.optNullableString("userId"),
|
|
485
|
+
username = json.optNullableString("username"),
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
fun loadFromFile(file: File): InstallationIdentity? =
|
|
489
|
+
try {
|
|
490
|
+
AtomicFile(file).openRead().bufferedReader().use { reader ->
|
|
491
|
+
fromJson(JSONObject(reader.readText()))
|
|
492
|
+
}
|
|
493
|
+
} catch (_: FileNotFoundException) {
|
|
494
|
+
null
|
|
495
|
+
} catch (e: Exception) {
|
|
496
|
+
Log.e(TAG, "Failed to load installation identity", e)
|
|
497
|
+
null
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
fun toJson(): JSONObject =
|
|
502
|
+
JSONObject().apply {
|
|
503
|
+
put("installId", installId)
|
|
504
|
+
put("userId", userId ?: JSONObject.NULL)
|
|
505
|
+
put("username", username ?: JSONObject.NULL)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fun saveToFile(file: File): Boolean =
|
|
509
|
+
AtomicFile(file).let { atomicFile ->
|
|
510
|
+
var output: FileOutputStream? = null
|
|
511
|
+
try {
|
|
512
|
+
file.parentFile?.mkdirs()
|
|
513
|
+
output = atomicFile.startWrite()
|
|
514
|
+
output.write(toJson().toString(2).toByteArray())
|
|
515
|
+
atomicFile.finishWrite(output)
|
|
516
|
+
true
|
|
517
|
+
} catch (e: Exception) {
|
|
518
|
+
output?.let(atomicFile::failWrite)
|
|
519
|
+
Log.e(TAG, "Failed to save installation identity", e)
|
|
520
|
+
false
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private fun JSONObject.optNullableString(key: String): String? =
|
|
526
|
+
if (has(key) && !isNull(key)) {
|
|
527
|
+
getString(key).takeIf { it.isNotEmpty() }
|
|
528
|
+
} else {
|
|
529
|
+
null
|
|
530
|
+
}
|
|
@@ -7,6 +7,7 @@ import android.os.Process
|
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import kotlinx.coroutines.Dispatchers
|
|
9
9
|
import kotlinx.coroutines.withContext
|
|
10
|
+
import java.io.File
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Core implementation class for HotUpdater functionality
|
|
@@ -16,6 +17,7 @@ class HotUpdaterImpl {
|
|
|
16
17
|
private val bundleStorage: BundleStorageService
|
|
17
18
|
private val preferences: PreferencesService
|
|
18
19
|
private val recoveryManager: HotUpdaterRecoveryManager
|
|
20
|
+
private val releaseCatalogCache: ReleaseCatalogCacheService
|
|
19
21
|
private var currentLaunchSelection: LaunchSelection? = null
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -30,6 +32,10 @@ class HotUpdaterImpl {
|
|
|
30
32
|
this.bundleStorage = bundleStorage
|
|
31
33
|
this.preferences = preferences
|
|
32
34
|
this.recoveryManager = HotUpdaterRecoveryManager(this.context)
|
|
35
|
+
this.releaseCatalogCache =
|
|
36
|
+
ReleaseCatalogCacheService(
|
|
37
|
+
File(this.context.noBackupFilesDir, "hot-updater-release-catalog-cache"),
|
|
38
|
+
)
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
/**
|
|
@@ -148,7 +154,6 @@ class HotUpdaterImpl {
|
|
|
148
154
|
NativeConfigUtils.getString(
|
|
149
155
|
context,
|
|
150
156
|
NativeConfigUtils.CHANNEL_META_DATA_KEY,
|
|
151
|
-
"hot_updater_channel",
|
|
152
157
|
) ?: DEFAULT_CHANNEL
|
|
153
158
|
|
|
154
159
|
/**
|
|
@@ -220,7 +225,6 @@ class HotUpdaterImpl {
|
|
|
220
225
|
NativeConfigUtils.getString(
|
|
221
226
|
context,
|
|
222
227
|
NativeConfigUtils.FINGERPRINT_HASH_META_DATA_KEY,
|
|
223
|
-
"hot_updater_fingerprint_hash",
|
|
224
228
|
)
|
|
225
229
|
}
|
|
226
230
|
|
|
@@ -232,7 +236,6 @@ class HotUpdaterImpl {
|
|
|
232
236
|
NativeConfigUtils.getString(
|
|
233
237
|
context,
|
|
234
238
|
NativeConfigUtils.FINGERPRINT_HASH_META_DATA_KEY,
|
|
235
|
-
"hot_updater_fingerprint_hash",
|
|
236
239
|
)
|
|
237
240
|
|
|
238
241
|
/**
|
|
@@ -240,6 +243,11 @@ class HotUpdaterImpl {
|
|
|
240
243
|
* @return The channel name or null if not set
|
|
241
244
|
*/
|
|
242
245
|
fun getChannel(): String {
|
|
246
|
+
val activeSelection = bundleStorage.getActiveUpdateState()["activeSelection"] as? Map<*, *>
|
|
247
|
+
val receiptChannel = activeSelection?.get("channel") as? String
|
|
248
|
+
if (!receiptChannel.isNullOrEmpty()) {
|
|
249
|
+
return receiptChannel
|
|
250
|
+
}
|
|
243
251
|
val overriddenChannel = preferences.getItem(CHANNEL_STORAGE_KEY)
|
|
244
252
|
if (!overriddenChannel.isNullOrEmpty()) {
|
|
245
253
|
return overriddenChannel
|
|
@@ -248,7 +256,6 @@ class HotUpdaterImpl {
|
|
|
248
256
|
return NativeConfigUtils.getString(
|
|
249
257
|
context,
|
|
250
258
|
NativeConfigUtils.CHANNEL_META_DATA_KEY,
|
|
251
|
-
"hot_updater_channel",
|
|
252
259
|
) ?: DEFAULT_CHANNEL
|
|
253
260
|
}
|
|
254
261
|
|
|
@@ -276,8 +283,12 @@ class HotUpdaterImpl {
|
|
|
276
283
|
manifestFileHash: String?,
|
|
277
284
|
changedAssets: Map<String, ChangedAssetDescriptor>?,
|
|
278
285
|
channel: String?,
|
|
286
|
+
selection: PersistedSelection? = null,
|
|
279
287
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
280
288
|
) {
|
|
289
|
+
if (selection != null && !bundleStorage.stageReleaseSelection(selection)) {
|
|
290
|
+
throw IllegalStateException("Release catalog selection is stale")
|
|
291
|
+
}
|
|
281
292
|
bundleStorage.updateBundle(
|
|
282
293
|
bundleId,
|
|
283
294
|
fileUrl,
|
|
@@ -363,13 +374,68 @@ class HotUpdaterImpl {
|
|
|
363
374
|
}
|
|
364
375
|
}
|
|
365
376
|
|
|
366
|
-
/**
|
|
367
|
-
* Returns the launch report for the current process.
|
|
368
|
-
* Startup success and rollback are finalized before JS reads it.
|
|
369
|
-
* @return Map containing status and optional crashedBundleId
|
|
370
|
-
*/
|
|
371
377
|
fun notifyAppReady(): Map<String, Any?> = bundleStorage.notifyAppReady()
|
|
372
378
|
|
|
379
|
+
fun acceptReleaseCatalog(
|
|
380
|
+
catalogId: String,
|
|
381
|
+
scopeKey: String,
|
|
382
|
+
generation: Long,
|
|
383
|
+
catalogHash: String,
|
|
384
|
+
channel: String,
|
|
385
|
+
selectionContextHash: String,
|
|
386
|
+
): Boolean =
|
|
387
|
+
bundleStorage.acceptReleaseCatalog(
|
|
388
|
+
catalogId,
|
|
389
|
+
scopeKey,
|
|
390
|
+
generation,
|
|
391
|
+
catalogHash,
|
|
392
|
+
channel,
|
|
393
|
+
selectionContextHash,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
fun getActiveUpdateState(): Map<String, Any?> = bundleStorage.getActiveUpdateState()
|
|
397
|
+
|
|
398
|
+
fun getReleaseCatalogCache(partition: String): String? = releaseCatalogCache.get(partition)
|
|
399
|
+
|
|
400
|
+
fun setReleaseCatalogCache(
|
|
401
|
+
partition: String,
|
|
402
|
+
payload: String,
|
|
403
|
+
): Boolean = releaseCatalogCache.set(partition, payload)
|
|
404
|
+
|
|
405
|
+
fun removeReleaseCatalogCache(partition: String): Boolean = releaseCatalogCache.remove(partition)
|
|
406
|
+
|
|
407
|
+
fun isReleaseSelectionCurrent(
|
|
408
|
+
catalogId: String,
|
|
409
|
+
scopeKey: String,
|
|
410
|
+
generation: Long,
|
|
411
|
+
catalogHash: String,
|
|
412
|
+
channel: String,
|
|
413
|
+
selectionContextHash: String,
|
|
414
|
+
): Boolean =
|
|
415
|
+
bundleStorage.isReleaseSelectionCurrent(
|
|
416
|
+
catalogId,
|
|
417
|
+
scopeKey,
|
|
418
|
+
generation,
|
|
419
|
+
catalogHash,
|
|
420
|
+
channel,
|
|
421
|
+
selectionContextHash,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean = bundleStorage.commitReleaseSelection(selection)
|
|
425
|
+
|
|
426
|
+
fun getInstallId(): String = bundleStorage.getInstallId()
|
|
427
|
+
|
|
428
|
+
fun getUserId(): String? = bundleStorage.getUserId()
|
|
429
|
+
|
|
430
|
+
fun getUsername(): String? = bundleStorage.getUsername()
|
|
431
|
+
|
|
432
|
+
fun setUser(
|
|
433
|
+
userId: String?,
|
|
434
|
+
username: String?,
|
|
435
|
+
) {
|
|
436
|
+
bundleStorage.setUser(userId, username)
|
|
437
|
+
}
|
|
438
|
+
|
|
373
439
|
/**
|
|
374
440
|
* Gets the crashed bundle history.
|
|
375
441
|
* @return List of crashed bundle IDs
|
|
@@ -12,10 +12,9 @@ object NativeConfigUtils {
|
|
|
12
12
|
fun getString(
|
|
13
13
|
context: Context,
|
|
14
14
|
metaDataKey: String,
|
|
15
|
-
stringResourceName: String,
|
|
16
15
|
): String? {
|
|
17
|
-
// Programmatic overrides take priority over manifest metadata
|
|
18
|
-
//
|
|
16
|
+
// Programmatic overrides take priority over manifest metadata
|
|
17
|
+
// (brownfield/AAR setups, see HotUpdaterConfig).
|
|
19
18
|
val override =
|
|
20
19
|
when (metaDataKey) {
|
|
21
20
|
FINGERPRINT_HASH_META_DATA_KEY -> HotUpdaterConfig.fingerprintHash
|
|
@@ -26,7 +25,6 @@ object NativeConfigUtils {
|
|
|
26
25
|
override?.takeIf { it.isNotEmpty() }?.let { return it }
|
|
27
26
|
|
|
28
27
|
return getManifestMetaDataString(context, metaDataKey)
|
|
29
|
-
?: getStringResource(context, stringResourceName)
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
private fun getManifestMetaDataString(
|
|
@@ -59,16 +57,4 @@ object NativeConfigUtils {
|
|
|
59
57
|
value
|
|
60
58
|
?.toString()
|
|
61
59
|
?.takeIf { it.isNotEmpty() }
|
|
62
|
-
|
|
63
|
-
private fun getStringResource(
|
|
64
|
-
context: Context,
|
|
65
|
-
name: String,
|
|
66
|
-
): String? {
|
|
67
|
-
val id = StringResourceUtils.getIdentifier(context, name)
|
|
68
|
-
return if (id != 0) {
|
|
69
|
-
context.getString(id).takeIf { it.isNotEmpty() }
|
|
70
|
-
} else {
|
|
71
|
-
null
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
60
|
}
|