@onekeyfe/react-native-background-thread 1.1.50 → 1.1.52
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.
|
@@ -108,6 +108,88 @@ class BackgroundThreadManager private constructor() {
|
|
|
108
108
|
return null
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Creates a JSBundleLoader that loads two bundles sequentially from Android assets:
|
|
113
|
+
* first the common bundle (polyfills + shared modules), then the
|
|
114
|
+
* entry-specific bundle (entry-only modules + require(entryId)).
|
|
115
|
+
*/
|
|
116
|
+
private fun createSequentialAssetBundleLoader(
|
|
117
|
+
appContext: android.content.Context,
|
|
118
|
+
commonAssetName: String,
|
|
119
|
+
entryAssetName: String
|
|
120
|
+
): JSBundleLoader {
|
|
121
|
+
return object : JSBundleLoader() {
|
|
122
|
+
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
123
|
+
// Step 1: Load common bundle (polyfills + shared modules)
|
|
124
|
+
delegate.loadScriptFromAssets(appContext.assets, "assets://$commonAssetName", false)
|
|
125
|
+
BTLogger.info("Common bundle loaded from assets: $commonAssetName")
|
|
126
|
+
|
|
127
|
+
// Step 2: Load entry-specific bundle
|
|
128
|
+
delegate.loadScriptFromAssets(appContext.assets, "assets://$entryAssetName", false)
|
|
129
|
+
BTLogger.info("Entry bundle loaded from assets: $entryAssetName")
|
|
130
|
+
|
|
131
|
+
return "assets://$entryAssetName"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a JSBundleLoader that loads two bundles sequentially from local files:
|
|
138
|
+
* first the common bundle, then the entry-specific bundle.
|
|
139
|
+
*/
|
|
140
|
+
private fun createSequentialFileBundleLoader(
|
|
141
|
+
commonPath: String,
|
|
142
|
+
entryPath: String,
|
|
143
|
+
entrySourceURL: String
|
|
144
|
+
): JSBundleLoader {
|
|
145
|
+
return object : JSBundleLoader() {
|
|
146
|
+
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
147
|
+
// Step 1: Load common bundle (polyfills + shared modules)
|
|
148
|
+
val commonFile = File(commonPath)
|
|
149
|
+
if (!commonFile.exists()) {
|
|
150
|
+
BTLogger.error("Common bundle file does not exist: $commonPath")
|
|
151
|
+
throw RuntimeException("Common bundle file does not exist: $commonPath")
|
|
152
|
+
}
|
|
153
|
+
delegate.loadScriptFromFile(commonFile.absolutePath, "common.bundle", false)
|
|
154
|
+
BTLogger.info("Common bundle loaded from file: $commonPath")
|
|
155
|
+
|
|
156
|
+
// Step 2: Load entry-specific bundle
|
|
157
|
+
val entryFile = File(entryPath)
|
|
158
|
+
if (!entryFile.exists()) {
|
|
159
|
+
BTLogger.error("Entry bundle file does not exist: $entryPath")
|
|
160
|
+
throw RuntimeException("Entry bundle file does not exist: $entryPath")
|
|
161
|
+
}
|
|
162
|
+
delegate.loadScriptFromFile(entryFile.absolutePath, entrySourceURL, false)
|
|
163
|
+
BTLogger.info("Entry bundle loaded from file: $entryPath")
|
|
164
|
+
|
|
165
|
+
return entrySourceURL
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if common.bundle exists in the Android assets directory.
|
|
172
|
+
*/
|
|
173
|
+
private fun hasCommonBundleInAssets(appContext: android.content.Context): Boolean {
|
|
174
|
+
return try {
|
|
175
|
+
appContext.assets.open("common.bundle").close()
|
|
176
|
+
true
|
|
177
|
+
} catch (e: Exception) {
|
|
178
|
+
false
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the common bundle path for OTA (file-based) loading.
|
|
184
|
+
* Looks for common.bundle in the same directory as the entry bundle.
|
|
185
|
+
*/
|
|
186
|
+
private fun resolveCommonBundlePath(entryBundlePath: String): String? {
|
|
187
|
+
val entryFile = File(entryBundlePath)
|
|
188
|
+
val parentDir = entryFile.parentFile ?: return null
|
|
189
|
+
val commonFile = File(parentDir, "common.bundle")
|
|
190
|
+
return if (commonFile.exists()) commonFile.absolutePath else null
|
|
191
|
+
}
|
|
192
|
+
|
|
111
193
|
private fun createDownloadedBundleLoader(appContext: android.content.Context, entryURL: String): JSBundleLoader {
|
|
112
194
|
return object : JSBundleLoader() {
|
|
113
195
|
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
@@ -163,10 +245,43 @@ class BackgroundThreadManager private constructor() {
|
|
|
163
245
|
val localBundlePath = resolveLocalBundlePath(entryURL)
|
|
164
246
|
val bundleLoader =
|
|
165
247
|
when {
|
|
248
|
+
// Debug mode: remote URL — use single bundle (Metro dev server)
|
|
166
249
|
isRemoteBundleUrl(entryURL) -> createDownloadedBundleLoader(appContext, entryURL)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
250
|
+
|
|
251
|
+
// OTA / local file path — try sequential loading with common bundle
|
|
252
|
+
localBundlePath != null -> {
|
|
253
|
+
val commonPath = resolveCommonBundlePath(localBundlePath)
|
|
254
|
+
if (commonPath != null) {
|
|
255
|
+
BTLogger.info("Using sequential file bundle loader: common=$commonPath, entry=$localBundlePath")
|
|
256
|
+
createSequentialFileBundleLoader(commonPath, localBundlePath, entryURL)
|
|
257
|
+
} else {
|
|
258
|
+
BTLogger.info("No common bundle found for OTA path, using single bundle: $localBundlePath")
|
|
259
|
+
createLocalFileBundleLoader(localBundlePath, entryURL)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Assets-based loading — try sequential loading with common.bundle in assets
|
|
264
|
+
entryURL.startsWith("assets://") -> {
|
|
265
|
+
val entryAssetName = entryURL.removePrefix("assets://")
|
|
266
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
267
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryAssetName")
|
|
268
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryAssetName)
|
|
269
|
+
} else {
|
|
270
|
+
BTLogger.info("No common.bundle in assets, using single bundle: $entryURL")
|
|
271
|
+
JSBundleLoader.createAssetLoader(appContext, entryURL, true)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Bare filename (e.g. "background.bundle") — treat as asset
|
|
276
|
+
else -> {
|
|
277
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
278
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryURL")
|
|
279
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryURL)
|
|
280
|
+
} else {
|
|
281
|
+
BTLogger.info("No common.bundle in assets, using single bundle: assets://$entryURL")
|
|
282
|
+
JSBundleLoader.createAssetLoader(appContext, "assets://$entryURL", true)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
170
285
|
}
|
|
171
286
|
|
|
172
287
|
val delegate = DefaultReactHostDelegate(
|
|
@@ -253,30 +368,45 @@ class BackgroundThreadManager private constructor() {
|
|
|
253
368
|
* @throws IllegalStateException if background runtime is not started
|
|
254
369
|
* @throws IllegalArgumentException if segment file does not exist
|
|
255
370
|
*/
|
|
256
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Register a HBC segment in the background runtime with completion callback.
|
|
373
|
+
* Dispatches to the background JS queue thread and invokes the callback
|
|
374
|
+
* only after registerSegment has actually executed.
|
|
375
|
+
*
|
|
376
|
+
* @param segmentId The segment ID to register
|
|
377
|
+
* @param path Absolute file path to the .seg.hbc file
|
|
378
|
+
* @param onComplete Called with null on success, or an Exception on failure
|
|
379
|
+
*/
|
|
380
|
+
fun registerSegmentInBackground(segmentId: Int, path: String, onComplete: (Exception?) -> Unit) {
|
|
257
381
|
if (!isStarted) {
|
|
258
|
-
|
|
382
|
+
onComplete(IllegalStateException("Background runtime not started"))
|
|
383
|
+
return
|
|
259
384
|
}
|
|
260
385
|
|
|
261
386
|
val file = File(path)
|
|
262
387
|
if (!file.exists()) {
|
|
263
|
-
|
|
388
|
+
onComplete(IllegalArgumentException("Segment file not found: $path"))
|
|
389
|
+
return
|
|
264
390
|
}
|
|
265
391
|
|
|
266
392
|
val context = bgReactHost?.currentReactContext
|
|
267
|
-
|
|
393
|
+
if (context == null) {
|
|
394
|
+
onComplete(IllegalStateException("Background ReactContext not available"))
|
|
395
|
+
return
|
|
396
|
+
}
|
|
268
397
|
|
|
269
398
|
context.runOnJSQueueThread {
|
|
270
399
|
try {
|
|
271
400
|
if (context.hasCatalystInstance()) {
|
|
272
401
|
context.catalystInstance.registerSegment(segmentId, path)
|
|
273
402
|
BTLogger.info("Segment registered in background runtime: id=$segmentId, path=$path")
|
|
403
|
+
onComplete(null)
|
|
274
404
|
} else {
|
|
275
|
-
|
|
405
|
+
onComplete(IllegalStateException("Background CatalystInstance not available for segment registration"))
|
|
276
406
|
}
|
|
277
407
|
} catch (e: Exception) {
|
|
278
408
|
BTLogger.error("Failed to register segment in background runtime: ${e.message}")
|
|
279
|
-
|
|
409
|
+
onComplete(e)
|
|
280
410
|
}
|
|
281
411
|
}
|
|
282
412
|
}
|
|
@@ -29,12 +29,13 @@ class BackgroundThreadModule(reactContext: ReactApplicationContext) :
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
override fun loadSegmentInBackground(segmentId: Double, path: String, promise: Promise) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
BackgroundThreadManager.getInstance()
|
|
33
|
+
.registerSegmentInBackground(segmentId.toInt(), path) { error ->
|
|
34
|
+
if (error != null) {
|
|
35
|
+
promise.reject("BG_SEGMENT_LOAD_ERROR", error.message, error)
|
|
36
|
+
} else {
|
|
37
|
+
promise.resolve(null)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -170,6 +170,8 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
170
170
|
|
|
171
171
|
- (NSURL *)bundleURL
|
|
172
172
|
{
|
|
173
|
+
// When _jsBundleSource is set (dev mode or explicit override), use it as-is.
|
|
174
|
+
// This is a single full bundle (not split), so DON'T use common+entry strategy.
|
|
173
175
|
if (!_jsBundleSource.empty()) {
|
|
174
176
|
NSString *jsBundleSourceNS = [NSString stringWithUTF8String:_jsBundleSource.c_str()];
|
|
175
177
|
NSURL *resolvedURL = resolveBundleSourceURL(jsBundleSourceNS);
|
|
@@ -180,11 +182,19 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
180
182
|
[BTLogger warn:[NSString stringWithFormat:@"Unable to resolve custom jsBundleSource=%@", jsBundleSourceNS]];
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
// Default: load common bundle (shared polyfills + modules).
|
|
186
|
+
// The background entry bundle is loaded later in hostDidStart:.
|
|
187
|
+
NSURL *commonURL = resolveMainBundleResourceURL(@"common.jsbundle");
|
|
188
|
+
if (commonURL) {
|
|
189
|
+
return commonURL;
|
|
186
190
|
}
|
|
187
|
-
return [[NSBundle mainBundle] URLForResource:@"
|
|
191
|
+
return [[NSBundle mainBundle] URLForResource:@"common" withExtension:@"jsbundle"];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (NSString *)resolveBackgroundEntryBundlePath
|
|
195
|
+
{
|
|
196
|
+
NSURL *url = resolveMainBundleResourceURL(@"background.bundle");
|
|
197
|
+
return url.path;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
- (void)hostDidStart:(RCTHost *)host
|
|
@@ -203,6 +213,26 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
203
213
|
return;
|
|
204
214
|
}
|
|
205
215
|
|
|
216
|
+
// When _jsBundleSource is set, the bundle loaded in bundleURL was already
|
|
217
|
+
// a full single bundle (dev mode / explicit override), so skip entry loading.
|
|
218
|
+
BOOL isSplitBundle = _jsBundleSource.empty();
|
|
219
|
+
|
|
220
|
+
// Read the background entry bundle data before entering the executor block
|
|
221
|
+
// (only needed in split-bundle mode).
|
|
222
|
+
NSData *bgBundleData = nil;
|
|
223
|
+
NSString *bgBundleSourceURL = nil;
|
|
224
|
+
if (isSplitBundle) {
|
|
225
|
+
NSString *bgBundlePath = [self resolveBackgroundEntryBundlePath];
|
|
226
|
+
if (bgBundlePath) {
|
|
227
|
+
bgBundleData = [NSData dataWithContentsOfFile:bgBundlePath];
|
|
228
|
+
bgBundleSourceURL = bgBundlePath.lastPathComponent ?: @"background.bundle";
|
|
229
|
+
[BTLogger info:[NSString stringWithFormat:@"Background entry bundle loaded from %@ (%lu bytes)",
|
|
230
|
+
bgBundlePath, (unsigned long)bgBundleData.length]];
|
|
231
|
+
} else {
|
|
232
|
+
[BTLogger warn:@"Background entry bundle not found, __setupBackgroundRPCHandler may not be defined"];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
206
236
|
[_rctInstance callFunctionOnBufferedRuntimeExecutor:[=](jsi::Runtime &runtime) {
|
|
207
237
|
[self setupErrorHandler:runtime];
|
|
208
238
|
|
|
@@ -218,6 +248,17 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
218
248
|
};
|
|
219
249
|
SharedRPC::install(runtime, std::move(bgExecutor), "background");
|
|
220
250
|
[BTLogger info:@"SharedStore and SharedRPC installed in background runtime"];
|
|
251
|
+
|
|
252
|
+
// In split-bundle mode, evaluate the background entry bundle now.
|
|
253
|
+
// This must happen BEFORE invokeOptionalGlobalFunction since the entry
|
|
254
|
+
// bundle defines __setupBackgroundRPCHandler.
|
|
255
|
+
if (isSplitBundle && bgBundleData && bgBundleData.length > 0) {
|
|
256
|
+
auto buffer = std::make_shared<jsi::StringBuffer>(
|
|
257
|
+
std::string(static_cast<const char *>(bgBundleData.bytes), bgBundleData.length));
|
|
258
|
+
runtime.evaluateJavaScript(std::move(buffer), [bgBundleSourceURL UTF8String]);
|
|
259
|
+
[BTLogger info:@"Background entry bundle evaluated in runtime"];
|
|
260
|
+
}
|
|
261
|
+
|
|
221
262
|
invokeOptionalGlobalFunction(runtime, "__setupBackgroundRPCHandler");
|
|
222
263
|
}];
|
|
223
264
|
}
|