@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.
- package/HotUpdater.podspec +7 -11
- package/android/{generated/java/com/hotupdater → app/build/generated/source/codegen/java/com/facebook/fbreact/specs}/NativeHotUpdaterSpec.java +3 -2
- package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +68 -0
- package/android/app/build/generated/source/codegen/jni/HotUpdater.h +31 -0
- package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec-generated.cpp +2 -2
- 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
- package/{ios/generated/HotUpdaterSpecJSI.h → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h} +53 -6
- package/{ios/generated → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec}/HotUpdaterSpecJSI-generated.cpp +2 -3
- package/android/{generated → app/build/generated/source/codegen}/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +59 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
- package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
- package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
- package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
- package/android/src/newarch/HotUpdaterModule.kt +31 -34
- package/android/src/newarch/ReactIntegrationManager.kt +17 -3
- package/android/src/oldarch/HotUpdaterModule.kt +32 -34
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -2
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
- package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
- package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
- package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
- package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
- package/ios/HotUpdater/Package.resolved +15 -0
- package/ios/HotUpdater/Public/HotUpdater.h +29 -0
- package/lib/commonjs/checkForUpdate.js +70 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -0
- package/lib/commonjs/error.js +14 -0
- package/lib/commonjs/error.js.map +1 -0
- package/lib/commonjs/fetchUpdateInfo.js +74 -0
- package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
- package/lib/commonjs/hooks/useEventCallback.js +17 -0
- package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
- package/lib/commonjs/index.js +234 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native.js +132 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/runUpdateProcess.js +69 -0
- package/lib/commonjs/runUpdateProcess.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
- package/lib/commonjs/store.js +48 -0
- package/lib/commonjs/store.js.map +1 -0
- package/lib/commonjs/wrap.js +98 -0
- package/lib/commonjs/wrap.js.map +1 -0
- package/lib/module/checkForUpdate.js +64 -0
- package/lib/module/checkForUpdate.js.map +1 -0
- package/lib/module/error.js +9 -0
- package/lib/module/error.js.map +1 -0
- package/lib/module/fetchUpdateInfo.js +69 -0
- package/lib/module/fetchUpdateInfo.js.map +1 -0
- package/lib/module/hooks/useEventCallback.js +13 -0
- package/lib/module/hooks/useEventCallback.js.map +1 -0
- package/lib/module/index.js +211 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +119 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/runUpdateProcess.js +64 -0
- package/lib/module/runUpdateProcess.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js +5 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -0
- package/lib/module/store.js +42 -0
- package/lib/module/store.js.map +1 -0
- package/lib/module/wrap.js +94 -0
- package/lib/module/wrap.js.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
- package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +64 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
- package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +22 -0
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -0
- package/lib/typescript/module/error.d.ts.map +1 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +202 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +64 -0
- package/lib/typescript/module/native.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
- package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
- package/lib/typescript/module/store.d.ts +11 -0
- package/lib/typescript/module/store.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts +51 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -0
- package/package.json +59 -30
- package/src/checkForUpdate.ts +59 -9
- package/src/fetchUpdateInfo.ts +40 -12
- package/src/index.ts +37 -11
- package/src/native.ts +87 -41
- package/src/runUpdateProcess.ts +2 -2
- package/src/specs/NativeHotUpdater.ts +8 -10
- package/src/wrap.tsx +9 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
- package/dist/checkForUpdate.d.ts +0 -12
- package/dist/fetchUpdateInfo.d.ts +0 -3
- package/dist/index.js +0 -341
- package/dist/index.mjs +0 -301
- package/dist/native.d.ts +0 -41
- package/ios/HotUpdater/HotUpdater.h +0 -15
- package/ios/HotUpdater/HotUpdater.mm +0 -468
- package/ios/HotUpdater/HotUpdater.modulemap +0 -6
- package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
- package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
- package/react-native.config.js +0 -12
- package/src/global.d.ts +0 -3
- /package/android/{generated → app/build/generated/source/codegen}/jni/CMakeLists.txt +0 -0
- /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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
}
|