@onekeyfe/react-native-background-thread 3.0.90 → 3.0.92
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 +4 -1
- package/ios/BackgroundThreadManager.mm +159 -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
|
|
|
@@ -76,7 +76,10 @@ typedef NS_ENUM(NSInteger, EBgMgrSegmentEvalError) {
|
|
|
76
76
|
/// @param entryURL The custom entry URL for the background runner
|
|
77
77
|
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
78
78
|
thenStartBackgroundRunnerWithEntryURL:(NSString *)entryURL;
|
|
79
|
-
|
|
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;
|
|
80
83
|
/// Start background runner with default entry URL
|
|
81
84
|
- (void)startBackgroundRunner;
|
|
82
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 {
|
|
@@ -210,15 +223,30 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
210
223
|
// see a nil strong reference and bail out — instead of dispatching
|
|
211
224
|
// callFunctionOnBufferedRuntimeExecutor on a freed instance and
|
|
212
225
|
// crashing (EXC_BAD_ACCESS / use-after-free).
|
|
226
|
+
__weak RCTHost *weakHost = host;
|
|
213
227
|
__weak id weakInstance = instance;
|
|
214
|
-
RPCRuntimeExecutor mainExecutor = [weakInstance](std::function<void(jsi::Runtime &)> work) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
});
|
|
222
250
|
};
|
|
223
251
|
SharedRPC::install(runtime, std::move(mainExecutor), "main");
|
|
224
252
|
// Set the integration-health flag from inside the JS-thread lambda so
|
|
@@ -243,7 +271,13 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
243
271
|
startBackgroundRunnerWithEntryURL:entryURL];
|
|
244
272
|
}];
|
|
245
273
|
}
|
|
246
|
-
|
|
274
|
+
+ (void)installSharedBridgeInMainRuntime:(RCTHost *)host
|
|
275
|
+
thenStartBackgroundRunnerWithDevVendorConfig:(NSDictionary<NSString *, NSString *> *)config {
|
|
276
|
+
[self installSharedBridgeInMainRuntime:host completion:^{
|
|
277
|
+
[[BackgroundThreadManager sharedInstance]
|
|
278
|
+
startBackgroundRunnerWithDevVendorConfig:config];
|
|
279
|
+
}];
|
|
280
|
+
}
|
|
247
281
|
#pragma mark - Public Methods
|
|
248
282
|
|
|
249
283
|
- (void)startBackgroundRunner {
|
|
@@ -256,6 +290,35 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
256
290
|
}
|
|
257
291
|
|
|
258
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 {
|
|
259
322
|
if (self.isStarted) {
|
|
260
323
|
// Surface the URL mismatch when a second start... call would
|
|
261
324
|
// otherwise silently drop the host's intent — most common in the
|
|
@@ -278,18 +341,27 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
278
341
|
// Updated unconditionally — first-time and re-start with a new URL
|
|
279
342
|
// both reflect into lastEntryURL.
|
|
280
343
|
self.lastEntryURL = entryURL;
|
|
344
|
+
self.lastDevVendorConfig = devVendorConfig;
|
|
281
345
|
[BTLogger info:[NSString stringWithFormat:@"Starting background runner with entryURL: %@", entryURL]];
|
|
282
346
|
|
|
283
347
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
348
|
+
NSUInteger runtimeGeneration = ++self.backgroundRuntimeGeneration;
|
|
284
349
|
NSDictionary *initialProperties = @{};
|
|
285
350
|
NSDictionary *launchOptions = @{};
|
|
286
351
|
self.reactNativeFactoryDelegate = [[BackgroundReactNativeDelegate alloc] init];
|
|
287
352
|
self.reactNativeFactory = CreateBackgroundReactNativeFactory(self.reactNativeFactoryDelegate);
|
|
288
|
-
|
|
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
|
+
}
|
|
289
361
|
// Only set jsBundleSource for debug HTTP URLs or explicit OTA overrides.
|
|
290
362
|
// Leaving the default release name ("background.bundle") unset lets the
|
|
291
363
|
// delegate fall back to split-bundle mode (common.bundle + entry).
|
|
292
|
-
if (![entryURL isEqualToString:@"background.bundle"]) {
|
|
364
|
+
if (!devVendorConfig && ![entryURL isEqualToString:@"background.bundle"]) {
|
|
293
365
|
[self.reactNativeFactoryDelegate setJsBundleSource:std::string([entryURL UTF8String])];
|
|
294
366
|
}
|
|
295
367
|
|
|
@@ -476,6 +548,20 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
476
548
|
{
|
|
477
549
|
BOOL isUI = [mode isEqualToString:@"ui"];
|
|
478
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
|
+
}
|
|
479
565
|
|
|
480
566
|
if (!isUI && !isAll) {
|
|
481
567
|
NSError *error = [NSError errorWithDomain:@"BackgroundThread"
|
|
@@ -552,6 +638,59 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
552
638
|
}
|
|
553
639
|
}
|
|
554
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
|
+
|
|
555
694
|
#pragma mark - Restart Health Check
|
|
556
695
|
|
|
557
696
|
// Two-stage post-reload health-check.
|
|
@@ -634,6 +773,7 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
634
773
|
|
|
635
774
|
if (isAll && !strongSelf.isStarted) {
|
|
636
775
|
NSString *cachedURL = strongSelf.lastEntryURL;
|
|
776
|
+
NSDictionary<NSString *, NSString *> *devVendorConfig = strongSelf.lastDevVendorConfig;
|
|
637
777
|
// Treat the bundled default name as "no real cache" so the
|
|
638
778
|
// OTA warn still fires for hosts that initially bootstrapped
|
|
639
779
|
// with the default URL and haven't yet swapped to an OTA-
|
|
@@ -641,7 +781,12 @@ static NSString *const MODULE_DEBUG_URL = @"http://localhost:8082/apps/mobile/ba
|
|
|
641
781
|
// lastEntryURL that distinguishes them.
|
|
642
782
|
BOOL hasCustomCachedURL = cachedURL.length > 0
|
|
643
783
|
&& ![cachedURL isEqualToString:@"background.bundle"];
|
|
644
|
-
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) {
|
|
645
790
|
// Preferred path: replay the host's last entry URL. On OTA
|
|
646
791
|
// devices this is the OTA-resolved bundle, which keeps the
|
|
647
792
|
// bg moduleId table aligned with the new main bundle and
|