@hot-updater/react-native 0.20.0-rc.0 → 0.20.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/android/build.gradle +6 -3
- package/android/gradle.properties +2 -2
- package/android/proguard-rules.pro +2 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +1 -11
- package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +4 -3
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +37 -30
- package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -3
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +3 -5
- package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +34 -12
- package/android/src/newarch/HotUpdaterModule.kt +4 -8
- package/android/src/newarch/HotUpdaterPackage.kt +35 -0
- package/android/src/newarch/ReactIntegrationManager.kt +26 -21
- package/android/src/oldarch/HotUpdaterModule.kt +1 -2
- package/android/src/oldarch/ReactIntegrationManager.kt +7 -26
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +22 -7
- package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +9 -9
- package/lib/commonjs/wrap.js +2 -1
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +2 -2
- package/lib/typescript/module/wrap.d.ts +2 -2
- package/package.json +6 -6
- package/src/wrap.tsx +2 -2
- package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeHotUpdaterSpec.java +0 -94
- package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +0 -36
- package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +0 -68
- package/android/app/build/generated/source/codegen/jni/HotUpdater.h +0 -31
- package/android/app/build/generated/source/codegen/jni/HotUpdaterSpec-generated.cpp +0 -68
- package/android/app/build/generated/source/codegen/jni/HotUpdaterSpec.h +0 -31
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI-generated.cpp +0 -69
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h +0 -168
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp +0 -69
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +0 -172
- package/ios/HotUpdater/Package.resolved +0 -15
- /package/android/src/{main/java/com/hotupdater → oldarch}/HotUpdaterPackage.kt +0 -0
package/android/build.gradle
CHANGED
|
@@ -68,6 +68,9 @@ android {
|
|
|
68
68
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
69
69
|
consumerProguardFiles 'proguard-rules.pro'
|
|
70
70
|
buildConfigField "long", "BUILD_TIMESTAMP", "${System.currentTimeMillis()}L"
|
|
71
|
+
|
|
72
|
+
def minBundleId = project.hasProperty("MIN_BUNDLE_ID") ? project.properties["MIN_BUNDLE_ID"] : null
|
|
73
|
+
buildConfigField "String", "MIN_BUNDLE_ID", minBundleId == null ? "\"null\"" : "\"${minBundleId}\""
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
buildFeatures {
|
|
@@ -91,15 +94,15 @@ android {
|
|
|
91
94
|
|
|
92
95
|
sourceSets {
|
|
93
96
|
main {
|
|
94
|
-
if (isNewArchitectureEnabled()) {
|
|
97
|
+
if (!isNewArchitectureEnabled()) {
|
|
98
|
+
java.srcDirs += ["src/oldarch"]
|
|
99
|
+
} else {
|
|
95
100
|
java.srcDirs += [
|
|
96
101
|
"src/newarch",
|
|
97
102
|
// Codegen specs
|
|
98
103
|
"generated/java",
|
|
99
104
|
"generated/jni"
|
|
100
105
|
]
|
|
101
|
-
} else {
|
|
102
|
-
java.srcDirs += ["src/oldarch"]
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# New Architecture
|
|
8
8
|
# Keep fields accessed via reflection in ReactHost
|
|
9
|
+
# Support both Java (mReactHostDelegate) and Kotlin (reactHostDelegate) field names
|
|
9
10
|
-keepclassmembers class com.facebook.react.runtime.ReactHostImpl {
|
|
10
11
|
private final ** mReactHostDelegate;
|
|
12
|
+
private val reactHostDelegate: **;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
-keepclassmembers class * implements com.facebook.react.runtime.ReactHostDelegate {
|
|
@@ -2,22 +2,12 @@ package com.hotupdater
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.view.View
|
|
6
|
-
import com.facebook.react.ReactPackage
|
|
7
|
-
import com.facebook.react.bridge.NativeModule
|
|
8
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
-
import com.facebook.react.uimanager.ReactShadowNode
|
|
10
|
-
import com.facebook.react.uimanager.ViewManager
|
|
11
6
|
|
|
12
7
|
/**
|
|
13
8
|
* Main React Native package for HotUpdater
|
|
14
9
|
*/
|
|
15
|
-
class HotUpdater
|
|
16
|
-
override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
|
17
|
-
|
|
18
|
-
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
|
|
19
|
-
listOf(HotUpdaterModule(context)).toMutableList()
|
|
20
|
-
|
|
10
|
+
class HotUpdater {
|
|
21
11
|
companion object {
|
|
22
12
|
/**
|
|
23
13
|
* Gets the app version
|
|
@@ -26,12 +26,13 @@ object HotUpdaterFactory {
|
|
|
26
26
|
*/
|
|
27
27
|
private fun createHotUpdaterImpl(context: Context): HotUpdaterImpl {
|
|
28
28
|
val appContext = context.applicationContext
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
|
|
30
|
+
// Get isolation key using the utility method
|
|
31
|
+
val isolationKey = HotUpdaterImpl.getIsolationKey(appContext)
|
|
31
32
|
|
|
32
33
|
// Create services
|
|
33
34
|
val fileSystem = FileManagerService(appContext)
|
|
34
|
-
val preferences = VersionedPreferencesService(appContext,
|
|
35
|
+
val preferences = VersionedPreferencesService(appContext, isolationKey)
|
|
35
36
|
val downloadService = HttpDownloadService()
|
|
36
37
|
val unzipService = ZipFileUnzipService()
|
|
37
38
|
|
|
@@ -66,13 +66,45 @@ class HotUpdaterImpl(
|
|
|
66
66
|
DEFAULT_CHANNEL
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets the complete isolation key for preferences storage
|
|
72
|
+
* @param context Application context
|
|
73
|
+
* @return The isolation key in format: HotUpdaterPrefs_{fingerprintOrVersion}_{channel}
|
|
74
|
+
*/
|
|
75
|
+
fun getIsolationKey(context: Context): String {
|
|
76
|
+
// Get fingerprint hash directly from resources
|
|
77
|
+
val fingerprintId = context.resources.getIdentifier("hot_updater_fingerprint_hash", "string", context.packageName)
|
|
78
|
+
val fingerprintHash =
|
|
79
|
+
if (fingerprintId != 0) {
|
|
80
|
+
context.getString(fingerprintId).takeIf { it.isNotEmpty() }
|
|
81
|
+
} else {
|
|
82
|
+
null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get app version and channel
|
|
86
|
+
val appVersion = getAppVersion(context) ?: "unknown"
|
|
87
|
+
val appChannel = getChannel(context)
|
|
88
|
+
|
|
89
|
+
// Use fingerprint if available, otherwise use app version
|
|
90
|
+
val baseKey = if (!fingerprintHash.isNullOrEmpty()) fingerprintHash else appVersion
|
|
91
|
+
|
|
92
|
+
// Build complete isolation key
|
|
93
|
+
return "HotUpdaterPrefs_${baseKey}_$appChannel"
|
|
94
|
+
}
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
/**
|
|
72
|
-
*
|
|
98
|
+
* Get minimum bundle ID string
|
|
73
99
|
* @return The minimum bundle ID string
|
|
74
100
|
*/
|
|
75
|
-
fun getMinBundleId(): String =
|
|
101
|
+
fun getMinBundleId(): String = BuildConfig.MIN_BUNDLE_ID.takeIf { it != "null" } ?: generateMinBundleIdFromBuildTimestamp()
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generates a bundle ID based on build timestamp
|
|
105
|
+
* @return The generated minimum bundle ID string
|
|
106
|
+
*/
|
|
107
|
+
private fun generateMinBundleIdFromBuildTimestamp(): String =
|
|
76
108
|
try {
|
|
77
109
|
val buildTimestampMs = BuildConfig.BUILD_TIMESTAMP
|
|
78
110
|
val bytes =
|
|
@@ -167,45 +199,20 @@ class HotUpdaterImpl(
|
|
|
167
199
|
* @param activity Current activity (optional)
|
|
168
200
|
*/
|
|
169
201
|
fun reload(activity: Activity? = null) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
val application = activity.application
|
|
176
|
-
if (application == null) {
|
|
177
|
-
Log.e("HotUpdaterImpl", "Application is null, cannot reload")
|
|
178
|
-
return
|
|
179
|
-
}
|
|
202
|
+
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
203
|
+
val application = activity?.application ?: return
|
|
180
204
|
|
|
181
205
|
try {
|
|
182
|
-
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
183
206
|
val reactApplication = reactIntegrationManager.getReactApplication(application)
|
|
184
207
|
val bundleURL = getJSBundleFile()
|
|
185
208
|
|
|
186
209
|
reactIntegrationManager.setJSBundle(reactApplication, bundleURL)
|
|
187
210
|
|
|
188
211
|
Handler(Looper.getMainLooper()).post {
|
|
189
|
-
|
|
190
|
-
reactIntegrationManager.reload(reactApplication)
|
|
191
|
-
} catch (e: Exception) {
|
|
192
|
-
Log.e("HotUpdaterImpl", "Failed to reload on main thread", e)
|
|
193
|
-
}
|
|
212
|
+
reactIntegrationManager.reload(reactApplication)
|
|
194
213
|
}
|
|
195
214
|
} catch (e: Exception) {
|
|
196
215
|
Log.e("HotUpdaterImpl", "Failed to reload application", e)
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Gets the current activity from ReactApplicationContext
|
|
202
|
-
* @param context Context that might be a ReactApplicationContext
|
|
203
|
-
* @return The current activity or null
|
|
204
|
-
*/
|
|
205
|
-
@Suppress("UNUSED_PARAMETER")
|
|
206
|
-
fun getCurrentActivity(context: Context): Activity? {
|
|
207
|
-
// This would need to be implemented differently or moved
|
|
208
|
-
// since it requires ReactApplicationContext which introduces circular dependencies
|
|
209
|
-
return null
|
|
210
|
-
}
|
|
211
218
|
}
|
|
@@ -25,9 +25,6 @@ open class ReactIntegrationManagerBase(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
public fun getReactApplication(application: Application?): ReactApplication {
|
|
28
|
-
if (application == null) {
|
|
29
|
-
throw IllegalArgumentException("Application is null")
|
|
30
|
-
}
|
|
31
28
|
if (application is ReactApplication) {
|
|
32
29
|
return application
|
|
33
30
|
} else {
|
|
@@ -32,24 +32,22 @@ interface PreferencesService {
|
|
|
32
32
|
*/
|
|
33
33
|
class VersionedPreferencesService(
|
|
34
34
|
private val context: Context,
|
|
35
|
-
private val
|
|
36
|
-
private val appChannel: String,
|
|
35
|
+
private val isolationKey: String,
|
|
37
36
|
) : PreferencesService {
|
|
38
37
|
private val prefs: SharedPreferences
|
|
39
38
|
|
|
40
39
|
init {
|
|
41
|
-
val prefsName = "HotUpdaterPrefs_${appVersion}_$appChannel"
|
|
42
40
|
|
|
43
41
|
val sharedPrefsDir = File(context.applicationInfo.dataDir, "shared_prefs")
|
|
44
42
|
if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
|
|
45
43
|
sharedPrefsDir.listFiles()?.forEach { file ->
|
|
46
|
-
if (file.name.startsWith("HotUpdaterPrefs_") && file.name != "$
|
|
44
|
+
if (file.name.startsWith("HotUpdaterPrefs_") && file.name != "$isolationKey.xml") {
|
|
47
45
|
file.delete()
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
prefs = context.getSharedPreferences(
|
|
50
|
+
prefs = context.getSharedPreferences(isolationKey, Context.MODE_PRIVATE)
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
override fun getItem(key: String): String? = prefs.getString(key, null)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import java.io.BufferedInputStream
|
|
4
5
|
import java.io.File
|
|
5
|
-
import java.
|
|
6
|
+
import java.io.FileInputStream
|
|
7
|
+
import java.io.FileOutputStream
|
|
8
|
+
import java.util.zip.ZipEntry
|
|
9
|
+
import java.util.zip.ZipInputStream
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Interface for unzip operations
|
|
@@ -21,7 +25,7 @@ interface UnzipService {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
* Implementation of UnzipService using
|
|
28
|
+
* Implementation of UnzipService using ZipInputStream for 16KB page compatibility
|
|
25
29
|
*/
|
|
26
30
|
class ZipFileUnzipService : UnzipService {
|
|
27
31
|
override fun extractZipFile(
|
|
@@ -29,17 +33,35 @@ class ZipFileUnzipService : UnzipService {
|
|
|
29
33
|
destinationPath: String,
|
|
30
34
|
): Boolean =
|
|
31
35
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
val destinationDir = File(destinationPath)
|
|
37
|
+
if (!destinationDir.exists()) {
|
|
38
|
+
destinationDir.mkdirs()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
FileInputStream(filePath).use { fileInputStream ->
|
|
42
|
+
BufferedInputStream(fileInputStream).use { bufferedInputStream ->
|
|
43
|
+
ZipInputStream(bufferedInputStream).use { zipInputStream ->
|
|
44
|
+
var entry: ZipEntry? = zipInputStream.nextEntry
|
|
45
|
+
while (entry != null) {
|
|
46
|
+
val file = File(destinationPath, entry.name)
|
|
47
|
+
|
|
48
|
+
// Validate that the entry path doesn't escape the destination directory
|
|
49
|
+
if (!file.canonicalPath.startsWith(destinationDir.canonicalPath)) {
|
|
50
|
+
Log.w("UnzipService", "Skipping potentially malicious zip entry: ${entry.name}")
|
|
51
|
+
entry = zipInputStream.nextEntry
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (entry.isDirectory) {
|
|
56
|
+
file.mkdirs()
|
|
57
|
+
} else {
|
|
58
|
+
file.parentFile?.mkdirs()
|
|
59
|
+
FileOutputStream(file).use { output ->
|
|
60
|
+
zipInputStream.copyTo(output)
|
|
61
|
+
}
|
|
42
62
|
}
|
|
63
|
+
zipInputStream.closeEntry()
|
|
64
|
+
entry = zipInputStream.nextEntry
|
|
43
65
|
}
|
|
44
66
|
}
|
|
45
67
|
}
|
|
@@ -5,20 +5,18 @@ import androidx.fragment.app.FragmentActivity
|
|
|
5
5
|
import androidx.lifecycle.lifecycleScope
|
|
6
6
|
import com.facebook.react.bridge.Promise
|
|
7
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
-
import com.facebook.react.bridge.ReactMethod
|
|
9
8
|
import com.facebook.react.bridge.ReadableMap
|
|
10
9
|
import com.facebook.react.bridge.WritableNativeMap
|
|
11
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
11
|
import kotlinx.coroutines.launch
|
|
13
12
|
|
|
14
13
|
class HotUpdaterModule internal constructor(
|
|
15
|
-
|
|
16
|
-
) : HotUpdaterSpec(
|
|
17
|
-
private val mReactApplicationContext: ReactApplicationContext =
|
|
14
|
+
reactContext: ReactApplicationContext,
|
|
15
|
+
) : HotUpdaterSpec(reactContext) {
|
|
16
|
+
private val mReactApplicationContext: ReactApplicationContext = reactContext
|
|
18
17
|
|
|
19
18
|
override fun getName(): String = NAME
|
|
20
19
|
|
|
21
|
-
@ReactMethod
|
|
22
20
|
override fun reload() {
|
|
23
21
|
try {
|
|
24
22
|
HotUpdater.reload(mReactApplicationContext)
|
|
@@ -27,13 +25,11 @@ class HotUpdaterModule internal constructor(
|
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
@ReactMethod
|
|
31
28
|
override fun updateBundle(
|
|
32
29
|
params: ReadableMap,
|
|
33
30
|
promise: Promise,
|
|
34
31
|
) {
|
|
35
|
-
|
|
36
|
-
(currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
|
|
32
|
+
(mReactApplicationContext.currentActivity as FragmentActivity?)?.lifecycleScope?.launch {
|
|
37
33
|
try {
|
|
38
34
|
val bundleId = params.getString("bundleId")!!
|
|
39
35
|
val fileUrl = params.getString("fileUrl")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class HotUpdaterPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(
|
|
12
|
+
name: String,
|
|
13
|
+
reactContext: ReactApplicationContext,
|
|
14
|
+
): NativeModule? =
|
|
15
|
+
if (name == HotUpdaterModule.NAME) {
|
|
16
|
+
HotUpdaterModule(reactContext)
|
|
17
|
+
} else {
|
|
18
|
+
null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
|
|
22
|
+
ReactModuleInfoProvider {
|
|
23
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
24
|
+
moduleInfos[HotUpdaterModule.NAME] =
|
|
25
|
+
ReactModuleInfo(
|
|
26
|
+
HotUpdaterModule.NAME,
|
|
27
|
+
HotUpdaterModule.NAME,
|
|
28
|
+
false, // canOverrideExistingModule
|
|
29
|
+
false, // needsEagerInit
|
|
30
|
+
false, // isCxxModule
|
|
31
|
+
true, // isTurboModule
|
|
32
|
+
)
|
|
33
|
+
moduleInfos
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -17,7 +17,19 @@ class ReactIntegrationManager(
|
|
|
17
17
|
try {
|
|
18
18
|
val reactHost = application.reactHost
|
|
19
19
|
check(reactHost != null)
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
// Try both Java and Kotlin field names for compatibility
|
|
22
|
+
val reactHostDelegateField =
|
|
23
|
+
try {
|
|
24
|
+
reactHost::class.java.getDeclaredField("mReactHostDelegate")
|
|
25
|
+
} catch (e: NoSuchFieldException) {
|
|
26
|
+
try {
|
|
27
|
+
reactHost::class.java.getDeclaredField("reactHostDelegate")
|
|
28
|
+
} catch (e2: NoSuchFieldException) {
|
|
29
|
+
throw RuntimeException("Neither mReactHostDelegate nor reactHostDelegate field found", e2)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
reactHostDelegateField.isAccessible = true
|
|
22
34
|
val reactHostDelegate =
|
|
23
35
|
reactHostDelegateField.get(
|
|
@@ -28,6 +40,8 @@ class ReactIntegrationManager(
|
|
|
28
40
|
jsBundleLoaderField.set(reactHostDelegate, getJSBundlerLoader(bundleURL))
|
|
29
41
|
} catch (e: Exception) {
|
|
30
42
|
try {
|
|
43
|
+
// Fallback to old architecture if ReactHost is not available
|
|
44
|
+
@Suppress("DEPRECATION")
|
|
31
45
|
val instanceManager = application.reactNativeHost.reactInstanceManager
|
|
32
46
|
val bundleLoader: JSBundleLoader? = this.getJSBundlerLoader(bundleURL)
|
|
33
47
|
val bundleLoaderField: Field =
|
|
@@ -53,37 +67,28 @@ class ReactIntegrationManager(
|
|
|
53
67
|
try {
|
|
54
68
|
val reactHost = application.reactHost
|
|
55
69
|
if (reactHost != null) {
|
|
56
|
-
val
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
|
|
60
|
-
reactHost.onHostResume(activity)
|
|
61
|
-
}
|
|
62
|
-
reactHost.reload("Requested by HotUpdater")
|
|
63
|
-
} else {
|
|
64
|
-
Log.d("HotUpdater", "ReactContext is null, cannot reload safely")
|
|
70
|
+
val activity = reactHost.currentReactContext?.currentActivity
|
|
71
|
+
if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
|
|
72
|
+
reactHost.onHostResume(activity)
|
|
65
73
|
}
|
|
74
|
+
reactHost.reload("Requested by HotUpdater")
|
|
66
75
|
} else {
|
|
76
|
+
// Fallback to old architecture if ReactHost is not available
|
|
77
|
+
@Suppress("DEPRECATION")
|
|
67
78
|
val reactNativeHost = application.reactNativeHost
|
|
68
79
|
try {
|
|
69
80
|
reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
|
|
70
81
|
} catch (e: Exception) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
val currentReactContext = reactNativeHost.reactInstanceManager.currentReactContext
|
|
74
|
-
val currentActivity = currentReactContext?.currentActivity
|
|
82
|
+
val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
|
|
75
83
|
if (currentActivity == null) {
|
|
76
|
-
Log.d("HotUpdater", "No current activity available for fallback reload")
|
|
77
84
|
return
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
currentActivity.
|
|
82
|
-
currentActivity.recreate()
|
|
83
|
-
}
|
|
84
|
-
} catch (e: Exception) {
|
|
85
|
-
Log.d("HotUpdater", "Failed to recreate activity: ${e.message}")
|
|
87
|
+
currentActivity.runOnUiThread {
|
|
88
|
+
currentActivity.recreate()
|
|
86
89
|
}
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
} catch (e: Exception) {
|
|
@@ -32,8 +32,7 @@ class HotUpdaterModule internal constructor(
|
|
|
32
32
|
params: ReadableMap,
|
|
33
33
|
promise: Promise,
|
|
34
34
|
) {
|
|
35
|
-
|
|
36
|
-
(currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
|
|
35
|
+
(mReactApplicationContext.currentActivity as FragmentActivity?)?.lifecycleScope?.launch {
|
|
37
36
|
try {
|
|
38
37
|
val bundleId = params.getString("bundleId")!!
|
|
39
38
|
val fileUrl = params.getString("fileUrl")
|
|
@@ -34,36 +34,17 @@ class ReactIntegrationManager(
|
|
|
34
34
|
* Reload the React Native application.
|
|
35
35
|
*/
|
|
36
36
|
public fun reload(application: ReactApplication) {
|
|
37
|
+
val reactNativeHost = application.reactNativeHost
|
|
37
38
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
val currentReactContext = reactInstanceManager.currentReactContext
|
|
43
|
-
if (currentReactContext == null) {
|
|
44
|
-
Log.d("HotUpdater", "ReactContext is null, cannot reload safely")
|
|
39
|
+
reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
|
|
40
|
+
} catch (e: Exception) {
|
|
41
|
+
val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
|
|
42
|
+
if (currentActivity == null) {
|
|
45
43
|
return
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} catch (e: Exception) {
|
|
51
|
-
Log.d("HotUpdater", "Failed to recreate context in background: ${e.message}")
|
|
52
|
-
|
|
53
|
-
// Fallback to activity recreation if available
|
|
54
|
-
val currentActivity = currentReactContext.currentActivity
|
|
55
|
-
if (currentActivity == null) {
|
|
56
|
-
Log.d("HotUpdater", "No current activity available for fallback reload")
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
currentActivity.runOnUiThread {
|
|
62
|
-
currentActivity.recreate()
|
|
63
|
-
}
|
|
64
|
-
} catch (e: Exception) {
|
|
65
|
-
Log.d("HotUpdater", "Failed to recreate activity: ${e.message}")
|
|
66
|
-
}
|
|
46
|
+
currentActivity.runOnUiThread {
|
|
47
|
+
currentActivity.recreate()
|
|
67
48
|
}
|
|
68
49
|
} catch (e: Exception) {
|
|
69
50
|
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
@@ -38,13 +38,9 @@ import React
|
|
|
38
38
|
self.preferences = preferences
|
|
39
39
|
super.init()
|
|
40
40
|
|
|
41
|
-
// Configure preferences with
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
appVersion: appVersion,
|
|
45
|
-
appChannel: HotUpdaterImpl.appChannel
|
|
46
|
-
)
|
|
47
|
-
}
|
|
41
|
+
// Configure preferences with isolation key
|
|
42
|
+
let isolationKey = HotUpdaterImpl.getIsolationKey()
|
|
43
|
+
(preferences as? VersionedPreferencesService)?.configure(isolationKey: isolationKey)
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
// MARK: - Static Properties
|
|
@@ -62,6 +58,25 @@ import React
|
|
|
62
58
|
public static var appChannel: String {
|
|
63
59
|
return Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_CHANNEL") as? String ?? Self.DEFAULT_CHANNEL
|
|
64
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets the complete isolation key for preferences storage.
|
|
64
|
+
* @return The isolation key in format: hotupdater_{fingerprintOrVersion}_{channel}_
|
|
65
|
+
*/
|
|
66
|
+
public static func getIsolationKey() -> String {
|
|
67
|
+
// Get fingerprint hash from Info.plist
|
|
68
|
+
let fingerprintHash = Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_FINGERPRINT_HASH") as? String
|
|
69
|
+
|
|
70
|
+
// Get app version and channel
|
|
71
|
+
let appVersion = self.appVersion ?? "unknown"
|
|
72
|
+
let appChannel = self.appChannel
|
|
73
|
+
|
|
74
|
+
// Use fingerprint if available, otherwise use app version
|
|
75
|
+
let baseKey = (fingerprintHash != nil && !fingerprintHash!.isEmpty) ? fingerprintHash! : appVersion
|
|
76
|
+
|
|
77
|
+
// Build complete isolation key
|
|
78
|
+
return "hotupdater_\(baseKey)_\(appChannel)_"
|
|
79
|
+
}
|
|
65
80
|
|
|
66
81
|
// MARK: - Channel Management
|
|
67
82
|
|
|
@@ -13,19 +13,19 @@ protocol PreferencesService {
|
|
|
13
13
|
|
|
14
14
|
class VersionedPreferencesService: PreferencesService {
|
|
15
15
|
private let userDefaults: UserDefaults
|
|
16
|
-
private var
|
|
16
|
+
private var isolationKey: String = ""
|
|
17
17
|
|
|
18
18
|
init(userDefaults: UserDefaults = .standard) {
|
|
19
19
|
self.userDefaults = userDefaults
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Configures the service with
|
|
24
|
-
* @param
|
|
23
|
+
* Configures the service with isolation key.
|
|
24
|
+
* @param isolationKey The complete isolation key to use for storage
|
|
25
25
|
*/
|
|
26
|
-
func configure(
|
|
27
|
-
self.
|
|
28
|
-
NSLog("[PreferencesService] Configured with
|
|
26
|
+
func configure(isolationKey: String) {
|
|
27
|
+
self.isolationKey = isolationKey
|
|
28
|
+
NSLog("[PreferencesService] Configured with isolation key: \(self.isolationKey)")
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -35,11 +35,11 @@ class VersionedPreferencesService: PreferencesService {
|
|
|
35
35
|
* @throws PreferencesError if configuration is missing
|
|
36
36
|
*/
|
|
37
37
|
private func prefixedKey(forKey key: String) throws -> String {
|
|
38
|
-
guard !
|
|
39
|
-
NSLog("[PreferencesService] Warning: PreferencesService used before configure(
|
|
38
|
+
guard !isolationKey.isEmpty else {
|
|
39
|
+
NSLog("[PreferencesService] Warning: PreferencesService used before configure(isolationKey:) was called. Isolation key is empty.")
|
|
40
40
|
throw PreferencesError.configurationError
|
|
41
41
|
}
|
|
42
|
-
return "\(
|
|
42
|
+
return "\(isolationKey)\(key)"
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
package/lib/commonjs/wrap.js
CHANGED
|
@@ -10,7 +10,8 @@ var _useEventCallback = require("./hooks/useEventCallback.js");
|
|
|
10
10
|
var _native = require("./native.js");
|
|
11
11
|
var _store = require("./store.js");
|
|
12
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
-
function
|
|
13
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
14
15
|
function wrap(options) {
|
|
15
16
|
const {
|
|
16
17
|
reloadOnForceUpdate = true,
|
package/lib/commonjs/wrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_checkForUpdate","_useEventCallback","_native","_store","_jsxRuntime","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_checkForUpdate","_useEventCallback","_native","_store","_jsxRuntime","_getRequireWildcardCache","e","WeakMap","r","t","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","wrap","options","reloadOnForceUpdate","restOptions","WrappedComponent","HotUpdaterHOC","props","progress","useHotUpdaterStore","state","message","setMessage","useState","updateStatus","setUpdateStatus","initHotUpdater","useEventCallback","updateInfo","checkForUpdate","source","requestHeaders","onError","onUpdateProcessCompleted","status","shouldForceUpdate","id","getBundleId","updateBundle","catch","error","isSuccess","Error","reload","useEffect","onProgress","useLayoutEffect","fallbackComponent","Fallback","jsx"],"sourceRoot":"../../src","sources":["wrap.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,uBAAA,CAAAC,OAAA;AAEA,IAAAC,eAAA,GAAAD,OAAA;AAEA,IAAAE,iBAAA,GAAAF,OAAA;AACA,IAAAG,OAAA,GAAAH,OAAA;AAEA,IAAAI,MAAA,GAAAJ,OAAA;AAA6C,IAAAK,WAAA,GAAAL,OAAA;AAAA,SAAAM,yBAAAC,CAAA,6BAAAC,OAAA,mBAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAF,wBAAA,YAAAA,CAAAC,CAAA,WAAAA,CAAA,GAAAG,CAAA,GAAAD,CAAA,KAAAF,CAAA;AAAA,SAAAR,wBAAAQ,CAAA,EAAAE,CAAA,SAAAA,CAAA,IAAAF,CAAA,IAAAA,CAAA,CAAAI,UAAA,SAAAJ,CAAA,eAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,WAAAK,OAAA,EAAAL,CAAA,QAAAG,CAAA,GAAAJ,wBAAA,CAAAG,CAAA,OAAAC,CAAA,IAAAA,CAAA,CAAAG,GAAA,CAAAN,CAAA,UAAAG,CAAA,CAAAI,GAAA,CAAAP,CAAA,OAAAQ,CAAA,KAAAC,SAAA,UAAAC,CAAA,GAAAC,MAAA,CAAAC,cAAA,IAAAD,MAAA,CAAAE,wBAAA,WAAAC,CAAA,IAAAd,CAAA,oBAAAc,CAAA,OAAAC,cAAA,CAAAC,IAAA,CAAAhB,CAAA,EAAAc,CAAA,SAAAG,CAAA,GAAAP,CAAA,GAAAC,MAAA,CAAAE,wBAAA,CAAAb,CAAA,EAAAc,CAAA,UAAAG,CAAA,KAAAA,CAAA,CAAAV,GAAA,IAAAU,CAAA,CAAAC,GAAA,IAAAP,MAAA,CAAAC,cAAA,CAAAJ,CAAA,EAAAM,CAAA,EAAAG,CAAA,IAAAT,CAAA,CAAAM,CAAA,IAAAd,CAAA,CAAAc,CAAA,YAAAN,CAAA,CAAAH,OAAA,GAAAL,CAAA,EAAAG,CAAA,IAAAA,CAAA,CAAAe,GAAA,CAAAlB,CAAA,EAAAQ,CAAA,GAAAA,CAAA;AAmDtC,SAASW,IAAIA,CAClBC,OAA0B,EAC4C;EACtE,MAAM;IAAEC,mBAAmB,GAAG,IAAI;IAAE,GAAGC;EAAY,CAAC,GAAGF,OAAO;EAE9D,OAAQG,gBAAwC,IAAK;IACnD,MAAMC,aAA0B,GAAIC,KAAQ,IAAK;MAC/C,MAAMC,QAAQ,GAAG,IAAAC,yBAAkB,EAAEC,KAAK,IAAKA,KAAK,CAACF,QAAQ,CAAC;MAE9D,MAAM,CAACG,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAC,eAAQ,EAAgB,IAAI,CAAC;MAC3D,MAAM,CAACC,YAAY,EAAEC,eAAe,CAAC,GACnC,IAAAF,eAAQ,EAAe,kBAAkB,CAAC;MAE5C,MAAMG,cAAc,GAAG,IAAAC,kCAAgB,EAAC,YAAY;QAClD,IAAI;UACFF,eAAe,CAAC,kBAAkB,CAAC;UAEnC,MAAMG,UAAU,GAAG,MAAM,IAAAC,8BAAc,EAAC;YACtCC,MAAM,EAAEhB,WAAW,CAACgB,MAAM;YAC1BC,cAAc,EAAEjB,WAAW,CAACiB,cAAc;YAC1CC,OAAO,EAAElB,WAAW,CAACkB;UACvB,CAAC,CAAC;UAEFV,UAAU,CAACM,UAAU,EAAEP,OAAO,IAAI,IAAI,CAAC;UAEvC,IAAI,CAACO,UAAU,EAAE;YACfd,WAAW,CAACmB,wBAAwB,GAAG;cACrCC,MAAM,EAAE,YAAY;cACpBC,iBAAiB,EAAE,KAAK;cACxBd,OAAO,EAAE,IAAI;cACbe,EAAE,EAAE,IAAAC,mBAAW,EAAC;YAClB,CAAC,CAAC;YACFZ,eAAe,CAAC,0BAA0B,CAAC;YAC3C;UACF;UAEA,IAAIG,UAAU,CAACO,iBAAiB,KAAK,KAAK,EAAE;YAC1C,KAAKP,UAAU,CAACU,YAAY,CAAC,CAAC,CAACC,KAAK,CAAEC,KAAK,IAAK;cAC9C1B,WAAW,CAACkB,OAAO,GAAGQ,KAAK,CAAC;YAC9B,CAAC,CAAC;YAEF1B,WAAW,CAACmB,wBAAwB,GAAG;cACrCG,EAAE,EAAER,UAAU,CAACQ,EAAE;cACjBF,MAAM,EAAEN,UAAU,CAACM,MAAM;cACzBC,iBAAiB,EAAEP,UAAU,CAACO,iBAAiB;cAC/Cd,OAAO,EAAEO,UAAU,CAACP;YACtB,CAAC,CAAC;YACFI,eAAe,CAAC,0BAA0B,CAAC;YAC3C;UACF;UACA;UACAA,eAAe,CAAC,UAAU,CAAC;UAC3B,MAAMgB,SAAS,GAAG,MAAMb,UAAU,CAACU,YAAY,CAAC,CAAC;UAEjD,IAAI,CAACG,SAAS,EAAE;YACd,MAAM,IAAIC,KAAK,CACb,yDACF,CAAC;UACH;UAEA,IAAI7B,mBAAmB,EAAE;YACvB,IAAA8B,cAAM,EAAC,CAAC;UACV;UAEA7B,WAAW,CAACmB,wBAAwB,GAAG;YACrCG,EAAE,EAAER,UAAU,CAACQ,EAAE;YACjBF,MAAM,EAAEN,UAAU,CAACM,MAAM;YACzBC,iBAAiB,EAAEP,UAAU,CAACO,iBAAiB;YAC/Cd,OAAO,EAAEO,UAAU,CAACP;UACtB,CAAC,CAAC;UAEFI,eAAe,CAAC,0BAA0B,CAAC;QAC7C,CAAC,CAAC,OAAOe,KAAK,EAAE;UACd1B,WAAW,CAACkB,OAAO,GAAGQ,KAAK,CAAC;UAC5Bf,eAAe,CAAC,0BAA0B,CAAC;QAC7C;MACF,CAAC,CAAC;MAEF,IAAAmB,gBAAS,EAAC,MAAM;QACd9B,WAAW,CAAC+B,UAAU,GAAG3B,QAAQ,CAAC;MACpC,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;MAEd,IAAA4B,sBAAe,EAAC,MAAM;QACpBpB,cAAc,CAAC,CAAC;MAClB,CAAC,EAAE,EAAE,CAAC;MAEN,IACEZ,WAAW,CAACiC,iBAAiB,IAC7BvB,YAAY,KAAK,0BAA0B,EAC3C;QACA,MAAMwB,QAAQ,GAAGlC,WAAW,CAACiC,iBAAiB;QAC9C,oBACE,IAAAzD,WAAA,CAAA2D,GAAA,EAACD,QAAQ;UACP9B,QAAQ,EAAEA,QAAS;UACnBgB,MAAM,EAAEV,YAAa;UACrBH,OAAO,EAAEA;QAAQ,CAClB,CAAC;MAEN;MAEA,oBAAO,IAAA/B,WAAA,CAAA2D,GAAA,EAAClC,gBAAgB;QAAA,GAAKE;MAAK,CAAG,CAAC;IACxC,CAAC;IAED,OAAOD,aAAa;EACtB,CAAC;AACH","ignoreList":[]}
|