@onekeyfe/react-native-background-thread 3.0.89 → 3.0.91
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/android/src/main/cpp/cpp-adapter.cpp +215 -31
- package/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt +701 -106
- package/ios/BackgroundRunnerReactNativeDelegate.h +7 -1
- package/ios/BackgroundRunnerReactNativeDelegate.mm +213 -46
- package/ios/BackgroundThreadManager.h +14 -0
- package/ios/BackgroundThreadManager.mm +175 -14
- package/package.json +1 -1
|
@@ -30,7 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
30
30
|
* @return Initialized delegate instance with filtered module access
|
|
31
31
|
*/
|
|
32
32
|
- (instancetype)init;
|
|
33
|
-
|
|
33
|
+
/// Configure debug startup as local common HBC followed by a remote
|
|
34
|
+
/// modules-only background entry bundle.
|
|
35
|
+
- (void)configureDevVendorCommonBundlePath:(NSString *)commonBundlePath
|
|
36
|
+
entryURL:(NSURL *)entryURL
|
|
37
|
+
fingerprint:(NSString *)fingerprint
|
|
38
|
+
backgroundHMREnabled:(BOOL)backgroundHMREnabled
|
|
39
|
+
runtimeGeneration:(NSUInteger)runtimeGeneration;
|
|
34
40
|
/**
|
|
35
41
|
* Register a HBC segment in the background runtime (Phase 2.5 spike).
|
|
36
42
|
* Uses RCTInstance's registerSegmentWithId:path: API.
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
#include "SharedRPC.h"
|
|
13
13
|
|
|
14
14
|
#import <React/RCTBridge.h>
|
|
15
|
+
#import <React/RCTAssert.h>
|
|
15
16
|
#import <React/RCTBundleURLProvider.h>
|
|
16
|
-
|
|
17
|
+
#import <React/RCTJavaScriptLoader.h>
|
|
17
18
|
#if __has_include(<react/utils/FollyConvert.h>)
|
|
18
19
|
// static libs / header maps (no use_frameworks!)
|
|
19
20
|
#import <react/utils/FollyConvert.h>
|
|
@@ -42,7 +43,33 @@
|
|
|
42
43
|
namespace jsi = facebook::jsi;
|
|
43
44
|
namespace TurboModuleConvertUtils = facebook::react::TurboModuleConvertUtils;
|
|
44
45
|
using namespace facebook::react;
|
|
45
|
-
|
|
46
|
+
namespace {
|
|
47
|
+
constexpr int64_t kDevVendorLoadTimeoutSeconds = 60;
|
|
48
|
+
class BackgroundNSDataJSIBuffer : public facebook::jsi::Buffer {
|
|
49
|
+
public:
|
|
50
|
+
explicit BackgroundNSDataJSIBuffer(NSData *data) : data_(data) {}
|
|
51
|
+
size_t size() const override { return data_.length; }
|
|
52
|
+
const uint8_t *data() const override {
|
|
53
|
+
return static_cast<const uint8_t *>(data_.bytes);
|
|
54
|
+
}
|
|
55
|
+
private:
|
|
56
|
+
NSData *data_;
|
|
57
|
+
};
|
|
58
|
+
} // namespace
|
|
59
|
+
@interface BTDevVendorDownloadState : NSObject
|
|
60
|
+
@property (nonatomic, strong, nullable) NSData *data;
|
|
61
|
+
@property (nonatomic, strong, nullable) NSError *error;
|
|
62
|
+
@property (nonatomic, strong) dispatch_semaphore_t ready;
|
|
63
|
+
@end
|
|
64
|
+
@implementation BTDevVendorDownloadState
|
|
65
|
+
- (instancetype)init
|
|
66
|
+
{
|
|
67
|
+
if (self = [super init]) {
|
|
68
|
+
_ready = dispatch_semaphore_create(0);
|
|
69
|
+
}
|
|
70
|
+
return self;
|
|
71
|
+
}
|
|
72
|
+
@end
|
|
46
73
|
static void stubJsiFunction(jsi::Runtime &runtime, jsi::Object &object, const char *name)
|
|
47
74
|
{
|
|
48
75
|
object.setProperty(
|
|
@@ -163,6 +190,11 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
163
190
|
RCTInstance *_rctInstance;
|
|
164
191
|
std::string _origin;
|
|
165
192
|
std::string _jsBundleSource;
|
|
193
|
+
NSString *_devVendorCommonBundlePath;
|
|
194
|
+
NSURL *_devVendorEntryURL;
|
|
195
|
+
NSString *_devVendorFingerprint;
|
|
196
|
+
BOOL _devVendorBackgroundHMREnabled;
|
|
197
|
+
NSUInteger _backgroundRuntimeGeneration;
|
|
166
198
|
// YES when `bundleURL` observed an active OTA main bundle on its most
|
|
167
199
|
// recent invocation, regardless of whether OTA common actually resolved.
|
|
168
200
|
// Captures the invariant "this delegate is locked to OTA territory; IPA
|
|
@@ -182,7 +214,11 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
182
214
|
|
|
183
215
|
- (void)cleanupResources;
|
|
184
216
|
- (void)setupErrorHandler:(jsi::Runtime &)runtime;
|
|
185
|
-
|
|
217
|
+
- (void)finishHostStartWithBundleData:(NSData * _Nullable)bundleData
|
|
218
|
+
sourceURL:(NSString * _Nullable)sourceURL
|
|
219
|
+
fingerprint:(NSString * _Nullable)fingerprint
|
|
220
|
+
evaluateEntry:(BOOL)evaluateEntry
|
|
221
|
+
downloadState:(BTDevVendorDownloadState * _Nullable)downloadState;
|
|
186
222
|
@end
|
|
187
223
|
|
|
188
224
|
@implementation BackgroundReactNativeDelegate
|
|
@@ -221,7 +257,18 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
221
257
|
{
|
|
222
258
|
_jsBundleSource = jsBundleSource;
|
|
223
259
|
}
|
|
224
|
-
|
|
260
|
+
- (void)configureDevVendorCommonBundlePath:(NSString *)commonBundlePath
|
|
261
|
+
entryURL:(NSURL *)entryURL
|
|
262
|
+
fingerprint:(NSString *)fingerprint
|
|
263
|
+
backgroundHMREnabled:(BOOL)backgroundHMREnabled
|
|
264
|
+
runtimeGeneration:(NSUInteger)runtimeGeneration
|
|
265
|
+
{
|
|
266
|
+
_devVendorCommonBundlePath = [commonBundlePath copy];
|
|
267
|
+
_devVendorEntryURL = [entryURL copy];
|
|
268
|
+
_devVendorFingerprint = [fingerprint copy];
|
|
269
|
+
_devVendorBackgroundHMREnabled = backgroundHMREnabled;
|
|
270
|
+
_backgroundRuntimeGeneration = runtimeGeneration;
|
|
271
|
+
}
|
|
225
272
|
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
|
226
273
|
{
|
|
227
274
|
return [self bundleURL];
|
|
@@ -232,7 +279,9 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
232
279
|
// Reset on every call so a re-entry (e.g. host restart) can't carry over
|
|
233
280
|
// a stale OTA assertion from a previous load.
|
|
234
281
|
_otaActiveAtBundleResolve = NO;
|
|
235
|
-
|
|
282
|
+
if (_devVendorCommonBundlePath.length > 0) {
|
|
283
|
+
return [NSURL fileURLWithPath:_devVendorCommonBundlePath];
|
|
284
|
+
}
|
|
236
285
|
// When _jsBundleSource is set (dev mode or explicit override), use it as-is.
|
|
237
286
|
// This is a single full bundle (not split), so DON'T use common+entry strategy.
|
|
238
287
|
if (!_jsBundleSource.empty()) {
|
|
@@ -323,7 +372,56 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
323
372
|
if (!_rctInstance) {
|
|
324
373
|
return;
|
|
325
374
|
}
|
|
326
|
-
|
|
375
|
+
if (_devVendorEntryURL) {
|
|
376
|
+
NSURL *entryURL = _devVendorEntryURL;
|
|
377
|
+
NSString *fingerprint = _devVendorFingerprint;
|
|
378
|
+
BTDevVendorDownloadState *downloadState = [[BTDevVendorDownloadState alloc] init];
|
|
379
|
+
[RCTJavaScriptLoader loadBundleAtURL:entryURL
|
|
380
|
+
onProgress:^(RCTLoadingProgress *progress) { (void)progress; }
|
|
381
|
+
onComplete:^(NSError *error, RCTSource *source) {
|
|
382
|
+
downloadState.error = error;
|
|
383
|
+
downloadState.data = source.data;
|
|
384
|
+
dispatch_semaphore_signal(downloadState.ready);
|
|
385
|
+
}];
|
|
386
|
+
[self finishHostStartWithBundleData:nil
|
|
387
|
+
sourceURL:entryURL.absoluteString
|
|
388
|
+
fingerprint:fingerprint
|
|
389
|
+
evaluateEntry:YES
|
|
390
|
+
downloadState:downloadState];
|
|
391
|
+
if (_devVendorBackgroundHMREnabled) {
|
|
392
|
+
NSURLComponents *hmrComponents = [NSURLComponents componentsWithURL:entryURL
|
|
393
|
+
resolvingAgainstBaseURL:NO];
|
|
394
|
+
hmrComponents.path = @"/apps/mobile/background.bundle";
|
|
395
|
+
NSURL *hmrRegistrationURL = hmrComponents.URL;
|
|
396
|
+
if (!hmrRegistrationURL) {
|
|
397
|
+
[BTLogger error:@"[BackgroundHMR] failed to construct registration URL"];
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
NSString *path = hmrRegistrationURL.path;
|
|
401
|
+
if ([path hasPrefix:@"/"]) {
|
|
402
|
+
path = [path substringFromIndex:1];
|
|
403
|
+
}
|
|
404
|
+
NSString *scheme = hmrRegistrationURL.scheme ?: @"http";
|
|
405
|
+
NSNumber *port = hmrRegistrationURL.port ?: @([scheme isEqualToString:@"https"] ? 443 : 80);
|
|
406
|
+
[host callFunctionOnJSModule:@"HMRClient"
|
|
407
|
+
method:@"setup"
|
|
408
|
+
args:@[
|
|
409
|
+
@"ios",
|
|
410
|
+
path ?: @"apps/mobile/background.bundle",
|
|
411
|
+
hmrRegistrationURL.host ?: @"127.0.0.1",
|
|
412
|
+
port,
|
|
413
|
+
@YES,
|
|
414
|
+
scheme,
|
|
415
|
+
hmrRegistrationURL.absoluteString
|
|
416
|
+
]];
|
|
417
|
+
[BTLogger info:[NSString stringWithFormat:
|
|
418
|
+
@"[BackgroundHMR] client setup queued generation=%lu downloadURL=%@ registrationURL=%@",
|
|
419
|
+
(unsigned long)_backgroundRuntimeGeneration,
|
|
420
|
+
entryURL.absoluteString,
|
|
421
|
+
hmrRegistrationURL.absoluteString]];
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
327
425
|
// When _jsBundleSource is set, the bundle loaded in bundleURL was already
|
|
328
426
|
// a full single bundle (dev mode / explicit override), so skip entry loading.
|
|
329
427
|
BOOL isSplitBundle = _jsBundleSource.empty();
|
|
@@ -359,49 +457,118 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
359
457
|
[BTLogger warn:@"Background entry bundle not found, __setupBackgroundRPCHandler may not be defined"];
|
|
360
458
|
}
|
|
361
459
|
}
|
|
362
|
-
|
|
460
|
+
[self finishHostStartWithBundleData:bgBundleData
|
|
461
|
+
sourceURL:bgBundleSourceURL
|
|
462
|
+
fingerprint:nil
|
|
463
|
+
evaluateEntry:isSplitBundle
|
|
464
|
+
downloadState:nil];
|
|
465
|
+
}
|
|
466
|
+
- (void)finishHostStartWithBundleData:(NSData * _Nullable)bundleData
|
|
467
|
+
sourceURL:(NSString * _Nullable)sourceURL
|
|
468
|
+
fingerprint:(NSString * _Nullable)fingerprint
|
|
469
|
+
evaluateEntry:(BOOL)evaluateEntry
|
|
470
|
+
downloadState:(BTDevVendorDownloadState * _Nullable)downloadState
|
|
471
|
+
{
|
|
472
|
+
RCTInstance *instance = _rctInstance;
|
|
473
|
+
if (!instance) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
363
476
|
CFAbsoluteTime bgStartTime = CFAbsoluteTimeGetCurrent();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
477
|
+
__weak RCTInstance *weakBgInstance = instance;
|
|
478
|
+
|
|
479
|
+
[instance callFunctionOnBufferedRuntimeExecutor:[=](jsi::Runtime &runtime) {
|
|
480
|
+
try {
|
|
481
|
+
NSData *entryData = bundleData;
|
|
482
|
+
if (downloadState) {
|
|
483
|
+
// Queue this executor before the background surface starts. Once the
|
|
484
|
+
// local common HBC completes, wait for Metro without blocking main.
|
|
485
|
+
dispatch_time_t deadline = dispatch_time(
|
|
486
|
+
DISPATCH_TIME_NOW, kDevVendorLoadTimeoutSeconds * NSEC_PER_SEC);
|
|
487
|
+
if (dispatch_semaphore_wait(downloadState.ready, deadline) != 0) {
|
|
488
|
+
NSError *timeoutError = [NSError errorWithDomain:@"BackgroundThread"
|
|
489
|
+
code:21
|
|
490
|
+
userInfo:@{
|
|
491
|
+
NSLocalizedDescriptionKey:
|
|
492
|
+
@"Timed out loading dev-vendor background delta"
|
|
493
|
+
}];
|
|
494
|
+
[BTLogger error:@"Dev-vendor background delta timed out after 60 seconds"];
|
|
495
|
+
dispatch_async(dispatch_get_main_queue(), ^{ RCTFatal(timeoutError); });
|
|
496
|
+
return;
|
|
381
497
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
498
|
+
if (downloadState.error || downloadState.data.length == 0) {
|
|
499
|
+
NSError *loadError = downloadState.error ?: [NSError errorWithDomain:@"BackgroundThread"
|
|
500
|
+
code:20
|
|
501
|
+
userInfo:@{
|
|
502
|
+
NSLocalizedDescriptionKey:
|
|
503
|
+
@"Dev-vendor background delta is empty"
|
|
504
|
+
}];
|
|
505
|
+
[BTLogger error:[NSString stringWithFormat:
|
|
506
|
+
@"Dev-vendor background delta failed: %@", loadError.localizedDescription]];
|
|
507
|
+
dispatch_async(dispatch_get_main_queue(), ^{ RCTFatal(loadError); });
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
entryData = downloadState.data;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (fingerprint.length > 0) {
|
|
514
|
+
jsi::Value marker = runtime.global().getProperty(
|
|
515
|
+
runtime, "__ONEKEY_DEV_VENDOR_FINGERPRINT__");
|
|
516
|
+
if (!marker.isString() ||
|
|
517
|
+
marker.asString(runtime).utf8(runtime) != std::string(fingerprint.UTF8String)) {
|
|
518
|
+
throw std::runtime_error("Dev-vendor common fingerprint mismatch");
|
|
519
|
+
}
|
|
520
|
+
runtime.global().setProperty(
|
|
521
|
+
runtime,
|
|
522
|
+
"__ONEKEY_DEV_VENDOR_FULL_BUNDLE_URL__",
|
|
523
|
+
jsi::String::createFromUtf8(runtime, sourceURL.UTF8String));
|
|
524
|
+
runtime.global().setProperty(
|
|
525
|
+
runtime,
|
|
526
|
+
"__ONEKEY_RUNTIME_TARGET__",
|
|
527
|
+
jsi::String::createFromUtf8(runtime, "background"));
|
|
528
|
+
runtime.global().setProperty(
|
|
529
|
+
runtime,
|
|
530
|
+
"__ONEKEY_BACKGROUND_RUNTIME_GENERATION__",
|
|
531
|
+
static_cast<double>(_backgroundRuntimeGeneration));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
[self setupErrorHandler:runtime];
|
|
535
|
+
SharedStore::install(runtime);
|
|
536
|
+
|
|
537
|
+
RPCRuntimeExecutor bgExecutor = [weakBgInstance](std::function<void(jsi::Runtime &)> work) {
|
|
538
|
+
RCTInstance *strongBgInstance = weakBgInstance;
|
|
539
|
+
if (!strongBgInstance) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
[strongBgInstance callFunctionOnBufferedRuntimeExecutor:[work](jsi::Runtime &rt) {
|
|
543
|
+
work(rt);
|
|
544
|
+
}];
|
|
545
|
+
};
|
|
546
|
+
SharedRPC::install(runtime, std::move(bgExecutor), "background");
|
|
547
|
+
[BTLogger info:@"SharedStore and SharedRPC installed in background runtime"];
|
|
548
|
+
|
|
549
|
+
// The entry defines __setupBackgroundRPCHandler, so it must execute
|
|
550
|
+
// before the runtime is reported ready.
|
|
551
|
+
if (evaluateEntry && entryData.length > 0 && sourceURL.length > 0) {
|
|
552
|
+
CFAbsoluteTime bgEvalStart = CFAbsoluteTimeGetCurrent();
|
|
553
|
+
auto buffer = std::make_shared<BackgroundNSDataJSIBuffer>(entryData);
|
|
554
|
+
runtime.evaluateJavaScript(buffer, sourceURL.UTF8String);
|
|
555
|
+
double bgEvalMs = (CFAbsoluteTimeGetCurrent() - bgEvalStart) * 1000.0;
|
|
556
|
+
[BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg entry evaluated in %.1fms", bgEvalMs]];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
double bgTotalMs = (CFAbsoluteTimeGetCurrent() - bgStartTime) * 1000.0;
|
|
560
|
+
[BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg hostDidStart total setup in %.1fms", bgTotalMs]];
|
|
561
|
+
invokeOptionalGlobalFunction(runtime, "__setupBackgroundRPCHandler");
|
|
562
|
+
} catch (const std::exception &exception) {
|
|
563
|
+
NSString *message = [NSString stringWithUTF8String:exception.what()];
|
|
564
|
+
NSError *evaluationError = [NSError errorWithDomain:@"BackgroundThread"
|
|
565
|
+
code:21
|
|
566
|
+
userInfo:@{
|
|
567
|
+
NSLocalizedDescriptionKey:
|
|
568
|
+
message ?: @"Dev-vendor background evaluation failed"
|
|
569
|
+
}];
|
|
570
|
+
dispatch_async(dispatch_get_main_queue(), ^{ RCTFatal(evaluationError); });
|
|
399
571
|
}
|
|
400
|
-
|
|
401
|
-
double bgTotalMs = (CFAbsoluteTimeGetCurrent() - bgStartTime) * 1000.0;
|
|
402
|
-
[BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg hostDidStart total setup in %.1fms", bgTotalMs]];
|
|
403
|
-
|
|
404
|
-
invokeOptionalGlobalFunction(runtime, "__setupBackgroundRPCHandler");
|
|
405
572
|
}];
|
|
406
573
|
}
|
|
407
574
|
|
|
@@ -66,6 +66,20 @@ typedef NS_ENUM(NSInteger, EBgMgrSegmentEvalError) {
|
|
|
66
66
|
/// @param host The RCTHost for the main runtime
|
|
67
67
|
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host;
|
|
68
68
|
|
|
69
|
+
/// Install the main-runtime bridge and start the background runtime only after
|
|
70
|
+
/// that installation has executed on the main JS runtime. Expo initializes its
|
|
71
|
+
/// module registry on the JS thread, so this ordered entry point prevents a
|
|
72
|
+
/// second Expo-backed runtime from registering modules during foreground
|
|
73
|
+
/// runtime startup.
|
|
74
|
+
///
|
|
75
|
+
/// @param host The RCTHost for the main runtime
|
|
76
|
+
/// @param entryURL The custom entry URL for the background runner
|
|
77
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
78
|
+
thenStartBackgroundRunnerWithEntryURL:(NSString *)entryURL;
|
|
79
|
+
/// Install the main-runtime bridge and start a background runtime from a
|
|
80
|
+
/// local common HBC followed by a remote modules-only entry bundle.
|
|
81
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
82
|
+
thenStartBackgroundRunnerWithDevVendorConfig:(NSDictionary<NSString *, NSString *> *)config;
|
|
69
83
|
/// Start background runner with default entry URL
|
|
70
84
|
- (void)startBackgroundRunner;
|
|
71
85
|
|
|
@@ -142,7 +142,11 @@ static const NSTimeInterval kBgSegmentEvalWatchdogSeconds = 30.0;
|
|
|
142
142
|
// same reason as isStarted: written from the caller's thread (public API
|
|
143
143
|
// doesn't pin start... to main) and read on main by the health-check.
|
|
144
144
|
@property (atomic, copy) NSString *lastEntryURL;
|
|
145
|
-
|
|
145
|
+
// Debug-only dev-vendor bootstrap contract. Retained across mode='all'
|
|
146
|
+
// reloads so the self-heal path cannot fall back to a monolithic bundle.
|
|
147
|
+
@property (atomic, copy, nullable) NSDictionary<NSString *, NSString *> *lastDevVendorConfig;
|
|
148
|
+
@property (nonatomic, assign) NSUInteger backgroundRuntimeGeneration;
|
|
149
|
+
@property (nonatomic, assign) BOOL backgroundHMRRestartInFlight;
|
|
146
150
|
// Forward declaration so restartWithMode: can call it; definition lives
|
|
147
151
|
// after restartWithMode: for readability (the call site is the natural
|
|
148
152
|
// place to start reading the restart flow).
|
|
@@ -150,6 +154,11 @@ static const NSTimeInterval kBgSegmentEvalWatchdogSeconds = 30.0;
|
|
|
150
154
|
isAll:(BOOL)isAll
|
|
151
155
|
generation:(NSUInteger)myGen
|
|
152
156
|
retried:(BOOL)retried;
|
|
157
|
+
- (void)startBackgroundRunnerWithDevVendorConfig:(NSDictionary<NSString *, NSString *> *)config;
|
|
158
|
+
- (void)startBackgroundRunnerWithEntryURL:(NSString *)entryURL
|
|
159
|
+
devVendorConfig:(NSDictionary<NSString *, NSString *> * _Nullable)devVendorConfig;
|
|
160
|
+
- (void)restartBackgroundForHMRReason:(NSString *)reason
|
|
161
|
+
completion:(void (^)(NSError * _Nullable error))completion;
|
|
153
162
|
@end
|
|
154
163
|
|
|
155
164
|
// First-stage delay for the post-reload health-check. Picked so the host's
|
|
@@ -164,7 +173,11 @@ static BackgroundThreadManager *_sharedInstance = nil;
|
|
|
164
173
|
static dispatch_once_t onceToken;
|
|
165
174
|
static NSString *const MODULE_NAME = @"background";
|
|
166
175
|
static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/background.bundle?platform=ios&dev=true&lazy=false&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server&app=so.onekey.wallet&transform.routerRoot=app&transform.engine=hermes&transform.bytecode=1&unstable_transformProfile=hermes-stable";
|
|
167
|
-
|
|
176
|
+
static NSString *const DEV_VENDOR_COMMON_PATH_KEY = @"commonBundlePath";
|
|
177
|
+
static NSString *const DEV_VENDOR_ENTRY_URL_KEY = @"entryURL";
|
|
178
|
+
static NSString *const DEV_VENDOR_FINGERPRINT_KEY = @"fingerprint";
|
|
179
|
+
static NSString *const DEV_VENDOR_BACKGROUND_HMR_KEY = @"backgroundHMREnabled";
|
|
180
|
+
static NSString *const BACKGROUND_HMR_REASON_PREFIX = @"onekey-bg-hmr:";
|
|
168
181
|
#pragma mark - Singleton
|
|
169
182
|
|
|
170
183
|
+ (instancetype)sharedInstance {
|
|
@@ -185,7 +198,8 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
185
198
|
|
|
186
199
|
#pragma mark - SharedBridge
|
|
187
200
|
|
|
188
|
-
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
201
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
202
|
+
completion:(dispatch_block_t)completion {
|
|
189
203
|
if (!host) {
|
|
190
204
|
[BTLogger error:@"Cannot install SharedBridge: RCTHost is nil"];
|
|
191
205
|
return;
|
|
@@ -209,15 +223,30 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
209
223
|
// see a nil strong reference and bail out — instead of dispatching
|
|
210
224
|
// callFunctionOnBufferedRuntimeExecutor on a freed instance and
|
|
211
225
|
// crashing (EXC_BAD_ACCESS / use-after-free).
|
|
226
|
+
__weak RCTHost *weakHost = host;
|
|
212
227
|
__weak id weakInstance = instance;
|
|
213
|
-
RPCRuntimeExecutor mainExecutor = [weakInstance](std::function<void(jsi::Runtime &)> work) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
RPCRuntimeExecutor mainExecutor = [weakHost, weakInstance](std::function<void(jsi::Runtime &)> work) {
|
|
229
|
+
// RCTHost invalidates and swaps its RCTInstance on the main queue.
|
|
230
|
+
// Serialize the identity check with that swap: retaining the old
|
|
231
|
+
// Objective-C wrapper is not sufficient because its C++
|
|
232
|
+
// ReactInstance may already have been reset during reload.
|
|
233
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
234
|
+
RCTHost *strongHost = weakHost;
|
|
235
|
+
id strongInstance = weakInstance;
|
|
236
|
+
if (!strongHost || !strongInstance) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
Ivar currentInstanceIvar = class_getInstanceVariable([strongHost class], "_instance");
|
|
241
|
+
id currentInstance = object_getIvar(strongHost, currentInstanceIvar);
|
|
242
|
+
if (currentInstance != strongInstance) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
[strongInstance callFunctionOnBufferedRuntimeExecutor:[work](jsi::Runtime &rt) {
|
|
247
|
+
work(rt);
|
|
248
|
+
}];
|
|
249
|
+
});
|
|
221
250
|
};
|
|
222
251
|
SharedRPC::install(runtime, std::move(mainExecutor), "main");
|
|
223
252
|
// Set the integration-health flag from inside the JS-thread lambda so
|
|
@@ -225,9 +254,30 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
225
254
|
// reload observer reads this to detect host hostDidStart: omissions.
|
|
226
255
|
[BackgroundThreadManager sharedInstance].mainSharedBridgeInstalled = YES;
|
|
227
256
|
[BTLogger info:@"SharedStore and SharedRPC installed in main runtime"];
|
|
257
|
+
if (completion) {
|
|
258
|
+
completion();
|
|
259
|
+
}
|
|
228
260
|
}];
|
|
229
261
|
}
|
|
230
262
|
|
|
263
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host {
|
|
264
|
+
[self installSharedBridgeInMainRuntime:host completion:nil];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
268
|
+
thenStartBackgroundRunnerWithEntryURL:(NSString *)entryURL {
|
|
269
|
+
[self installSharedBridgeInMainRuntime:host completion:^{
|
|
270
|
+
[[BackgroundThreadManager sharedInstance]
|
|
271
|
+
startBackgroundRunnerWithEntryURL:entryURL];
|
|
272
|
+
}];
|
|
273
|
+
}
|
|
274
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
275
|
+
thenStartBackgroundRunnerWithDevVendorConfig:(NSDictionary<NSString *, NSString *> *)config {
|
|
276
|
+
[self installSharedBridgeInMainRuntime:host completion:^{
|
|
277
|
+
[[BackgroundThreadManager sharedInstance]
|
|
278
|
+
startBackgroundRunnerWithDevVendorConfig:config];
|
|
279
|
+
}];
|
|
280
|
+
}
|
|
231
281
|
#pragma mark - Public Methods
|
|
232
282
|
|
|
233
283
|
- (void)startBackgroundRunner {
|
|
@@ -240,6 +290,35 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
240
290
|
}
|
|
241
291
|
|
|
242
292
|
- (void)startBackgroundRunnerWithEntryURL:(NSString *)entryURL {
|
|
293
|
+
[self startBackgroundRunnerWithEntryURL:entryURL devVendorConfig:nil];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
- (void)startBackgroundRunnerWithDevVendorConfig:(NSDictionary<NSString *, NSString *> *)config {
|
|
297
|
+
NSString *commonBundlePath = config[DEV_VENDOR_COMMON_PATH_KEY];
|
|
298
|
+
NSString *entryURL = config[DEV_VENDOR_ENTRY_URL_KEY];
|
|
299
|
+
NSString *fingerprint = config[DEV_VENDOR_FINGERPRINT_KEY];
|
|
300
|
+
NSString *backgroundHMRValue = config[DEV_VENDOR_BACKGROUND_HMR_KEY];
|
|
301
|
+
NSURL *parsedEntryURL = [NSURL URLWithString:entryURL];
|
|
302
|
+
NSRegularExpression *fingerprintPattern =
|
|
303
|
+
[NSRegularExpression regularExpressionWithPattern:@"^[0-9a-f]{64}$"
|
|
304
|
+
options:0
|
|
305
|
+
error:nil];
|
|
306
|
+
if (commonBundlePath.length == 0 ||
|
|
307
|
+
![[NSFileManager defaultManager] fileExistsAtPath:commonBundlePath] ||
|
|
308
|
+
parsedEntryURL.host.length == 0 ||
|
|
309
|
+
!([backgroundHMRValue isEqualToString:@"true"] ||
|
|
310
|
+
[backgroundHMRValue isEqualToString:@"false"]) ||
|
|
311
|
+
[fingerprintPattern numberOfMatchesInString:fingerprint
|
|
312
|
+
options:0
|
|
313
|
+
range:NSMakeRange(0, fingerprint.length)] != 1) {
|
|
314
|
+
[BTLogger error:@"Invalid dev-vendor background bootstrap config"];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
[self startBackgroundRunnerWithEntryURL:entryURL devVendorConfig:config];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
- (void)startBackgroundRunnerWithEntryURL:(NSString *)entryURL
|
|
321
|
+
devVendorConfig:(NSDictionary<NSString *, NSString *> * _Nullable)devVendorConfig {
|
|
243
322
|
if (self.isStarted) {
|
|
244
323
|
// Surface the URL mismatch when a second start... call would
|
|
245
324
|
// otherwise silently drop the host's intent — most common in the
|
|
@@ -262,18 +341,27 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
262
341
|
// Updated unconditionally — first-time and re-start with a new URL
|
|
263
342
|
// both reflect into lastEntryURL.
|
|
264
343
|
self.lastEntryURL = entryURL;
|
|
344
|
+
self.lastDevVendorConfig = devVendorConfig;
|
|
265
345
|
[BTLogger info:[NSString stringWithFormat:@"Starting background runner with entryURL: %@", entryURL]];
|
|
266
346
|
|
|
267
347
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
348
|
+
NSUInteger runtimeGeneration = ++self.backgroundRuntimeGeneration;
|
|
268
349
|
NSDictionary *initialProperties = @{};
|
|
269
350
|
NSDictionary *launchOptions = @{};
|
|
270
351
|
self.reactNativeFactoryDelegate = [[BackgroundReactNativeDelegate alloc] init];
|
|
271
352
|
self.reactNativeFactory = CreateBackgroundReactNativeFactory(self.reactNativeFactoryDelegate);
|
|
272
|
-
|
|
353
|
+
if (devVendorConfig) {
|
|
354
|
+
[self.reactNativeFactoryDelegate
|
|
355
|
+
configureDevVendorCommonBundlePath:devVendorConfig[DEV_VENDOR_COMMON_PATH_KEY]
|
|
356
|
+
entryURL:[NSURL URLWithString:devVendorConfig[DEV_VENDOR_ENTRY_URL_KEY]]
|
|
357
|
+
fingerprint:devVendorConfig[DEV_VENDOR_FINGERPRINT_KEY]
|
|
358
|
+
backgroundHMREnabled:[devVendorConfig[DEV_VENDOR_BACKGROUND_HMR_KEY] isEqualToString:@"true"]
|
|
359
|
+
runtimeGeneration:runtimeGeneration];
|
|
360
|
+
}
|
|
273
361
|
// Only set jsBundleSource for debug HTTP URLs or explicit OTA overrides.
|
|
274
362
|
// Leaving the default release name ("background.bundle") unset lets the
|
|
275
363
|
// delegate fall back to split-bundle mode (common.bundle + entry).
|
|
276
|
-
if (![entryURL isEqualToString:@"background.bundle"]) {
|
|
364
|
+
if (!devVendorConfig && ![entryURL isEqualToString:@"background.bundle"]) {
|
|
277
365
|
[self.reactNativeFactoryDelegate setJsBundleSource:std::string([entryURL UTF8String])];
|
|
278
366
|
}
|
|
279
367
|
|
|
@@ -460,6 +548,20 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
460
548
|
{
|
|
461
549
|
BOOL isUI = [mode isEqualToString:@"ui"];
|
|
462
550
|
BOOL isAll = [mode isEqualToString:@"all"];
|
|
551
|
+
BOOL isBackgroundHMR = [mode isEqualToString:@"background"];
|
|
552
|
+
|
|
553
|
+
if (isBackgroundHMR) {
|
|
554
|
+
#if DEBUG
|
|
555
|
+
[self restartBackgroundForHMRReason:reason completion:completion];
|
|
556
|
+
#else
|
|
557
|
+
NSError *error = [NSError errorWithDomain:@"BackgroundThread"
|
|
558
|
+
code:12
|
|
559
|
+
userInfo:@{NSLocalizedDescriptionKey:
|
|
560
|
+
@"Background HMR restart is unavailable in release builds"}];
|
|
561
|
+
if (completion) completion(error);
|
|
562
|
+
#endif
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
463
565
|
|
|
464
566
|
if (!isUI && !isAll) {
|
|
465
567
|
NSError *error = [NSError errorWithDomain:@"BackgroundThread"
|
|
@@ -536,6 +638,59 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
536
638
|
}
|
|
537
639
|
}
|
|
538
640
|
|
|
641
|
+
- (void)restartBackgroundForHMRReason:(NSString *)reason
|
|
642
|
+
completion:(void (^)(NSError * _Nullable error))completion
|
|
643
|
+
{
|
|
644
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
645
|
+
if (![reason hasPrefix:BACKGROUND_HMR_REASON_PREFIX]) {
|
|
646
|
+
NSError *error = [NSError errorWithDomain:@"BackgroundThread"
|
|
647
|
+
code:13
|
|
648
|
+
userInfo:@{NSLocalizedDescriptionKey:
|
|
649
|
+
@"Background HMR restart is missing its runtime generation"}];
|
|
650
|
+
if (completion) completion(error);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
NSString *generationAndReason = [reason substringFromIndex:BACKGROUND_HMR_REASON_PREFIX.length];
|
|
654
|
+
NSRange separator = [generationAndReason rangeOfString:@":"];
|
|
655
|
+
NSString *generationText = separator.location == NSNotFound
|
|
656
|
+
? generationAndReason
|
|
657
|
+
: [generationAndReason substringToIndex:separator.location];
|
|
658
|
+
NSScanner *scanner = [NSScanner scannerWithString:generationText];
|
|
659
|
+
unsigned long long generation = 0;
|
|
660
|
+
if (![scanner scanUnsignedLongLong:&generation] || !scanner.isAtEnd ||
|
|
661
|
+
generation != self.backgroundRuntimeGeneration) {
|
|
662
|
+
[BTLogger info:[NSString stringWithFormat:
|
|
663
|
+
@"[BackgroundHMR] ignored stale restart requested=%@ active=%lu",
|
|
664
|
+
generationText,
|
|
665
|
+
(unsigned long)self.backgroundRuntimeGeneration]];
|
|
666
|
+
if (completion) completion(nil);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
NSDictionary<NSString *, NSString *> *config = self.lastDevVendorConfig;
|
|
670
|
+
if (![config[DEV_VENDOR_BACKGROUND_HMR_KEY] isEqualToString:@"true"] ||
|
|
671
|
+
self.backgroundHMRRestartInFlight) {
|
|
672
|
+
[BTLogger info:@"[BackgroundHMR] restart coalesced or disabled"];
|
|
673
|
+
if (completion) completion(nil);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
self.backgroundHMRRestartInFlight = YES;
|
|
678
|
+
++self.backgroundRuntimeGeneration;
|
|
679
|
+
SharedRPC::invalidate("background");
|
|
680
|
+
self.reactNativeFactory = nil;
|
|
681
|
+
self.reactNativeFactoryDelegate = nil;
|
|
682
|
+
self.isStarted = NO;
|
|
683
|
+
if (completion) completion(nil);
|
|
684
|
+
|
|
685
|
+
// Leave the outgoing TurboModule callback before creating its replacement.
|
|
686
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
687
|
+
[BTLogger info:@"[BackgroundHMR] starting replacement background runtime"];
|
|
688
|
+
[self startBackgroundRunnerWithDevVendorConfig:config];
|
|
689
|
+
self.backgroundHMRRestartInFlight = NO;
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
539
694
|
#pragma mark - Restart Health Check
|
|
540
695
|
|
|
541
696
|
// Two-stage post-reload health-check.
|
|
@@ -618,6 +773,7 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
618
773
|
|
|
619
774
|
if (isAll && !strongSelf.isStarted) {
|
|
620
775
|
NSString *cachedURL = strongSelf.lastEntryURL;
|
|
776
|
+
NSDictionary<NSString *, NSString *> *devVendorConfig = strongSelf.lastDevVendorConfig;
|
|
621
777
|
// Treat the bundled default name as "no real cache" so the
|
|
622
778
|
// OTA warn still fires for hosts that initially bootstrapped
|
|
623
779
|
// with the default URL and haven't yet swapped to an OTA-
|
|
@@ -625,7 +781,12 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
625
781
|
// lastEntryURL that distinguishes them.
|
|
626
782
|
BOOL hasCustomCachedURL = cachedURL.length > 0
|
|
627
783
|
&& ![cachedURL isEqualToString:@"background.bundle"];
|
|
628
|
-
if (
|
|
784
|
+
if (devVendorConfig) {
|
|
785
|
+
[BTLogger info:[NSString stringWithFormat:
|
|
786
|
+
@"restart(%@): bg not respawned by host AppDelegate; replaying dev-vendor common + delta",
|
|
787
|
+
mode]];
|
|
788
|
+
[strongSelf startBackgroundRunnerWithDevVendorConfig:devVendorConfig];
|
|
789
|
+
} else if (hasCustomCachedURL) {
|
|
629
790
|
// Preferred path: replay the host's last entry URL. On OTA
|
|
630
791
|
// devices this is the OTA-resolved bundle, which keeps the
|
|
631
792
|
// bg moduleId table aligned with the new main bundle and
|