@hot-updater/react-native 0.0.4 → 0.1.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 +2 -0
- package/README.md +62 -2
- package/android/build.gradle +120 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +290 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterModule.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterPackage.kt +36 -0
- package/android/src/newarch/HotUpdaterSpec.kt +6 -0
- package/android/src/oldarch/HotUpdaterSpec.kt +15 -0
- package/dist/index.cjs +220 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +193 -0
- package/ios/HotUpdater/HotUpdater.h +3 -1
- package/ios/HotUpdater/HotUpdater.mm +136 -97
- package/package.json +21 -11
- package/lib/error.d.ts +0 -4
- package/lib/error.d.ts.map +0 -1
- package/lib/error.js +0 -25
- package/lib/index.d.ts +0 -25
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -133
- package/lib/types.d.ts +0 -7
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -1
- package/lib/wrapNetworkError.d.ts +0 -2
- package/lib/wrapNetworkError.d.ts.map +0 -1
- package/lib/wrapNetworkError.js +0 -54
package/HotUpdater.podspec
CHANGED
|
@@ -17,6 +17,8 @@ Pod::Spec.new do |s|
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm}"
|
|
18
18
|
s.public_header_files = ['ios/HotUpdater/HotUpdater.h']
|
|
19
19
|
|
|
20
|
+
s.dependency "SSZipArchive", "~> 2.2.2"
|
|
21
|
+
|
|
20
22
|
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
21
23
|
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
|
22
24
|
if respond_to?(:install_modules_dependencies, true)
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# hot-updater (WIP)
|
|
2
2
|
React Native OTA solution for internal infrastructure
|
|
3
3
|
|
|
4
|
-
## Usage
|
|
4
|
+
## IOS Usage
|
|
5
5
|
* as-is
|
|
6
6
|
```objective-c
|
|
7
7
|
// filename: ios/MyApp/AppDelegate.mm
|
|
@@ -33,4 +33,64 @@ React Native OTA solution for internal infrastructure
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// ...
|
|
36
|
-
```
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Android Usage
|
|
39
|
+
```kotlin
|
|
40
|
+
package com.hotupdaterexample
|
|
41
|
+
|
|
42
|
+
import android.app.Application
|
|
43
|
+
import com.facebook.react.PackageList
|
|
44
|
+
import com.facebook.react.ReactApplication
|
|
45
|
+
import com.facebook.react.ReactHost
|
|
46
|
+
import com.facebook.react.ReactNativeHost
|
|
47
|
+
import com.facebook.react.ReactPackage
|
|
48
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
49
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
50
|
+
import com.facebook.react.defaults.DefaultReactNativeHost
|
|
51
|
+
import com.facebook.soloader.SoLoader
|
|
52
|
+
import com.hotupdater.HotUpdater
|
|
53
|
+
|
|
54
|
+
class MainApplication : Application(), ReactApplication {
|
|
55
|
+
|
|
56
|
+
override val reactNativeHost: ReactNativeHost =
|
|
57
|
+
object : DefaultReactNativeHost(this) {
|
|
58
|
+
override fun getPackages(): List<ReactPackage> =
|
|
59
|
+
PackageList(this).packages.apply {
|
|
60
|
+
// Packages that cannot be autolinked yet can be added manually here, for example:
|
|
61
|
+
// add(MyReactNativePackage())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override fun getJSMainModuleName(): String = "index"
|
|
65
|
+
|
|
66
|
+
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
|
67
|
+
|
|
68
|
+
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
69
|
+
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
|
70
|
+
|
|
71
|
+
override fun getJSBundleFile(): String? {
|
|
72
|
+
// This field
|
|
73
|
+
return HotUpdater.getJSBundleFile() ?: super.getJSBundleFile()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override val reactHost: ReactHost
|
|
78
|
+
get() = getDefaultReactHost(applicationContext, reactNativeHost)
|
|
79
|
+
|
|
80
|
+
override fun onCreate() {
|
|
81
|
+
super.onCreate()
|
|
82
|
+
SoLoader.init(this, false)
|
|
83
|
+
// This field
|
|
84
|
+
HotUpdater.init(applicationContext, reactNativeHost)
|
|
85
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
86
|
+
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
|
87
|
+
load()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Android Debug
|
|
94
|
+
```sh
|
|
95
|
+
> adb logcat -s HotUpdater
|
|
96
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
|
|
3
|
+
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["HotUpdater_kotlinVersion"]
|
|
4
|
+
|
|
5
|
+
repositories {
|
|
6
|
+
google()
|
|
7
|
+
mavenCentral()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
dependencies {
|
|
11
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
12
|
+
// noinspection DifferentKotlinGradleVersion
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def isNewArchitectureEnabled() {
|
|
18
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.android.library"
|
|
22
|
+
apply plugin: "kotlin-android"
|
|
23
|
+
|
|
24
|
+
if (isNewArchitectureEnabled()) {
|
|
25
|
+
apply plugin: "com.facebook.react"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def getExtOrDefault(name) {
|
|
29
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["HotUpdater_" + name]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def getExtOrIntegerDefault(name) {
|
|
33
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["HotUpdater_" + name]).toInteger()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def supportsNamespace() {
|
|
37
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
38
|
+
def major = parsed[0].toInteger()
|
|
39
|
+
def minor = parsed[1].toInteger()
|
|
40
|
+
|
|
41
|
+
// Namespace support was added in 7.3.0
|
|
42
|
+
return (major == 7 && minor >= 3) || major >= 8
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
android {
|
|
46
|
+
if (supportsNamespace()) {
|
|
47
|
+
namespace "com.hotupdater"
|
|
48
|
+
|
|
49
|
+
sourceSets {
|
|
50
|
+
main {
|
|
51
|
+
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
57
|
+
|
|
58
|
+
defaultConfig {
|
|
59
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
60
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
61
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
buildFeatures {
|
|
66
|
+
buildConfig true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
buildTypes {
|
|
70
|
+
release {
|
|
71
|
+
minifyEnabled false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lintOptions {
|
|
76
|
+
disable "GradleCompatible"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
compileOptions {
|
|
80
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
81
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sourceSets {
|
|
85
|
+
main {
|
|
86
|
+
if (isNewArchitectureEnabled()) {
|
|
87
|
+
java.srcDirs += [
|
|
88
|
+
"src/newarch",
|
|
89
|
+
// This is needed to build Kotlin project with NewArch enabled
|
|
90
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
91
|
+
]
|
|
92
|
+
} else {
|
|
93
|
+
java.srcDirs += ["src/oldarch"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
repositories {
|
|
100
|
+
mavenCentral()
|
|
101
|
+
google()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
105
|
+
|
|
106
|
+
dependencies {
|
|
107
|
+
// For < 0.71, this will be from the local maven repo
|
|
108
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
109
|
+
//noinspection GradleDynamicVersion
|
|
110
|
+
implementation "com.facebook.react:react-native:+"
|
|
111
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isNewArchitectureEnabled()) {
|
|
115
|
+
react {
|
|
116
|
+
jsRootDir = file("../src/")
|
|
117
|
+
libraryName = "HotUpdater"
|
|
118
|
+
codegenJavaPackageName = "com.hotupdater"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
import com.facebook.react.ReactInstanceManager
|
|
9
|
+
import com.facebook.react.ReactNativeHost
|
|
10
|
+
import com.facebook.react.bridge.JSBundleLoader
|
|
11
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
12
|
+
import java.io.File
|
|
13
|
+
import java.lang.reflect.Field
|
|
14
|
+
import java.net.URL
|
|
15
|
+
import java.util.zip.ZipFile
|
|
16
|
+
|
|
17
|
+
class HotUpdater internal constructor(context: Context, reactNativeHost: ReactNativeHost) {
|
|
18
|
+
private val mContext: Context = context
|
|
19
|
+
private val mReactNativeHost: ReactNativeHost = reactNativeHost
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
private var mCurrentInstance: HotUpdater? = null
|
|
23
|
+
|
|
24
|
+
fun init(context: Context, reactNativeHost: ReactNativeHost): HotUpdater {
|
|
25
|
+
Log.d("HotUpdater", "Initializing HotUpdater")
|
|
26
|
+
|
|
27
|
+
return mCurrentInstance
|
|
28
|
+
?: synchronized(this) {
|
|
29
|
+
mCurrentInstance
|
|
30
|
+
?: HotUpdater(context, reactNativeHost).also {
|
|
31
|
+
mCurrentInstance = it
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun getAppVersion(): String? {
|
|
37
|
+
return mCurrentInstance?.getAppVersion()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun initializeOnAppUpdate() {
|
|
41
|
+
mCurrentInstance?.initializeOnAppUpdate()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun reload() {
|
|
45
|
+
mCurrentInstance?.reload()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun getJSBundleFile(): String? {
|
|
49
|
+
Log.d("HotUpdater", "Getting JS bundle file ${mCurrentInstance?.getBundleURL()}")
|
|
50
|
+
return mCurrentInstance?.getBundleURL()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fun getBundleVersion(): Double? {
|
|
54
|
+
return mCurrentInstance?.getBundleVersion()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fun updateBundle(prefix: String, url: String?): Boolean? {
|
|
58
|
+
return mCurrentInstance?.updateBundle(prefix, url)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private val documentsDir: String
|
|
63
|
+
get() = mContext.getExternalFilesDir(null)?.absolutePath ?: mContext.filesDir.absolutePath
|
|
64
|
+
|
|
65
|
+
private fun convertFileSystemPathFromBasePath(basePath: String): String {
|
|
66
|
+
val separator = if (basePath.startsWith("/")) "" else "/"
|
|
67
|
+
return "$documentsDir$separator$basePath"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private fun stripPrefixFromPath(prefix: String, path: String): String {
|
|
71
|
+
return if (path.startsWith("/$prefix/")) {
|
|
72
|
+
path.replaceFirst("/$prefix/", "")
|
|
73
|
+
} else {
|
|
74
|
+
path
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private fun loadBundleLegacy() {
|
|
79
|
+
val currentActivity: Activity? =
|
|
80
|
+
mReactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
|
|
81
|
+
if (currentActivity == null) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
currentActivity.runOnUiThread { currentActivity.recreate() }
|
|
86
|
+
}
|
|
87
|
+
private var mLifecycleEventListener: LifecycleEventListener? = null
|
|
88
|
+
|
|
89
|
+
private fun clearLifecycleEventListener() {
|
|
90
|
+
if (mLifecycleEventListener != null) {
|
|
91
|
+
mReactNativeHost.reactInstanceManager.currentReactContext?.removeLifecycleEventListener(
|
|
92
|
+
mLifecycleEventListener
|
|
93
|
+
)
|
|
94
|
+
mLifecycleEventListener = null
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private fun setJSBundle(instanceManager: ReactInstanceManager, latestJSBundleFile: String?) {
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
var latestJSBundleLoader: JSBundleLoader? = null
|
|
102
|
+
|
|
103
|
+
if (latestJSBundleFile != null && latestJSBundleFile.lowercase().startsWith("assets://")
|
|
104
|
+
) {
|
|
105
|
+
latestJSBundleLoader =
|
|
106
|
+
JSBundleLoader.createAssetLoader(
|
|
107
|
+
instanceManager.currentReactContext,
|
|
108
|
+
latestJSBundleFile,
|
|
109
|
+
false
|
|
110
|
+
)
|
|
111
|
+
} else if (latestJSBundleFile != null) {
|
|
112
|
+
latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile)
|
|
113
|
+
}
|
|
114
|
+
val bundleLoaderField: Field =
|
|
115
|
+
instanceManager::class.java.getDeclaredField("mBundleLoader")
|
|
116
|
+
bundleLoaderField.isAccessible = true
|
|
117
|
+
|
|
118
|
+
if (latestJSBundleLoader != null) {
|
|
119
|
+
bundleLoaderField.set(instanceManager, latestJSBundleLoader)
|
|
120
|
+
} else {
|
|
121
|
+
bundleLoaderField.set(instanceManager, null)
|
|
122
|
+
}
|
|
123
|
+
} catch (e: Exception) {
|
|
124
|
+
throw IllegalAccessException("Could not setJSBundle")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fun initializeOnAppUpdate() {
|
|
129
|
+
val sharedPreferences =
|
|
130
|
+
mContext.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
131
|
+
|
|
132
|
+
val currentVersion = getAppVersion()
|
|
133
|
+
val savedVersion = sharedPreferences.getString("HotUpdaterAppVersion", null)
|
|
134
|
+
|
|
135
|
+
if (currentVersion != savedVersion) {
|
|
136
|
+
val editor = sharedPreferences.edit()
|
|
137
|
+
editor.remove("HotUpdaterBundleURL")
|
|
138
|
+
editor.remove("HotUpdaterBundleVersion")
|
|
139
|
+
editor.putString("HotUpdaterAppVersion", currentVersion)
|
|
140
|
+
editor.apply()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fun reload() {
|
|
145
|
+
Log.d("HotUpdater", "HotUpdater requested a reload ${getBundleURL()}")
|
|
146
|
+
|
|
147
|
+
setJSBundle(mReactNativeHost.reactInstanceManager, getBundleURL())
|
|
148
|
+
|
|
149
|
+
clearLifecycleEventListener()
|
|
150
|
+
try {
|
|
151
|
+
Handler(Looper.getMainLooper()).post {
|
|
152
|
+
try {
|
|
153
|
+
mReactNativeHost.reactInstanceManager.recreateReactContextInBackground()
|
|
154
|
+
} catch (t: Throwable) {
|
|
155
|
+
loadBundleLegacy()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (t: Throwable) {
|
|
159
|
+
loadBundleLegacy()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fun getAppVersion(): String {
|
|
164
|
+
val packageInfo = mContext.packageManager.getPackageInfo(mContext.packageName, 0)
|
|
165
|
+
return packageInfo.versionName
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun getBundleURL(): String {
|
|
169
|
+
val sharedPreferences =
|
|
170
|
+
mContext.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
171
|
+
val urlString = sharedPreferences.getString("HotUpdaterBundleURL", null)
|
|
172
|
+
if (urlString.isNullOrEmpty()) {
|
|
173
|
+
return "assets://index.android.bundle"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return urlString
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun setBundleURL(bundleURL: String?) {
|
|
180
|
+
val sharedPreferences =
|
|
181
|
+
mContext.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
182
|
+
with(sharedPreferences.edit()) {
|
|
183
|
+
putString("HotUpdaterBundleURL", bundleURL)
|
|
184
|
+
apply()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
private fun setBundleVersion(bundleVersion: String?) {
|
|
188
|
+
val sharedPreferences =
|
|
189
|
+
mContext.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
190
|
+
with(sharedPreferences.edit()) {
|
|
191
|
+
putString("HotUpdaterBundleVersion", bundleVersion)
|
|
192
|
+
apply()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fun getBundleVersion(): Double? {
|
|
197
|
+
val sharedPreferences =
|
|
198
|
+
mContext.getSharedPreferences("HotUpdaterPrefs", Context.MODE_PRIVATE)
|
|
199
|
+
val bundleVersion = sharedPreferences.getString("HotUpdaterBundleVersion", null)
|
|
200
|
+
Log.d("HotUpdater", "Bundle version: $bundleVersion")
|
|
201
|
+
return if (bundleVersion != null && bundleVersion.isNotEmpty()) {
|
|
202
|
+
try {
|
|
203
|
+
bundleVersion.toDouble()
|
|
204
|
+
} catch (e: Exception) {
|
|
205
|
+
-1.0
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
-1.0
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private fun extractZipFileAtPath(filePath: String, destinationPath: String): Boolean {
|
|
213
|
+
return try {
|
|
214
|
+
ZipFile(filePath).use { zip ->
|
|
215
|
+
zip.entries().asSequence().forEach { entry ->
|
|
216
|
+
val file = File(destinationPath, entry.name)
|
|
217
|
+
if (entry.isDirectory) {
|
|
218
|
+
file.mkdirs()
|
|
219
|
+
} else {
|
|
220
|
+
file.parentFile?.mkdirs()
|
|
221
|
+
zip.getInputStream(entry).use { input ->
|
|
222
|
+
file.outputStream().use { output -> input.copyTo(output) }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
true
|
|
228
|
+
} catch (e: Exception) {
|
|
229
|
+
Log.d("HotUpdater", "Failed to unzip file: ${e.message}")
|
|
230
|
+
false
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fun updateBundle(prefix: String, url: String?): Boolean {
|
|
235
|
+
if (url == null) {
|
|
236
|
+
setBundleURL(null)
|
|
237
|
+
setBundleVersion(null)
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
val downloadUrl = URL(url)
|
|
242
|
+
|
|
243
|
+
val basePath = stripPrefixFromPath(prefix, downloadUrl.path)
|
|
244
|
+
val path = convertFileSystemPathFromBasePath(basePath)
|
|
245
|
+
|
|
246
|
+
val data =
|
|
247
|
+
try {
|
|
248
|
+
downloadUrl.readBytes()
|
|
249
|
+
} catch (e: Exception) {
|
|
250
|
+
Log.d("HotUpdater", "Failed to download data from URL: $url")
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
val file = File(path)
|
|
255
|
+
try {
|
|
256
|
+
file.parentFile?.mkdirs()
|
|
257
|
+
file.writeBytes(data)
|
|
258
|
+
} catch (e: Exception) {
|
|
259
|
+
Log.d("HotUpdater", "Failed to save data: ${e.message}")
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
val extractedPath = file.parentFile?.path
|
|
264
|
+
if (extractedPath == null) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!extractZipFileAtPath(path, extractedPath)) {
|
|
269
|
+
Log.d("HotUpdater", "Failed to extract zip file.")
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
val extractedDirectory = File(extractedPath)
|
|
274
|
+
val indexFile = extractedDirectory.walk().find { it.name == "index.android.bundle.js" }
|
|
275
|
+
|
|
276
|
+
if (indexFile != null) {
|
|
277
|
+
val bundlePath = indexFile.path
|
|
278
|
+
Log.d("HotUpdater", "Setting bundle URL: $bundlePath")
|
|
279
|
+
setBundleURL(bundlePath)
|
|
280
|
+
} else {
|
|
281
|
+
Log.d("HotUpdater", "index.android.bundle.js not found.")
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
setBundleVersion(prefix)
|
|
286
|
+
Log.d("HotUpdater", "Downloaded and extracted file successfully.")
|
|
287
|
+
|
|
288
|
+
return true
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Callback
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
|
|
7
|
+
class HotUpdaterModule internal constructor(context: ReactApplicationContext) :
|
|
8
|
+
HotUpdaterSpec(context) {
|
|
9
|
+
|
|
10
|
+
private val mReactApplicationContext: ReactApplicationContext = context
|
|
11
|
+
|
|
12
|
+
override fun getName(): String {
|
|
13
|
+
return NAME
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@ReactMethod
|
|
17
|
+
override fun initializeOnAppUpdate() {
|
|
18
|
+
HotUpdater.initializeOnAppUpdate()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@ReactMethod
|
|
22
|
+
override fun reload() {
|
|
23
|
+
HotUpdater.reload()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@ReactMethod
|
|
27
|
+
override fun getAppVersion(callback: Callback) {
|
|
28
|
+
callback.invoke(HotUpdater.getAppVersion())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@ReactMethod
|
|
32
|
+
override fun getBundleVersion(callback: Callback) {
|
|
33
|
+
callback.invoke(HotUpdater.getBundleVersion())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@ReactMethod
|
|
37
|
+
override fun updateBundle(prefix: String, url: String?, callback: Callback) {
|
|
38
|
+
val result = HotUpdater.updateBundle(prefix, url)
|
|
39
|
+
callback.invoke(result)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
companion object {
|
|
43
|
+
const val NAME = "HotUpdater"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class HotUpdaterPackage : TurboReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == HotUpdaterModule.NAME) {
|
|
13
|
+
HotUpdaterModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
22
|
+
val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
23
|
+
moduleInfos[HotUpdaterModule.NAME] =
|
|
24
|
+
ReactModuleInfo(
|
|
25
|
+
HotUpdaterModule.NAME,
|
|
26
|
+
HotUpdaterModule.NAME,
|
|
27
|
+
false, // canOverrideExistingModule
|
|
28
|
+
false, // needsEagerInit
|
|
29
|
+
true, // hasConstants
|
|
30
|
+
false, // isCxxModule
|
|
31
|
+
isTurboModule // isTurboModule
|
|
32
|
+
)
|
|
33
|
+
moduleInfos
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Callback
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
|
|
7
|
+
abstract class HotUpdaterSpec internal constructor(context: ReactApplicationContext) :
|
|
8
|
+
ReactContextBaseJavaModule(context) {
|
|
9
|
+
|
|
10
|
+
abstract fun updateBundle(prefix: String, url: String?, callback: Callback)
|
|
11
|
+
abstract fun reload()
|
|
12
|
+
abstract fun initializeOnAppUpdate()
|
|
13
|
+
abstract fun getAppVersion(callback: Callback)
|
|
14
|
+
abstract fun getBundleVersion(callback: Callback)
|
|
15
|
+
}
|