react-native-ota-hot-update 2.1.15 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +16 -2
  2. package/android/generated/java/com/otahotupdate/NativeOtaHotUpdateSpec.java +8 -0
  3. package/android/generated/jni/RNOtaHotUpdateSpec-generated.cpp +12 -0
  4. package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI-generated.cpp +14 -0
  5. package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI.h +18 -0
  6. package/android/src/main/java/com/otahotupdate/CrashHandler.kt +55 -0
  7. package/android/src/main/java/com/otahotupdate/OtaHotUpdate.kt +15 -23
  8. package/android/src/main/java/com/otahotupdate/OtaHotUpdateModule.kt +36 -95
  9. package/android/src/main/java/com/otahotupdate/SharedPrefs.kt +1 -0
  10. package/android/src/main/java/com/otahotupdate/Utils.kt +94 -0
  11. package/android/src/oldarch/OtaHotUpdateSpec.kt +2 -0
  12. package/ios/OtaHotUpdate.mm +76 -10
  13. package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec-generated.mm +14 -0
  14. package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec.h +6 -0
  15. package/ios/generated/RNOtaHotUpdateSpecJSI-generated.cpp +14 -0
  16. package/ios/generated/RNOtaHotUpdateSpecJSI.h +18 -0
  17. package/lib/commonjs/NativeOtaHotUpdate.js.map +1 -1
  18. package/lib/commonjs/index.js +45 -27
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/module/NativeOtaHotUpdate.js.map +1 -1
  21. package/lib/module/index.js +45 -27
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts +2 -0
  24. package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/src/index.d.ts +5 -1
  26. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/src/type.d.ts +10 -0
  28. package/lib/typescript/commonjs/src/type.d.ts.map +1 -1
  29. package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts +2 -0
  30. package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts.map +1 -1
  31. package/lib/typescript/module/src/index.d.ts +5 -1
  32. package/lib/typescript/module/src/index.d.ts.map +1 -1
  33. package/lib/typescript/module/src/type.d.ts +10 -0
  34. package/lib/typescript/module/src/type.d.ts.map +1 -1
  35. package/package.json +2 -13
  36. package/plugin/build/index.js +1 -1
  37. package/plugin/src/index.ts +1 -1
  38. package/react-native.config.js +1 -1
  39. package/src/NativeOtaHotUpdate.ts +2 -0
  40. package/src/index.tsx +52 -30
  41. package/src/type.ts +12 -0
package/README.md CHANGED
@@ -35,6 +35,17 @@ Auto linking already, need pod install for ios:
35
35
  cd ios && pod install
36
36
  ```
37
37
 
38
+ ### Expo
39
+
40
+ Modify `app.json`:
41
+
42
+ ```angular2html
43
+ "plugins": [
44
+ "react-native-ota-hot-update",
45
+ ...
46
+ ]
47
+ ```
48
+
38
49
  ### IOS
39
50
  Open `AppDelegate.m` and add this:
40
51
 
@@ -132,7 +143,7 @@ Open `MainApplication.kt` and add these codes bellow:
132
143
  import com.otahotupdate.OtaHotUpdate
133
144
  ...
134
145
  override fun getJSBundleFile(): String? {
135
- return OtaHotUpdate.bundleJS
146
+ return OtaHotUpdate.bundleJS(this@MainApplication)
136
147
  }
137
148
 
138
149
  ```
@@ -142,9 +153,12 @@ MainApplication.java:
142
153
  @Nullable
143
154
  @Override
144
155
  protected String getJSBundleFile() {
145
- return OtaHotUpdate.getBundleJS();
156
+ return OtaHotUpdate.getBundleJS(this);
146
157
  }
