@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.
Files changed (34) hide show
  1. package/android/build.gradle +6 -3
  2. package/android/gradle.properties +2 -2
  3. package/android/proguard-rules.pro +2 -0
  4. package/android/src/main/java/com/hotupdater/HotUpdater.kt +1 -11
  5. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +4 -3
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +37 -30
  7. package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -3
  8. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +3 -5
  9. package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +34 -12
  10. package/android/src/newarch/HotUpdaterModule.kt +4 -8
  11. package/android/src/newarch/HotUpdaterPackage.kt +35 -0
  12. package/android/src/newarch/ReactIntegrationManager.kt +26 -21
  13. package/android/src/oldarch/HotUpdaterModule.kt +1 -2
  14. package/android/src/oldarch/ReactIntegrationManager.kt +7 -26
  15. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +22 -7
  16. package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +9 -9
  17. package/lib/commonjs/wrap.js +2 -1
  18. package/lib/commonjs/wrap.js.map +1 -1
  19. package/lib/typescript/commonjs/wrap.d.ts +2 -2
  20. package/lib/typescript/module/wrap.d.ts +2 -2
  21. package/package.json +6 -6
  22. package/src/wrap.tsx +2 -2
  23. package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeHotUpdaterSpec.java +0 -94
  24. package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +0 -36
  25. package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +0 -68
  26. package/android/app/build/generated/source/codegen/jni/HotUpdater.h +0 -31
  27. package/android/app/build/generated/source/codegen/jni/HotUpdaterSpec-generated.cpp +0 -68
  28. package/android/app/build/generated/source/codegen/jni/HotUpdaterSpec.h +0 -31
  29. package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI-generated.cpp +0 -69
  30. package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h +0 -168
  31. package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp +0 -69
  32. package/android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +0 -172
  33. package/ios/HotUpdater/Package.resolved +0 -15
  34. /package/android/src/{main/java/com/hotupdater → oldarch}/HotUpdaterPackage.kt +0 -0
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  HotUpdater_kotlinVersion=1.7.0
2
2
  HotUpdater_minSdkVersion=21
3
- HotUpdater_targetSdkVersion=31
4
- HotUpdater_compileSdkVersion=31
3
+ HotUpdater_targetSdkVersion=35
4
+ HotUpdater_compileSdkVersion=35
5
5
  HotUpdater_ndkversion=21.4.7075529
@@ -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 : ReactPackage {
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
- val appVersion = HotUpdaterImpl.getAppVersion(appContext) ?: "unknown"
30
- val appChannel = HotUpdaterImpl.getChannel(appContext)
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, appVersion, appChannel)
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
- * Generates a bundle ID based on build timestamp
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
- if (activity == null) {
171
- Log.e("HotUpdaterImpl", "Activity is null, cannot reload")
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
- try {
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 appVersion: String,
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 != "$prefsName.xml") {
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(prefsName, Context.MODE_PRIVATE)
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.util.zip.ZipFile
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 standard Zip API
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
- ZipFile(filePath).use { zip ->
33
- zip.entries().asSequence().forEach { entry ->
34
- val file = File(destinationPath, entry.name)
35
- if (entry.isDirectory) {
36
- file.mkdirs()
37
- } else {
38
- file.parentFile?.mkdirs()
39
- zip.getInputStream(entry).use { input ->
40
- file.outputStream().use { output ->
41
- input.copyTo(output)
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
- context: ReactApplicationContext,
16
- ) : HotUpdaterSpec(context) {
17
- private val mReactApplicationContext: ReactApplicationContext = context
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
- // Use lifecycleScope when currentActivity is FragmentActivity
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
- val reactHostDelegateField = reactHost::class.java.getDeclaredField("mReactHostDelegate")
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 currentReactContext = reactHost.currentReactContext
57
- if (currentReactContext != null) {
58
- val activity = currentReactContext.currentActivity
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
- Log.d("HotUpdater", "Failed to recreate context in background: ${e.message}")
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
- try {
81
- currentActivity.runOnUiThread {
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
- // Use lifecycleScope when currentActivity is FragmentActivity
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
- val reactNativeHost = application.reactNativeHost
39
- val reactInstanceManager = reactNativeHost.reactInstanceManager
40
-
41
- // Check if React instance is available before attempting reload
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
- try {
49
- reactInstanceManager.recreateReactContextInBackground()
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 app version
42
- if let appVersion = HotUpdaterImpl.appVersion {
43
- (preferences as? VersionedPreferencesService)?.configure(
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 keyPrefix: String = ""
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 app version for key prefixing.
24
- * @param appVersion The app version to use for key prefixing
23
+ * Configures the service with isolation key.
24
+ * @param isolationKey The complete isolation key to use for storage
25
25
  */
26
- func configure(appVersion: String?, appChannel: String) {
27
- self.keyPrefix = "hotupdater_\(appVersion ?? "unknown")_\(appChannel)_"
28
- NSLog("[PreferencesService] Configured with appVersion: \(appVersion ?? "nil"). Key prefix: \(self.keyPrefix)")
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 !keyPrefix.isEmpty else {
39
- NSLog("[PreferencesService] Warning: PreferencesService used before configure(appVersion:) was called. Key prefix is empty.")
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 "\(keyPrefix)\(key)"
42
+ return "\(isolationKey)\(key)"
43
43
  }
44
44
 
45
45
  /**
@@ -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 _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
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,
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireWildcard","require","_checkForUpdate","_useEventCallback","_native","_store","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","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,SAAAD,wBAAAO,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAT,uBAAA,YAAAA,CAAAO,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAmDtC,SAASkB,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,IAAAxD,WAAA,CAAA0D,GAAA,EAACD,QAAQ;UACP9B,QAAQ,EAAEA,QAAS;UACnBgB,MAAM,EAAEV,YAAa;UACrBH,OAAO,EAAEA;QAAQ,CAClB,CAAC;MAEN;MAEA,oBAAO,IAAA9B,WAAA,CAAA0D,GAAA,EAAClC,gBAAgB;QAAA,GAAKE;MAAK,CAAG,CAAC;IACxC,CAAC;IAED,OAAOD,aAAa;EACtB,CAAC;AACH","ignoreList":[]}
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":"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","ignoreList":[]}