@hot-updater/react-native 0.9.0 → 0.10.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.
package/android/build.gradle
CHANGED
|
@@ -11,6 +11,7 @@ buildscript {
|
|
|
11
11
|
classpath "com.android.tools.build:gradle:7.2.1"
|
|
12
12
|
// noinspection DifferentKotlinGradleVersion
|
|
13
13
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
|
+
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -122,6 +123,9 @@ dependencies {
|
|
|
122
123
|
implementation 'com.facebook.react:react-native:+'
|
|
123
124
|
}
|
|
124
125
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
126
|
+
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
|
|
127
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
128
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
if (isNewArchitectureEnabled()) {
|
|
@@ -12,6 +12,8 @@ import com.facebook.react.bridge.NativeModule
|
|
|
12
12
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
13
|
import com.facebook.react.uimanager.ReactShadowNode
|
|
14
14
|
import com.facebook.react.uimanager.ViewManager
|
|
15
|
+
import kotlinx.coroutines.Dispatchers
|
|
16
|
+
import kotlinx.coroutines.withContext
|
|
15
17
|
import java.io.File
|
|
16
18
|
import java.net.HttpURLConnection
|
|
17
19
|
import java.net.URL
|
|
@@ -28,9 +30,9 @@ class HotUpdater : ReactPackage {
|
|
|
28
30
|
context: Context,
|
|
29
31
|
basePath: String,
|
|
30
32
|
): String {
|
|
31
|
-
val documentsDir =
|
|
33
|
+
val documentsDir =
|
|
34
|
+
context.getExternalFilesDir(null)?.absolutePath ?: context.filesDir.absolutePath
|
|
32
35
|
val separator = if (basePath.startsWith("/")) "" else "/"
|
|
33
|
-
|
|
34
36
|
return "$documentsDir$separator$basePath"
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -65,12 +67,11 @@ class HotUpdater : ReactPackage {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
68
|
-
|
|
69
70
|
val activity: Activity? = getCurrentActivity(context)
|
|
70
|
-
val reactApplication: ReactApplication =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
reactIntegrationManager.setJSBundle(reactApplication,
|
|
71
|
+
val reactApplication: ReactApplication =
|
|
72
|
+
reactIntegrationManager.getReactApplication(activity?.application)
|
|
73
|
+
val newBundleURL = getJSBundleFile(context)
|
|
74
|
+
reactIntegrationManager.setJSBundle(reactApplication, newBundleURL)
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
private fun extractZipFileAtPath(
|
|
@@ -106,19 +107,17 @@ class HotUpdater : ReactPackage {
|
|
|
106
107
|
|
|
107
108
|
fun reload(context: Context) {
|
|
108
109
|
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
109
|
-
|
|
110
110
|
val activity: Activity? = getCurrentActivity(context)
|
|
111
|
-
val reactApplication: ReactApplication =
|
|
111
|
+
val reactApplication: ReactApplication =
|
|
112
|
+
reactIntegrationManager.getReactApplication(activity?.application)
|
|
112
113
|
val bundleURL = getJSBundleFile(context)
|
|
113
|
-
|
|
114
114
|
reactIntegrationManager.setJSBundle(reactApplication, bundleURL)
|
|
115
|
-
|
|
116
115
|
Handler(Looper.getMainLooper()).post {
|
|
117
116
|
reactIntegrationManager.reload(reactApplication)
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
fun getJSBundleFile(context: Context): String {
|
|
122
121
|
val sharedPreferences =
|
|
123
122
|
context.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
124
123
|
val urlString = sharedPreferences.getString("HotUpdaterBundleURL", null)
|
|
@@ -135,7 +134,7 @@ class HotUpdater : ReactPackage {
|
|
|
135
134
|
return urlString
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
fun updateBundle(
|
|
137
|
+
suspend fun updateBundle(
|
|
139
138
|
context: Context,
|
|
140
139
|
bundleId: String,
|
|
141
140
|
zipUrl: String?,
|
|
@@ -148,66 +147,81 @@ class HotUpdater : ReactPackage {
|
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
val downloadUrl = URL(zipUrl)
|
|
151
|
-
|
|
152
150
|
val basePath = stripPrefixFromPath(bundleId, downloadUrl.path)
|
|
153
151
|
val path = convertFileSystemPathFromBasePath(context, basePath)
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
val file = File(path)
|
|
167
|
-
file.parentFile?.mkdirs()
|
|
153
|
+
val isSuccess =
|
|
154
|
+
withContext(Dispatchers.IO) {
|
|
155
|
+
val conn =
|
|
156
|
+
try {
|
|
157
|
+
downloadUrl.openConnection() as HttpURLConnection
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
Log.d("HotUpdater", "Failed to open connection: ${e.message}")
|
|
160
|
+
return@withContext false
|
|
161
|
+
}
|
|
168
162
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
val
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
try {
|
|
164
|
+
conn.connect()
|
|
165
|
+
val totalSize = conn.contentLength
|
|
166
|
+
if (totalSize <= 0) {
|
|
167
|
+
Log.d("HotUpdater", "Invalid content length: $totalSize")
|
|
168
|
+
return@withContext false
|
|
169
|
+
}
|
|
174
170
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
val file = File(path)
|
|
172
|
+
file.parentFile?.mkdirs()
|
|
173
|
+
|
|
174
|
+
conn.inputStream.use { input ->
|
|
175
|
+
file.outputStream().use { output ->
|
|
176
|
+
val buffer = ByteArray(8 * 1024)
|
|
177
|
+
var bytesRead: Int
|
|
178
|
+
var totalRead = 0L
|
|
179
|
+
var lastProgressTime = System.currentTimeMillis()
|
|
180
|
+
|
|
181
|
+
while (input.read(buffer).also { bytesRead = it } != -1) {
|
|
182
|
+
output.write(buffer, 0, bytesRead)
|
|
183
|
+
totalRead += bytesRead
|
|
184
|
+
val currentTime = System.currentTimeMillis()
|
|
185
|
+
if (currentTime - lastProgressTime >= 100) { // Check every 100ms
|
|
186
|
+
val progress = totalRead.toDouble() / totalSize
|
|
187
|
+
progressCallback.invoke(progress)
|
|
188
|
+
lastProgressTime = currentTime
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Send final progress (100%) after download completes
|
|
192
|
+
progressCallback.invoke(1.0)
|
|
193
|
+
}
|
|
180
194
|
}
|
|
195
|
+
} catch (e: Exception) {
|
|
196
|
+
Log.d("HotUpdater", "Failed to download data from URL: $zipUrl, Error: ${e.message}")
|
|
197
|
+
return@withContext false
|
|
198
|
+
} finally {
|
|
199
|
+
conn.disconnect()
|
|
181
200
|
}
|
|
182
|
-
}
|
|
183
|
-
} catch (e: Exception) {
|
|
184
|
-
Log.d("HotUpdater", "Failed to download data from URL: $zipUrl, Error: ${e.message}")
|
|
185
|
-
return false
|
|
186
|
-
} finally {
|
|
187
|
-
connection?.disconnect()
|
|
188
|
-
}
|
|
189
201
|
|
|
190
|
-
|
|
202
|
+
val extractedPath = File(path).parentFile?.path ?: return@withContext false
|
|
191
203
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
204
|
+
if (!extractZipFileAtPath(path, extractedPath)) {
|
|
205
|
+
Log.d("HotUpdater", "Failed to extract zip file.")
|
|
206
|
+
return@withContext false
|
|
207
|
+
}
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
|
|
209
|
+
val extractedDirectory = File(extractedPath)
|
|
210
|
+
val indexFile = extractedDirectory.walk().find { it.name == "index.android.bundle" }
|
|
199
211
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
if (indexFile != null) {
|
|
213
|
+
val bundlePath = indexFile.path
|
|
214
|
+
Log.d("HotUpdater", "Setting bundle URL: $bundlePath")
|
|
215
|
+
setBundleURL(context, bundlePath)
|
|
216
|
+
} else {
|
|
217
|
+
Log.d("HotUpdater", "index.android.bundle not found.")
|
|
218
|
+
return@withContext false
|
|
219
|
+
}
|
|
208
220
|
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
Log.d("HotUpdater", "Downloaded and extracted file successfully.")
|
|
222
|
+
true
|
|
223
|
+
}
|
|
224
|
+
return isSuccess
|
|
211
225
|
}
|
|
212
226
|
}
|
|
213
227
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
|
+
import androidx.fragment.app.FragmentActivity
|
|
4
|
+
import androidx.lifecycle.lifecycleScope
|
|
3
5
|
import com.facebook.react.bridge.Promise
|
|
4
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
7
|
import com.facebook.react.bridge.ReactMethod
|
|
6
8
|
import com.facebook.react.bridge.WritableNativeMap
|
|
7
9
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import kotlinx.coroutines.launch
|
|
8
11
|
|
|
9
12
|
class HotUpdaterModule internal constructor(
|
|
10
13
|
context: ReactApplicationContext,
|
|
@@ -29,18 +32,26 @@ class HotUpdaterModule internal constructor(
|
|
|
29
32
|
zipUrl: String?,
|
|
30
33
|
promise: Promise,
|
|
31
34
|
) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
// Use lifecycleScope when currentActivity is FragmentActivity
|
|
36
|
+
(currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
|
|
37
|
+
val isSuccess =
|
|
38
|
+
HotUpdater.updateBundle(
|
|
39
|
+
mReactApplicationContext,
|
|
40
|
+
bundleId,
|
|
41
|
+
zipUrl,
|
|
42
|
+
) { progress ->
|
|
43
|
+
val params =
|
|
44
|
+
WritableNativeMap().apply {
|
|
45
|
+
putDouble("progress", progress)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this@HotUpdaterModule
|
|
49
|
+
.mReactApplicationContext
|
|
50
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
51
|
+
.emit("onProgress", params)
|
|
52
|
+
}
|
|
53
|
+
promise.resolve(isSuccess)
|
|
54
|
+
}
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
override fun addListener(eventName: String?) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
|
+
import androidx.fragment.app.FragmentActivity
|
|
4
|
+
import androidx.lifecycle.lifecycleScope
|
|
3
5
|
import com.facebook.react.bridge.Promise
|
|
4
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
7
|
import com.facebook.react.bridge.ReactMethod
|
|
6
8
|
import com.facebook.react.bridge.WritableNativeMap
|
|
7
9
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import kotlinx.coroutines.launch
|
|
8
11
|
|
|
9
12
|
class HotUpdaterModule internal constructor(
|
|
10
13
|
context: ReactApplicationContext,
|
|
@@ -29,18 +32,26 @@ class HotUpdaterModule internal constructor(
|
|
|
29
32
|
zipUrl: String?,
|
|
30
33
|
promise: Promise,
|
|
31
34
|
) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
// Use lifecycleScope when currentActivity is FragmentActivity
|
|
36
|
+
(currentActivity as? FragmentActivity)?.lifecycleScope?.launch {
|
|
37
|
+
val isSuccess =
|
|
38
|
+
HotUpdater.updateBundle(
|
|
39
|
+
mReactApplicationContext,
|
|
40
|
+
bundleId,
|
|
41
|
+
zipUrl,
|
|
42
|
+
) { progress ->
|
|
43
|
+
val params =
|
|
44
|
+
WritableNativeMap().apply {
|
|
45
|
+
putDouble("progress", progress)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this@HotUpdaterModule
|
|
49
|
+
.mReactApplicationContext
|
|
50
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
51
|
+
.emit("onProgress", params)
|
|
52
|
+
}
|
|
53
|
+
promise.resolve(isSuccess)
|
|
54
|
+
}
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
companion object {
|
|
@@ -252,6 +252,7 @@ RCT_EXPORT_MODULE();
|
|
|
252
252
|
RCT_EXPORT_METHOD(reload) {
|
|
253
253
|
NSLog(@"HotUpdater requested a reload");
|
|
254
254
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
255
|
+
[super.bridge setValue:[HotUpdater bundleURL] forKey:@"bundleURL"];
|
|
255
256
|
RCTTriggerReloadCommandListeners(@"HotUpdater requested a reload");
|
|
256
257
|
});
|
|
257
258
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"react-native-builder-bob": "^0.33.1"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@hot-updater/js": "0.
|
|
82
|
-
"@hot-updater/core": "0.
|
|
81
|
+
"@hot-updater/js": "0.10.1",
|
|
82
|
+
"@hot-updater/core": "0.10.1"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"build": "rslib build",
|