@hot-updater/react-native 0.16.7-0 → 0.18.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 (143) hide show
  1. package/HotUpdater.podspec +7 -11
  2. package/android/{generated/java/com/hotupdater → app/build/generated/source/codegen/java/com/facebook/fbreact/specs}/NativeHotUpdaterSpec.java +3 -2
  3. package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +68 -0
  4. package/android/app/build/generated/source/codegen/jni/HotUpdater.h +31 -0
  5. package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec-generated.cpp +2 -2
  6. package/android/{generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp → app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI-generated.cpp} +3 -4
  7. package/{ios/generated/HotUpdaterSpecJSI.h → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h} +53 -6
  8. package/{ios/generated → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec}/HotUpdaterSpecJSI-generated.cpp +2 -3
  9. package/android/{generated → app/build/generated/source/codegen}/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +59 -8
  10. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
  11. package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
  12. package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
  13. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
  14. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
  15. package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
  16. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
  17. package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
  18. package/android/src/newarch/HotUpdaterModule.kt +31 -34
  19. package/android/src/newarch/ReactIntegrationManager.kt +17 -3
  20. package/android/src/oldarch/HotUpdaterModule.kt +32 -34
  21. package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
  22. package/android/src/oldarch/ReactIntegrationManager.kt +0 -2
  23. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
  24. package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
  25. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
  26. package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
  27. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
  28. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
  29. package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
  30. package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
  32. package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
  33. package/ios/HotUpdater/Package.resolved +15 -0
  34. package/ios/HotUpdater/Public/HotUpdater.h +29 -0
  35. package/lib/commonjs/checkForUpdate.js +70 -0
  36. package/lib/commonjs/checkForUpdate.js.map +1 -0
  37. package/lib/commonjs/error.js +14 -0
  38. package/lib/commonjs/error.js.map +1 -0
  39. package/lib/commonjs/fetchUpdateInfo.js +74 -0
  40. package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
  41. package/lib/commonjs/hooks/useEventCallback.js +17 -0
  42. package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
  43. package/lib/commonjs/index.js +234 -0
  44. package/lib/commonjs/index.js.map +1 -0
  45. package/lib/commonjs/native.js +132 -0
  46. package/lib/commonjs/native.js.map +1 -0
  47. package/lib/commonjs/package.json +1 -0
  48. package/lib/commonjs/runUpdateProcess.js +69 -0
  49. package/lib/commonjs/runUpdateProcess.js.map +1 -0
  50. package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
  51. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
  52. package/lib/commonjs/store.js +48 -0
  53. package/lib/commonjs/store.js.map +1 -0
  54. package/lib/commonjs/wrap.js +98 -0
  55. package/lib/commonjs/wrap.js.map +1 -0
  56. package/lib/module/checkForUpdate.js +64 -0
  57. package/lib/module/checkForUpdate.js.map +1 -0
  58. package/lib/module/error.js +9 -0
  59. package/lib/module/error.js.map +1 -0
  60. package/lib/module/fetchUpdateInfo.js +69 -0
  61. package/lib/module/fetchUpdateInfo.js.map +1 -0
  62. package/lib/module/hooks/useEventCallback.js +13 -0
  63. package/lib/module/hooks/useEventCallback.js.map +1 -0
  64. package/lib/module/index.js +211 -0
  65. package/lib/module/index.js.map +1 -0
  66. package/lib/module/native.js +119 -0
  67. package/lib/module/native.js.map +1 -0
  68. package/lib/module/package.json +1 -0
  69. package/lib/module/runUpdateProcess.js +64 -0
  70. package/lib/module/runUpdateProcess.js.map +1 -0
  71. package/lib/module/specs/NativeHotUpdater.js +5 -0
  72. package/lib/module/specs/NativeHotUpdater.js.map +1 -0
  73. package/lib/module/store.js +42 -0
  74. package/lib/module/store.js.map +1 -0
  75. package/lib/module/wrap.js +94 -0
  76. package/lib/module/wrap.js.map +1 -0
  77. package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
  78. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
  79. package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
  80. package/lib/typescript/commonjs/error.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
  82. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
  83. package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
  84. package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
  85. package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
  86. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/native.d.ts +64 -0
  88. package/lib/typescript/commonjs/native.d.ts.map +1 -0
  89. package/lib/typescript/commonjs/package.json +1 -0
  90. package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
  91. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
  92. package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
  93. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
  94. package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
  95. package/lib/typescript/commonjs/store.d.ts.map +1 -0
  96. package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
  97. package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
  98. package/lib/typescript/module/checkForUpdate.d.ts +22 -0
  99. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
  100. package/lib/typescript/module/error.d.ts +4 -0
  101. package/lib/typescript/module/error.d.ts.map +1 -0
  102. package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
  103. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
  104. package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
  105. package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
  106. package/lib/typescript/module/index.d.ts +202 -0
  107. package/lib/typescript/module/index.d.ts.map +1 -0
  108. package/lib/typescript/module/native.d.ts +64 -0
  109. package/lib/typescript/module/native.d.ts.map +1 -0
  110. package/lib/typescript/module/package.json +1 -0
  111. package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
  112. package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
  113. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
  114. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
  115. package/lib/typescript/module/store.d.ts +11 -0
  116. package/lib/typescript/module/store.d.ts.map +1 -0
  117. package/lib/typescript/module/wrap.d.ts +51 -0
  118. package/lib/typescript/module/wrap.d.ts.map +1 -0
  119. package/package.json +59 -30
  120. package/src/checkForUpdate.ts +59 -9
  121. package/src/fetchUpdateInfo.ts +40 -12
  122. package/src/index.ts +37 -11
  123. package/src/native.ts +87 -41
  124. package/src/runUpdateProcess.ts +2 -2
  125. package/src/specs/NativeHotUpdater.ts +8 -10
  126. package/src/wrap.tsx +9 -13
  127. package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
  128. package/dist/checkForUpdate.d.ts +0 -12
  129. package/dist/fetchUpdateInfo.d.ts +0 -3
  130. package/dist/index.js +0 -341
  131. package/dist/index.mjs +0 -301
  132. package/dist/native.d.ts +0 -41
  133. package/ios/HotUpdater/HotUpdater.h +0 -15
  134. package/ios/HotUpdater/HotUpdater.mm +0 -468
  135. package/ios/HotUpdater/HotUpdater.modulemap +0 -6
  136. package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
  137. package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
  138. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
  139. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
  140. package/react-native.config.js +0 -12
  141. package/src/global.d.ts +0 -3
  142. /package/android/{generated → app/build/generated/source/codegen}/jni/CMakeLists.txt +0 -0
  143. /package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec.h +0 -0
