@hot-updater/react-native 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/HotUpdater.podspec +7 -11
  2. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
  3. package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
  4. package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
  5. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
  7. package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
  8. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
  9. package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
  10. package/android/src/newarch/HotUpdaterModule.kt +31 -34
  11. package/android/src/oldarch/HotUpdaterModule.kt +32 -34
  12. package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
  13. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
  14. package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
  15. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
  16. package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
  17. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
  18. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
  19. package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
  20. package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
  21. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
  22. package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
  23. package/ios/HotUpdater/Public/HotUpdater.h +29 -0
  24. package/lib/commonjs/checkForUpdate.js +70 -0
  25. package/lib/commonjs/checkForUpdate.js.map +1 -0
  26. package/lib/commonjs/error.js +14 -0
  27. package/lib/commonjs/error.js.map +1 -0
  28. package/lib/commonjs/fetchUpdateInfo.js +74 -0
  29. package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
  30. package/lib/commonjs/hooks/useEventCallback.js +17 -0
  31. package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
  32. package/lib/commonjs/index.js +234 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/commonjs/native.js +132 -0
  35. package/lib/commonjs/native.js.map +1 -0
  36. package/lib/commonjs/package.json +1 -0
  37. package/lib/commonjs/runUpdateProcess.js +69 -0
  38. package/lib/commonjs/runUpdateProcess.js.map +1 -0
  39. package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
  40. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
  41. package/lib/commonjs/store.js +48 -0
  42. package/lib/commonjs/store.js.map +1 -0
  43. package/lib/commonjs/wrap.js +98 -0
  44. package/lib/commonjs/wrap.js.map +1 -0
  45. package/lib/module/checkForUpdate.js +64 -0
  46. package/lib/module/checkForUpdate.js.map +1 -0
  47. package/lib/module/error.js +9 -0
  48. package/lib/module/error.js.map +1 -0
  49. package/lib/module/fetchUpdateInfo.js +69 -0
  50. package/lib/module/fetchUpdateInfo.js.map +1 -0
  51. package/lib/module/hooks/useEventCallback.js +13 -0
  52. package/lib/module/hooks/useEventCallback.js.map +1 -0
  53. package/lib/module/index.js +211 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/native.js +119 -0
  56. package/lib/module/native.js.map +1 -0
  57. package/lib/module/package.json +1 -0
  58. package/lib/module/runUpdateProcess.js +64 -0
  59. package/lib/module/runUpdateProcess.js.map +1 -0
  60. package/lib/module/specs/NativeHotUpdater.js +5 -0
  61. package/lib/module/specs/NativeHotUpdater.js.map +1 -0
  62. package/lib/module/store.js +42 -0
  63. package/lib/module/store.js.map +1 -0
  64. package/lib/module/wrap.js +94 -0
  65. package/lib/module/wrap.js.map +1 -0
  66. package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
  67. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
  68. package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
  69. package/lib/typescript/commonjs/error.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
  71. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
  72. package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
  73. package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
  74. package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
  75. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/native.d.ts +64 -0
  77. package/lib/typescript/commonjs/native.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/package.json +1 -0
  79. package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
  80. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
  81. package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
  82. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
  83. package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
  84. package/lib/typescript/commonjs/store.d.ts.map +1 -0
  85. package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
  86. package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
  87. package/lib/typescript/module/checkForUpdate.d.ts +22 -0
  88. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
  89. package/lib/typescript/module/error.d.ts +4 -0
  90. package/lib/typescript/module/error.d.ts.map +1 -0
  91. package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
  92. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
  93. package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
  94. package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
  95. package/lib/typescript/module/index.d.ts +202 -0
  96. package/lib/typescript/module/index.d.ts.map +1 -0
  97. package/lib/typescript/module/native.d.ts +64 -0
  98. package/lib/typescript/module/native.d.ts.map +1 -0
  99. package/lib/typescript/module/package.json +1 -0
  100. package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
  101. package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
  102. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
  103. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
  104. package/lib/typescript/module/store.d.ts +11 -0
  105. package/lib/typescript/module/store.d.ts.map +1 -0
  106. package/lib/typescript/module/wrap.d.ts +51 -0
  107. package/lib/typescript/module/wrap.d.ts.map +1 -0
  108. package/package.json +59 -30
  109. package/src/checkForUpdate.ts +59 -9
  110. package/src/fetchUpdateInfo.ts +40 -12
  111. package/src/index.ts +37 -11
  112. package/src/native.ts +87 -41
  113. package/src/runUpdateProcess.ts +2 -2
  114. package/src/specs/NativeHotUpdater.ts +8 -10
  115. package/src/wrap.tsx +9 -13
  116. package/android/generated/java/com/hotupdater/NativeHotUpdaterSpec.java +0 -93
  117. package/android/generated/jni/CMakeLists.txt +0 -36
  118. package/android/generated/jni/HotUpdaterSpec-generated.cpp +0 -68
  119. package/android/generated/jni/HotUpdaterSpec.h +0 -31
  120. package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp +0 -70
  121. package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +0 -121
  122. package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
  123. package/dist/checkForUpdate.d.ts +0 -12
  124. package/dist/fetchUpdateInfo.d.ts +0 -3
  125. package/dist/index.js +0 -341
  126. package/dist/index.mjs +0 -301
  127. package/dist/native.d.ts +0 -41
  128. package/ios/HotUpdater/HotUpdater.h +0 -15
  129. package/ios/HotUpdater/HotUpdater.mm +0 -468
  130. package/ios/HotUpdater/HotUpdater.modulemap +0 -6
  131. package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
  132. package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
  133. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
  134. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
  135. package/ios/generated/HotUpdaterSpecJSI-generated.cpp +0 -70
  136. package/ios/generated/HotUpdaterSpecJSI.h +0 -121
  137. package/react-native.config.js +0 -12
  138. package/src/global.d.ts +0 -3