147
158
  ```
159
+
160
+ For java it maybe can be like: `OtaHotUpdate.Companion.getBundleJS(this)` depend on kotlin / jdk version on your project, you can use android studio to get the correct format coding.
161
+
148
162
  Open `AndroidManifest.xml` :
149
163
 
150
164
  `<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />`
@@ -52,10 +52,18 @@ public abstract class NativeOtaHotUpdateSpec extends ReactContextBaseJavaModule
52
52
  @DoNotStrip
53
53
  public abstract void getCurrentVersion(double a, Promise promise);
54
54
 
55
+ @ReactMethod
56
+ @DoNotStrip
57
+ public abstract void getUpdateMetadata(double a, Promise promise);
58
+
55
59
  @ReactMethod
56
60
  @DoNotStrip
57
61
  public abstract void setCurrentVersion(String version, Promise promise);
58
62
 
63
+ @ReactMethod
64
+ @DoNotStrip
65
+ public abstract void setUpdateMetadata(String metadata, Promise promise);
66
+
59
67
  @ReactMethod
60
68
  @DoNotStrip
61
69
  public abstract void rollbackToPreviousBundle(double a, Promise promise);
@@ -37,11 +37,21 @@ static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_getCurrentV
37
37
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "getCurrentVersion", "(DLcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
38
38
  }
39
39
 
40
+ static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_getUpdateMetadata(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
41
+ static jmethodID cachedMethodId = nullptr;
42
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "getUpdateMetadata", "(DLcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
43
+ }
44
+
40
45
  static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_setCurrentVersion(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
41
46
  static jmethodID cachedMethodId = nullptr;
42
47
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "setCurrentVersion", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
43
48
  }
44
49
 
50
+ static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_setUpdateMetadata(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
51
+ static jmethodID cachedMethodId = nullptr;
52
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "setUpdateMetadata", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
53
+ }
54
+
45
55
  static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_rollbackToPreviousBundle(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
46
56
  static jmethodID cachedMethodId = nullptr;
47
57
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "rollbackToPreviousBundle", "(DLcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
@@ -54,7 +64,9 @@ NativeOtaHotUpdateSpecJSI::NativeOtaHotUpdateSpecJSI(const JavaTurboModule::Init
54
64
  methodMap_["deleteBundle"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_deleteBundle};
55
65
  methodMap_["restart"] = MethodMetadata {0, __hostFunction_NativeOtaHotUpdateSpecJSI_restart};
56
66
  methodMap_["getCurrentVersion"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_getCurrentVersion};
67
+ methodMap_["getUpdateMetadata"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_getUpdateMetadata};
57
68
  methodMap_["setCurrentVersion"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_setCurrentVersion};
69
+ methodMap_["setUpdateMetadata"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_setUpdateMetadata};
58
70
  methodMap_["rollbackToPreviousBundle"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_rollbackToPreviousBundle};
59
71
  }
60
72
 
@@ -42,12 +42,24 @@ static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getCurrentVersion(
42
42
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber()
43
43
  );
44
44
  }
45
+ static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getUpdateMetadata(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
46
+ return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->getUpdateMetadata(
47
+ rt,
48
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber()
49
+ );
50
+ }
45
51
  static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_setCurrentVersion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
46
52
  return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->setCurrentVersion(
47
53
  rt,
48
54
  count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
49
55
  );
50
56
  }
57
+ static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_setUpdateMetadata(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
58
+ return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->setUpdateMetadata(
59
+ rt,
60
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt)
61
+ );
62
+ }
51
63
  static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_rollbackToPreviousBundle(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
52
64
  return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->rollbackToPreviousBundle(
53
65
  rt,
@@ -62,7 +74,9 @@ NativeOtaHotUpdateCxxSpecJSI::NativeOtaHotUpdateCxxSpecJSI(std::shared_ptr<CallI
62
74
  methodMap_["deleteBundle"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_deleteBundle};
63
75
  methodMap_["restart"] = MethodMetadata {0, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_restart};
64
76
  methodMap_["getCurrentVersion"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getCurrentVersion};
77
+ methodMap_["getUpdateMetadata"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getUpdateMetadata};
65
78
  methodMap_["setCurrentVersion"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_setCurrentVersion};
79
+ methodMap_["setUpdateMetadata"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_setUpdateMetadata};
66
80
  methodMap_["rollbackToPreviousBundle"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_rollbackToPreviousBundle};
67
81
  }
68
82
 
@@ -25,7 +25,9 @@ public:
25
25
  virtual jsi::Value deleteBundle(jsi::Runtime &rt, double i) = 0;
26
26
  virtual void restart(jsi::Runtime &rt) = 0;
27
27
  virtual jsi::Value getCurrentVersion(jsi::Runtime &rt, double a) = 0;
28
+ virtual jsi::Value getUpdateMetadata(jsi::Runtime &rt, double a) = 0;
28
29
  virtual jsi::Value setCurrentVersion(jsi::Runtime &rt, jsi::String version) = 0;
30
+ virtual jsi::Value setUpdateMetadata(jsi::Runtime &rt, jsi::String metadata) = 0;
29
31
  virtual jsi::Value rollbackToPreviousBundle(jsi::Runtime &rt, double a) = 0;
30
32
 
31
33
  };
@@ -93,6 +95,14 @@ private:
93
95
  return bridging::callFromJs<jsi::Value>(
94
96
  rt, &T::getCurrentVersion, jsInvoker_, instance_, std::move(a));
95
97
  }
98
+ jsi::Value getUpdateMetadata(jsi::Runtime &rt, double a) override {
99
+ static_assert(
100
+ bridging::getParameterCount(&T::getUpdateMetadata) == 2,
101
+ "Expected getUpdateMetadata(...) to have 2 parameters");
102
+
103
+ return bridging::callFromJs<jsi::Value>(
104
+ rt, &T::getUpdateMetadata, jsInvoker_, instance_, std::move(a));
105
+ }
96
106
  jsi::Value setCurrentVersion(jsi::Runtime &rt, jsi::String version) override {
97
107
  static_assert(
98
108
  bridging::getParameterCount(&T::setCurrentVersion) == 2,
@@ -101,6 +111,14 @@ private:
101
111
  return bridging::callFromJs<jsi::Value>(
102
112
  rt, &T::setCurrentVersion, jsInvoker_, instance_, std::move(version));
103
113
  }
114
+ jsi::Value setUpdateMetadata(jsi::Runtime &rt, jsi::String metadata) override {
115
+ static_assert(
116
+ bridging::getParameterCount(&T::setUpdateMetadata) == 2,
117
+ "Expected setUpdateMetadata(...) to have 2 parameters");
118
+
119
+ return bridging::callFromJs<jsi::Value>(
120
+ rt, &T::setUpdateMetadata, jsInvoker_, instance_, std::move(metadata));
121
+ }
104
122
  jsi::Value rollbackToPreviousBundle(jsi::Runtime &rt, double a) override {
105
123
  static_assert(
106
124
  bridging::getParameterCount(&T::rollbackToPreviousBundle) == 2,
@@ -0,0 +1,55 @@
1
+ package com.otahotupdate
2
+
3
+ import android.content.Context
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.widget.Toast
7
+ import androidx.appcompat.app.AlertDialog
8
+ import com.jakewharton.processphoenix.ProcessPhoenix
9
+ import com.rnhotupdate.Common.PATH
10
+ import com.rnhotupdate.Common.PREVIOUS_PATH
11
+ import com.rnhotupdate.Common.VERSION
12
+ import com.rnhotupdate.SharedPrefs
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.GlobalScope
15
+ import kotlinx.coroutines.delay
16
+ import kotlinx.coroutines.launch
17
+
18
+ class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
19
+ private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
20
+ private val utils: Utils = Utils(context)
21
+ private var beginning = true
22
+ init {
23
+ GlobalScope.launch(Dispatchers.IO) {
24
+ delay(2000)
25
+ beginning = false
26
+ }
27
+ }
28
+ override fun uncaughtException(thread: Thread, throwable: Throwable) {
29
+ if (beginning) {
30
+ //begin remove and using previous bundle
31
+ val sharedPrefs = SharedPrefs(context)
32
+ val oldPath = sharedPrefs.getString(PREVIOUS_PATH)
33
+ if (oldPath != "") {
34
+ val isDeleted = utils.deleteOldBundleIfneeded(PATH)
35
+ if (isDeleted) {
36
+ sharedPrefs.putString(PATH, oldPath)
37
+ sharedPrefs.putString(PREVIOUS_PATH, "")
38
+ } else {
39
+ sharedPrefs.putString(PATH, "")
40
+ }
41
+ } else {
42
+ sharedPrefs.putString(PATH, "")
43
+ }
44
+ sharedPrefs.putString(VERSION, "0")
45
+ Toast.makeText(context, "Failed to load the update. Please try again.", Toast.LENGTH_LONG).show()
46
+ GlobalScope.launch(Dispatchers.IO) {
47
+ delay(1500)
48
+ ProcessPhoenix.triggerRebirth(context)
49
+ }
50
+ } else {
51
+ defaultHandler?.uncaughtException(thread, throwable)
52
+ }
53
+ }
54
+ }
55
+
@@ -4,7 +4,7 @@ import android.content.Context
4
4
  import android.content.pm.PackageInfo
5
5
  import android.content.pm.PackageManager
6
6
  import android.os.Build
7
- import com.facebook.react.TurboReactPackage
7
+ import com.facebook.react.BaseReactPackage
8
8
  import com.facebook.react.bridge.NativeModule
9
9
  import com.facebook.react.bridge.ReactApplicationContext
10
10
  import com.facebook.react.module.model.ReactModuleInfo
@@ -16,10 +16,7 @@ import com.rnhotupdate.Common.VERSION
16
16
  import com.rnhotupdate.SharedPrefs
17
17
 
18
18
 
19
- class OtaHotUpdate(context: Context?) : TurboReactPackage() {
20
- init {
21
- mContext = context
22
- }
19
+ class OtaHotUpdate : BaseReactPackage() {
23
20
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
24
21
  return if (name == OtaHotUpdateModule.NAME) {
25
22
  OtaHotUpdateModule(reactContext)
@@ -37,7 +34,6 @@ class OtaHotUpdate(context: Context?) : TurboReactPackage() {
37
34
  OtaHotUpdateModule.NAME,
38
35
  false, // canOverrideExistingModule
39
36
  false, // needsEagerInit
40
- true, // hasConstants
41
37
  false, // isCxxModule
42
38
  isTurboModule // isTurboModule
43
39
  )
@@ -53,24 +49,20 @@ class OtaHotUpdate(context: Context?) : TurboReactPackage() {
53
49
  packageManager.getPackageInfo(packageName, 0)
54
50
  }
55
51
  }
56
- private var mContext: Context? = null
57
- val bundleJS: String
58
- get() {
59
- if (mContext == null) {
60
- return DEFAULT_BUNDLE
61
- }
62
- val sharedPrefs = SharedPrefs(mContext!!)
63
- val pathBundle = sharedPrefs.getString(PATH)
64
- val version = sharedPrefs.getString(VERSION)
65
- val currentVersionName = sharedPrefs.getString(CURRENT_VERSION_NAME)
66
- if (pathBundle == "" || (currentVersionName != mContext?.getPackageInfo()?.versionName)) {
67
- if (version != "") {
68
- // reset version number because bundle is wrong version, need download from new version
69
- sharedPrefs.putString(VERSION, "")
70
- }
71
- return DEFAULT_BUNDLE
52
+ fun bundleJS(context: Context): String {
53
+ Thread.setDefaultUncaughtExceptionHandler(CrashHandler(context))
54
+ val sharedPrefs = SharedPrefs(context)
55
+ val pathBundle = sharedPrefs.getString(PATH)
56
+ val version = sharedPrefs.getString(VERSION)
57
+ val currentVersionName = sharedPrefs.getString(CURRENT_VERSION_NAME)
58
+ if (pathBundle == "" || (currentVersionName != context.getPackageInfo().versionName)) {
59
+ if (version != "") {
60
+ // reset version number because bundle is wrong version, need download from new version
61
+ sharedPrefs.putString(VERSION, "")
72
62
  }
73
- return pathBundle!!
63
+ return DEFAULT_BUNDLE
74
64
  }
65
+ return pathBundle!!
66
+ }
75
67
  }
76
68
  }
@@ -1,8 +1,6 @@
1
1
  package com.otahotupdate
2
2
 
3
3
  import android.content.Context
4
- import android.icu.text.SimpleDateFormat
5
- import android.util.Log
6
4
  import com.facebook.react.bridge.Promise
7
5
  import com.facebook.react.bridge.ReactApplicationContext
8
6
  import com.facebook.react.bridge.ReactMethod
@@ -12,107 +10,25 @@ import com.rnhotupdate.Common.CURRENT_VERSION_NAME
12
10
  import com.rnhotupdate.Common.PATH
13
11
  import com.rnhotupdate.Common.PREVIOUS_PATH
14
12
  import com.rnhotupdate.Common.VERSION
13
+ import com.rnhotupdate.Common.METADATA
15
14
  import com.rnhotupdate.SharedPrefs
16
15
  import java.io.File
17
- import java.util.Date
18
- import java.util.Locale
19
- import java.util.zip.ZipFile
20
16
 
21
17
  class OtaHotUpdateModule internal constructor(context: ReactApplicationContext) :
22
18
  OtaHotUpdateSpec(context) {
23
-
19
+ private val utils: Utils = Utils(context)
24
20
  override fun getName(): String {
25
21
  return NAME
26
22
  }
27
23
 
28
- private fun deleteDirectory(directory: File): Boolean {
29
- if (directory.isDirectory) {
30
- // List all files and directories in the current directory
31
- val files = directory.listFiles()
32
- if (files != null) {
33
- // Recursively delete all files and directories
34
- for (file in files) {
35
- if (!deleteDirectory(file)) {
36
- return false
37
- }
38
- }
39
- }
40
- }
41
- // Finally, delete the empty directory or file
42
- return directory.delete()
43
- }
44
- private fun deleteOldBundleIfneeded(pathKey: String?): Boolean {
45
- val pathName = if (pathKey != null) pathKey else PREVIOUS_PATH
46
- val sharedPrefs = SharedPrefs(reactApplicationContext)
47
- val path = sharedPrefs.getString(pathName)
48
- val file = File(path)
49
- if (file.exists() && file.isFile) {
50
- val isDeleted = deleteDirectory(file.parentFile)
51
- sharedPrefs.putString(pathName, "")
52
- return isDeleted
53
- } else {
54
- return false
55
- }
56
- }
57
- private fun extractZipFile(
58
- zipFile: File,extension: String
59
- ): String? {
60
- return try {
61
- val outputDir = zipFile.parentFile
62
- val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
63
- var topLevelFolder: String? = null
64
- var bundlePath: String? = null
65
- ZipFile(zipFile).use { zip ->
66
- zip.entries().asSequence().forEach { entry ->
67
- zip.getInputStream(entry).use { input ->
68
- if (topLevelFolder == null) {
69
- // Get root folder of zip file after unzip
70
- val parts = entry.name.split("/")
71
- if (parts.size > 1) {
72
- topLevelFolder = parts.first()
73
- }
74
- }
75
- val outputFile = File(outputDir, entry.name)
76
- if (entry.isDirectory) {
77
- if (!outputFile.exists()) outputFile.mkdirs()
78
- } else {
79
- if (outputFile.parentFile?.exists() != true) outputFile.parentFile?.mkdirs()
80
- outputFile.outputStream().use { output ->
81
- input.copyTo(output)
82
- }
83
- if (outputFile.absolutePath.endsWith(extension)) {
84
- bundlePath = outputFile.absolutePath
85
- return@use // Exit early if found
86
- }
87
- }
88
- }
89
- }
90
- }
91
- // Rename the detected top-level folder
92
- if (topLevelFolder != null) {
93
- val extractedFolder = File(outputDir, topLevelFolder)
94
- val renamedFolder = File(outputDir, "output_$timestamp")
95
- if (extractedFolder.exists()) {
96
- extractedFolder.renameTo(renamedFolder)
97
- // Update bundlePath if the file was inside the renamed folder
98
- bundlePath = bundlePath?.replace(extractedFolder.absolutePath, renamedFolder.absolutePath)
99
- }
100
- }
101
- bundlePath
102
- } catch (e: Exception) {
103
- e.printStackTrace()
104
- null
105
- }
106
- }
107
-
108
24
 
109
25
  @ReactMethod
110
26
  override fun setupBundlePath(path: String?, extension: String?, promise: Promise) {
111
27
  if (path != null) {
112
28
  val file = File(path)
113
29
  if (file.exists() && file.isFile) {
114
- deleteOldBundleIfneeded(null)
115
- val fileUnzip = extractZipFile(file, extension ?: ".bundle")
30
+ utils.deleteOldBundleIfneeded(null)
31
+ val fileUnzip = utils.extractZipFile(file, extension ?: ".bundle")
116
32
  if (fileUnzip != null) {
117
33
  file.delete()
118
34
  val sharedPrefs = SharedPrefs(reactApplicationContext)
@@ -121,11 +37,14 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
121
37
  sharedPrefs.putString(PREVIOUS_PATH, oldPath)
122
38
  }
123
39
  sharedPrefs.putString(PATH, fileUnzip)
124
- sharedPrefs.putString(CURRENT_VERSION_NAME, reactApplicationContext?.getPackageInfo()?.versionName)
40
+ sharedPrefs.putString(
41
+ CURRENT_VERSION_NAME,
42
+ reactApplicationContext?.getPackageInfo()?.versionName
43
+ )
125
44
  promise.resolve(true)
126
45
  } else {
127
46
  file.delete()
128
- deleteDirectory(file.parentFile)
47
+ utils.deleteDirectory(file.parentFile)
129
48
  val sharedPrefs = SharedPrefs(reactApplicationContext)
130
49
  sharedPrefs.putString(PATH, "")
131
50
  promise.resolve(false)
@@ -140,8 +59,8 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
140
59
 
141
60
  @ReactMethod
142
61
  override fun deleteBundle(i: Double, promise: Promise) {
143
- val isDeleted = deleteOldBundleIfneeded(PATH)
144
- val isDeletedOldPath = deleteOldBundleIfneeded(PREVIOUS_PATH)
62
+ val isDeleted = utils.deleteOldBundleIfneeded(PATH)
63
+ val isDeletedOldPath = utils.deleteOldBundleIfneeded(PREVIOUS_PATH)
145
64
  val sharedPrefs = SharedPrefs(reactApplicationContext)
146
65
  sharedPrefs.putString(VERSION, "0")
147
66
  promise.resolve(isDeleted && isDeletedOldPath)
@@ -150,7 +69,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
150
69
  @ReactMethod
151
70
  override fun restart() {
152
71
  val context: Context? = currentActivity
153
- ProcessPhoenix.triggerRebirth(context);
72
+ ProcessPhoenix.triggerRebirth(context)
154
73
  }
155
74
 
156
75
  @ReactMethod
@@ -171,11 +90,33 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
171
90
  promise.resolve(true)
172
91
  }
173
92
 
93
+ @ReactMethod
94
+ override fun getUpdateMetadata(a: Double, promise: Promise) {
95
+ val sharedPrefs = SharedPrefs(reactApplicationContext)
96
+ val metadata = sharedPrefs.getString(METADATA)
97
+ if (metadata != "") {
98
+ promise.resolve(metadata);
99
+ } else {
100
+ promise.resolve(null);
101
+ }
102
+ }
103
+
104
+ @ReactMethod
105
+ override fun setUpdateMetadata(metadata: String?, promise: Promise) {
106
+ val sharedPrefs = SharedPrefs(reactApplicationContext)
107
+ sharedPrefs.putString(METADATA, metadata)
108
+ promise.resolve(true)
109
+ }
110
+
111
+
174
112
  @ReactMethod
175
113
  override fun setExactBundlePath(path: String?, promise: Promise) {
176
114
  val sharedPrefs = SharedPrefs(reactApplicationContext)
177
115
  sharedPrefs.putString(PATH, path)
178
- sharedPrefs.putString(CURRENT_VERSION_NAME, reactApplicationContext?.getPackageInfo()?.versionName)
116
+ sharedPrefs.putString(
117
+ CURRENT_VERSION_NAME,
118
+ reactApplicationContext?.getPackageInfo()?.versionName
119
+ )
179
120
  promise.resolve(true)
180
121
  }
181
122
 
@@ -184,7 +125,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
184
125
  val sharedPrefs = SharedPrefs(reactApplicationContext)
185
126
  val oldPath = sharedPrefs.getString(PREVIOUS_PATH)
186
127
  if (oldPath != "") {
187
- val isDeleted = deleteOldBundleIfneeded(PATH)
128
+ val isDeleted = utils.deleteOldBundleIfneeded(PATH)
188
129
  if (isDeleted) {
189
130
  sharedPrefs.putString(PATH, oldPath)
190
131
  sharedPrefs.putString(PREVIOUS_PATH, "")
@@ -30,4 +30,5 @@ object Common {
30
30
  val CURRENT_VERSION_NAME = "CURRENT_VERSION_NAME"
31
31
  val SHARED_PREFERENCE_NAME = "HOT-UPDATE-REACT_NATIVE"
32
32
  val DEFAULT_BUNDLE = "assets://index.android.bundle"
33
+ val METADATA = "METADATA"
33
34
  }
@@ -0,0 +1,94 @@
1
+ package com.otahotupdate
2
+
3
+ import android.content.Context
4
+ import android.icu.text.SimpleDateFormat
5
+ import com.rnhotupdate.Common.PREVIOUS_PATH
6
+ import com.rnhotupdate.SharedPrefs
7
+ import java.io.File
8
+ import java.util.Date
9
+ import java.util.Locale
10
+ import java.util.zip.ZipFile
11
+
12
+ class Utils internal constructor(private val context: Context) {
13
+
14
+ fun deleteDirectory(directory: File): Boolean {
15
+ if (directory.isDirectory) {
16
+ // List all files and directories in the current directory
17
+ val files = directory.listFiles()
18
+ if (files != null) {
19
+ // Recursively delete all files and directories
20
+ for (file in files) {
21
+ if (!deleteDirectory(file)) {
22
+ return false
23
+ }
24
+ }
25
+ }
26
+ }
27
+ // Finally, delete the empty directory or file
28
+ return directory.delete()
29
+ }
30
+ fun deleteOldBundleIfneeded(pathKey: String?): Boolean {
31
+ val pathName = if (pathKey != null) pathKey else PREVIOUS_PATH
32
+ val sharedPrefs = SharedPrefs(context)
33
+ val path = sharedPrefs.getString(pathName)
34
+ val file = File(path)
35
+ if (file.exists() && file.isFile) {
36
+ val isDeleted = deleteDirectory(file.parentFile)
37
+ sharedPrefs.putString(pathName, "")
38
+ return isDeleted
39
+ } else {
40
+ return false
41
+ }
42
+ }
43
+
44
+ fun extractZipFile(
45
+ zipFile: File,extension: String
46
+ ): String? {
47
+ return try {
48
+ val outputDir = zipFile.parentFile
49
+ val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
50
+ var topLevelFolder: String? = null
51
+ var bundlePath: String? = null
52
+ ZipFile(zipFile).use { zip ->
53
+ zip.entries().asSequence().forEach { entry ->
54
+ zip.getInputStream(entry).use { input ->
55
+ if (topLevelFolder == null) {
56
+ // Get root folder of zip file after unzip
57
+ val parts = entry.name.split("/")
58
+ if (parts.size > 1) {
59
+ topLevelFolder = parts.first()
60
+ }
61
+ }
62
+ val outputFile = File(outputDir, entry.name)
63
+ if (entry.isDirectory) {
64
+ if (!outputFile.exists()) outputFile.mkdirs()
65
+ } else {
66
+ if (outputFile.parentFile?.exists() != true) outputFile.parentFile?.mkdirs()
67
+ outputFile.outputStream().use { output ->
68
+ input.copyTo(output)
69
+ }
70
+ if (outputFile.absolutePath.endsWith(extension)) {
71
+ bundlePath = outputFile.absolutePath
72
+ return@use // Exit early if found
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ // Rename the detected top-level folder
79
+ if (topLevelFolder != null) {
80
+ val extractedFolder = File(outputDir, topLevelFolder)
81
+ val renamedFolder = File(outputDir, "output_$timestamp")
82
+ if (extractedFolder.exists()) {
83
+ extractedFolder.renameTo(renamedFolder)
84
+ // Update bundlePath if the file was inside the renamed folder
85
+ bundlePath = bundlePath?.replace(extractedFolder.absolutePath, renamedFolder.absolutePath)
86
+ }
87
+ }
88
+ bundlePath
89
+ } catch (e: Exception) {
90
+ e.printStackTrace()
91
+ null
92
+ }
93
+ }
94
+ }
@@ -14,4 +14,6 @@ abstract class OtaHotUpdateSpec internal constructor(context: ReactApplicationCo
14
14
  abstract fun setCurrentVersion(version: String?, promise: Promise)
15
15
  abstract fun setExactBundlePath(path: String?, promise: Promise)
16
16
  abstract fun rollbackToPreviousBundle(a: Double, promise: Promise)
17
+ abstract fun getUpdateMetadata(a: Double, promise: Promise)
18
+ abstract fun setUpdateMetadata(metadata: String?, promise: Promise)
17
19
  }