@@ -0,0 +1,176 @@
1
+ package com.hotupdater
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import android.util.Log
8
+
9
+ /**
10
+ * Core implementation class for HotUpdater functionality
11
+ */
12
+ class HotUpdaterImpl(
13
+ private val context: Context,
14
+ private val bundleStorage: BundleStorageService,
15
+ private val preferences: PreferencesService,
16
+ ) {
17
+ /**
18
+ * Gets the app version
19
+ * @param context Application context
20
+ * @return App version name or null if not available
21
+ */
22
+ fun getAppVersion(): String? =
23
+ try {
24
+ val packageInfo =
25
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
26
+ context.packageManager.getPackageInfo(
27
+ context.packageName,
28
+ android.content.pm.PackageManager.PackageInfoFlags
29
+ .of(0),
30
+ )
31
+ } else {
32
+ @Suppress("DEPRECATION")
33
+ context.packageManager.getPackageInfo(context.packageName, 0)
34
+ }
35
+ packageInfo.versionName
36
+ } catch (e: Exception) {
37
+ null
38
+ }
39
+
40
+ companion object {
41
+ private const val DEFAULT_CHANNEL = "production"
42
+
43
+ fun getAppVersion(context: Context): String? =
44
+ try {
45
+ val packageInfo =
46
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
47
+ context.packageManager.getPackageInfo(
48
+ context.packageName,
49
+ android.content.pm.PackageManager.PackageInfoFlags
50
+ .of(0),
51
+ )
52
+ } else {
53
+ @Suppress("DEPRECATION")
54
+ context.packageManager.getPackageInfo(context.packageName, 0)
55
+ }
56
+ packageInfo.versionName
57
+ } catch (e: Exception) {
58
+ null
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Generates a bundle ID based on build timestamp
64
+ * @return The minimum bundle ID string
65
+ */
66
+ fun getMinBundleId(): String =
67
+ try {
68
+ val buildTimestampMs = BuildConfig.BUILD_TIMESTAMP
69
+ val bytes =
70
+ ByteArray(16).apply {
71
+ this[0] = ((buildTimestampMs shr 40) and 0xFF).toByte()
72
+ this[1] = ((buildTimestampMs shr 32) and 0xFF).toByte()
73
+ this[2] = ((buildTimestampMs shr 24) and 0xFF).toByte()
74
+ this[3] = ((buildTimestampMs shr 16) and 0xFF).toByte()
75
+ this[4] = ((buildTimestampMs shr 8) and 0xFF).toByte()
76
+ this[5] = (buildTimestampMs and 0xFF).toByte()
77
+ this[6] = 0x70.toByte()
78
+ this[7] = 0x00.toByte()
79
+ this[8] = 0x80.toByte()
80
+ this[9] = 0x00.toByte()
81
+ this[10] = 0x00.toByte()
82
+ this[11] = 0x00.toByte()
83
+ this[12] = 0x00.toByte()
84
+ this[13] = 0x00.toByte()
85
+ this[14] = 0x00.toByte()
86
+ this[15] = 0x00.toByte()
87
+ }
88
+ String.format(
89
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
90
+ bytes[0].toInt() and 0xFF,
91
+ bytes[1].toInt() and 0xFF,
92
+ bytes[2].toInt() and 0xFF,
93
+ bytes[3].toInt() and 0xFF,
94
+ bytes[4].toInt() and 0xFF,
95
+ bytes[5].toInt() and 0xFF,
96
+ bytes[6].toInt() and 0xFF,
97
+ bytes[7].toInt() and 0xFF,
98
+ bytes[8].toInt() and 0xFF,
99
+ bytes[9].toInt() and 0xFF,
100
+ bytes[10].toInt() and 0xFF,
101
+ bytes[11].toInt() and 0xFF,
102
+ bytes[12].toInt() and 0xFF,
103
+ bytes[13].toInt() and 0xFF,
104
+ bytes[14].toInt() and 0xFF,
105
+ bytes[15].toInt() and 0xFF,
106
+ )
107
+ } catch (e: Exception) {
108
+ "00000000-0000-0000-0000-000000000000"
109
+ }
110
+
111
+ /**
112
+ * Gets the current update channel
113
+ * @return The channel name or null if not set
114
+ */
115
+ fun getChannel(): String {
116
+ val id = context.resources.getIdentifier("hot_updater_channel", "string", context.packageName)
117
+ return if (id != 0) {
118
+ context.getString(id).takeIf { it.isNotEmpty() } ?: DEFAULT_CHANNEL
119
+ } else {
120
+ DEFAULT_CHANNEL
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Gets the path to the bundle file
126
+ * @return The path to the bundle file
127
+ */
128
+ fun getJSBundleFile(): String = bundleStorage.getBundleURL()
129
+
130
+ /**
131
+ * Updates the bundle from the specified URL
132
+ * @param bundleId ID of the bundle to update
133
+ * @param fileUrl URL of the bundle file to download (or null to reset)
134
+ * @param progressCallback Callback for download progress updates
135
+ * @return true if the update was successful
136
+ */
137
+ suspend fun updateBundle(
138
+ bundleId: String,
139
+ fileUrl: String?,
140
+ progressCallback: (Double) -> Unit,
141
+ ): Boolean = bundleStorage.updateBundle(bundleId, fileUrl, progressCallback)
142
+
143
+ /**
144
+ * Reloads the React Native application
145
+ * @param activity Current activity (optional)
146
+ */
147
+ fun reload(activity: Activity? = null) {
148
+ val reactIntegrationManager = ReactIntegrationManager(context)
149
+ val application = activity?.application ?: return
150
+
151
+ try {
152
+ val reactApplication = reactIntegrationManager.getReactApplication(application)
153
+ val bundleURL = getJSBundleFile()
154
+
155
+ reactIntegrationManager.setJSBundle(reactApplication, bundleURL)
156
+
157
+ Handler(Looper.getMainLooper()).post {
158
+ reactIntegrationManager.reload(reactApplication)
159
+ }
160
+ } catch (e: Exception) {
161
+ Log.e("HotUpdaterImpl", "Failed to reload application", e)
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Gets the current activity from ReactApplicationContext
167
+ * @param context Context that might be a ReactApplicationContext
168
+ * @return The current activity or null
169
+ */
170
+ @Suppress("UNUSED_PARAMETER")
171
+ fun getCurrentActivity(context: Context): Activity? {
172
+ // This would need to be implemented differently or moved
173
+ // since it requires ReactApplicationContext which introduces circular dependencies
174
+ return null
175
+ }
176
+ }
@@ -0,0 +1,98 @@
1
+ package com.hotupdater
2
+
3
+ import android.util.Log
4
+ import kotlinx.coroutines.Dispatchers
5
+ import kotlinx.coroutines.withContext
6
+ import java.io.File
7
+ import java.net.HttpURLConnection
8
+ import java.net.URL
9
+
10
+ /**
11
+ * Result wrapper for download operations
12
+ */
13
+ sealed class DownloadResult {
14
+ data class Success(
15
+ val file: File,
16
+ ) : DownloadResult()
17
+
18
+ data class Error(
19
+ val exception: Exception,
20
+ ) : DownloadResult()
21
+ }
22
+
23
+ /**
24
+ * Interface for download operations
25
+ */
26
+ interface DownloadService {
27
+ /**
28
+ * Downloads a file from a URL
29
+ * @param fileUrl The URL to download from
30
+ * @param destination The local file to save to
31
+ * @param progressCallback Callback for download progress updates
32
+ * @return Result indicating success or failure
33
+ */
34
+ suspend fun downloadFile(
35
+ fileUrl: URL,
36
+ destination: File,
37
+ progressCallback: (Double) -> Unit,
38
+ ): DownloadResult
39
+ }
40
+
41
+ /**
42
+ * Implementation of DownloadService using HttpURLConnection
43
+ */
44
+ class HttpDownloadService : DownloadService {
45
+ override suspend fun downloadFile(
46
+ fileUrl: URL,
47
+ destination: File,
48
+ progressCallback: (Double) -> Unit,
49
+ ): DownloadResult =
50
+ withContext(Dispatchers.IO) {
51
+ val conn =
52
+ try {
53
+ fileUrl.openConnection() as HttpURLConnection
54
+ } catch (e: Exception) {
55
+ Log.d("DownloadService", "Failed to open connection: ${e.message}")
56
+ return@withContext DownloadResult.Error(e)
57
+ }
58
+
59
+ try {
60
+ conn.connect()
61
+ val totalSize = conn.contentLength
62
+ if (totalSize <= 0) {
63
+ Log.d("DownloadService", "Invalid content length: $totalSize")
64
+ return@withContext DownloadResult.Error(Exception("Invalid content length: $totalSize"))
65
+ }
66
+
67
+ // Make sure parent directories exist
68
+ destination.parentFile?.mkdirs()
69
+
70
+ conn.inputStream.use { input ->
71
+ destination.outputStream().use { output ->
72
+ val buffer = ByteArray(8 * 1024)
73
+ var bytesRead: Int
74
+ var totalRead = 0L
75
+ var lastProgressTime = System.currentTimeMillis()
76
+
77
+ while (input.read(buffer).also { bytesRead = it } != -1) {
78
+ output.write(buffer, 0, bytesRead)
79
+ totalRead += bytesRead
80
+ val currentTime = System.currentTimeMillis()
81
+ if (currentTime - lastProgressTime >= 100) {
82
+ val progress = totalRead.toDouble() / totalSize
83
+ progressCallback.invoke(progress)
84
+ lastProgressTime = currentTime
85
+ }
86
+ }
87
+ progressCallback.invoke(1.0)
88
+ }
89
+ }
90
+ DownloadResult.Success(destination)
91
+ } catch (e: Exception) {
92
+ Log.d("DownloadService", "Failed to download data from URL: $fileUrl, Error: ${e.message}")
93
+ DownloadResult.Error(e)
94
+ } finally {
95
+ conn.disconnect()
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,69 @@
1
+ package com.hotupdater
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import java.io.File
6
+
7
+ /**
8
+ * Interface for preference storage operations
9
+ */
10
+ interface PreferencesService {
11
+ /**
12
+ * Gets a stored preference value
13
+ * @param key The key to retrieve
14
+ * @return The stored value or null if not found
15
+ */
16
+ fun getItem(key: String): String?
17
+
18
+ /**
19
+ * Sets a preference value
20
+ * @param key The key to store under
21
+ * @param value The value to store (or null to remove)
22
+ */
23
+ fun setItem(
24
+ key: String,
25
+ value: String?,
26
+ )
27
+ }
28
+
29
+ /**
30
+ * Implementation of PreferencesService using SharedPreferences
31
+ * Modified from original HotUpdaterPrefs to follow the service pattern
32
+ */
33
+ class VersionedPreferencesService(
34
+ private val context: Context,
35
+ private val appVersion: String,
36
+ ) : PreferencesService {
37
+ private val prefs: SharedPreferences
38
+
39
+ init {
40
+ val prefsName = "HotUpdaterPrefs_$appVersion"
41
+
42
+ val sharedPrefsDir = File(context.applicationInfo.dataDir, "shared_prefs")
43
+ if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
44
+ sharedPrefsDir.listFiles()?.forEach { file ->
45
+ if (file.name.startsWith("HotUpdaterPrefs_") && file.name != "$prefsName.xml") {
46
+ file.delete()
47
+ }
48
+ }
49
+ }
50
+
51
+ prefs = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
52
+ }
53
+
54
+ override fun getItem(key: String): String? = prefs.getString(key, null)
55
+
56
+ override fun setItem(
57
+ key: String,
58
+ value: String?,
59
+ ) {
60
+ prefs.edit().apply {
61
+ if (value == null) {
62
+ remove(key)
63
+ } else {
64
+ putString(key, value)
65
+ }
66
+ apply()
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,52 @@
1
+ package com.hotupdater
2
+
3
+ import android.util.Log
4
+ import java.io.File
5
+ import java.util.zip.ZipFile
6
+
7
+ /**
8
+ * Interface for unzip operations
9
+ */
10
+ interface UnzipService {
11
+ /**
12
+ * Extracts a zip file to a destination directory
13
+ * @param filePath Path to the zip file
14
+ * @param destinationPath Directory to extract contents to
15
+ * @return true if extraction was successful, false otherwise
16
+ */
17
+ fun extractZipFile(
18
+ filePath: String,
19
+ destinationPath: String,
20
+ ): Boolean
21
+ }
22
+
23
+ /**
24
+ * Implementation of UnzipService using standard Zip API
25
+ */
26
+ class ZipFileUnzipService : UnzipService {
27
+ override fun extractZipFile(
28
+ filePath: String,
29
+ destinationPath: String,
30
+ ): Boolean =
31
+ 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)
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ true
48
+ } catch (e: Exception) {
49
+ Log.d("UnzipService", "Failed to unzip file: ${e.message}")
50
+ false
51
+ }
52
+ }
@@ -1,10 +1,12 @@
1
1
  package com.hotupdater
2
2
 
3
+ import android.util.Log
3
4
  import androidx.fragment.app.FragmentActivity
4
5
  import androidx.lifecycle.lifecycleScope
5
6
  import com.facebook.react.bridge.Promise
6
7
  import com.facebook.react.bridge.ReactApplicationContext
7
8
  import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableMap
8
10
  import com.facebook.react.bridge.WritableNativeMap
9
11
  import com.facebook.react.modules.core.DeviceEventManagerModule
10
12
  import kotlinx.coroutines.launch
@@ -18,54 +20,49 @@ class HotUpdaterModule internal constructor(
18
20
 
19
21
  @ReactMethod
20
22
  override fun reload() {
21
- HotUpdater.reload(mReactApplicationContext)
22
- }
23
-
24
- @ReactMethod
25
- override fun getAppVersion(promise: Promise) {
26
- promise.resolve(HotUpdater.getAppVersion(mReactApplicationContext))
27
- }
28
-
29
- @ReactMethod
30
- override fun setChannel(
31
- channel: String,
32
- promise: Promise,
33
- ) {
34
- HotUpdater.setChannel(mReactApplicationContext, channel)
35
- promise.resolve(null)
23
+ try {
24
+ HotUpdater.reload(mReactApplicationContext)
25
+ } catch (e: Exception) {
26
+ Log.d("HotUpdater", "Failed to reload", e)
27
+ }
36
28
  }
37
29
 
38
30
  @ReactMethod
39
31
  override fun updateBundle(
40
- bundleId: String,
41
- zipUrl: String?,
32
+ params: ReadableMap,
42
33
  promise: Promise,
43
34
  ) {
44
35
  // Use lifecycleScope when currentActivity is FragmentActivity
45
36
  (currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
46
- val isSuccess =
47
- HotUpdater.updateBundle(
48
- mReactApplicationContext,
49
- bundleId,
50
- zipUrl,
51
- ) { progress ->
52
- val params =
53
- WritableNativeMap().apply {
54
- putDouble("progress", progress)
55
- }
37
+ try {
38
+ val bundleId = params.getString("bundleId")!!
39
+ val fileUrl = params.getString("fileUrl")
40
+ val isSuccess =
41
+ HotUpdater.updateBundle(
42
+ mReactApplicationContext,
43
+ bundleId,
44
+ fileUrl,
45
+ ) { progress ->
46
+ val progressParams =
47
+ WritableNativeMap().apply {
48
+ putDouble("progress", progress)
49
+ }
56
50
 
57
- this@HotUpdaterModule
58
- .mReactApplicationContext
59
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
60
- .emit("onProgress", params)
61
- }
62
- promise.resolve(isSuccess)
51
+ this@HotUpdaterModule
52
+ .mReactApplicationContext
53
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
54
+ .emit("onProgress", progressParams)
55
+ }
56
+ promise.resolve(isSuccess)
57
+ } catch (e: Exception) {
58
+ promise.reject("updateBundle", e)
59
+ }
63
60
  }
64
61
  }
65
62
 
66
63
  override fun getTypedExportedConstants(): Map<String, Any?> {
67
64
  val constants: MutableMap<String, Any?> = HashMap()
68
- constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId()
65
+ constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId(mReactApplicationContext)
69
66
  constants["APP_VERSION"] = HotUpdater.getAppVersion(mReactApplicationContext)
70
67
  constants["CHANNEL"] = HotUpdater.getChannel(mReactApplicationContext)
71
68
  return constants
@@ -3,7 +3,9 @@ package com.hotupdater
3
3
  import android.content.Context
4
4
  import android.util.Log
5
5
  import com.facebook.react.ReactApplication
6
+ import com.facebook.react.bridge.JSBundleLoader
6
7
  import com.facebook.react.common.LifecycleState
8
+ import java.lang.reflect.Field
7
9
 
8
10
  class ReactIntegrationManager(
9
11
  context: Context,
@@ -25,8 +27,22 @@ class ReactIntegrationManager(
25
27
  jsBundleLoaderField.isAccessible = true
26
28
  jsBundleLoaderField.set(reactHostDelegate, getJSBundlerLoader(bundleURL))
27
29
  } catch (e: Exception) {
30
+ try {
31
+ val instanceManager = application.reactNativeHost.reactInstanceManager
32
+ val bundleLoader: JSBundleLoader? = this.getJSBundlerLoader(bundleURL)
33
+ val bundleLoaderField: Field =
34
+ instanceManager::class.java.getDeclaredField("mBundleLoader")
35
+ bundleLoaderField.isAccessible = true
36
+
37
+ if (bundleLoader != null) {
38
+ bundleLoaderField.set(instanceManager, bundleLoader)
39
+ } else {
40
+ bundleLoaderField.set(instanceManager, null)
41
+ }
42
+ } catch (e: Exception) {
43
+ Log.d("HotUpdater", "Failed to setJSBundle (fallback): ${e.message}")
44
+ }
28
45
  Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
29
- throw IllegalAccessException("Could not setJSBundle")
30
46
  }
31
47
  }
32
48
 
@@ -57,12 +73,10 @@ class ReactIntegrationManager(
57
73
  }
58
74
  } catch (e: Exception) {
59
75
  Log.d("HotUpdater", "Failed to reload: ${e.message}")
60
- throw e
61
76
  }
62
77
  }
63
78
  } catch (e: Exception) {
64
79
  Log.d("HotUpdater", "Failed to reload: ${e.message}")
65
- throw e
66
80
  }
67
81
  }
68
82
  }
@@ -1,10 +1,12 @@
1
1
  package com.hotupdater
2
2
 
3
+ import android.util.Log
3
4
  import androidx.fragment.app.FragmentActivity
4
5
  import androidx.lifecycle.lifecycleScope
5
6
  import com.facebook.react.bridge.Promise
6
7
  import com.facebook.react.bridge.ReactApplicationContext
7
8
  import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableMap
8
10
  import com.facebook.react.bridge.WritableNativeMap
9
11
  import com.facebook.react.modules.core.DeviceEventManagerModule
10
12
  import kotlinx.coroutines.launch
@@ -18,48 +20,44 @@ class HotUpdaterModule internal constructor(
18
20
 
19
21
  @ReactMethod
20
22
  override fun reload() {
21
- HotUpdater.reload(mReactApplicationContext)
22
- }
23
-
24
- @ReactMethod
25
- override fun getAppVersion(promise: Promise) {
26
- promise.resolve(HotUpdater.getAppVersion(mReactApplicationContext))
27
- }
28
-
29
- @ReactMethod
30
- override fun setChannel(
31
- channel: String,
32
- promise: Promise,
33
- ) {
34
- HotUpdater.setChannel(mReactApplicationContext, channel)
35
- promise.resolve(null)
23
+ try {
24
+ HotUpdater.reload(mReactApplicationContext)
25
+ } catch (e: Exception) {
26
+ Log.d("HotUpdater", "Failed to reload", e)
27
+ }
36
28
  }
37
29
 
38
30
  @ReactMethod
39
31
  override fun updateBundle(
40
- bundleId: String,
41
- zipUrl: String?,
32
+ params: ReadableMap,
42
33
  promise: Promise,
43
34
  ) {
44
35
  // Use lifecycleScope when currentActivity is FragmentActivity
45
36
  (currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
46
- val isSuccess =
47
- HotUpdater.updateBundle(
48
- mReactApplicationContext,
49
- bundleId,
50
- zipUrl,
51
- ) { progress ->
52
- val params =
53
- WritableNativeMap().apply {
54
- putDouble("progress", progress)
55
- }
37
+ try {
38
+ val bundleId = params.getString("bundleId")!!
39
+ val fileUrl = params.getString("fileUrl")
40
+
41
+ val isSuccess =
42
+ HotUpdater.updateBundle(
43
+ mReactApplicationContext,
44
+ bundleId,
45
+ fileUrl,
46
+ ) { progress ->
47
+ val progressParams =
48
+ WritableNativeMap().apply {
49
+ putDouble("progress", progress)
50
+ }
56
51
 
57
- this@HotUpdaterModule
58
- .mReactApplicationContext
59
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
60
- .emit("onProgress", params)
61
- }
62
- promise.resolve(isSuccess)
52
+ this@HotUpdaterModule
53
+ .mReactApplicationContext
54
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
55
+ .emit("onProgress", progressParams)
56
+ }
57
+ promise.resolve(isSuccess)
58
+ } catch (e: Exception) {
59
+ promise.reject("updateBundle", e)
60
+ }
63
61
  }
64
62
  }
65
63
 
@@ -79,7 +77,7 @@ class HotUpdaterModule internal constructor(
79
77
 
80
78
  override fun getConstants(): Map<String, Any?> {
81
79
  val constants: MutableMap<String, Any?> = HashMap()
82
- constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId()
80
+ constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId(mReactApplicationContext)
83
81
  constants["APP_VERSION"] = HotUpdater.getAppVersion(mReactApplicationContext)
84
82
  constants["CHANNEL"] = HotUpdater.getChannel(mReactApplicationContext)
85
83
  return constants
@@ -3,22 +3,15 @@ package com.hotupdater
3
3
  import com.facebook.react.bridge.Promise
4
4
  import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.facebook.react.bridge.ReactContextBaseJavaModule
6
+ import com.facebook.react.bridge.ReadableMap
6
7
 
7
8
  abstract class HotUpdaterSpec internal constructor(
8
9
  context: ReactApplicationContext,
9
10
  ) : ReactContextBaseJavaModule(context) {
10
11
  abstract fun updateBundle(
11
- bundleId: String,
12
- zipUrl: String?,
12
+ params: ReadableMap,
13
13
  promise: Promise,
14
14
  )
15
15
 
16
16
  abstract fun reload()
17
-
18
- abstract fun getAppVersion(promise: Promise)
19
-
20
- abstract fun setChannel(
21
- channel: String,
22
- promise: Promise,
23
- )
24
17
  }
@@ -27,7 +27,6 @@ class ReactIntegrationManager(
27
27
  }
28
28
  } catch (e: Exception) {
29
29
  Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
30
- throw IllegalAccessException("Could not setJSBundle")
31
30
  }
32
31
  }
33
32
 
@@ -49,7 +48,6 @@ class ReactIntegrationManager(
49
48
  }
50
49
  } catch (e: Exception) {
51
50
  Log.d("HotUpdater", "Failed to reload: ${e.message}")
52
- throw e
53
51
  }
54
52
  }
55
53
  }