@@ -2,23 +2,16 @@ package com.hotupdater
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
- import android.os.Handler
6
- import android.os.Looper
7
- import android.util.Log
8
5
  import android.view.View
9
- import com.facebook.react.ReactApplication
10
6
  import com.facebook.react.ReactPackage
11
7
  import com.facebook.react.bridge.NativeModule
12
8
  import com.facebook.react.bridge.ReactApplicationContext
13
9
  import com.facebook.react.uimanager.ReactShadowNode
14
10
  import com.facebook.react.uimanager.ViewManager
15
- import kotlinx.coroutines.Dispatchers
16
- import kotlinx.coroutines.withContext
17
- import java.io.File
18
- import java.net.HttpURLConnection
19
- import java.net.URL
20
- import java.util.zip.ZipFile
21
11
 
12
+ /**
13
+ * Main React Native package for HotUpdater
14
+ */
22
15
  class HotUpdater : ReactPackage {
23
16
  override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
24
17
 
@@ -26,309 +19,73 @@ class HotUpdater : ReactPackage {
26
19
  listOf(HotUpdaterModule(context)).toMutableList()
27
20
 
28
21
  companion object {
29
- fun getAppVersion(context: Context): String? {
30
- val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
31
- return packageInfo.versionName
32
- }
33
-
34
- @Volatile
35
- private var prefsInstance: HotUpdaterPrefs? = null
36
-
37
- @Volatile
38
- private var cachedAppVersion: String? = null
39
-
40
- private fun getPrefs(context: Context): HotUpdaterPrefs {
41
- val appContext = context.applicationContext
42
- val currentAppVersion = getAppVersion(appContext) ?: "unknown"
43
- synchronized(this) {
44
- if (prefsInstance == null || cachedAppVersion != currentAppVersion) {
45
- prefsInstance = HotUpdaterPrefs(appContext, currentAppVersion)
46
- cachedAppVersion = currentAppVersion
47
- }
48
- return prefsInstance!!
49
- }
50
- }
51
-
52
- private fun setBundleURL(
22
+ /**
23
+ * Gets the app version
24
+ * @param context Application context
25
+ * @return App version name or null if not available
26
+ */
27
+ fun getAppVersion(context: Context): String? = HotUpdaterFactory.getInstance(context).getAppVersion()
28
+
29
+ /**
30
+ * Generates a bundle ID based on build timestamp
31
+ * @param context Application context
32
+ * @return The minimum bundle ID string
33
+ */
34
+ fun getMinBundleId(context: Context): String = HotUpdaterFactory.getInstance(context).getMinBundleId()
35
+
36
+ /**
37
+ * Gets the current update channel
38
+ * @param context Application context
39
+ * @return The channel name or null if not set
40
+ */
41
+ fun getChannel(context: Context): String? = HotUpdaterFactory.getInstance(context).getChannel()
42
+
43
+ /**
44
+ * Gets the path to the bundle file
45
+ * @param context Application context
46
+ * @return The path to the bundle file
47
+ */
48
+ fun getJSBundleFile(context: Context): String = HotUpdaterFactory.getInstance(context).getJSBundleFile()
49
+
50
+ /**
51
+ * Updates the bundle from the specified URL
52
+ * @param context Application context
53
+ * @param bundleId ID of the bundle to update
54
+ * @param fileUrl URL of the bundle file to download (or null to reset)
55
+ * @param progressCallback Callback for download progress updates
56
+ * @return true if the update was successful
57
+ */
58
+ suspend fun updateBundle(
53
59
  context: Context,
54
- bundleURL: String?,
55
- ) {
56
- val updaterPrefs = getPrefs(context)
57
- updaterPrefs.setItem("HotUpdaterBundleURL", bundleURL)
58
-
59
- if (bundleURL == null) {
60
- return
61
- }
62
-
63
- val reactIntegrationManager = ReactIntegrationManager(context)
64
- val activity: Activity? = getCurrentActivity(context)
65
- val reactApplication: ReactApplication =
66
- reactIntegrationManager.getReactApplication(activity?.application)
67
- val newBundleURL = getJSBundleFile(context)
68
- reactIntegrationManager.setJSBundle(reactApplication, newBundleURL)
69
- }
70
-
71
- private fun extractZipFileAtPath(
72
- filePath: String,
73
- destinationPath: String,
60
+ bundleId: String,
61
+ fileUrl: String?,
62
+ progressCallback: (Double) -> Unit,
74
63
  ): Boolean =
75
- try {
76
- ZipFile(filePath).use { zip ->
77
- zip.entries().asSequence().forEach { entry ->
78
- val file = File(destinationPath, entry.name)
79
- if (entry.isDirectory) {
80
- file.mkdirs()
81
- } else {
82
- file.parentFile?.mkdirs()
83
- zip.getInputStream(entry).use { input ->
84
- file.outputStream().use { output -> input.copyTo(output) }
85
- }
86
- }
87
- }
88
- }
89
- true
90
- } catch (e: Exception) {
91
- Log.d("HotUpdater", "Failed to unzip file: ${e.message}")
92
- false
93
- }
64
+ HotUpdaterFactory.getInstance(context).updateBundle(
65
+ bundleId,
66
+ fileUrl,
67
+ progressCallback,
68
+ )
69
+
70
+ /**
71
+ * Reloads the React Native application
72
+ * @param context Application context
73
+ */
74
+ fun reload(context: Context) {
75
+ val currentActivity = getCurrentActivity(context)
76
+ HotUpdaterFactory.getInstance(context).reload(currentActivity)
77
+ }
94
78
 
79
+ /**
80
+ * Gets the current activity from ReactApplicationContext
81
+ * @param context Context that might be a ReactApplicationContext
82
+ * @return The current activity or null
83
+ */
95
84
  private fun getCurrentActivity(context: Context): Activity? =
96
85
  if (context is ReactApplicationContext) {
97
86
  context.currentActivity
98
87
  } else {
99
88
  null
100
89
  }
101
-
102
- fun reload(context: Context) {
103
- val reactIntegrationManager = ReactIntegrationManager(context)
104
- val activity: Activity? = getCurrentActivity(context)
105
- val reactApplication: ReactApplication =
106
- reactIntegrationManager.getReactApplication(activity?.application)
107
- val bundleURL = getJSBundleFile(context)
108
- reactIntegrationManager.setJSBundle(reactApplication, bundleURL)
109
- Handler(Looper.getMainLooper()).post {
110
- reactIntegrationManager.reload(reactApplication)
111
- }
112
- }
113
-
114
- fun getJSBundleFile(context: Context): String {
115
- val updaterPrefs = getPrefs(context)
116
- val urlString = updaterPrefs.getItem("HotUpdaterBundleURL")
117
- if (urlString.isNullOrEmpty()) {
118
- return "assets://index.android.bundle"
119
- }
120
-
121
- val file = File(urlString)
122
- if (!file.exists()) {
123
- updaterPrefs.setItem("HotUpdaterBundleURL", null)
124
- return "assets://index.android.bundle"
125
- }
126
- return urlString
127
- }
128
-
129
- fun setChannel(
130
- context: Context,
131
- channel: String,
132
- ) {
133
- val updaterPrefs = getPrefs(context)
134
- updaterPrefs.setItem("HotUpdaterChannel", channel)
135
- }
136
-
137
- fun getChannel(context: Context): String? {
138
- val updaterPrefs = getPrefs(context)
139
- return updaterPrefs.getItem("HotUpdaterChannel")
140
- }
141
-
142
- suspend fun updateBundle(
143
- context: Context,
144
- bundleId: String,
145
- zipUrl: String?,
146
- progressCallback: ((Double) -> Unit),
147
- ): Boolean {
148
- Log.d("HotUpdater", "updateBundle bundleId $bundleId zipUrl $zipUrl")
149
- if (zipUrl.isNullOrEmpty()) {
150
- setBundleURL(context, null)
151
- return true
152
- }
153
-
154
- val baseDir = context.getExternalFilesDir(null)
155
- val bundleStoreDir = File(baseDir, "bundle-store")
156
- if (!bundleStoreDir.exists()) {
157
- bundleStoreDir.mkdirs()
158
- }
159
-
160
- val finalBundleDir = File(bundleStoreDir, bundleId)
161
- if (finalBundleDir.exists()) {
162
- Log.d("HotUpdater", "Bundle for bundleId $bundleId already exists. Using cached bundle.")
163
- val existingIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
164
- if (existingIndexFile != null) {
165
- finalBundleDir.setLastModified(System.currentTimeMillis())
166
- setBundleURL(context, existingIndexFile.absolutePath)
167
- cleanupOldBundles(bundleStoreDir)
168
- return true
169
- } else {
170
- finalBundleDir.deleteRecursively()
171
- }
172
- }
173
-
174
- val tempDir = File(baseDir, "bundle-temp")
175
- if (tempDir.exists()) {
176
- tempDir.deleteRecursively()
177
- }
178
- tempDir.mkdirs()
179
-
180
- val tempZipFile = File(tempDir, "bundle.zip")
181
- val extractedDir = File(tempDir, "extracted")
182
- extractedDir.mkdirs()
183
-
184
- val isSuccess =
185
- withContext(Dispatchers.IO) {
186
- val downloadUrl = URL(zipUrl)
187
- val conn =
188
- try {
189
- downloadUrl.openConnection() as HttpURLConnection
190
- } catch (e: Exception) {
191
- Log.d("HotUpdater", "Failed to open connection: ${e.message}")
192
- tempDir.deleteRecursively()
193
- return@withContext false
194
- }
195
-
196
- try {
197
- conn.connect()
198
- val totalSize = conn.contentLength
199
- if (totalSize <= 0) {
200
- Log.d("HotUpdater", "Invalid content length: $totalSize")
201
- tempDir.deleteRecursively()
202
- return@withContext false
203
- }
204
- conn.inputStream.use { input ->
205
- tempZipFile.outputStream().use { output ->
206
- val buffer = ByteArray(8 * 1024)
207
- var bytesRead: Int
208
- var totalRead = 0L
209
- var lastProgressTime = System.currentTimeMillis()
210
-
211
- while (input.read(buffer).also { bytesRead = it } != -1) {
212
- output.write(buffer, 0, bytesRead)
213
- totalRead += bytesRead
214
- val currentTime = System.currentTimeMillis()
215
- if (currentTime - lastProgressTime >= 100) {
216
- val progress = totalRead.toDouble() / totalSize
217
- progressCallback.invoke(progress)
218
- lastProgressTime = currentTime
219
- }
220
- }
221
- progressCallback.invoke(1.0)
222
- }
223
- }
224
- } catch (e: Exception) {
225
- Log.d("HotUpdater", "Failed to download data from URL: $zipUrl, Error: ${e.message}")
226
- tempDir.deleteRecursively()
227
- return@withContext false
228
- } finally {
229
- conn.disconnect()
230
- }
231
-
232
- if (!extractZipFileAtPath(tempZipFile.absolutePath, extractedDir.absolutePath)) {
233
- Log.d("HotUpdater", "Failed to extract zip file.")
234
- tempDir.deleteRecursively()
235
- return@withContext false
236
- }
237
- true
238
- }
239
-
240
- if (!isSuccess) {
241
- tempDir.deleteRecursively()
242
- return false
243
- }
244
-
245
- val indexFileExtracted = extractedDir.walk().find { it.name == "index.android.bundle" }
246
- if (indexFileExtracted == null) {
247
- Log.d("HotUpdater", "index.android.bundle not found in extracted files.")
248
- tempDir.deleteRecursively()
249
- return false
250
- }
251
-
252
- if (finalBundleDir.exists()) {
253
- finalBundleDir.deleteRecursively()
254
- }
255
- if (!extractedDir.renameTo(finalBundleDir)) {
256
- extractedDir.copyRecursively(finalBundleDir, overwrite = true)
257
- extractedDir.deleteRecursively()
258
- }
259
-
260
- val finalIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
261
- if (finalIndexFile == null) {
262
- Log.d("HotUpdater", "index.android.bundle not found in final directory.")
263
- tempDir.deleteRecursively()
264
- return false
265
- }
266
-
267
- finalBundleDir.setLastModified(System.currentTimeMillis())
268
- val bundlePath = finalIndexFile.absolutePath
269
- Log.d("HotUpdater", "Setting bundle URL: $bundlePath")
270
- setBundleURL(context, bundlePath)
271
- cleanupOldBundles(bundleStoreDir)
272
- tempDir.deleteRecursively()
273
-
274
- Log.d("HotUpdater", "Downloaded and extracted file successfully.")
275
- return true
276
- }
277
-
278
- private fun cleanupOldBundles(bundleStoreDir: File) {
279
- val bundles = bundleStoreDir.listFiles { file -> file.isDirectory }?.toList() ?: return
280
- val sortedBundles = bundles.sortedByDescending { it.lastModified() }
281
- if (sortedBundles.size > 1) {
282
- sortedBundles.drop(1).forEach { oldBundle ->
283
- Log.d("HotUpdater", "Removing old bundle: ${oldBundle.name}")
284
- oldBundle.deleteRecursively()
285
- }
286
- }
287
- }
288
-
289
- fun getMinBundleId(): String =
290
- try {
291
- val buildTimestampMs = BuildConfig.BUILD_TIMESTAMP
292
- val bytes =
293
- ByteArray(16).apply {
294
- this[0] = ((buildTimestampMs shr 40) and 0xFF).toByte()
295
- this[1] = ((buildTimestampMs shr 32) and 0xFF).toByte()
296
- this[2] = ((buildTimestampMs shr 24) and 0xFF).toByte()
297
- this[3] = ((buildTimestampMs shr 16) and 0xFF).toByte()
298
- this[4] = ((buildTimestampMs shr 8) and 0xFF).toByte()
299
- this[5] = (buildTimestampMs and 0xFF).toByte()
300
- this[6] = 0x70.toByte()
301
- this[7] = 0x00.toByte()
302
- this[8] = 0x80.toByte()
303
- this[9] = 0x00.toByte()
304
- this[10] = 0x00.toByte()
305
- this[11] = 0x00.toByte()
306
- this[12] = 0x00.toByte()
307
- this[13] = 0x00.toByte()
308
- this[14] = 0x00.toByte()
309
- this[15] = 0x00.toByte()
310
- }
311
- String.format(
312
- "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
313
- bytes[0].toInt() and 0xFF,
314
- bytes[1].toInt() and 0xFF,
315
- bytes[2].toInt() and 0xFF,
316
- bytes[3].toInt() and 0xFF,
317
- bytes[4].toInt() and 0xFF,
318
- bytes[5].toInt() and 0xFF,
319
- bytes[6].toInt() and 0xFF,
320
- bytes[7].toInt() and 0xFF,
321
- bytes[8].toInt() and 0xFF,
322
- bytes[9].toInt() and 0xFF,
323
- bytes[10].toInt() and 0xFF,
324
- bytes[11].toInt() and 0xFF,
325
- bytes[12].toInt() and 0xFF,
326
- bytes[13].toInt() and 0xFF,
327
- bytes[14].toInt() and 0xFF,
328
- bytes[15].toInt() and 0xFF,
329
- )
330
- } catch (e: Exception) {
331
- "00000000-0000-0000-0000-000000000000"
332
- }
333
90
  }
334
91
  }
@@ -0,0 +1,49 @@
1
+ package com.hotupdater
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Factory for creating HotUpdaterImpl instances with proper dependency injection
7
+ */
8
+ object HotUpdaterFactory {
9
+ @Volatile
10
+ private var instance: HotUpdaterImpl? = null
11
+
12
+ /**
13
+ * Gets the singleton instance of HotUpdaterImpl
14
+ * @param context Application context
15
+ * @return HotUpdaterImpl instance
16
+ */
17
+ fun getInstance(context: Context): HotUpdaterImpl =
18
+ instance ?: synchronized(this) {
19
+ instance ?: createHotUpdaterImpl(context).also { instance = it }
20
+ }
21
+
22
+ /**
23
+ * Creates a new HotUpdaterImpl instance with all dependencies
24
+ * @param context Application context
25
+ * @return New HotUpdaterImpl instance
26
+ */
27
+ private fun createHotUpdaterImpl(context: Context): HotUpdaterImpl {
28
+ val appContext = context.applicationContext
29
+ val appVersion = HotUpdaterImpl.getAppVersion(appContext) ?: "unknown"
30
+
31
+ // Create services
32
+ val fileSystem = FileManagerService(appContext)
33
+ val preferences = VersionedPreferencesService(appContext, appVersion)
34
+ val downloadService = HttpDownloadService()
35
+ val unzipService = ZipFileUnzipService()
36
+
37
+ // Create bundle storage with dependencies
38
+ val bundleStorage =
39
+ BundleFileStorageService(
40
+ fileSystem,
41
+ downloadService,
42
+ unzipService,
43
+ preferences,
44
+ )
45
+
46
+ // Create and return the implementation
47
+ return HotUpdaterImpl(appContext, bundleStorage, preferences)
48
+ }
49
+ }
@@ -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
+ }