react-native-ota-hot-update 2.4.0-rc.2 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -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/OtaHotUpdateModule.kt +66 -16
- package/android/src/oldarch/OtaHotUpdateSpec.kt +1 -0
- package/ios/OtaHotUpdate.mm +85 -42
- 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
|
@@ -8,6 +8,7 @@ This React Native module allows you to manage hot updates (in-app update) with m
|
|
|
8
8
|
- Host the bundle on your own server or in a Git repository.
|
|
9
9
|
- Rollback function
|
|
10
10
|
- Crash Handling: Handles crash exceptions when an incorrect bundle is updated and will automatically roll back to the previous bundle version.
|
|
11
|
+
- bundle management features
|
|
11
12
|
|
|
12
13
|
1. **Demo via server**
|
|
13
14
|
|
|
@@ -21,13 +22,35 @@ iOS GIF | Android GIF
|
|
|
21
22
|
:-------------------------:|:-------------------------:
|
|
22
23
|
<img src="./iosgit.gif" title="iOS GIF" width="250"> | <img src="./androidgithotupdate.gif" title="Android GIF" width="250">
|
|
23
24
|
|
|
25
|
+
3. **Demo bundle management**
|
|
26
|
+
|
|
27
|
+
[<img src="https://raw.githubusercontent.com/vantuan88291/react-native-ota-hot-update/e4d4bd928dc096bbb4fc329ce89e80d288e44ff1/ioshotupdate-new.gif" title="iOS Bundle Management Demo" width="300">](https://raw.githubusercontent.com/vantuan88291/react-native-ota-hot-update/e4d4bd928dc096bbb4fc329ce89e80d288e44ff1/ioshotupdate-new.gif)
|
|
28
|
+
|
|
24
29
|
[](https://img.shields.io/npm/dw/react-native-ota-hot-update)
|
|
25
30
|
[](https://img.shields.io/npm/v/react-native-ota-hot-update?color=red)
|
|
26
31
|
|
|
32
|
+
|
|
27
33
|
## New architecture supported
|
|
28
34
|
|
|
29
35
|
New architecture backward compatibility supported from version 2, it also supported old architecture, for source code of version 1.x.x please refer to branch `oldArch`, you might need install version 1.x.x if you are using react native < 0.70
|
|
30
36
|
|
|
37
|
+
### 🚀 Preview Release: Enhanced Bundle Management (v2.4.0-rc.1)
|
|
38
|
+
|
|
39
|
+
We're excited to introduce a preview release with enhanced bundle management features! Version **2.4.0-rc.1** includes:
|
|
40
|
+
|
|
41
|
+
- **Configurable bundle history**: Manage multiple bundle versions (configurable via `maxBundleVersions`)
|
|
42
|
+
- **New APIs**: `getBundleList()`, `deleteBundleById()`, `clearAllBundles()`
|
|
43
|
+
- **Bundle identification**: Descriptive folder names with version and timestamp
|
|
44
|
+
- **Automatic cleanup**: Older bundles are automatically removed when exceeding the limit
|
|
45
|
+
|
|
46
|
+
This is an experimental release for testing. Please try it out and share your feedback! All discussions and issues related to this feature can be found in [PR #132](https://github.com/vantuan88291/react-native-ota-hot-update/pull/132).
|
|
47
|
+
- Branch: `task/manage_bundle`
|
|
48
|
+
|
|
49
|
+
To install the preview version:
|
|
50
|
+
```bash
|
|
51
|
+
yarn add react-native-ota-hot-update@2.4.0-rc.1
|
|
52
|
+
```
|
|
53
|
+
|
|
31
54
|
## Installation
|
|
32
55
|
|
|
33
56
|
if you don't want to manage the download progress, need to install blob util together:
|
|
@@ -228,3 +251,51 @@ Using Strapi, you can build a tailored admin panel to manage React Native hot up
|
|
|
228
251
|
|
|
229
252
|
### Sponsor this project
|
|
230
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;
|
|
@@ -22,7 +22,12 @@ import kotlinx.coroutines.SupervisorJob
|
|
|
22
22
|
import kotlinx.coroutines.cancel
|
|
23
23
|
import kotlinx.coroutines.launch
|
|
24
24
|
import kotlinx.coroutines.withContext
|
|
25
|
+
import android.util.Base64
|
|
26
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
27
|
+
import java.util.concurrent.Executors
|
|
25
28
|
import java.io.File
|
|
29
|
+
import java.io.FileOutputStream
|
|
30
|
+
import java.io.IOException
|
|
26
31
|
import org.json.JSONArray
|
|
27
32
|
import org.json.JSONObject
|
|
28
33
|
|
|
@@ -37,6 +42,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
37
42
|
OtaHotUpdateSpec(context) {
|
|
38
43
|
private val utils: Utils = Utils(context)
|
|
39
44
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
45
|
+
private val fileWriterExecutor = Executors.newSingleThreadExecutor()
|
|
40
46
|
|
|
41
47
|
override fun getName(): String {
|
|
42
48
|
return NAME
|
|
@@ -45,12 +51,13 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
45
51
|
override fun invalidate() {
|
|
46
52
|
super.invalidate()
|
|
47
53
|
scope.cancel()
|
|
54
|
+
fileWriterExecutor.shutdown()
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
private fun loadBundleHistory(): List<BundleVersion> {
|
|
51
58
|
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
52
59
|
val historyJson = sharedPrefs.getString(BUNDLE_HISTORY)
|
|
53
|
-
|
|
60
|
+
|
|
54
61
|
// If history exists, load it
|
|
55
62
|
if (!historyJson.isNullOrEmpty()) {
|
|
56
63
|
return try {
|
|
@@ -68,20 +75,20 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
68
75
|
emptyList()
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
|
|
78
|
+
|
|
72
79
|
// Migration: If history is empty but PATH exists, migrate from old system
|
|
73
80
|
val currentPath = sharedPrefs.getString(PATH)
|
|
74
81
|
val currentVersion = sharedPrefs.getString(VERSION)
|
|
75
82
|
val previousPath = sharedPrefs.getString(PREVIOUS_PATH)
|
|
76
83
|
val previousVersion = sharedPrefs.getString(PREVIOUS_VERSION)
|
|
77
|
-
|
|
84
|
+
|
|
78
85
|
if (currentPath.isNullOrEmpty()) {
|
|
79
86
|
return emptyList()
|
|
80
87
|
}
|
|
81
|
-
|
|
88
|
+
|
|
82
89
|
// Migrate current bundle
|
|
83
90
|
val migratedHistory = mutableListOf<BundleVersion>()
|
|
84
|
-
|
|
91
|
+
|
|
85
92
|
// Add current bundle if has version
|
|
86
93
|
if (!currentVersion.isNullOrEmpty()) {
|
|
87
94
|
try {
|
|
@@ -101,7 +108,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
101
108
|
// Version is not a number, skip
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
|
-
|
|
111
|
+
|
|
105
112
|
// Add previous bundle if exists
|
|
106
113
|
if (!previousPath.isNullOrEmpty() && !previousVersion.isNullOrEmpty()) {
|
|
107
114
|
try {
|
|
@@ -121,12 +128,12 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
121
128
|
// Version is not a number, skip
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
|
-
|
|
131
|
+
|
|
125
132
|
// Save migrated history if any
|
|
126
133
|
if (migratedHistory.isNotEmpty()) {
|
|
127
134
|
saveBundleHistory(migratedHistory.sortedByDescending { it.version })
|
|
128
135
|
}
|
|
129
|
-
|
|
136
|
+
|
|
130
137
|
return migratedHistory.sortedByDescending { it.version }
|
|
131
138
|
}
|
|
132
139
|
|
|
@@ -204,7 +211,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
204
211
|
utils.deleteOldBundleIfneeded(null)
|
|
205
212
|
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
206
213
|
val oldPath = sharedPrefs.getString(PATH)
|
|
207
|
-
|
|
214
|
+
|
|
208
215
|
// If version is provided, save to history system
|
|
209
216
|
if (version != null) {
|
|
210
217
|
val maxVersionsToKeep = maxVersions ?: Common.DEFAULT_MAX_BUNDLE_VERSIONS
|
|
@@ -213,7 +220,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
213
220
|
// No version (e.g., Git update) - just set path, no history
|
|
214
221
|
sharedPrefs.putString(PATH, fileUnzip)
|
|
215
222
|
}
|
|
216
|
-
|
|
223
|
+
|
|
217
224
|
sharedPrefs.putString(
|
|
218
225
|
CURRENT_VERSION_CODE,
|
|
219
226
|
reactApplicationContext.getVersionCode()
|
|
@@ -254,21 +261,21 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
254
261
|
try {
|
|
255
262
|
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
256
263
|
val currentPath = sharedPrefs.getString(PATH)
|
|
257
|
-
|
|
264
|
+
|
|
258
265
|
// Delete current bundle from file system
|
|
259
266
|
val isDeleted = utils.deleteOldBundleIfneeded(PATH)
|
|
260
|
-
|
|
267
|
+
|
|
261
268
|
// Remove current bundle from history if exists
|
|
262
269
|
if (currentPath != null && currentPath.isNotEmpty()) {
|
|
263
270
|
val history = loadBundleHistory()
|
|
264
271
|
val updatedHistory = history.filter { it.path != currentPath }
|
|
265
272
|
saveBundleHistory(updatedHistory)
|
|
266
273
|
}
|
|
267
|
-
|
|
274
|
+
|
|
268
275
|
// Clear paths and version (no longer clear PREVIOUS_PATH, use history instead)
|
|
269
276
|
sharedPrefs.putString(PATH, "")
|
|
270
277
|
sharedPrefs.putString(VERSION, "0")
|
|
271
|
-
|
|
278
|
+
|
|
272
279
|
withContext(Dispatchers.Main) {
|
|
273
280
|
promise.resolve(isDeleted)
|
|
274
281
|
}
|
|
@@ -282,7 +289,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
282
289
|
|
|
283
290
|
@ReactMethod
|
|
284
291
|
override fun restart() {
|
|
285
|
-
val context: Context? = currentActivity
|
|
292
|
+
val context: Context? = reactApplicationContext.currentActivity
|
|
286
293
|
ProcessPhoenix.triggerRebirth(context)
|
|
287
294
|
}
|
|
288
295
|
|
|
@@ -364,7 +371,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
364
371
|
if (isDeleted) {
|
|
365
372
|
sharedPrefs.putString(PATH, previousBundle.path)
|
|
366
373
|
sharedPrefs.putString(VERSION, previousBundle.version.toString())
|
|
367
|
-
|
|
374
|
+
|
|
368
375
|
withContext(Dispatchers.Main) {
|
|
369
376
|
promise.resolve(true)
|
|
370
377
|
}
|
|
@@ -509,6 +516,49 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
509
516
|
}
|
|
510
517
|
}
|
|
511
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Write file with base64 content on native thread
|
|
521
|
+
* This runs on a background thread, not blocking JS thread
|
|
522
|
+
*/
|
|
523
|
+
@ReactMethod
|
|
524
|
+
override fun writeFile(path: String?, base64Content: String?, encoding: String?, promise: Promise) {
|
|
525
|
+
if (path == null || base64Content == null) {
|
|
526
|
+
promise.reject("INVALID_ARG", "Path and base64Content are required", null)
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
fileWriterExecutor.execute {
|
|
531
|
+
try {
|
|
532
|
+
// Decode base64 to bytes
|
|
533
|
+
val bytes = Base64.decode(base64Content, Base64.DEFAULT)
|
|
534
|
+
|
|
535
|
+
// Ensure parent directory exists
|
|
536
|
+
val file = File(path)
|
|
537
|
+
val parentDir = file.parentFile
|
|
538
|
+
if (parentDir != null && !parentDir.exists()) {
|
|
539
|
+
parentDir.mkdirs()
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Write file on background thread
|
|
543
|
+
FileOutputStream(file).use { fos ->
|
|
544
|
+
fos.write(bytes)
|
|
545
|
+
fos.flush()
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Resolve on UI thread (React Native requirement)
|
|
549
|
+
UiThreadUtil.runOnUiThread {
|
|
550
|
+
promise.resolve(true)
|
|
551
|
+
}
|
|
552
|
+
} catch (e: IOException) {
|
|
553
|
+
UiThreadUtil.runOnUiThread {
|
|
554
|
+
promise.reject("WRITE_ERROR", "Failed to write file: ${e.message}", e)
|
|
555
|
+
}
|
|
556
|
+
} catch (e: Exception) {
|
|
557
|
+
UiThreadUtil.runOnUiThread {
|
|
558
|
+
promise.reject("WRITE_ERROR", "Unexpected error: ${e.message}", e)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
512
562
|
}
|
|
513
563
|
|
|
514
564
|
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];
|
|
@@ -237,7 +240,7 @@ void OTAExceptionHandler(NSException *exception) {
|
|
|
237
240
|
[dateFormatter setDateFormat:@"yyyy_MM_dd_HH_mm"];
|
|
238
241
|
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
239
242
|
NSString *timestamp = [dateFormatter stringFromDate:[NSDate date]];
|
|
240
|
-
NSString *folderName = version != nil
|
|
243
|
+
NSString *folderName = version != nil
|
|
241
244
|
? [NSString stringWithFormat:@"output_v%@_%@", version, timestamp]
|
|
242
245
|
: [NSString stringWithFormat:@"output_%@", timestamp];
|
|
243
246
|
NSString *newFolderPath = [directoryPath stringByAppendingPathComponent:folderName];
|
|
@@ -303,7 +306,7 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
303
306
|
if (extractedFilePath) {
|
|
304
307
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
305
308
|
NSString *oldPath = [defaults stringForKey:@"PATH"];
|
|
306
|
-
|
|
309
|
+
|
|
307
310
|
// If version is provided, save to history system
|
|
308
311
|
if (version != nil) {
|
|
309
312
|
// Default maxVersions to 2 if not provided (backward compatible)
|
|
@@ -314,7 +317,7 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
314
317
|
// No version (e.g., Git update) - just set path, no history
|
|
315
318
|
[defaults setObject:extractedFilePath forKey:@"PATH"];
|
|
316
319
|
}
|
|
317
|
-
|
|
320
|
+
|
|
318
321
|
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
|
|
319
322
|
[defaults synchronize];
|
|
320
323
|
isBeginning = YES;
|
|
@@ -331,10 +334,10 @@ RCT_EXPORT_METHOD(deleteBundle:(double)i
|
|
|
331
334
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
332
335
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
333
336
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
334
|
-
|
|
337
|
+
|
|
335
338
|
// Delete current bundle from file system
|
|
336
339
|
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
337
|
-
|
|
340
|
+
|
|
338
341
|
// Remove current bundle from history if exists
|
|
339
342
|
if (currentPath && currentPath.length > 0) {
|
|
340
343
|
NSArray *history = [self loadBundleHistory];
|
|
@@ -346,12 +349,12 @@ RCT_EXPORT_METHOD(deleteBundle:(double)i
|
|
|
346
349
|
}
|
|
347
350
|
[self saveBundleHistory:updatedHistory];
|
|
348
351
|
}
|
|
349
|
-
|
|
352
|
+
|
|
350
353
|
// Clear paths and version
|
|
351
354
|
[defaults removeObjectForKey:@"PATH"];
|
|
352
355
|
[defaults setObject:@"0" forKey:@"VERSION"];
|
|
353
356
|
[defaults synchronize];
|
|
354
|
-
|
|
357
|
+
|
|
355
358
|
resolve(@(isDeleted));
|
|
356
359
|
}
|
|
357
360
|
// Expose deleteBundle method to JavaScript
|
|
@@ -360,7 +363,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
360
363
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
361
364
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
362
365
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
363
|
-
|
|
366
|
+
|
|
364
367
|
// Use history to find previous version
|
|
365
368
|
NSArray *history = [self loadBundleHistory];
|
|
366
369
|
if (history.count > 0 && currentPath && currentPath.length > 0) {
|
|
@@ -372,7 +375,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
372
375
|
break;
|
|
373
376
|
}
|
|
374
377
|
}
|
|
375
|
-
|
|
378
|
+
|
|
376
379
|
if (currentBundle) {
|
|
377
380
|
// Find previous version (older than current, max version)
|
|
378
381
|
NSDictionary *previousBundle = nil;
|
|
@@ -385,7 +388,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
385
388
|
}
|
|
386
389
|
}
|
|
387
390
|
}
|
|
388
|
-
|
|
391
|
+
|
|
389
392
|
if (previousBundle && [OtaHotUpdate isFilePathValid:previousBundle[@"path"]]) {
|
|
390
393
|
// Rollback to previous bundle from history
|
|
391
394
|
BOOL isDeleted = [OtaHotUpdate removeBundleIfNeeded:@"PATH"];
|
|
@@ -399,7 +402,7 @@ RCT_EXPORT_METHOD(rollbackToPreviousBundle:(double)i
|
|
|
399
402
|
}
|
|
400
403
|
}
|
|
401
404
|
}
|
|
402
|
-
|
|
405
|
+
|
|
403
406
|
resolve(@(NO));
|
|
404
407
|
}
|
|
405
408
|
|
|
@@ -478,7 +481,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
478
481
|
- (NSArray *)loadBundleHistory {
|
|
479
482
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
480
483
|
NSString *historyJson = [defaults stringForKey:@"BUNDLE_HISTORY"];
|
|
481
|
-
|
|
484
|
+
|
|
482
485
|
// If history exists, load it
|
|
483
486
|
if (historyJson && historyJson.length > 0) {
|
|
484
487
|
NSData *data = [historyJson dataUsingEncoding:NSUTF8StringEncoding];
|
|
@@ -488,20 +491,20 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
488
491
|
return history;
|
|
489
492
|
}
|
|
490
493
|
}
|
|
491
|
-
|
|
494
|
+
|
|
492
495
|
// Migration: If history is empty but PATH exists, migrate from old system
|
|
493
496
|
NSString *currentPath = [defaults stringForKey:@"PATH"];
|
|
494
497
|
NSString *currentVersion = [defaults stringForKey:@"VERSION"];
|
|
495
498
|
NSString *previousPath = [defaults stringForKey:@"OLD_PATH"];
|
|
496
499
|
NSString *previousVersion = [defaults stringForKey:@"PREVIOUS_VERSION"];
|
|
497
|
-
|
|
500
|
+
|
|
498
501
|
if (!currentPath || currentPath.length == 0) {
|
|
499
502
|
return @[];
|
|
500
503
|
}
|
|
501
|
-
|
|
504
|
+
|
|
502
505
|
// Migrate current bundle
|
|
503
506
|
NSMutableArray *migratedHistory = [NSMutableArray array];
|
|
504
|
-
|
|
507
|
+
|
|
505
508
|
// Add current bundle if has version
|
|
506
509
|
if (currentVersion && currentVersion.length > 0) {
|
|
507
510
|
NSInteger version = [currentVersion integerValue];
|
|
@@ -510,7 +513,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
510
513
|
NSDictionary *attributes = [fileManager attributesOfItemAtPath:currentPath error:nil];
|
|
511
514
|
NSDate *modificationDate = attributes[NSFileModificationDate];
|
|
512
515
|
long long timestamp = modificationDate ? (long long)([modificationDate timeIntervalSince1970] * 1000) : (long long)([[NSDate date] timeIntervalSince1970] * 1000);
|
|
513
|
-
|
|
516
|
+
|
|
514
517
|
NSMutableDictionary *bundle = [NSMutableDictionary dictionary];
|
|
515
518
|
bundle[@"version"] = @(version);
|
|
516
519
|
bundle[@"path"] = currentPath;
|
|
@@ -519,7 +522,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
519
522
|
[migratedHistory addObject:bundle];
|
|
520
523
|
}
|
|
521
524
|
}
|
|
522
|
-
|
|
525
|
+
|
|
523
526
|
// Add previous bundle if exists
|
|
524
527
|
if (previousPath && previousPath.length > 0 && previousVersion && previousVersion.length > 0) {
|
|
525
528
|
NSInteger version = [previousVersion integerValue];
|
|
@@ -528,7 +531,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
528
531
|
NSDictionary *attributes = [fileManager attributesOfItemAtPath:previousPath error:nil];
|
|
529
532
|
NSDate *modificationDate = attributes[NSFileModificationDate];
|
|
530
533
|
long long timestamp = modificationDate ? (long long)([modificationDate timeIntervalSince1970] * 1000) : (long long)([[NSDate date] timeIntervalSince1970] * 1000);
|
|
531
|
-
|
|
534
|
+
|
|
532
535
|
NSMutableDictionary *bundle = [NSMutableDictionary dictionary];
|
|
533
536
|
bundle[@"version"] = @(version);
|
|
534
537
|
bundle[@"path"] = previousPath;
|
|
@@ -537,7 +540,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
537
540
|
[migratedHistory addObject:bundle];
|
|
538
541
|
}
|
|
539
542
|
}
|
|
540
|
-
|
|
543
|
+
|
|
541
544
|
// Save migrated history if any
|
|
542
545
|
if (migratedHistory.count > 0) {
|
|
543
546
|
// Sort by version descending
|
|
@@ -551,7 +554,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
551
554
|
[self saveBundleHistory:sortedHistory];
|
|
552
555
|
return sortedHistory;
|
|
553
556
|
}
|
|
554
|
-
|
|
557
|
+
|
|
555
558
|
return @[];
|
|
556
559
|
}
|
|
557
560
|
|
|
@@ -573,7 +576,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
573
576
|
- (void)saveBundleVersion:(NSString *)path version:(NSInteger)version maxVersions:(NSInteger)maxVersions metadata:(NSString *)metadata {
|
|
574
577
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
575
578
|
NSArray *history = [self loadBundleHistory];
|
|
576
|
-
|
|
579
|
+
|
|
577
580
|
// Create new bundle entry
|
|
578
581
|
NSMutableDictionary *newBundle = [NSMutableDictionary dictionary];
|
|
579
582
|
newBundle[@"version"] = @(version);
|
|
@@ -582,11 +585,11 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
582
585
|
if (metadata) {
|
|
583
586
|
newBundle[@"metadata"] = metadata;
|
|
584
587
|
}
|
|
585
|
-
|
|
588
|
+
|
|
586
589
|
// Combine with existing history
|
|
587
590
|
NSMutableArray *updatedHistory = [NSMutableArray arrayWithObject:newBundle];
|
|
588
591
|
[updatedHistory addObjectsFromArray:history];
|
|
589
|
-
|
|
592
|
+
|
|
590
593
|
// Sort by version descending and remove duplicates
|
|
591
594
|
[updatedHistory sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
|
|
592
595
|
NSInteger v1 = [obj1[@"version"] integerValue];
|
|
@@ -595,7 +598,7 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
595
598
|
if (v1 < v2) return NSOrderedDescending;
|
|
596
599
|
return NSOrderedSame;
|
|
597
600
|
}];
|
|
598
|
-
|
|
601
|
+
|
|
599
602
|
// Remove duplicates by version
|
|
600
603
|
NSMutableArray *uniqueHistory = [NSMutableArray array];
|
|
601
604
|
NSMutableSet *seenVersions = [NSMutableSet set];
|
|
@@ -606,25 +609,25 @@ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
|
606
609
|
[uniqueHistory addObject:bundle];
|
|
607
610
|
}
|
|
608
611
|
}
|
|
609
|
-
|
|
612
|
+
|
|
610
613
|
// Keep only maxVersions most recent
|
|
611
614
|
NSArray *finalHistory = [uniqueHistory subarrayWithRange:NSMakeRange(0, MIN(maxVersions, uniqueHistory.count))];
|
|
612
|
-
|
|
615
|
+
|
|
613
616
|
// Delete old versions beyond limit
|
|
614
617
|
NSMutableSet *versionsToKeep = [NSMutableSet set];
|
|
615
618
|
for (NSDictionary *bundle in finalHistory) {
|
|
616
619
|
[versionsToKeep addObject:bundle[@"version"]];
|
|
617
620
|
}
|
|
618
|
-
|
|
621
|
+
|
|
619
622
|
for (NSDictionary *bundle in uniqueHistory) {
|
|
620
623
|
if (![versionsToKeep containsObject:bundle[@"version"]]) {
|
|
621
624
|
[OtaHotUpdate deleteBundleAtPath:bundle[@"path"]];
|
|
622
625
|
}
|
|
623
626
|
}
|
|
624
|
-
|
|
627
|
+
|
|
625
628
|
// Save updated history
|
|
626
629
|
[self saveBundleHistory:finalHistory];
|
|
627
|
-
|
|
630
|
+
|
|
628
631
|
// Set current path and version
|
|
629
632
|
[defaults setObject:path forKey:@"PATH"];
|
|
630
633
|
[defaults setObject:[NSString stringWithFormat:@"%ld", (long)version] forKey:@"VERSION"];
|
|
@@ -649,7 +652,7 @@ RCT_EXPORT_METHOD(getBundleList:(double)a
|
|
|
649
652
|
NSArray *history = [self loadBundleHistory];
|
|
650
653
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
651
654
|
NSString *activePath = [defaults stringForKey:@"PATH"];
|
|
652
|
-
|
|
655
|
+
|
|
653
656
|
NSMutableArray *bundleList = [NSMutableArray array];
|
|
654
657
|
for (NSDictionary *bundle in history) {
|
|
655
658
|
NSString *path = bundle[@"path"];
|
|
@@ -669,7 +672,7 @@ RCT_EXPORT_METHOD(getBundleList:(double)a
|
|
|
669
672
|
}
|
|
670
673
|
[bundleList addObject:bundleInfo];
|
|
671
674
|
}
|
|
672
|
-
|
|
675
|
+
|
|
673
676
|
NSError *error = nil;
|
|
674
677
|
NSData *data = [NSJSONSerialization dataWithJSONObject:bundleList options:0 error:&error];
|
|
675
678
|
if (!error && data) {
|
|
@@ -686,7 +689,7 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
686
689
|
NSArray *history = [self loadBundleHistory];
|
|
687
690
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
688
691
|
NSString *activePath = [defaults stringForKey:@"PATH"];
|
|
689
|
-
|
|
692
|
+
|
|
690
693
|
NSDictionary *bundleToDelete = nil;
|
|
691
694
|
for (NSDictionary *bundle in history) {
|
|
692
695
|
NSString *folderName = [self extractFolderName:bundle[@"path"]];
|
|
@@ -695,12 +698,12 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
695
698
|
break;
|
|
696
699
|
}
|
|
697
700
|
}
|
|
698
|
-
|
|
701
|
+
|
|
699
702
|
if (!bundleToDelete) {
|
|
700
703
|
resolve(@(NO));
|
|
701
704
|
return;
|
|
702
705
|
}
|
|
703
|
-
|
|
706
|
+
|
|
704
707
|
// If deleting active bundle, rollback to oldest remaining bundle or clear
|
|
705
708
|
if ([bundleToDelete[@"path"] isEqualToString:activePath]) {
|
|
706
709
|
NSMutableArray *remainingBundles = [NSMutableArray array];
|
|
@@ -725,10 +728,10 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
725
728
|
}
|
|
726
729
|
[defaults synchronize];
|
|
727
730
|
}
|
|
728
|
-
|
|
731
|
+
|
|
729
732
|
// Delete bundle folder
|
|
730
733
|
BOOL isDeleted = [OtaHotUpdate deleteBundleAtPath:bundleToDelete[@"path"]];
|
|
731
|
-
|
|
734
|
+
|
|
732
735
|
// Remove from history
|
|
733
736
|
NSMutableArray *updatedHistory = [NSMutableArray array];
|
|
734
737
|
for (NSDictionary *bundle in history) {
|
|
@@ -737,7 +740,7 @@ RCT_EXPORT_METHOD(deleteBundleById:(NSString *)id
|
|
|
737
740
|
}
|
|
738
741
|
}
|
|
739
742
|
[self saveBundleHistory:updatedHistory];
|
|
740
|
-
|
|
743
|
+
|
|
741
744
|
resolve(@(isDeleted));
|
|
742
745
|
}
|
|
743
746
|
|
|
@@ -746,23 +749,63 @@ RCT_EXPORT_METHOD(clearAllBundles:(double)a
|
|
|
746
749
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
747
750
|
NSArray *history = [self loadBundleHistory];
|
|
748
751
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
749
|
-
|
|
752
|
+
|
|
750
753
|
// Delete all bundle folders
|
|
751
754
|
for (NSDictionary *bundle in history) {
|
|
752
755
|
[OtaHotUpdate deleteBundleAtPath:bundle[@"path"]];
|
|
753
756
|
}
|
|
754
|
-
|
|
757
|
+
|
|
755
758
|
// Clear history
|
|
756
759
|
[self saveBundleHistory:@[]];
|
|
757
|
-
|
|
760
|
+
|
|
758
761
|
// Clear current path and version
|
|
759
762
|
[defaults removeObjectForKey:@"PATH"];
|
|
760
763
|
[defaults removeObjectForKey:@"VERSION"];
|
|
761
764
|
[defaults synchronize];
|
|
762
|
-
|
|
765
|
+
|
|
763
766
|
resolve(@(YES));
|
|
764
767
|
}
|
|
765
768
|
|
|
769
|
+
RCT_EXPORT_METHOD(writeFile:(NSString *)path
|
|
770
|
+
base64Content:(NSString *)base64Content
|
|
771
|
+
encoding:(NSString *)encoding
|
|
772
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
773
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
774
|
+
// Run on background queue to avoid blocking JS thread
|
|
775
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
776
|
+
@try {
|
|
777
|
+
// Decode base64 to NSData
|
|
778
|
+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Content options:0];
|
|
779
|
+
if (!data) {
|
|
780
|
+
reject(@"DECODE_ERROR", @"Failed to decode base64 content", nil);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Ensure parent directory exists
|
|
785
|
+
NSString *parentDir = [path stringByDeletingLastPathComponent];
|
|
786
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
787
|
+
if (![fileManager fileExistsAtPath:parentDir]) {
|
|
788
|
+
NSError *error;
|
|
789
|
+
if (![fileManager createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
790
|
+
reject(@"CREATE_DIR_ERROR", [NSString stringWithFormat:@"Failed to create directory: %@", error.localizedDescription], error);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Write file on background thread
|
|
796
|
+
NSError *error;
|
|
797
|
+
if (![data writeToFile:path options:NSDataWritingAtomic error:&error]) {
|
|
798
|
+
reject(@"WRITE_ERROR", [NSString stringWithFormat:@"Failed to write file: %@", error.localizedDescription], error);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
resolve(@(YES));
|
|
803
|
+
} @catch (NSException *exception) {
|
|
804
|
+
reject(@"WRITE_ERROR", [NSString stringWithFormat:@"Unexpected error: %@", exception.reason], nil);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
766
809
|
// Don't compile this code when we build for the old architecture.
|
|
767
810
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
768
811
|
- (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) => {
|