react-native-ota-hot-update 2.4.0 → 2.4.2
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/README.md +48 -0
- package/android/generated/java/com/otahotupdate/NativeOtaHotUpdateSpec.java +4 -0
- package/android/generated/jni/RNOtaHotUpdateSpec-generated.cpp +6 -0
- package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI-generated.cpp +9 -0
- package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI.h +9 -0
- package/android/src/main/java/com/otahotupdate/CrashHandler.kt +39 -12
- package/android/src/main/java/com/otahotupdate/OtaHotUpdateModule.kt +79 -6
- package/android/src/oldarch/OtaHotUpdateSpec.kt +1 -0
- package/ios/OtaHotUpdate.mm +123 -60
- package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec-generated.mm +7 -0
- package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec.h +5 -0
- package/ios/generated/RNOtaHotUpdateSpecJSI-generated.cpp +9 -0
- package/ios/generated/RNOtaHotUpdateSpecJSI.h +9 -0
- package/lib/commonjs/NativeOtaHotUpdate.js.map +1 -1
- package/lib/commonjs/gits/helper/fs.js +28 -2
- package/lib/commonjs/gits/helper/fs.js.map +1 -1
- package/lib/module/NativeOtaHotUpdate.js.map +1 -1
- package/lib/module/gits/helper/fs.js +28 -2
- package/lib/module/gits/helper/fs.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts +1 -0
- package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/gits/helper/fs.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts +1 -0
- package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts.map +1 -1
- package/lib/typescript/module/src/gits/helper/fs.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeOtaHotUpdate.ts +1 -0
- package/src/gits/helper/fs.ts +27 -3
package/README.md
CHANGED
|
@@ -251,3 +251,51 @@ Using Strapi, you can build a tailored admin panel to manage React Native hot up
|
|
|
251
251
|
|
|
252
252
|
### Sponsor this project
|
|
253
253
|
https://paypal.me/vantuan88291
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Architecture / Sequence Diagram
|
|
258
|
+
|
|
259
|
+
> Paste the content below into [https://sequencediagram.org/](https://sequencediagram.org/) to render the flow diagram.
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
title react-native-ota-hot-update - Main Flow
|
|
263
|
+
|
|
264
|
+
actor Developer
|
|
265
|
+
participant "Your Server\n(or Git Repo)" as Server
|
|
266
|
+
participant "RN App" as App
|
|
267
|
+
participant "Native Module\n(iOS / Android)" as Native
|
|
268
|
+
participant "Local Storage\n& File System" as Local
|
|
269
|
+
|
|
270
|
+
== Download & Install Update ==
|
|
271
|
+
|
|
272
|
+
Developer->Server: Upload bundle.zip + update.json
|
|
273
|
+
App->Server: Fetch update.json → check new version available
|
|
274
|
+
App->Server: Download bundle.zip
|
|
275
|
+
Server-->App: bundle.zip saved to local path
|
|
276
|
+
App->Native: setupBundlePath(zipPath, version)
|
|
277
|
+
Native->Local: Unzip → output_v{version}_{timestamp}/bundle
|
|
278
|
+
Native->Local: Save bundle PATH to SharedPrefs / UserDefaults
|
|
279
|
+
Native-->App: success
|
|
280
|
+
|
|
281
|
+
alt restartAfterInstall = true
|
|
282
|
+
App->Native: restart()
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
== App Startup - Which Bundle to Load? ==
|
|
286
|
+
|
|
287
|
+
App->Native: getBundle() / bundleJS()
|
|
288
|
+
Native->Local: Read saved PATH
|
|
289
|
+
alt PATH exists AND file on disk AND app version matches
|
|
290
|
+
Native-->App: Use OTA bundle path
|
|
291
|
+
else
|
|
292
|
+
Native-->App: Use default bundle (built-in)
|
|
293
|
+
end
|
|
294
|
+
App->App: Load JS Bundle
|
|
295
|
+
|
|
296
|
+
== Crash Auto-Rollback ==
|
|
297
|
+
|
|
298
|
+
App->App: Crash within 2s of startup
|
|
299
|
+
Native->Local: Restore previous bundle PATH
|
|
300
|
+
Native->App: Restart app with safe bundle
|
|
301
|
+
```
|
|
@@ -80,4 +80,8 @@ public abstract class NativeOtaHotUpdateSpec extends ReactContextBaseJavaModule
|
|
|
80
80
|
@ReactMethod
|
|
81
81
|
@DoNotStrip
|
|
82
82
|
public abstract void clearAllBundles(double a, Promise promise);
|
|
83
|
+
|
|
84
|
+
@ReactMethod
|
|
85
|
+
@DoNotStrip
|
|
86
|
+
public abstract void writeFile(String path, String base64Content, String encoding, Promise promise);
|
|
83
87
|
}
|
|
@@ -72,6 +72,11 @@ static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_clearAllBun
|
|
|
72
72
|
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "clearAllBundles", "(DLcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_writeFile(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
76
|
+
static jmethodID cachedMethodId = nullptr;
|
|
77
|
+
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "writeFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
NativeOtaHotUpdateSpecJSI::NativeOtaHotUpdateSpecJSI(const JavaTurboModule::InitParams ¶ms)
|
|
76
81
|
: JavaTurboModule(params) {
|
|
77
82
|
methodMap_["setupBundlePath"] = MethodMetadata {5, __hostFunction_NativeOtaHotUpdateSpecJSI_setupBundlePath};
|
|
@@ -86,6 +91,7 @@ NativeOtaHotUpdateSpecJSI::NativeOtaHotUpdateSpecJSI(const JavaTurboModule::Init
|
|
|
86
91
|
methodMap_["getBundleList"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_getBundleList};
|
|
87
92
|
methodMap_["deleteBundleById"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_deleteBundleById};
|
|
88
93
|
methodMap_["clearAllBundles"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_clearAllBundles};
|
|
94
|
+
methodMap_["writeFile"] = MethodMetadata {3, __hostFunction_NativeOtaHotUpdateSpecJSI_writeFile};
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
std::shared_ptr<TurboModule> RNOtaHotUpdateSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) {
|
|
@@ -87,6 +87,14 @@ static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_clearAllBundles(js
|
|
|
87
87
|
count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber()
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
|
+
static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_writeFile(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
|
|
91
|
+
return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->writeFile(
|
|
92
|
+
rt,
|
|
93
|
+
count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt),
|
|
94
|
+
count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt),
|
|
95
|
+
count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asString(rt)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
NativeOtaHotUpdateCxxSpecJSI::NativeOtaHotUpdateCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
|
|
92
100
|
: TurboModule("OtaHotUpdate", jsInvoker) {
|
|
@@ -102,6 +110,7 @@ NativeOtaHotUpdateCxxSpecJSI::NativeOtaHotUpdateCxxSpecJSI(std::shared_ptr<CallI
|
|
|
102
110
|
methodMap_["getBundleList"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getBundleList};
|
|
103
111
|
methodMap_["deleteBundleById"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_deleteBundleById};
|
|
104
112
|
methodMap_["clearAllBundles"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_clearAllBundles};
|
|
113
|
+
methodMap_["writeFile"] = MethodMetadata {3, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_writeFile};
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
|
package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI.h
CHANGED
|
@@ -32,6 +32,7 @@ public:
|
|
|
32
32
|
virtual jsi::Value getBundleList(jsi::Runtime &rt, double a) = 0;
|
|
33
33
|
virtual jsi::Value deleteBundleById(jsi::Runtime &rt, jsi::String id) = 0;
|
|
34
34
|
virtual jsi::Value clearAllBundles(jsi::Runtime &rt, double a) = 0;
|
|
35
|
+
virtual jsi::Value writeFile(jsi::Runtime &rt, jsi::String path, jsi::String base64Content, jsi::String encoding) = 0;
|
|
35
36
|
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -154,6 +155,14 @@ private:
|
|
|
154
155
|
return bridging::callFromJs<jsi::Value>(
|
|
155
156
|
rt, &T::clearAllBundles, jsInvoker_, instance_, std::move(a));
|
|
156
157
|
}
|
|
158
|
+
jsi::Value writeFile(jsi::Runtime &rt, jsi::String path, jsi::String base64Content, jsi::String encoding) override {
|
|
159
|
+
static_assert(
|
|
160
|
+
bridging::getParameterCount(&T::writeFile) == 4,
|
|
161
|
+
"Expected writeFile(...) to have 4 parameters");
|
|
162
|
+
|
|
163
|
+
return bridging::callFromJs<jsi::Value>(
|
|
164
|
+
rt, &T::writeFile, jsInvoker_, instance_, std::move(path), std::move(base64Content), std::move(encoding));
|
|
165
|
+
}
|
|
157
166
|
|
|
158
167
|
private:
|
|
159
168
|
friend class NativeOtaHotUpdateCxxSpec;
|
|
@@ -4,12 +4,15 @@ import android.content.Context
|
|
|
4
4
|
import android.widget.Toast
|
|
5
5
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
|
6
6
|
import com.rnhotupdate.Common.PATH
|
|
7
|
-
import com.rnhotupdate.Common.
|
|
7
|
+
import com.rnhotupdate.Common.VERSION
|
|
8
|
+
import com.rnhotupdate.Common.BUNDLE_HISTORY
|
|
8
9
|
import com.rnhotupdate.SharedPrefs
|
|
9
10
|
import kotlinx.coroutines.Dispatchers
|
|
10
11
|
import kotlinx.coroutines.GlobalScope
|
|
11
12
|
import kotlinx.coroutines.delay
|
|
12
13
|
import kotlinx.coroutines.launch
|
|
14
|
+
import org.json.JSONArray
|
|
15
|
+
import java.io.File
|
|
13
16
|
|
|
14
17
|
class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
|
|
15
18
|
private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
|
|
@@ -23,20 +26,45 @@ class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandl
|
|
|
23
26
|
}
|
|
24
27
|
override fun uncaughtException(thread: Thread, throwable: Throwable) {
|
|
25
28
|
if (beginning) {
|
|
26
|
-
//begin remove and using previous bundle
|
|
27
29
|
val sharedPrefs = SharedPrefs(context)
|
|
28
|
-
val
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
val currentPath = sharedPrefs.getString(PATH)
|
|
31
|
+
|
|
32
|
+
// Try to rollback using history system
|
|
33
|
+
val historyJson = sharedPrefs.getString(BUNDLE_HISTORY)
|
|
34
|
+
var rolledBack = false
|
|
35
|
+
|
|
36
|
+
if (!historyJson.isNullOrEmpty() && !currentPath.isNullOrEmpty()) {
|
|
37
|
+
try {
|
|
38
|
+
val jsonArray = JSONArray(historyJson)
|
|
39
|
+
val history = (0 until jsonArray.length()).map { i ->
|
|
40
|
+
val obj = jsonArray.getJSONObject(i)
|
|
41
|
+
Pair(obj.getInt("version"), obj.getString("path"))
|
|
42
|
+
}.sortedByDescending { it.first }
|
|
43
|
+
|
|
44
|
+
val currentBundle = history.find { it.second == currentPath }
|
|
45
|
+
if (currentBundle != null) {
|
|
46
|
+
val previousBundle = history
|
|
47
|
+
.filter { it.first < currentBundle.first }
|
|
48
|
+
.maxByOrNull { it.first }
|
|
49
|
+
|
|
50
|
+
if (previousBundle != null && File(previousBundle.second).exists()) {
|
|
51
|
+
val isDeleted = utils.deleteOldBundleIfneeded(PATH)
|
|
52
|
+
if (isDeleted) {
|
|
53
|
+
sharedPrefs.putString(PATH, previousBundle.second)
|
|
54
|
+
sharedPrefs.putString(VERSION, previousBundle.first.toString())
|
|
55
|
+
rolledBack = true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
// ignore, fall through to clear path
|
|
36
61
|
}
|
|
37
|
-
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!rolledBack) {
|
|
38
65
|
sharedPrefs.putString(PATH, "")
|
|
39
66
|
}
|
|
67
|
+
|
|
40
68
|
val errorMessage = throwable.message ?: "Unknown error occurred"
|
|
41
69
|
Toast.makeText(context, "Update failed: $errorMessage", Toast.LENGTH_LONG).show()
|
|
42
70
|
GlobalScope.launch(Dispatchers.IO) {
|
|
@@ -48,4 +76,3 @@ class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandl
|
|
|
48
76
|
}
|
|
49
77
|
}
|
|
50
78
|
}
|
|
51
|
-
|
|
@@ -14,7 +14,6 @@ import com.rnhotupdate.Common.VERSION
|
|
|
14
14
|
import com.rnhotupdate.Common.PREVIOUS_VERSION
|
|
15
15
|
import com.rnhotupdate.Common.METADATA
|
|
16
16
|
import com.rnhotupdate.Common.BUNDLE_HISTORY
|
|
17
|
-
import com.rnhotupdate.Common.DEFAULT_MAX_BUNDLE_VERSIONS
|
|
18
17
|
import com.rnhotupdate.SharedPrefs
|
|
19
18
|
import kotlinx.coroutines.CoroutineScope
|
|
20
19
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -22,7 +21,12 @@ import kotlinx.coroutines.SupervisorJob
|
|
|
22
21
|
import kotlinx.coroutines.cancel
|
|
23
22
|
import kotlinx.coroutines.launch
|
|
24
23
|
import kotlinx.coroutines.withContext
|
|
24
|
+
import android.util.Base64
|
|
25
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
26
|
+
import java.util.concurrent.Executors
|
|
25
27
|
import java.io.File
|
|
28
|
+
import java.io.FileOutputStream
|
|
29
|
+
import java.io.IOException
|
|
26
30
|
import org.json.JSONArray
|
|
27
31
|
import org.json.JSONObject
|
|
28
32
|
|
|
@@ -37,6 +41,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
37
41
|
OtaHotUpdateSpec(context) {
|
|
38
42
|
private val utils: Utils = Utils(context)
|
|
39
43
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
44
|
+
private val fileWriterExecutor = Executors.newSingleThreadExecutor()
|
|
40
45
|
|
|
41
46
|
override fun getName(): String {
|
|
42
47
|
return NAME
|
|
@@ -45,6 +50,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
45
50
|
override fun invalidate() {
|
|
46
51
|
super.invalidate()
|
|
47
52
|
scope.cancel()
|
|
53
|
+
fileWriterExecutor.shutdown()
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
private fun loadBundleHistory(): List<BundleVersion> {
|
|
@@ -182,7 +188,11 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
182
188
|
val versionsToKeep = finalHistory.map { it.version }.toSet()
|
|
183
189
|
updatedHistory.forEach { bundle ->
|
|
184
190
|
if (bundle.version !in versionsToKeep) {
|
|
185
|
-
|
|
191
|
+
val bundleFile = File(bundle.path)
|
|
192
|
+
val parentDir = bundleFile.parentFile
|
|
193
|
+
if (parentDir != null && parentDir.exists() && parentDir.isDirectory) {
|
|
194
|
+
utils.deleteDirectory(parentDir)
|
|
195
|
+
}
|
|
186
196
|
}
|
|
187
197
|
}
|
|
188
198
|
|
|
@@ -194,7 +204,13 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
194
204
|
sharedPrefs.putString(VERSION, version.toString())
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
private fun processBundleFile(
|
|
207
|
+
private fun processBundleFile(
|
|
208
|
+
path: String?,
|
|
209
|
+
extension: String?,
|
|
210
|
+
version: Int?,
|
|
211
|
+
maxVersions: Int?,
|
|
212
|
+
metadata: String?
|
|
213
|
+
): Boolean {
|
|
198
214
|
if (path != null) {
|
|
199
215
|
val file = File(path)
|
|
200
216
|
if (file.exists() && file.isFile) {
|
|
@@ -230,8 +246,16 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
230
246
|
throw Exception("Invalid path: $path")
|
|
231
247
|
}
|
|
232
248
|
}
|
|
249
|
+
|
|
233
250
|
@ReactMethod
|
|
234
|
-
override fun setupBundlePath(
|
|
251
|
+
override fun setupBundlePath(
|
|
252
|
+
path: String?,
|
|
253
|
+
extension: String?,
|
|
254
|
+
version: Double?,
|
|
255
|
+
maxVersions: Double?,
|
|
256
|
+
metadata: String?,
|
|
257
|
+
promise: Promise
|
|
258
|
+
) {
|
|
235
259
|
scope.launch {
|
|
236
260
|
try {
|
|
237
261
|
val versionInt = version?.toInt()
|
|
@@ -282,8 +306,11 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
282
306
|
|
|
283
307
|
@ReactMethod
|
|
284
308
|
override fun restart() {
|
|
285
|
-
val
|
|
286
|
-
|
|
309
|
+
val activity = reactApplicationContext.currentActivity
|
|
310
|
+
val context: Context = activity ?: reactApplicationContext
|
|
311
|
+
UiThreadUtil.runOnUiThread {
|
|
312
|
+
ProcessPhoenix.triggerRebirth(context)
|
|
313
|
+
}
|
|
287
314
|
}
|
|
288
315
|
|
|
289
316
|
@ReactMethod
|
|
@@ -509,6 +536,52 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
509
536
|
}
|
|
510
537
|
}
|
|
511
538
|
}
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
override fun writeFile(
|
|
543
|
+
path: String?,
|
|
544
|
+
base64Content: String?,
|
|
545
|
+
encoding: String?,
|
|
546
|
+
promise: Promise?
|
|
547
|
+
) {
|
|
548
|
+
if (path == null || base64Content == null) {
|
|
549
|
+
promise?.reject("INVALID_ARG", "Path and base64Content are required", null)
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
fileWriterExecutor.execute {
|
|
554
|
+
try {
|
|
555
|
+
// Decode base64 to bytes
|
|
556
|
+
val bytes = Base64.decode(base64Content, Base64.DEFAULT)
|
|
557
|
+
|
|
558
|
+
// Ensure parent directory exists
|
|
559
|
+
val file = File(path)
|
|
560
|
+
val parentDir = file.parentFile
|
|
561
|
+
if (parentDir != null && !parentDir.exists()) {
|
|
562
|
+
parentDir.mkdirs()
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Write file on background thread
|
|
566
|
+
FileOutputStream(file).use { fos ->
|
|
567
|
+
fos.write(bytes)
|
|
568
|
+
fos.flush()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Resolve on UI thread (React Native requirement)
|
|
572
|
+
UiThreadUtil.runOnUiThread {
|
|
573
|
+
promise?.resolve(true)
|
|
574
|
+
}
|
|
575
|
+
} catch (e: IOException) {
|
|
576
|
+
UiThreadUtil.runOnUiThread {
|
|
577
|
+
promise?.reject("WRITE_ERROR", "Failed to write file: ${e.message}", e)
|
|
578
|
+
}
|
|
579
|
+
} catch (e: Exception) {
|
|
580
|
+
UiThreadUtil.runOnUiThread {
|
|
581
|
+
promise?.reject("WRITE_ERROR", "Unexpected error: ${e.message}", e)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
512
585
|
}
|
|
513
586
|
|
|
514
587
|
companion object {
|
|
@@ -19,4 +19,5 @@ abstract class OtaHotUpdateSpec internal constructor(context: ReactApplicationCo
|
|
|
19
19
|
abstract fun getBundleList(a: Double, promise: Promise)
|
|
20
20
|
abstract fun deleteBundleById(id: String, promise: Promise)
|
|
21
21
|
abstract fun clearAllBundles(a: Double, promise: Promise)
|
|
22
|
+
abstract fun writeFile(path: String?, base64Content: String?, encoding: String?, promise: Promise)
|
|
22
23
|
}
|
package/ios/OtaHotUpdate.mm
CHANGED
|
@@ -14,6 +14,9 @@ static BOOL isBeginning = YES;
|
|
|
14
14
|
@implementation OtaHotUpdate
|
|
15
15
|
RCT_EXPORT_MODULE()
|
|
16
16
|
|
|
17
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
18
|
+
return NO;
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
- (instancetype)init {
|
|
19
22
|
self = [super init];
|
|
@@ -36,16 +39,24 @@ RCT_EXPORT_MODULE()
|
|
|
36
39
|
void OTASignalHandler(int sig) {
|
|
37
40
|
if (isBeginning) {
|
|
38
41
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
// Use PREVIOUS_BUNDLE_PATH (simple key, written by saveBundleVersion before each update)
|
|
43
|
+
NSString *previousPath = [defaults stringForKey:@"PREVIOUS_BUNDLE_PATH"];
|
|
44
|
+
if (previousPath && previousPath.length > 0) {
|
|
41
45
|
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
42
46
|
if (isDeleted) {
|
|
43
|
-
[defaults setObject:
|
|
47
|
+
[defaults setObject:previousPath forKey:@"PATH"];
|
|
48
|
+
NSString *previousVersion = [defaults stringForKey:@"PREVIOUS_BUNDLE_VERSION"];
|
|
49
|
+
if (previousVersion) {
|
|
50
|
+
[defaults setObject:previousVersion forKey:@"VERSION"];
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
[defaults removeObjectForKey:@"PATH"];
|
|
44
54
|
}
|
|
45
|
-
[defaults removeObjectForKey:@"OLD_PATH"];
|
|
46
55
|
} else {
|
|
47
56
|
[defaults removeObjectForKey:@"PATH"];
|
|
48
57
|
}
|
|
58
|
+
[defaults removeObjectForKey:@"PREVIOUS_BUNDLE_PATH"];
|
|
59
|
+
[defaults removeObjectForKey:@"PREVIOUS_BUNDLE_VERSION"];
|
|
49
60
|
[defaults synchronize];
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -55,20 +66,24 @@ void OTASignalHandler(int sig) {
|
|
|
55
66
|
void OTAExceptionHandler(NSException *exception) {
|
|
56
67
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
57
68
|
if (isBeginning) {
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
NSString *previousPath = [defaults stringForKey:@"PREVIOUS_BUNDLE_PATH"];
|
|
70
|
+
if (previousPath && previousPath.length > 0) {
|
|
71
|
+
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
72
|
+
if (isDeleted) {
|
|
73
|
+
[defaults setObject:previousPath forKey:@"PATH"];
|
|
74
|
+
NSString *previousVersion = [defaults stringForKey:@"PREVIOUS_BUNDLE_VERSION"];
|
|
75
|
+
if (previousVersion) {
|
|
76
|
+
[defaults setObject:previousVersion forKey:@"VERSION"];
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
[defaults removeObjectForKey:@"PATH"];
|
|
80
|
+
}
|
|
68
81
|
} else {
|
|
69
|
-
|
|
82
|
+
[defaults removeObjectForKey:@"PATH"];
|
|
70
83
|
}
|
|
71
|
-
|
|
84
|
+
[defaults removeObjectForKey:@"PREVIOUS_BUNDLE_PATH"];
|
|
85
|
+
[defaults removeObjectForKey:@"PREVIOUS_BUNDLE_VERSION"];
|
|
86
|
+
[defaults synchronize];
|
|
72
87
|
} else if (previousHandler) {
|
|
73
88
|
previousHandler(exception);
|
|
74
89
|
}
|
|
@@ -237,7 +252,7 @@ void OTAExceptionHandler(NSException *exception) {
|
|
|
237
252
|
[dateFormatter setDateFormat:@"yyyy_MM_dd_HH_mm"];
|
|
238
253
|
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
239
254
|
NSString *timestamp = [dateFormatter stringFromDate:[NSDate date]];
|
|
240
|
-
NSString *folderName = version != nil
|
|
255
|
+
NSString *folderName = version != nil
|
|
241
256
|
? [NSString stringWithFormat:@"output_v%@_%@", version, timestamp]
|
|
242
257
|
: [NSString stringWithFormat:@"output_%@", timestamp];
|
|
243
258
|
NSString *newFolderPath = [directoryPath stringByAppendingPathComponent:folderName];
|
|
@@ -297,13 +312,11 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
297
312
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
298
313
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
299
314
|
if ([OtaHotUpdate isFilePathValid:path]) {
|
|
300
|
-
[OtaHotUpdate removeBundleIfNeeded:nil];
|
|
301
315
|
//Unzip file
|
|
302
316
|
NSString *extractedFilePath = [self unzipFileAtPath:path extension:(extension != nil) ? extension : @".jsbundle" version:version];
|
|
303
317
|
if (extractedFilePath) {
|
|
304
318
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
305
|
-
|
|
306
|
-
|
|
319
|
+
|
|
307
320
|
// If version is provided, save to history system
|
|
308
321
|
if (version != nil) {
|
|
309
322
|
// Default maxVersions to 2 if not provided (backward compatible)
|
|
@@ -314,7 +327,7 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
314
327
|
// No version (e.g., Git update) - just set path, no history
|
|
315
328
|
[defaults setObject:extractedFilePath forKey:@"PATH"];
|
|
316
329
|
}
|
|
317
|
-
|
|
330
|
+
|
|
318
331
|
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
|
|
319
332
|
[defaults synchronize];
|
|
320
333
|
isBeginning = YES;
|
|
@@ -331,10 +344,10 @@ RCT_EXPORT_METHOD(deleteBundle:(double)i
|
|
|
331
344
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
332
345
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
333
346
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
334
|
-
|
|
347
|
+
|
|
335
348
|
// Delete current bundle from file system
|
|
336
349
|
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
337
|
-
|
|
350
|
+
|
|
338
351
|
// Remove current bundle from history if exists
|
|
339
352
|
if (currentPath && currentPath.length > 0) {
|
|
340
353
|
NSArray *history = [self loadBundleHistory];
|
|
@@ -346,12 +359,12 @@ RCT_EXPORT_METHOD(deleteBundle:(double)i
|
|
|
346
359
|
}
|
|
347
360
|
[self saveBundleHistory:updatedHistory];
|
|
348
361
|
}
|
|
349
|
-
|
|
362
|
+
|
|
350
363
|
// Clear paths and version
|
|
351
364
|
[defaults removeObjectForKey:@"PATH"];
|
|
352
365
|
[defaults setObject:@"0" forKey:@"VERSION"];
|
|
353
366
|
[defaults synchronize];
|
|
354
|
-
|
|
367
|
+
|
|
355
368
|
resolve(@(isDeleted));
|
|
356
369
|
}
|
|
357
370
|
// Expose deleteBundle method to JavaScript
|
|
@@ -360,7 +373,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
360
373
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
361
374
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
362
375
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
363
|
-
|
|
376
|
+
|
|
364
377
|
// Use history to find previous version
|
|
365
378
|
NSArray *history = [self loadBundleHistory];
|
|
366
379
|
if (history.count > 0 && currentPath && currentPath.length > 0) {
|
|
@@ -372,7 +385,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
372
385
|
break;
|
|
373
386
|
}
|
|
374
387
|
}
|
|
375
|
-
|
|
388
|
+
|
|
376
389
|
if (currentBundle) {
|
|
377
390
|
// Find previous version (older than current, max version)
|
|
378
391
|
NSDictionary *previousBundle = nil;
|
|
@@ -385,7 +398,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
385
398
|
}
|
|
386
399
|
}
|
|
387
400
|
}
|
|
388
|
-
|
|
401
|
+
|
|
389
402
|
if (previousBundle && [OtaHotUpdate isFilePathValid:previousBundle[@"path"]]) {
|
|
390
403
|
// Rollback to previous bundle from history
|
|
391
404
|
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
@@ -399,7 +412,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
399
412
|
}
|
|
400
413
|
}
|
|
401
414
|
}
|
|
402
|
-
|
|
415
|
+
|
|
403
416
|
resolve(@(NO));
|
|
404
417
|
}
|
|
405
418
|
|
|
@@ -478,7 +491,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
478
491
|
- (NSArray *)loadBundleHistory {
|
|
479
492
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
480
493
|
NSString *historyJson = [defaults stringForKey:@"BUNDLE_HISTORY"];
|
|
481
|
-
|
|
494
|
+
|
|
482
495
|
// If history exists, load it
|
|
483
496
|
if (historyJson && historyJson.length > 0) {
|
|
484
497
|
NSData *data = [historyJson dataUsingEncoding:NSUTF8StringEncoding];
|
|
@@ -488,20 +501,20 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
488
501
|
return history;
|
|
489
502
|
}
|
|
490
503
|
}
|
|
491
|
-
|
|
504
|
+
|
|
492
505
|
// Migration: If history is empty but PATH exists, migrate from old system
|
|
493
506
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
494
507
|
NSString *currentVersion = [defaults stringForKey:@"VERSION"];
|
|
495
508
|
NSString *previousPath = [defaults stringForKey:@"OLD_PATH"];
|
|
496
509
|
NSString *previousVersion = [defaults stringForKey:@"PREVIOUS_VERSION"];
|
|
497
|
-
|
|
510
|
+
|
|
498
511
|
if (!currentPath || currentPath.length == 0) {
|
|
499
512
|
return @[];
|
|
500
513
|
}
|
|
501
|
-
|
|
514
|
+
|
|
502
515
|
// Migrate current bundle
|
|
503
516
|
NSMutableArray *migratedHistory = [NSMutableArray array];
|
|
504
|
-
|
|
517
|
+
|
|
505
518
|
// Add current bundle if has version
|
|
506
519
|
if (currentVersion && currentVersion.length > 0) {
|
|
507
520
|
NSInteger version = [currentVersion integerValue];
|
|
@@ -510,7 +523,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
510
523
|
NSDictionary *attributes = [fileManager attributesOfItemAtPath:currentPath error:nil];
|
|
511
524
|
NSDate *modificationDate = attributes[NSFileModificationDate];
|
|
512
525
|
long long timestamp = modificationDate ? (long long)([modificationDate timeIntervalSince1970] * 1000) : (long long)([[NSDate date] timeIntervalSince1970] * 1000);
|
|
513
|
-
|
|
526
|
+
|
|
514
527
|
NSMutableDictionary *bundle = [NSMutableDictionary dictionary];
|
|
515
528
|
bundle[@"version"] = @(version);
|
|
516
529
|
bundle[@"path"] = currentPath;
|
|
@@ -519,7 +532,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
519
532
|
[migratedHistory addObject:bundle];
|
|
520
533
|
}
|
|
521
534
|
}
|
|
522
|
-
|
|
535
|
+
|
|
523
536
|
// Add previous bundle if exists
|
|
524
537
|
if (previousPath && previousPath.length > 0 && previousVersion && previousVersion.length > 0) {
|
|
525
538
|
NSInteger version = [previousVersion integerValue];
|
|
@@ -528,7 +541,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
528
541
|
NSDictionary *attributes = [fileManager attributesOfItemAtPath:previousPath error:nil];
|
|
529
542
|
NSDate *modificationDate = attributes[NSFileModificationDate];
|
|
530
543
|
long long timestamp = modificationDate ? (long long)([modificationDate timeIntervalSince1970] * 1000) : (long long)([[NSDate date] timeIntervalSince1970] * 1000);
|
|
531
|
-
|
|
544
|
+
|
|
532
545
|
NSMutableDictionary *bundle = [NSMutableDictionary dictionary];
|
|
533
546
|
bundle[@"version"] = @(version);
|
|
534
547
|
bundle[@"path"] = previousPath;
|
|
@@ -537,7 +550,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
537
550
|
[migratedHistory addObject:bundle];
|
|
538
551
|
}
|
|
539
552
|
}
|
|
540
|
-
|
|
553
|
+
|
|
541
554
|
// Save migrated history if any
|
|
542
555
|
if (migratedHistory.count > 0) {
|
|
543
556
|
// Sort by version descending
|
|
@@ -551,7 +564,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
551
564
|
[self saveBundleHistory:sortedHistory];
|
|
552
565
|
return sortedHistory;
|
|
553
566
|
}
|
|
554
|
-
|
|
567
|
+
|
|
555
568
|
return @[];
|
|
556
569
|
}
|
|
557
570
|
|
|
@@ -573,7 +586,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
573
586
|
- (void)saveBundleVersion:(NSString *)path version:(NSInteger)version maxVersions:(NSInteger)maxVersions metadata:(NSString *)metadata {
|
|
574
587
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
575
588
|
NSArray *history = [self loadBundleHistory];
|
|
576
|
-
|
|
589
|
+
|
|
577
590
|
// Create new bundle entry
|
|
578
591
|
NSMutableDictionary *newBundle = [NSMutableDictionary dictionary];
|
|
579
592
|
newBundle[@"version"] = @(version);
|
|
@@ -582,11 +595,11 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
582
595
|
if (metadata) {
|
|
583
596
|
newBundle[@"metadata"] = metadata;
|
|
584
597
|
}
|
|
585
|
-
|
|
598
|
+
|
|
586
599
|
// Combine with existing history
|
|
587
600
|
NSMutableArray *updatedHistory = [NSMutableArray arrayWithObject:newBundle];
|
|
588
601
|
[updatedHistory addObjectsFromArray:history];
|
|
589
|
-
|
|
602
|
+
|
|
590
603
|
// Sort by version descending and remove duplicates
|
|
591
604
|
[updatedHistory sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
|
|
592
605
|
NSInteger v1 = [obj1[@"version"] integerValue];
|
|
@@ -595,7 +608,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
595
608
|
if (v1 < v2) return NSOrderedDescending;
|
|
596
609
|
return NSOrderedSame;
|
|
597
610
|
}];
|
|
598
|
-
|
|
611
|
+
|
|
599
612
|
// Remove duplicates by version
|
|
600
613
|
NSMutableArray *uniqueHistory = [NSMutableArray array];
|
|
601
614
|
NSMutableSet *seenVersions = [NSMutableSet set];
|
|
@@ -606,25 +619,35 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
606
619
|
[uniqueHistory addObject:bundle];
|
|
607
620
|
}
|
|
608
621
|
}
|
|
609
|
-
|
|
622
|
+
|
|
610
623
|
// Keep only maxVersions most recent
|
|
611
624
|
NSArray *finalHistory = [uniqueHistory subarrayWithRange:NSMakeRange(0, MIN(maxVersions, uniqueHistory.count))];
|
|
612
|
-
|
|
625
|
+
|
|
613
626
|
// Delete old versions beyond limit
|
|
614
627
|
NSMutableSet *versionsToKeep = [NSMutableSet set];
|
|
615
628
|
for (NSDictionary *bundle in finalHistory) {
|
|
616
629
|
[versionsToKeep addObject:bundle[@"version"]];
|
|
617
630
|
}
|
|
618
|
-
|
|
631
|
+
|
|
619
632
|
for (NSDictionary *bundle in uniqueHistory) {
|
|
620
633
|
if (![versionsToKeep containsObject:bundle[@"version"]]) {
|
|
621
634
|
[OtaHotUpdate deleteBundleAtPath:bundle[@"path"]];
|
|
622
635
|
}
|
|
623
636
|
}
|
|
624
|
-
|
|
637
|
+
|
|
625
638
|
// Save updated history
|
|
626
639
|
[self saveBundleHistory:finalHistory];
|
|
627
|
-
|
|
640
|
+
|
|
641
|
+
// Before updating current path, save it as fallback for crash handler
|
|
642
|
+
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
643
|
+
NSString *currentVersion = [defaults stringForKey:@"VERSION"];
|
|
644
|
+
if (currentPath && currentPath.length > 0) {
|
|
645
|
+
[defaults setObject:currentPath forKey:@"PREVIOUS_BUNDLE_PATH"];
|
|
646
|
+
if (currentVersion) {
|
|
647
|
+
[defaults setObject:currentVersion forKey:@"PREVIOUS_BUNDLE_VERSION"];
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
628
651
|
// Set current path and version
|
|
629
652
|
[defaults setObject:path forKey:@"PATH"];
|
|
630
653
|
[defaults setObject:[NSString stringWithFormat:@"%ld", (long)version] forKey:@"VERSION"];
|
|
@@ -649,7 +672,7 @@ RCT_EXPORT_METHOD(getBundleList:(double)a
|
|
|
649
672
|
NSArray *history = [self loadBundleHistory];
|
|
650
673
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
651
674
|
NSString *activePath = [defaults stringForKey:@"PATH"];
|
|
652
|
-
|
|
675
|
+
|
|
653
676
|
NSMutableArray *bundleList = [NSMutableArray array];
|
|
654
677
|
for (NSDictionary *bundle in history) {
|
|
655
678
|
NSString *path = bundle[@"path"];
|
|
@@ -669,7 +692,7 @@ RCT_EXPORT_METHOD(getBundleList:(double)a
|
|
|
669
692
|
}
|
|
670
693
|
[bundleList addObject:bundleInfo];
|
|
671
694
|
}
|
|
672
|
-
|
|
695
|
+
|
|
673
696
|
NSError *error = nil;
|
|
674
697
|
NSData *data = [NSJSONSerialization dataWithJSONObject:bundleList options:0 error:&error];
|
|
675
698
|
if (!error && data) {
|
|
@@ -686,7 +709,7 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
686
709
|
NSArray *history = [self loadBundleHistory];
|
|
687
710
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
688
711
|
NSString *activePath = [defaults stringForKey:@"PATH"];
|
|
689
|
-
|
|
712
|
+
|
|
690
713
|
NSDictionary *bundleToDelete = nil;
|
|
691
714
|
for (NSDictionary *bundle in history) {
|
|
692
715
|
NSString *folderName = [self extractFolderName:bundle[@"path"]];
|
|
@@ -695,12 +718,12 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
695
718
|
break;
|
|
696
719
|
}
|
|
697
720
|
}
|
|
698
|
-
|
|
721
|
+
|
|
699
722
|
if (!bundleToDelete) {
|
|
700
723
|
resolve(@(NO));
|
|
701
724
|
return;
|
|
702
725
|
}
|
|
703
|
-
|
|
726
|
+
|
|
704
727
|
// If deleting active bundle, rollback to oldest remaining bundle or clear
|
|
705
728
|
if ([bundleToDelete[@"path"] isEqualToString:activePath]) {
|
|
706
729
|
NSMutableArray *remainingBundles = [NSMutableArray array];
|
|
@@ -725,10 +748,10 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
725
748
|
}
|
|
726
749
|
[defaults synchronize];
|
|
727
750
|
}
|
|
728
|
-
|
|
751
|
+
|
|
729
752
|
// Delete bundle folder
|
|
730
753
|
BOOL isDeleted = [OtaHotUpdate deleteBundleAtPath:bundleToDelete[@"path"]];
|
|
731
|
-
|
|
754
|
+
|
|
732
755
|
// Remove from history
|
|
733
756
|
NSMutableArray *updatedHistory = [NSMutableArray array];
|
|
734
757
|
for (NSDictionary *bundle in history) {
|
|
@@ -737,7 +760,7 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
737
760
|
}
|
|
738
761
|
}
|
|
739
762
|
[self saveBundleHistory:updatedHistory];
|
|
740
|
-
|
|
763
|
+
|
|
741
764
|
resolve(@(isDeleted));
|
|
742
765
|
}
|
|
743
766
|
|
|
@@ -746,23 +769,63 @@ RCT_EXPORT_METHOD(clearAllBundles:(double)a
|
|
|
746
769
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
747
770
|
NSArray *history = [self loadBundleHistory];
|
|
748
771
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
749
|
-
|
|
772
|
+
|
|
750
773
|
// Delete all bundle folders
|
|
751
774
|
for (NSDictionary *bundle in history) {
|
|
752
775
|
[OtaHotUpdate deleteBundleAtPath:bundle[@"path"]];
|
|
753
776
|
}
|
|
754
|
-
|
|
777
|
+
|
|
755
778
|
// Clear history
|
|
756
779
|
[self saveBundleHistory:@[]];
|
|
757
|
-
|
|
780
|
+
|
|
758
781
|
// Clear current path and version
|
|
759
782
|
[defaults removeObjectForKey:@"PATH"];
|
|
760
783
|
[defaults removeObjectForKey:@"VERSION"];
|
|
761
784
|
[defaults synchronize];
|
|
762
|
-
|
|
785
|
+
|
|
763
786
|
resolve(@(YES));
|
|
764
787
|
}
|
|
765
788
|
|
|
789
|
+
RCT_EXPORT_METHOD(writeFile:(NSString *)path
|
|
790
|
+
base64Content:(NSString *)base64Content
|
|
791
|
+
encoding:(NSString *)encoding
|
|
792
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
793
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
794
|
+
// Run on background queue to avoid blocking JS thread
|
|
795
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
796
|
+
@try {
|
|
797
|
+
// Decode base64 to NSData
|
|
798
|
+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Content options:0];
|
|
799
|
+
if (!data) {
|
|
800
|
+
reject(@"DECODE_ERROR", @"Failed to decode base64 content", nil);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Ensure parent directory exists
|
|
805
|
+
NSString *parentDir = [path stringByDeletingLastPathComponent];
|
|
806
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
807
|
+
if (![fileManager fileExistsAtPath:parentDir]) {
|
|
808
|
+
NSError *error;
|
|
809
|
+
if (![fileManager createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
810
|
+
reject(@"CREATE_DIR_ERROR", [NSString stringWithFormat:@"Failed to create directory: %@", error.localizedDescription], error);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Write file on background thread
|
|
816
|
+
NSError *error;
|
|
817
|
+
if (![data writeToFile:path options:NSDataWritingAtomic error:&error]) {
|
|
818
|
+
reject(@"WRITE_ERROR", [NSString stringWithFormat:@"Failed to write file: %@", error.localizedDescription], error);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
resolve(@(YES));
|
|
823
|
+
} @catch (NSException *exception) {
|
|
824
|
+
reject(@"WRITE_ERROR", [NSString stringWithFormat:@"Unexpected error: %@", exception.reason], nil);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
766
829
|
// Don't compile this code when we build for the old architecture.
|
|
767
830
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
768
831
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
@@ -74,6 +74,10 @@ namespace facebook::react {
|
|
|
74
74
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "clearAllBundles", @selector(clearAllBundles:resolve:reject:), args, count);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
static facebook::jsi::Value __hostFunction_NativeOtaHotUpdateSpecJSI_writeFile(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
78
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "writeFile", @selector(writeFile:base64Content:encoding:resolve:reject:), args, count);
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
NativeOtaHotUpdateSpecJSI::NativeOtaHotUpdateSpecJSI(const ObjCTurboModule::InitParams ¶ms)
|
|
78
82
|
: ObjCTurboModule(params) {
|
|
79
83
|
|
|
@@ -112,5 +116,8 @@ namespace facebook::react {
|
|
|
112
116
|
|
|
113
117
|
methodMap_["clearAllBundles"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateSpecJSI_clearAllBundles};
|
|
114
118
|
|
|
119
|
+
|
|
120
|
+
methodMap_["writeFile"] = MethodMetadata {3, __hostFunction_NativeOtaHotUpdateSpecJSI_writeFile};
|
|
121
|
+
|
|
115
122
|
}
|
|
116
123
|
} // namespace facebook::react
|
|
@@ -71,6 +71,11 @@
|
|
|
71
71
|
- (void)clearAllBundles:(double)a
|
|
72
72
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
73
73
|
reject:(RCTPromiseRejectBlock)reject;
|
|
74
|
+
- (void)writeFile:(NSString *)path
|
|
75
|
+
base64Content:(NSString *)base64Content
|
|
76
|
+
encoding:(NSString *)encoding
|
|
77
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
78
|
+
reject:(RCTPromiseRejectBlock)reject;
|
|
74
79
|
|
|
75
80
|
@end
|
|
76
81
|
|
|
@@ -87,6 +87,14 @@ static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_clearAllBundles(js
|
|
|
87
87
|
count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber()
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
|
+
static jsi::Value __hostFunction_NativeOtaHotUpdateCxxSpecJSI_writeFile(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
|
|
91
|
+
return static_cast<NativeOtaHotUpdateCxxSpecJSI *>(&turboModule)->writeFile(
|
|
92
|
+
rt,
|
|
93
|
+
count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt),
|
|
94
|
+
count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt),
|
|
95
|
+
count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asString(rt)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
NativeOtaHotUpdateCxxSpecJSI::NativeOtaHotUpdateCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
|
|
92
100
|
: TurboModule("OtaHotUpdate", jsInvoker) {
|
|
@@ -102,6 +110,7 @@ NativeOtaHotUpdateCxxSpecJSI::NativeOtaHotUpdateCxxSpecJSI(std::shared_ptr<CallI
|
|
|
102
110
|
methodMap_["getBundleList"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_getBundleList};
|
|
103
111
|
methodMap_["deleteBundleById"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_deleteBundleById};
|
|
104
112
|
methodMap_["clearAllBundles"] = MethodMetadata {1, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_clearAllBundles};
|
|
113
|
+
methodMap_["writeFile"] = MethodMetadata {3, __hostFunction_NativeOtaHotUpdateCxxSpecJSI_writeFile};
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
|
|
@@ -32,6 +32,7 @@ public:
|
|
|
32
32
|
virtual jsi::Value getBundleList(jsi::Runtime &rt, double a) = 0;
|
|
33
33
|
virtual jsi::Value deleteBundleById(jsi::Runtime &rt, jsi::String id) = 0;
|
|
34
34
|
virtual jsi::Value clearAllBundles(jsi::Runtime &rt, double a) = 0;
|
|
35
|
+
virtual jsi::Value writeFile(jsi::Runtime &rt, jsi::String path, jsi::String base64Content, jsi::String encoding) = 0;
|
|
35
36
|
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -154,6 +155,14 @@ private:
|
|
|
154
155
|
return bridging::callFromJs<jsi::Value>(
|
|
155
156
|
rt, &T::clearAllBundles, jsInvoker_, instance_, std::move(a));
|
|
156
157
|
}
|
|
158
|
+
jsi::Value writeFile(jsi::Runtime &rt, jsi::String path, jsi::String base64Content, jsi::String encoding) override {
|
|
159
|
+
static_assert(
|
|
160
|
+
bridging::getParameterCount(&T::writeFile) == 4,
|
|
161
|
+
"Expected writeFile(...) to have 4 parameters");
|
|
162
|
+
|
|
163
|
+
return bridging::callFromJs<jsi::Value>(
|
|
164
|
+
rt, &T::writeFile, jsInvoker_, instance_, std::move(path), std::move(base64Content), std::move(encoding));
|
|
165
|
+
}
|
|
157
166
|
|
|
158
167
|
private:
|
|
159
168
|
friend class NativeOtaHotUpdateCxxSpec;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeOtaHotUpdate.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeOtaHotUpdate.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAkBpCC,gCAAmB,CAACC,YAAY,CAAO,cAAc,CAAC","ignoreList":[]}
|
|
@@ -16,6 +16,17 @@ let RNFS = {
|
|
|
16
16
|
try {
|
|
17
17
|
RNFS = require('react-native-fs');
|
|
18
18
|
} catch {}
|
|
19
|
+
|
|
20
|
+
// Try to load native OtaHotUpdate module for better performance
|
|
21
|
+
let OtaHotUpdateNative = null;
|
|
22
|
+
try {
|
|
23
|
+
const {
|
|
24
|
+
NativeModules
|
|
25
|
+
} = require('react-native');
|
|
26
|
+
OtaHotUpdateNative = NativeModules.OtaHotUpdate;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// Native module not available, will fallback to RNFS
|
|
29
|
+
}
|
|
19
30
|
function Err(name) {
|
|
20
31
|
return class extends Error {
|
|
21
32
|
code = name;
|
|
@@ -83,12 +94,27 @@ const writeFile = async (path, content, opts) => {
|
|
|
83
94
|
encoding = opts.encoding;
|
|
84
95
|
}
|
|
85
96
|
if (typeof content === 'string') {
|
|
97
|
+
// Text file - use RNFS (fast enough for text)
|
|
86
98
|
encoding = encoding || 'utf8';
|
|
99
|
+
await RNFS.writeFile(path, content, encoding);
|
|
87
100
|
} else {
|
|
101
|
+
// Binary file - try native module first for better performance
|
|
88
102
|
encoding = 'base64';
|
|
89
|
-
|
|
103
|
+
const base64Content = _buffer.Buffer.from(content).toString('base64');
|
|
104
|
+
if (OtaHotUpdateNative && OtaHotUpdateNative.writeFile) {
|
|
105
|
+
try {
|
|
106
|
+
// Use native write (runs on native thread, doesn't block JS thread)
|
|
107
|
+
await OtaHotUpdateNative.writeFile(path, base64Content, encoding);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// Fallback to RNFS if native fails
|
|
110
|
+
console.warn('OtaHotUpdate.writeFile failed, falling back to RNFS:', e);
|
|
111
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// No native module available - use RNFS
|
|
115
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
116
|
+
}
|
|
90
117
|
}
|
|
91
|
-
await RNFS.writeFile(path, content, encoding);
|
|
92
118
|
};
|
|
93
119
|
exports.writeFile = writeFile;
|
|
94
120
|
const stat = async path => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_buffer","require","RNFS","unlink","console","log","readdir","mkdir","readFile","writeFile","stat","Err","name","Error","code","constructor","args","message","ENOENT","ENOTDIR","path","err","exports","opts","encoding","result","Buffer","from","content","toString","r","isSymbolicLink","lstat","rmdir","readlink","symlink","chmod"],"sourceRoot":"../../../../src","sources":["gits/helper/fs.ts"],"mappings":";;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAEA,IAAIC,IAAI,GAAG;EACTC,MAAM,EAAEC,OAAO,CAACC,GAAG;EACnBC,OAAO,EAAEF,OAAO,CAACC,GAAG;EACpBE,KAAK,EAAEH,OAAO,CAACC,GAAG;EAClBG,QAAQ,EAAEJ,OAAO,CAACC,GAAG;EACrBI,SAAS,EAAEL,OAAO,CAACC,GAAG;EACtBK,IAAI,EAAEN,OAAO,CAACC;AAChB,CAAC;AACD,IAAI;EACFH,IAAI,GAAGD,OAAO,CAAC,iBAAiB,CAAC;AACnC,CAAC,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"names":["_buffer","require","RNFS","unlink","console","log","readdir","mkdir","readFile","writeFile","stat","OtaHotUpdateNative","NativeModules","OtaHotUpdate","e","Err","name","Error","code","constructor","args","message","ENOENT","ENOTDIR","path","err","exports","opts","encoding","result","Buffer","from","content","base64Content","toString","warn","r","isSymbolicLink","lstat","rmdir","readlink","symlink","chmod"],"sourceRoot":"../../../../src","sources":["gits/helper/fs.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeOtaHotUpdate.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeOtaHotUpdate.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAkBlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,cAAc,CAAC","ignoreList":[]}
|
|
@@ -12,6 +12,17 @@ let RNFS = {
|
|
|
12
12
|
try {
|
|
13
13
|
RNFS = require('react-native-fs');
|
|
14
14
|
} catch {}
|
|
15
|
+
|
|
16
|
+
// Try to load native OtaHotUpdate module for better performance
|
|
17
|
+
let OtaHotUpdateNative = null;
|
|
18
|
+
try {
|
|
19
|
+
const {
|
|
20
|
+
NativeModules
|
|
21
|
+
} = require('react-native');
|
|
22
|
+
OtaHotUpdateNative = NativeModules.OtaHotUpdate;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// Native module not available, will fallback to RNFS
|
|
25
|
+
}
|
|
15
26
|
function Err(name) {
|
|
16
27
|
return class extends Error {
|
|
17
28
|
code = name;
|
|
@@ -76,12 +87,27 @@ export const writeFile = async (path, content, opts) => {
|
|
|
76
87
|
encoding = opts.encoding;
|
|
77
88
|
}
|
|
78
89
|
if (typeof content === 'string') {
|
|
90
|
+
// Text file - use RNFS (fast enough for text)
|
|
79
91
|
encoding = encoding || 'utf8';
|
|
92
|
+
await RNFS.writeFile(path, content, encoding);
|
|
80
93
|
} else {
|
|
94
|
+
// Binary file - try native module first for better performance
|
|
81
95
|
encoding = 'base64';
|
|
82
|
-
|
|
96
|
+
const base64Content = Buffer.from(content).toString('base64');
|
|
97
|
+
if (OtaHotUpdateNative && OtaHotUpdateNative.writeFile) {
|
|
98
|
+
try {
|
|
99
|
+
// Use native write (runs on native thread, doesn't block JS thread)
|
|
100
|
+
await OtaHotUpdateNative.writeFile(path, base64Content, encoding);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Fallback to RNFS if native fails
|
|
103
|
+
console.warn('OtaHotUpdate.writeFile failed, falling back to RNFS:', e);
|
|
104
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// No native module available - use RNFS
|
|
108
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
109
|
+
}
|
|
83
110
|
}
|
|
84
|
-
await RNFS.writeFile(path, content, encoding);
|
|
85
111
|
};
|
|
86
112
|
export const stat = async path => {
|
|
87
113
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Buffer","RNFS","unlink","console","log","readdir","mkdir","readFile","writeFile","stat","require","Err","name","Error","code","constructor","args","message","ENOENT","ENOTDIR","path","err","opts","encoding","result","from","content","toString","r","isSymbolicLink","lstat","rmdir","readlink","symlink","chmod"],"sourceRoot":"../../../../src","sources":["gits/helper/fs.ts"],"mappings":";;AAAA,SAASA,MAAM,QAAQ,QAAQ;AAE/B,IAAIC,IAAI,GAAG;EACTC,MAAM,EAAEC,OAAO,CAACC,GAAG;EACnBC,OAAO,EAAEF,OAAO,CAACC,GAAG;EACpBE,KAAK,EAAEH,OAAO,CAACC,GAAG;EAClBG,QAAQ,EAAEJ,OAAO,CAACC,GAAG;EACrBI,SAAS,EAAEL,OAAO,CAACC,GAAG;EACtBK,IAAI,EAAEN,OAAO,CAACC;AAChB,CAAC;AACD,IAAI;EACFH,IAAI,GAAGS,OAAO,CAAC,iBAAiB,CAAC;AACnC,CAAC,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"names":["Buffer","RNFS","unlink","console","log","readdir","mkdir","readFile","writeFile","stat","require","OtaHotUpdateNative","NativeModules","OtaHotUpdate","e","Err","name","Error","code","constructor","args","message","ENOENT","ENOTDIR","path","err","opts","encoding","result","from","content","base64Content","toString","warn","r","isSymbolicLink","lstat","rmdir","readlink","symlink","chmod"],"sourceRoot":"../../../../src","sources":["gits/helper/fs.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,6 +12,7 @@ export interface Spec extends TurboModule {
|
|
|
12
12
|
getBundleList(a: number): Promise<string>;
|
|
13
13
|
deleteBundleById(id: string): Promise<boolean>;
|
|
14
14
|
clearAllBundles(a: number): Promise<boolean>;
|
|
15
|
+
writeFile(path: string, base64Content: string, encoding: string): Promise<boolean>;
|
|
15
16
|
}
|
|
16
17
|
declare const _default: Spec;
|
|
17
18
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeOtaHotUpdate.d.ts","sourceRoot":"","sources":["../../../../src/NativeOtaHotUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9H,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeOtaHotUpdate.d.ts","sourceRoot":"","sources":["../../../../src/NativeOtaHotUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9H,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;;AAED,wBAAsE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../../../../../src/gits/helper/fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../../../../../src/gits/helper/fs.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,OAAO,SAAgB,MAAM,kBAezC,CAAC;AAEF,eAAO,MAAM,KAAK,SAAgB,MAAM,kBAEvC,CAAC;AAEF,eAAO,MAAM,QAAQ,SACb,MAAM,SACL,MAAM,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,kDAsB1C,CAAC;AACF,eAAO,MAAM,SAAS,SACd,MAAM,WACH,MAAM,GAAG,UAAU,SACrB,MAAM,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,kBAiC1C,CAAC;AAEF,eAAO,MAAM,IAAI,SAAgB,MAAM,kBAiBtC,CAAC;AAGF,eAAO,MAAM,KAAK,SApBe,MAAM,kBAoBd,CAAC;AAE1B,eAAO,MAAM,MAAM,SAAgB,MAAM,kBAYxC,CAAC;AAGF,eAAO,MAAM,KAAK,SAfiB,MAAM,kBAed,CAAC;AAG5B,eAAO,MAAM,QAAQ,sBAEpB,CAAC;AACF,eAAO,MAAM,OAAO,sBAEnB,CAAC;AAIF,eAAO,MAAM,KAAK,sBAEjB,CAAC"}
|
|
@@ -12,6 +12,7 @@ export interface Spec extends TurboModule {
|
|
|
12
12
|
getBundleList(a: number): Promise<string>;
|
|
13
13
|
deleteBundleById(id: string): Promise<boolean>;
|
|
14
14
|
clearAllBundles(a: number): Promise<boolean>;
|
|
15
|
+
writeFile(path: string, base64Content: string, encoding: string): Promise<boolean>;
|
|
15
16
|
}
|
|
16
17
|
declare const _default: Spec;
|
|
17
18
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeOtaHotUpdate.d.ts","sourceRoot":"","sources":["../../../../src/NativeOtaHotUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9H,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeOtaHotUpdate.d.ts","sourceRoot":"","sources":["../../../../src/NativeOtaHotUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9H,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,IAAI,CAAC;IAChB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;;AAED,wBAAsE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../../../../../src/gits/helper/fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../../../../../src/gits/helper/fs.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,OAAO,SAAgB,MAAM,kBAezC,CAAC;AAEF,eAAO,MAAM,KAAK,SAAgB,MAAM,kBAEvC,CAAC;AAEF,eAAO,MAAM,QAAQ,SACb,MAAM,SACL,MAAM,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,kDAsB1C,CAAC;AACF,eAAO,MAAM,SAAS,SACd,MAAM,WACH,MAAM,GAAG,UAAU,SACrB,MAAM,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,kBAiC1C,CAAC;AAEF,eAAO,MAAM,IAAI,SAAgB,MAAM,kBAiBtC,CAAC;AAGF,eAAO,MAAM,KAAK,SApBe,MAAM,kBAoBd,CAAC;AAE1B,eAAO,MAAM,MAAM,SAAgB,MAAM,kBAYxC,CAAC;AAGF,eAAO,MAAM,KAAK,SAfiB,MAAM,kBAed,CAAC;AAG5B,eAAO,MAAM,QAAQ,sBAEpB,CAAC;AACF,eAAO,MAAM,OAAO,sBAEnB,CAAC;AAIF,eAAO,MAAM,KAAK,sBAEjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -14,6 +14,7 @@ export interface Spec extends TurboModule {
|
|
|
14
14
|
getBundleList(a: number): Promise<string>;
|
|
15
15
|
deleteBundleById(id: string): Promise<boolean>;
|
|
16
16
|
clearAllBundles(a: number): Promise<boolean>;
|
|
17
|
+
writeFile(path: string, base64Content: string, encoding: string): Promise<boolean>;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export default TurboModuleRegistry.getEnforcing<Spec>('OtaHotUpdate');
|
package/src/gits/helper/fs.ts
CHANGED
|
@@ -12,6 +12,15 @@ try {
|
|
|
12
12
|
RNFS = require('react-native-fs');
|
|
13
13
|
} catch {}
|
|
14
14
|
|
|
15
|
+
// Try to load native OtaHotUpdate module for better performance
|
|
16
|
+
let OtaHotUpdateNative: any = null;
|
|
17
|
+
try {
|
|
18
|
+
const { NativeModules } = require('react-native');
|
|
19
|
+
OtaHotUpdateNative = NativeModules.OtaHotUpdate;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Native module not available, will fallback to RNFS
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
function Err(name: string) {
|
|
16
25
|
return class extends Error {
|
|
17
26
|
public code = name;
|
|
@@ -91,13 +100,28 @@ export const writeFile = async (
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
if (typeof content === 'string') {
|
|
103
|
+
// Text file - use RNFS (fast enough for text)
|
|
94
104
|
encoding = encoding || 'utf8';
|
|
105
|
+
await RNFS.writeFile(path, content, encoding);
|
|
95
106
|
} else {
|
|
107
|
+
// Binary file - try native module first for better performance
|
|
96
108
|
encoding = 'base64';
|
|
97
|
-
|
|
109
|
+
const base64Content = Buffer.from(content).toString('base64');
|
|
110
|
+
|
|
111
|
+
if (OtaHotUpdateNative && OtaHotUpdateNative.writeFile) {
|
|
112
|
+
try {
|
|
113
|
+
// Use native write (runs on native thread, doesn't block JS thread)
|
|
114
|
+
await OtaHotUpdateNative.writeFile(path, base64Content, encoding);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// Fallback to RNFS if native fails
|
|
117
|
+
console.warn('OtaHotUpdate.writeFile failed, falling back to RNFS:', e);
|
|
118
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
// No native module available - use RNFS
|
|
122
|
+
await RNFS.writeFile(path, base64Content, encoding);
|
|
123
|
+
}
|
|
98
124
|
}
|
|
99
|
-
|
|
100
|
-
await RNFS.writeFile(path, content as string, encoding);
|
|
101
125
|
};
|
|
102
126
|
|
|
103
127
|
export const stat = async (path: string) => {
|