react-native-update 10.58.1 → 10.59.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-CN.md +7 -0
- package/README.md +8 -0
- package/android/build.gradle +46 -14
- package/android/jni/Android.mk +2 -1
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateResult.java → BundlePreparationResult.java} +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/{CrashRescue.java → CrashHold.java} +12 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +28 -24
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateFlow.java → FlowBridge.java} +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +6 -3
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateCore.java → NativeCore.java} +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateConfig.java → PushyConfiguration.java} +13 -8
- package/android/src/main/java/cn/reactnative/modules/update/{PushyNativeUpdate.java → PushyRuntime.java} +15 -15
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +7 -7
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeCheckOrchestrator.java → SyncCoordinator.java} +107 -103
- package/android/src/main/java/cn/reactnative/modules/update/Texts.java +28 -0
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +29 -29
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +12 -9
- package/cpp/patch_core/archive_limits.h +5 -0
- package/cpp/patch_core/archive_patch_core.cpp +26 -19
- package/cpp/patch_core/archive_patch_core.h +17 -8
- package/cpp/patch_core/digest.h +6 -1
- package/cpp/patch_core/error_codes.h +8 -1
- package/cpp/patch_core/hbc_transform.h +5 -0
- package/cpp/patch_core/hbc_transform_wire.h +5 -0
- package/cpp/patch_core/install_record.h +5 -0
- package/cpp/patch_core/jni_natives.h +27 -0
- package/cpp/patch_core/jni_registration.cpp +103 -0
- package/cpp/patch_core/obscured_text.h +44 -0
- package/cpp/patch_core/patch_core.cpp +13 -13
- package/cpp/patch_core/patch_core.h +11 -6
- package/cpp/patch_core/patch_core_android.cpp +10 -10
- package/cpp/patch_core/state_core.h +5 -0
- package/cpp/patch_core/state_ops.h +5 -0
- package/cpp/patch_core/update_core_android.cpp +23 -22
- package/cpp/update_flow_core/flow_json.h +6 -1
- package/cpp/update_flow_core/update_flow_core.cpp +35 -18
- package/cpp/update_flow_core/update_flow_core.h +13 -8
- package/cpp/update_flow_core/update_flow_jni.cpp +11 -13
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +8 -8
- package/harmony/pushy/src/main/cpp/pushy.cpp +33 -33
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +15 -15
- package/harmony/pushy/src/main/ets/{NativeUpdateResult.ts → BundlePreparationResult.ts} +6 -6
- package/harmony/pushy/src/main/ets/DownloadTask.ts +60 -60
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +13 -11
- package/harmony/pushy/src/main/ets/InstallRecord.ts +4 -4
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +9 -9
- package/harmony/pushy/src/main/ets/PathUtils.ts +2 -2
- package/harmony/pushy/src/main/ets/{NativeUpdateConfig.ts → PushyConfiguration.ts} +12 -10
- package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +8 -8
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +42 -41
- package/harmony/pushy/src/main/ets/{NativeCheckOrchestrator.ts → SyncCoordinator.ts} +71 -70
- package/harmony/pushy/src/main/ets/Texts.ts +28 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +32 -31
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.h +8 -8
- package/ios/RCTPushy/RCTPushy.mm +155 -147
- package/ios/RCTPushy/RCTPushyConfiguration.h +13 -0
- package/ios/RCTPushy/{RCTPushyNativeConfig.mm → RCTPushyConfiguration.mm} +34 -7
- package/package.json +1 -1
- package/react-native-update.podspec +15 -3
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/ios/RCTPushy/RCTPushyNativeConfig.h +0 -5
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#import "RCTPushy.h"
|
|
2
|
-
#import "
|
|
2
|
+
#import "RCTPushyConfiguration.h"
|
|
3
3
|
#import "RCTPushyDownloader.h"
|
|
4
4
|
#import "ZipArchive.h"
|
|
5
5
|
#include "../../cpp/patch_core/archive_limits.h"
|
|
@@ -64,7 +64,7 @@ static NSString *const keyUuid = @"REACTNATIVECN_PUSHY_UUID";
|
|
|
64
64
|
static NSString *const keyHashInfo = @"REACTNATIVECN_PUSHY_HASH_";
|
|
65
65
|
static NSString *const keyFirstLoadMarked = @"REACTNATIVECN_PUSHY_FIRSTLOADMARKED_KEY";
|
|
66
66
|
static NSString *const keyRolledBackMarked = @"REACTNATIVECN_PUSHY_ROLLEDBACKMARKED_KEY";
|
|
67
|
-
static NSString *const KeyPackageUpdatedMarked =
|
|
67
|
+
static NSString *const KeyPackageUpdatedMarked = RCTPushyRevealText("0832d5f29aa549710b0939daf88ca058790f3dded7e888b451640d2ec3f69099bb43711523cafa8ea7bb4372132a");
|
|
68
68
|
// bundleHash cache: "<cacheKey>|<sha256hex>" where cacheKey identifies the
|
|
69
69
|
// installed binary (packageVersion + embedded bundle size + mtime). Recomputed
|
|
70
70
|
// only when the key changes, i.e. once per install.
|
|
@@ -84,7 +84,15 @@ static NSString *const PushyErrorDomain = @"cn.reactnative.pushy";
|
|
|
84
84
|
// file def
|
|
85
85
|
static NSString * const BUNDLE_FILE_NAME = @"index.bundlejs";
|
|
86
86
|
static NSString * const SOURCE_PATCH_NAME = @"__diff.json";
|
|
87
|
-
static NSString * const BUNDLE_PATCH_NAME =
|
|
87
|
+
static NSString * const BUNDLE_PATCH_NAME = RCTPushyRevealText("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9");
|
|
88
|
+
// Protocol strings and version-info flags read by JS, stored encoded
|
|
89
|
+
// (RCTPushyRevealText) so they do not appear as plain binary strings.
|
|
90
|
+
static NSString * const PushyOptionUrlKey = RCTPushyRevealText("2f07f0d0ba8e5d572e");
|
|
91
|
+
static NSString * const PushyIpaDeltaSuffix = RCTPushyRevealText("741ee4d0e09b69512137");
|
|
92
|
+
static NSString * const PushyPpkDeltaSuffix = RCTPushyRevealText("7407e4dae09b69512137");
|
|
93
|
+
static NSString * const PushyInfoCrashHold = RCTPushyRevealText("3905f5c2a6b96d56212a19");
|
|
94
|
+
static NSString * const PushyInfoForceBoot = RCTPushyRevealText("3c18e6d2aba9674a360d19ead5a695");
|
|
95
|
+
static NSString * const PushyStatusNone = RCTPushyRevealText("3418c1c1aa8a7c40");
|
|
88
96
|
#define VERSION_COMPLETE_FILE_NAME_LITERAL ".pushy-complete"
|
|
89
97
|
static NSString * const VERSION_COMPLETE_FILE_NAME = @VERSION_COMPLETE_FILE_NAME_LITERAL;
|
|
90
98
|
|
|
@@ -211,7 +219,7 @@ static NSError *PushyEnsureFreeSpace(NSString *path, long long bytesToWrite) {
|
|
|
211
219
|
if (shortfall == nil) {
|
|
212
220
|
return nil;
|
|
213
221
|
}
|
|
214
|
-
return PushyErrorWithCode(pushy::error_codes::
|
|
222
|
+
return PushyErrorWithCode(pushy::error_codes::kDeltaFailed, shortfall);
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
// SSZipArchive delegate enforcing cpp/patch_core/archive_limits.h while the
|
|
@@ -477,12 +485,12 @@ static std::string PushyToStdString(NSString *value) {
|
|
|
477
485
|
return std::string([value UTF8String]);
|
|
478
486
|
}
|
|
479
487
|
|
|
480
|
-
static NSError *PushyNSErrorFromStatus(const pushy::
|
|
488
|
+
static NSError *PushyNSErrorFromStatus(const pushy::delta::Status &status) {
|
|
481
489
|
return [NSError errorWithDomain:PushyErrorDomain
|
|
482
490
|
code:-1
|
|
483
491
|
userInfo:@{
|
|
484
492
|
NSLocalizedDescriptionKey: [NSString stringWithUTF8String:status.message.c_str()],
|
|
485
|
-
PushyErrorCodeKey: PushyCode(pushy::error_codes::
|
|
493
|
+
PushyErrorCodeKey: PushyCode(pushy::error_codes::kDeltaFailed),
|
|
486
494
|
}];
|
|
487
495
|
}
|
|
488
496
|
|
|
@@ -568,12 +576,12 @@ static BOOL PushyJsonIsAbsent(id value) {
|
|
|
568
576
|
// raise takes the whole app down (and trips the crash rescue) instead of
|
|
569
577
|
// failing the patch. NO with `reason` set for a malformed manifest.
|
|
570
578
|
static BOOL PushyPatchManifestFromJson(NSDictionary *json,
|
|
571
|
-
pushy::
|
|
579
|
+
pushy::delta::PatchManifest *manifest,
|
|
572
580
|
NSString **reason) {
|
|
573
581
|
id copies = json[@"copies"];
|
|
574
582
|
if (!PushyJsonIsAbsent(copies)) {
|
|
575
583
|
if (![copies isKindOfClass:[NSDictionary class]]) {
|
|
576
|
-
*reason = @"
|
|
584
|
+
*reason = @"delta manifest: copies is not an object";
|
|
577
585
|
return NO;
|
|
578
586
|
}
|
|
579
587
|
// Content checksum per copy target ("copiesCrc", pdiff manifests
|
|
@@ -588,13 +596,13 @@ static BOOL PushyPatchManifestFromJson(NSDictionary *json,
|
|
|
588
596
|
for (id to in (NSDictionary *)copies) {
|
|
589
597
|
id from = ((NSDictionary *)copies)[to];
|
|
590
598
|
if (![to isKindOfClass:[NSString class]] || ![from isKindOfClass:[NSString class]]) {
|
|
591
|
-
*reason = @"
|
|
599
|
+
*reason = @"delta manifest: copies entry is not a string";
|
|
592
600
|
return NO;
|
|
593
601
|
}
|
|
594
602
|
if ([from length] == 0) {
|
|
595
603
|
from = to;
|
|
596
604
|
}
|
|
597
|
-
pushy::
|
|
605
|
+
pushy::delta::CopyOperation operation;
|
|
598
606
|
operation.from = PushyToStdString(from);
|
|
599
607
|
operation.to = PushyToStdString(to);
|
|
600
608
|
NSNumber *expectedCrc = copiesCrc[to];
|
|
@@ -611,12 +619,12 @@ static BOOL PushyPatchManifestFromJson(NSDictionary *json,
|
|
|
611
619
|
id deletes = json[@"deletes"];
|
|
612
620
|
if (!PushyJsonIsAbsent(deletes)) {
|
|
613
621
|
if (![deletes isKindOfClass:[NSDictionary class]] && ![deletes isKindOfClass:[NSArray class]]) {
|
|
614
|
-
*reason = @"
|
|
622
|
+
*reason = @"delta manifest: deletes is not an object or array";
|
|
615
623
|
return NO;
|
|
616
624
|
}
|
|
617
625
|
for (id path in deletes) {
|
|
618
626
|
if (![path isKindOfClass:[NSString class]]) {
|
|
619
|
-
*reason = @"
|
|
627
|
+
*reason = @"delta manifest: deletes entry is not a string";
|
|
620
628
|
return NO;
|
|
621
629
|
}
|
|
622
630
|
manifest->deletes.push_back(PushyToStdString(path));
|
|
@@ -697,14 +705,14 @@ static void PushySwitchVersionLocked(NSString *hash) {
|
|
|
697
705
|
}
|
|
698
706
|
|
|
699
707
|
@interface RCTPushy ()
|
|
700
|
-
- (void)
|
|
708
|
+
- (void)fetchPackage:(PushyType)type
|
|
701
709
|
options:(NSDictionary *)options
|
|
702
710
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
703
711
|
rejecter:(RCTPromiseRejectBlock)reject;
|
|
704
|
-
- (void)
|
|
712
|
+
- (void)performFetch:(PushyType)type
|
|
705
713
|
options:(NSDictionary *)options
|
|
706
714
|
callback:(void (^)(NSError *error))callback;
|
|
707
|
-
- (NSError *)
|
|
715
|
+
- (NSError *)restartBridgeWithReason:(NSString *)reason;
|
|
708
716
|
- (void)unzipDownloadedPackage:(NSString *)zipFilePath
|
|
709
717
|
hash:(NSString *)hash
|
|
710
718
|
type:(PushyType)type
|
|
@@ -714,7 +722,7 @@ static void PushySwitchVersionLocked(NSString *hash) {
|
|
|
714
722
|
type:(PushyType)type
|
|
715
723
|
originHash:(NSString *)originHash
|
|
716
724
|
callback:(void (^)(NSError *error))callback;
|
|
717
|
-
- (void)
|
|
725
|
+
- (void)applyDeltaForHash:(NSString *)hash
|
|
718
726
|
type:(PushyType)type
|
|
719
727
|
fromBundle:(NSString *)bundleOrigin
|
|
720
728
|
source:(NSString *)sourceOrigin
|
|
@@ -738,16 +746,16 @@ static void PushySwitchVersionLocked(NSString *hash) {
|
|
|
738
746
|
@interface RCTPushyOrchestrator : NSObject
|
|
739
747
|
+ (void)persistConfiguration:(NSString *)config;
|
|
740
748
|
+ (BOOL)hasRunnableConfig;
|
|
741
|
-
+ (NSDictionary *)
|
|
749
|
+
+ (NSDictionary *)prepareBundle;
|
|
742
750
|
+ (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion;
|
|
743
751
|
#if TARGET_OS_TV && !DEBUG
|
|
744
752
|
+ (BOOL)restorePurgedLaunch:(NSString *)purgedVersion
|
|
745
753
|
rolledBack:(NSString *)launchRolledBackVersion;
|
|
746
754
|
#endif
|
|
747
|
-
+ (void)
|
|
755
|
+
+ (void)recordJsRound:(NSString *)config;
|
|
748
756
|
+ (void)startRoundWithDeadline:(NSTimeInterval)deadlineUptime;
|
|
749
757
|
+ (void)runOnce:(NSString *)launchRolledBackVersion deadline:(NSTimeInterval)deadlineUptime;
|
|
750
|
-
+ (void)
|
|
758
|
+
+ (void)runHoldRoundWithDeadline:(NSTimeInterval)deadlineUptime;
|
|
751
759
|
+ (BOOL)commitRoundWithGeneration:(uint64_t)generation
|
|
752
760
|
hashInfo:(NSDictionary *)hashInfoEntry
|
|
753
761
|
activate:(NSString *)hashToActivate
|
|
@@ -766,8 +774,8 @@ static std::atomic<bool> pushyRoundCompleted{false};
|
|
|
766
774
|
// Flipped the moment a crash is being held. JS is dead from that point on,
|
|
767
775
|
// so there is no second decision maker: the round force-activates whatever
|
|
768
776
|
// it downloads (§11.3).
|
|
769
|
-
static std::atomic<bool>
|
|
770
|
-
static std::atomic<bool>
|
|
777
|
+
static std::atomic<bool> pushyCrashHoldActive{false};
|
|
778
|
+
static std::atomic<bool> pushyHoldAttempted{false};
|
|
771
779
|
// Set when this process's round is the tvOS purged-version restore.
|
|
772
780
|
static std::atomic<bool> pushyPurgeRestoreActive{false};
|
|
773
781
|
// True while +bundleURL waits for that restore. Only then may the round
|
|
@@ -778,7 +786,7 @@ static std::atomic<bool> pushyPurgeRestoreActive{false};
|
|
|
778
786
|
// (and is left to JS) — never selected behind the packaged bundle's back.
|
|
779
787
|
static bool pushyPurgeRestoreWindowOpen = false;
|
|
780
788
|
static dispatch_semaphore_t pushyRoundDone;
|
|
781
|
-
static NSString *
|
|
789
|
+
static NSString *pushyLaunchRolledBackForHold = nil;
|
|
782
790
|
// A version this process downloaded but left for JS to activate. If the
|
|
783
791
|
// process then crashes, JS will never activate it — the crash handler
|
|
784
792
|
// activates it directly (bounded local work, no network). The generation is
|
|
@@ -796,27 +804,27 @@ static NSTimeInterval pushyProcessAnchorUptime = 0;
|
|
|
796
804
|
static NSString *pushyJsCompletedConfig = nil;
|
|
797
805
|
// The group supplements (rather than consumes) the crash-rescue semaphore.
|
|
798
806
|
static dispatch_group_t pushyHostRoundGroup;
|
|
799
|
-
static std::atomic<bool>
|
|
807
|
+
static std::atomic<bool> pushySyncReady{false};
|
|
800
808
|
static NSDictionary *pushyHostRoundResult = nil;
|
|
801
809
|
static NSString *pushyHostRoundConfig = nil;
|
|
802
810
|
static uint64_t pushyHostRoundGeneration = 0;
|
|
803
811
|
static std::atomic<uint64_t> pushyNativeConfigGeneration{0};
|
|
804
812
|
static uint64_t pushyHostRoundConfigGeneration = 0;
|
|
805
813
|
|
|
806
|
-
static const NSTimeInterval
|
|
807
|
-
static const NSTimeInterval
|
|
814
|
+
static const NSTimeInterval kPushyHoldTriggerUptime = 60;
|
|
815
|
+
static const NSTimeInterval kPushyHoldBudgetBackgroundThread = 10;
|
|
808
816
|
// A held main thread freezes UI teardown; stay well under the watchdog.
|
|
809
|
-
static const NSTimeInterval
|
|
817
|
+
static const NSTimeInterval kPushyHoldBudgetMainThread = 3.5;
|
|
810
818
|
// The purged-version restore blocks +bundleURL, usually inside
|
|
811
819
|
// application:didFinishLaunching: — stay clear of the launch watchdog.
|
|
812
820
|
static const NSTimeInterval kPushyPurgeRestoreBudget = 12;
|
|
813
821
|
|
|
814
|
-
static void
|
|
822
|
+
static void PushyMaybeHoldProcess(void) {
|
|
815
823
|
// Once per process; a second crashing thread passes straight through
|
|
816
824
|
// instead of waiting behind the first (§11.3: prefer under-rescuing over
|
|
817
825
|
// wedging the teardown).
|
|
818
826
|
bool expected = false;
|
|
819
|
-
if (!
|
|
827
|
+
if (!pushyHoldAttempted.compare_exchange_strong(expected, true)) {
|
|
820
828
|
return;
|
|
821
829
|
}
|
|
822
830
|
NSTimeInterval uptime = PushyMonotonicNow() - pushyProcessAnchorUptime;
|
|
@@ -824,14 +832,14 @@ static void PushyMaybeHoldForRescue(void) {
|
|
|
824
832
|
// Early crashes are the brick signature; a crash with an in-flight round
|
|
825
833
|
// is worth finishing regardless of uptime. Everything else is an ordinary
|
|
826
834
|
// crash whose UX must not be delayed.
|
|
827
|
-
if (uptime >=
|
|
835
|
+
if (uptime >= kPushyHoldTriggerUptime && !roundInFlight) {
|
|
828
836
|
return;
|
|
829
837
|
}
|
|
830
838
|
NSTimeInterval budget = [NSThread isMainThread]
|
|
831
|
-
?
|
|
832
|
-
:
|
|
839
|
+
? kPushyHoldBudgetMainThread
|
|
840
|
+
: kPushyHoldBudgetBackgroundThread;
|
|
833
841
|
NSTimeInterval deadline = PushyMonotonicNow() + budget;
|
|
834
|
-
NSLog(@"RCTPushy -- crash
|
|
842
|
+
NSLog(@"RCTPushy -- crash hold: holding process for up to %.1fs "
|
|
835
843
|
@"(uptime %.1fs)", budget, uptime);
|
|
836
844
|
|
|
837
845
|
// The rescue runs on its own queue and the dying thread only waits with a
|
|
@@ -841,15 +849,15 @@ static void PushyMaybeHoldForRescue(void) {
|
|
|
841
849
|
dispatch_semaphore_t done = dispatch_semaphore_create(0);
|
|
842
850
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
843
851
|
@try {
|
|
844
|
-
[RCTPushyOrchestrator
|
|
852
|
+
[RCTPushyOrchestrator runHoldRoundWithDeadline:deadline];
|
|
845
853
|
} @catch (NSException *exception) {
|
|
846
|
-
NSLog(@"RCTPushy -- crash
|
|
854
|
+
NSLog(@"RCTPushy -- crash hold failed: %@", exception.reason);
|
|
847
855
|
}
|
|
848
856
|
dispatch_semaphore_signal(done);
|
|
849
857
|
});
|
|
850
858
|
if (dispatch_semaphore_wait(done, dispatch_time(DISPATCH_TIME_NOW,
|
|
851
859
|
(int64_t)(budget * NSEC_PER_SEC))) != 0) {
|
|
852
|
-
NSLog(@"RCTPushy -- crash
|
|
860
|
+
NSLog(@"RCTPushy -- crash hold: budget exhausted, letting go");
|
|
853
861
|
}
|
|
854
862
|
}
|
|
855
863
|
|
|
@@ -858,9 +866,9 @@ static void PushyMaybeHoldForRescue(void) {
|
|
|
858
866
|
// process is still alive and JS will never run again — a natural,
|
|
859
867
|
// false-positive-free window to finish the cold-start check. The previous
|
|
860
868
|
// handler (crash reporters chain the same way) always runs afterwards.
|
|
861
|
-
static void
|
|
869
|
+
static void PushyCrashHoldExceptionHandler(NSException *exception) {
|
|
862
870
|
@try {
|
|
863
|
-
|
|
871
|
+
PushyMaybeHoldProcess();
|
|
864
872
|
} @catch (NSException *inner) {
|
|
865
873
|
// The dying process owes the previous handler its turn no matter
|
|
866
874
|
// what the rescue did.
|
|
@@ -870,11 +878,11 @@ static void PushyCrashRescueExceptionHandler(NSException *exception) {
|
|
|
870
878
|
}
|
|
871
879
|
}
|
|
872
880
|
|
|
873
|
-
static void
|
|
881
|
+
static void PushyInstallCrashHoldHandler(void) {
|
|
874
882
|
static dispatch_once_t onceToken;
|
|
875
883
|
dispatch_once(&onceToken, ^{
|
|
876
884
|
pushyPreviousExceptionHandler = NSGetUncaughtExceptionHandler();
|
|
877
|
-
NSSetUncaughtExceptionHandler(&
|
|
885
|
+
NSSetUncaughtExceptionHandler(&PushyCrashHoldExceptionHandler);
|
|
878
886
|
});
|
|
879
887
|
}
|
|
880
888
|
|
|
@@ -1104,11 +1112,11 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
1104
1112
|
}
|
|
1105
1113
|
|
|
1106
1114
|
+ (void)configure:(NSDictionary<NSString *, id> *)options
|
|
1107
|
-
completion:(
|
|
1115
|
+
completion:(RCTPushyConfigurationCompletion)completion
|
|
1108
1116
|
{
|
|
1109
1117
|
NSError *validationError = nil;
|
|
1110
1118
|
// Snapshot nested mutable caller values before crossing a queue boundary.
|
|
1111
|
-
NSString *config =
|
|
1119
|
+
NSString *config = RCTPushyNormalizeConfiguration(options, &validationError);
|
|
1112
1120
|
static dispatch_queue_t configQueue;
|
|
1113
1121
|
static dispatch_once_t once;
|
|
1114
1122
|
dispatch_once(&once, ^{
|
|
@@ -1132,19 +1140,19 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
1132
1140
|
});
|
|
1133
1141
|
}
|
|
1134
1142
|
|
|
1135
|
-
+ (void)
|
|
1143
|
+
+ (void)prepareBundleWithCompletion:(RCTPushyBundlePreparationCompletion)completion
|
|
1136
1144
|
{
|
|
1137
1145
|
static dispatch_queue_t hostQueue;
|
|
1138
1146
|
static dispatch_once_t once;
|
|
1139
1147
|
dispatch_once(&once, ^{
|
|
1140
|
-
hostQueue = dispatch_queue_create("cn.reactnative.pushy.host-
|
|
1148
|
+
hostQueue = dispatch_queue_create("cn.reactnative.pushy.host-sync", DISPATCH_QUEUE_SERIAL);
|
|
1141
1149
|
});
|
|
1142
1150
|
dispatch_async(hostQueue, ^{
|
|
1143
1151
|
NSDictionary *result;
|
|
1144
1152
|
@try {
|
|
1145
|
-
result = [RCTPushyOrchestrator
|
|
1153
|
+
result = [RCTPushyOrchestrator prepareBundle];
|
|
1146
1154
|
} @catch (NSException *exception) {
|
|
1147
|
-
RCTLogWarn(@"RCTPushy -- native host
|
|
1155
|
+
RCTLogWarn(@"RCTPushy -- native host sync failed: %@", exception.reason);
|
|
1148
1156
|
result = PushyHostResult(@"failed", @"internal_error", nil, NO);
|
|
1149
1157
|
}
|
|
1150
1158
|
if (completion != nil) {
|
|
@@ -1286,7 +1294,7 @@ RCT_EXPORT_METHOD(markJsCheckCompleted:(NSString *)config
|
|
|
1286
1294
|
PushyRejectError(reject, PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
1287
1295
|
return;
|
|
1288
1296
|
}
|
|
1289
|
-
[RCTPushyOrchestrator
|
|
1297
|
+
[RCTPushyOrchestrator recordJsRound:config];
|
|
1290
1298
|
resolve(@true);
|
|
1291
1299
|
}
|
|
1292
1300
|
|
|
@@ -1328,21 +1336,21 @@ RCT_EXPORT_METHOD(downloadFullUpdate:(NSDictionary *)options
|
|
|
1328
1336
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1329
1337
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1330
1338
|
{
|
|
1331
|
-
[self
|
|
1339
|
+
[self fetchPackage:PushyTypeFullDownload options:options resolver:resolve rejecter:reject];
|
|
1332
1340
|
}
|
|
1333
1341
|
|
|
1334
1342
|
RCT_EXPORT_METHOD(downloadPatchFromPackage:(NSDictionary *)options
|
|
1335
1343
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1336
1344
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1337
1345
|
{
|
|
1338
|
-
[self
|
|
1346
|
+
[self fetchPackage:PushyTypePatchFromPackage options:options resolver:resolve rejecter:reject];
|
|
1339
1347
|
}
|
|
1340
1348
|
|
|
1341
1349
|
RCT_EXPORT_METHOD(downloadPatchFromPpk:(NSDictionary *)options
|
|
1342
1350
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1343
1351
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1344
1352
|
{
|
|
1345
|
-
[self
|
|
1353
|
+
[self fetchPackage:PushyTypePatchFromPpk options:options resolver:resolve rejecter:reject];
|
|
1346
1354
|
}
|
|
1347
1355
|
|
|
1348
1356
|
RCT_EXPORT_METHOD(downloadAndInstallApk:(NSDictionary *)options
|
|
@@ -1377,7 +1385,7 @@ RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
|
|
|
1377
1385
|
return;
|
|
1378
1386
|
}
|
|
1379
1387
|
|
|
1380
|
-
NSError *reloadError = [self
|
|
1388
|
+
NSError *reloadError = [self restartBridgeWithReason:@"pushy restart"];
|
|
1381
1389
|
if (reloadError != nil) {
|
|
1382
1390
|
PushyRejectError(reject, reloadError);
|
|
1383
1391
|
return;
|
|
@@ -1388,7 +1396,7 @@ RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
|
|
|
1388
1396
|
RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
|
|
1389
1397
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1390
1398
|
{
|
|
1391
|
-
NSError *reloadError = [self
|
|
1399
|
+
NSError *reloadError = [self restartBridgeWithReason:@"pushy restartApp"];
|
|
1392
1400
|
if (reloadError != nil) {
|
|
1393
1401
|
PushyRejectError(reject, reloadError);
|
|
1394
1402
|
return;
|
|
@@ -1499,7 +1507,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1499
1507
|
// promise settles here, after the cleanup, so a failed wipe reaches
|
|
1500
1508
|
// JS as RESET_FAILED (Android parity) instead of a warning nobody
|
|
1501
1509
|
// aggregates.
|
|
1502
|
-
pushy::
|
|
1510
|
+
pushy::delta::Status status = pushy::delta::CleanupOldEntries(
|
|
1503
1511
|
PushyToStdString([RCTPushy downloadDir]),
|
|
1504
1512
|
std::vector<std::string>{PushyToStdString(keepVersion)},
|
|
1505
1513
|
0
|
|
@@ -1533,12 +1541,12 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1533
1541
|
hasListeners = NO;
|
|
1534
1542
|
}
|
|
1535
1543
|
|
|
1536
|
-
- (void)
|
|
1544
|
+
- (void)fetchPackage:(PushyType)type
|
|
1537
1545
|
options:(NSDictionary *)options
|
|
1538
1546
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1539
1547
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
1540
1548
|
{
|
|
1541
|
-
[self
|
|
1549
|
+
[self performFetch:type options:options callback:^(NSError *error) {
|
|
1542
1550
|
if (error != nil) {
|
|
1543
1551
|
if (error.userInfo[PushyErrorCodeKey] == nil) {
|
|
1544
1552
|
// Unclassified (system/network) errors from the download
|
|
@@ -1560,7 +1568,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1560
1568
|
// here: it consumes one-shot launch state (first_time, the first-load mark,
|
|
1561
1569
|
// ignoreRollback), and consuming it for a reload that never happens would
|
|
1562
1570
|
// make the next cold start roll the freshly switched version back.
|
|
1563
|
-
- (NSError *)
|
|
1571
|
+
- (NSError *)restartBridgeWithReason:(NSString *)reason
|
|
1564
1572
|
{
|
|
1565
1573
|
#if PUSHY_HAS_RELOAD_COMMAND
|
|
1566
1574
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -1575,7 +1583,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1575
1583
|
RCTBridge *bridge = self.bridge;
|
|
1576
1584
|
if (bridge == nil || object_getClass(bridge) == NSClassFromString(@"RCTBridgeProxy")) {
|
|
1577
1585
|
return PushyErrorWithCode(pushy::error_codes::kRestartFailed,
|
|
1578
|
-
@"no bridge available to
|
|
1586
|
+
@"no bridge available to restart (bridgeless host without the RN restart command)");
|
|
1579
1587
|
}
|
|
1580
1588
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1581
1589
|
[bridge reload];
|
|
@@ -1584,9 +1592,9 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1584
1592
|
#endif
|
|
1585
1593
|
}
|
|
1586
1594
|
|
|
1587
|
-
- (void)
|
|
1595
|
+
- (void)performFetch:(PushyType)type options:(NSDictionary *)options callback:(void (^)(NSError *error))callback
|
|
1588
1596
|
{
|
|
1589
|
-
NSString *updateUrl = PushyOptionString(options,
|
|
1597
|
+
NSString *updateUrl = PushyOptionString(options, PushyOptionUrlKey);
|
|
1590
1598
|
NSString *hash = PushyOptionString(options, @"hash");
|
|
1591
1599
|
|
|
1592
1600
|
if (PushyStringIsBlank(updateUrl) || !PushyIsSafePathComponent(hash)) {
|
|
@@ -1632,7 +1640,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1632
1640
|
}
|
|
1633
1641
|
};
|
|
1634
1642
|
void (^deferredStart)(void) = ^{
|
|
1635
|
-
[self
|
|
1643
|
+
[self performFetch:type options:options callback:callback];
|
|
1636
1644
|
};
|
|
1637
1645
|
PushyDownloadRegistration registration = PushyRegisterDownload(
|
|
1638
1646
|
hash, type, deadlineUptime, callback, progress, deferredStart);
|
|
@@ -1669,7 +1677,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1669
1677
|
// version directory in one atomic step.
|
|
1670
1678
|
NSString *bundlePath = [staging stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
1671
1679
|
if (![fileManager fileExistsAtPath:bundlePath]) {
|
|
1672
|
-
finalError = PushyErrorWithCode(pushy::error_codes::
|
|
1680
|
+
finalError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
1673
1681
|
@"bundle missing after install");
|
|
1674
1682
|
} else {
|
|
1675
1683
|
std::string bundleSha256 = pushy::digest::Sha256File(PushyToStdString(bundlePath));
|
|
@@ -1768,8 +1776,8 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1768
1776
|
} @catch (NSException *exception) {
|
|
1769
1777
|
// An uncaught exception in a GCD block is fatal (and would
|
|
1770
1778
|
// trip the crash rescue); a bad package is a PATCH_FAILED.
|
|
1771
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1772
|
-
exception.reason ?: @"
|
|
1779
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
1780
|
+
exception.reason ?: @"delta failed"));
|
|
1773
1781
|
}
|
|
1774
1782
|
});
|
|
1775
1783
|
}];
|
|
@@ -1782,7 +1790,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1782
1790
|
{
|
|
1783
1791
|
switch (type) {
|
|
1784
1792
|
case PushyTypePatchFromPackage:
|
|
1785
|
-
[self
|
|
1793
|
+
[self applyDeltaForHash:hash
|
|
1786
1794
|
type:type
|
|
1787
1795
|
fromBundle:[[RCTPushy binaryBundleURL] path]
|
|
1788
1796
|
source:[[NSBundle mainBundle] resourcePath]
|
|
@@ -1790,7 +1798,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1790
1798
|
return;
|
|
1791
1799
|
case PushyTypePatchFromPpk: {
|
|
1792
1800
|
NSString *lastVersionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:originHash];
|
|
1793
|
-
[self
|
|
1801
|
+
[self applyDeltaForHash:hash
|
|
1794
1802
|
type:type
|
|
1795
1803
|
fromBundle:[lastVersionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]
|
|
1796
1804
|
source:lastVersionDir
|
|
@@ -1803,7 +1811,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1803
1811
|
}
|
|
1804
1812
|
}
|
|
1805
1813
|
|
|
1806
|
-
- (void)
|
|
1814
|
+
- (void)applyDeltaForHash:(NSString *)hash
|
|
1807
1815
|
type:(PushyType)type
|
|
1808
1816
|
fromBundle:(NSString *)bundleOrigin
|
|
1809
1817
|
source:(NSString *)sourceOrigin
|
|
@@ -1818,13 +1826,13 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1818
1826
|
NSString *destination = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
1819
1827
|
long long manifestBytes = RCTPushyFileSize(sourcePatch);
|
|
1820
1828
|
if (manifestBytes > pushy::archive_limits::kMaxManifestBytes) {
|
|
1821
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1822
|
-
[NSString stringWithFormat:@"
|
|
1829
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
1830
|
+
[NSString stringWithFormat:@"delta manifest too large: %lld bytes", manifestBytes]));
|
|
1823
1831
|
return;
|
|
1824
1832
|
}
|
|
1825
1833
|
NSData *data = [NSData dataWithContentsOfFile:sourcePatch];
|
|
1826
1834
|
if (data == nil) {
|
|
1827
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1835
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @"missing delta manifest"));
|
|
1828
1836
|
return;
|
|
1829
1837
|
}
|
|
1830
1838
|
|
|
@@ -1833,19 +1841,19 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1833
1841
|
if (error != nil) {
|
|
1834
1842
|
// Classify as a patch failure like the sibling manifest branches;
|
|
1835
1843
|
// unclassified errors would otherwise be tagged DOWNLOAD_FAILED by the
|
|
1836
|
-
//
|
|
1837
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1844
|
+
// fetchPackage fallback even though the download itself succeeded.
|
|
1845
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed, error.localizedDescription));
|
|
1838
1846
|
return;
|
|
1839
1847
|
}
|
|
1840
1848
|
if (![jsonObject isKindOfClass:[NSDictionary class]]) {
|
|
1841
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1849
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @"invalid delta manifest"));
|
|
1842
1850
|
return;
|
|
1843
1851
|
}
|
|
1844
1852
|
NSDictionary *json = (NSDictionary *)jsonObject;
|
|
1845
|
-
pushy::
|
|
1853
|
+
pushy::delta::PatchManifest manifest;
|
|
1846
1854
|
NSString *manifestReason = nil;
|
|
1847
1855
|
if (!PushyPatchManifestFromJson(json, &manifest, &manifestReason)) {
|
|
1848
|
-
callback(PushyErrorWithCode(pushy::error_codes::
|
|
1856
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed, manifestReason));
|
|
1849
1857
|
return;
|
|
1850
1858
|
}
|
|
1851
1859
|
|
|
@@ -1858,7 +1866,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1858
1866
|
}
|
|
1859
1867
|
|
|
1860
1868
|
pushy::archive_patch::ArchivePatchPlan plan;
|
|
1861
|
-
pushy::
|
|
1869
|
+
pushy::delta::Status planStatus = pushy::archive_patch::BuildArchivePatchPlan(
|
|
1862
1870
|
type == PushyTypePatchFromPackage
|
|
1863
1871
|
? pushy::archive_patch::ArchivePatchType::kPatchFromPackage
|
|
1864
1872
|
: pushy::archive_patch::ArchivePatchType::kPatchFromPpk,
|
|
@@ -1871,8 +1879,8 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1871
1879
|
return;
|
|
1872
1880
|
}
|
|
1873
1881
|
|
|
1874
|
-
pushy::
|
|
1875
|
-
pushy::
|
|
1882
|
+
pushy::delta::FileSourcePatchOptions options;
|
|
1883
|
+
pushy::delta::Status optionStatus = pushy::archive_patch::BuildFileSourcePatchOptions(
|
|
1876
1884
|
plan,
|
|
1877
1885
|
PushyToStdString(sourceOrigin),
|
|
1878
1886
|
PushyToStdString(unzipDir),
|
|
@@ -1901,7 +1909,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1901
1909
|
}
|
|
1902
1910
|
}
|
|
1903
1911
|
|
|
1904
|
-
pushy::
|
|
1912
|
+
pushy::delta::Status status = pushy::delta::ApplyPatchFromFileSource(options);
|
|
1905
1913
|
if (!status.ok) {
|
|
1906
1914
|
callback(PushyNSErrorFromStatus(status));
|
|
1907
1915
|
return;
|
|
@@ -2025,7 +2033,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
2025
2033
|
long long archiveBytes = RCTPushyFileSize(path);
|
|
2026
2034
|
NSError *preflight = nil;
|
|
2027
2035
|
if (archiveBytes > pushy::archive_limits::kMaxArchiveBytes) {
|
|
2028
|
-
preflight = PushyErrorWithCode(pushy::error_codes::
|
|
2036
|
+
preflight = PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
2029
2037
|
[NSString stringWithFormat:@"archive too large: %lld bytes", archiveBytes]);
|
|
2030
2038
|
} else {
|
|
2031
2039
|
preflight = PushyEnsureFreeSpace(destination, MAX(0LL, archiveBytes) * 2);
|
|
@@ -2063,15 +2071,15 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
2063
2071
|
|
|
2064
2072
|
NSError *unzipError = error;
|
|
2065
2073
|
if (guard.violation != nil) {
|
|
2066
|
-
unzipError = PushyErrorWithCode(pushy::error_codes::
|
|
2074
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed, guard.violation);
|
|
2067
2075
|
} else if (!succeeded && unzipError == nil) {
|
|
2068
|
-
unzipError = PushyErrorWithCode(pushy::error_codes::
|
|
2076
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @"unzip failed");
|
|
2069
2077
|
} else if (unzipError != nil && unzipError.userInfo[PushyErrorCodeKey] == nil) {
|
|
2070
2078
|
// SSZipArchive's own NSError (corrupt zip, bad magic, ...) has
|
|
2071
|
-
// no stable code; without one,
|
|
2079
|
+
// no stable code; without one, fetchPackage's fallback would
|
|
2072
2080
|
// classify it as DOWNLOAD_FAILED even though the download
|
|
2073
2081
|
// succeeded — keep the classification deterministic.
|
|
2074
|
-
unzipError = PushyErrorWithCode(pushy::error_codes::
|
|
2082
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
2075
2083
|
unzipError.localizedDescription ?: @"unzip failed");
|
|
2076
2084
|
}
|
|
2077
2085
|
completionHandler(unzipError);
|
|
@@ -2095,7 +2103,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
2095
2103
|
// process booted from: two switches without a restart would otherwise
|
|
2096
2104
|
// evict the running bundle while its on-demand assets are still
|
|
2097
2105
|
// being served.
|
|
2098
|
-
pushy::
|
|
2106
|
+
pushy::delta::Status status = pushy::delta::CleanupOldEntries(
|
|
2099
2107
|
PushyToStdString(downloadDir),
|
|
2100
2108
|
std::vector<std::string>{
|
|
2101
2109
|
state.current_version,
|
|
@@ -2114,9 +2122,9 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
2114
2122
|
{
|
|
2115
2123
|
switch (type) {
|
|
2116
2124
|
case PushyTypePatchFromPackage:
|
|
2117
|
-
return
|
|
2125
|
+
return PushyIpaDeltaSuffix;
|
|
2118
2126
|
case PushyTypePatchFromPpk:
|
|
2119
|
-
return
|
|
2127
|
+
return PushyPpkDeltaSuffix;
|
|
2120
2128
|
case PushyTypeFullDownload:
|
|
2121
2129
|
break;
|
|
2122
2130
|
}
|
|
@@ -2210,10 +2218,10 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
2210
2218
|
// NSAllowsArbitraryLoads. Same rule as RCTPushyDownloader. Returning nil
|
|
2211
2219
|
// delivers the 3xx itself as the final response, which the status check in
|
|
2212
2220
|
// PushyHttpRequest then treats as a failed endpoint.
|
|
2213
|
-
@interface
|
|
2221
|
+
@interface PushyRequestRedirectGuard : NSObject <NSURLSessionTaskDelegate>
|
|
2214
2222
|
@end
|
|
2215
2223
|
|
|
2216
|
-
@implementation
|
|
2224
|
+
@implementation PushyRequestRedirectGuard
|
|
2217
2225
|
|
|
2218
2226
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
|
|
2219
2227
|
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|
@@ -2255,7 +2263,7 @@ static NSString *PushyHttpRequest(NSString *urlString, NSString *method,
|
|
|
2255
2263
|
// final response.
|
|
2256
2264
|
NSURLSession *session = [NSURLSession
|
|
2257
2265
|
sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
|
|
2258
|
-
delegate:[
|
|
2266
|
+
delegate:[PushyRequestRedirectGuard new]
|
|
2259
2267
|
delegateQueue:nil];
|
|
2260
2268
|
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
|
|
2261
2269
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
@@ -2290,13 +2298,13 @@ static NSString *PushyNormalizeEndpointBase(NSString *base) {
|
|
|
2290
2298
|
return base;
|
|
2291
2299
|
}
|
|
2292
2300
|
|
|
2293
|
-
static BOOL
|
|
2301
|
+
static BOOL PushyIsValidResponse(NSString *responseText) {
|
|
2294
2302
|
if (responseText == nil) {
|
|
2295
2303
|
return NO;
|
|
2296
2304
|
}
|
|
2297
|
-
// Shared schema rule (update_flow_core::
|
|
2305
|
+
// Shared schema rule (update_flow_core::IsValidResponse): a 200 with
|
|
2298
2306
|
// `{"error": ...}` is a failed endpoint, not a verdict.
|
|
2299
|
-
return
|
|
2307
|
+
return flowcore::IsValidResponse(PushyToStdString(responseText)) ? YES : NO;
|
|
2300
2308
|
}
|
|
2301
2309
|
|
|
2302
2310
|
@implementation RCTPushyOrchestrator
|
|
@@ -2316,10 +2324,10 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2316
2324
|
pushyNativeConfigGeneration.fetch_add(1);
|
|
2317
2325
|
[defaults setObject:config forKey:keyNativeConfig];
|
|
2318
2326
|
[defaults removeObjectForKey:keyNativeCheckCache];
|
|
2319
|
-
[self
|
|
2327
|
+
[self recordJsRound:nil];
|
|
2320
2328
|
});
|
|
2321
|
-
if (
|
|
2322
|
-
|
|
2329
|
+
if (pushySyncReady.load() && [self hasRunnableConfig]) {
|
|
2330
|
+
PushyInstallCrashHoldHandler();
|
|
2323
2331
|
}
|
|
2324
2332
|
}
|
|
2325
2333
|
|
|
@@ -2343,12 +2351,12 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2343
2351
|
pushyHostRoundGroup = dispatch_group_create();
|
|
2344
2352
|
dispatch_group_enter(pushyHostRoundGroup);
|
|
2345
2353
|
pushyProcessAnchorUptime = PushyMonotonicNow();
|
|
2346
|
-
|
|
2347
|
-
|
|
2354
|
+
pushyLaunchRolledBackForHold = [launchRolledBackVersion copy];
|
|
2355
|
+
pushySyncReady.store(true);
|
|
2348
2356
|
// The crash-hold rescue shares the orchestrator's rollout gate: no
|
|
2349
2357
|
// persisted config, no handler (§11.3).
|
|
2350
2358
|
if ([PushyDefaults() stringForKey:keyNativeConfig].length > 0) {
|
|
2351
|
-
|
|
2359
|
+
PushyInstallCrashHoldHandler();
|
|
2352
2360
|
}
|
|
2353
2361
|
});
|
|
2354
2362
|
}
|
|
@@ -2367,10 +2375,10 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2367
2375
|
[defaults objectForKey:keyNativeCheckIncomplete] != nil ? 0 : 5;
|
|
2368
2376
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delaySeconds * NSEC_PER_SEC),
|
|
2369
2377
|
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
2370
|
-
if ([self
|
|
2378
|
+
if ([self hasJsRound]) {
|
|
2371
2379
|
// Not consuming the round: a later crash rescue may still
|
|
2372
2380
|
// need it.
|
|
2373
|
-
NSLog(@"RCTPushy -- native
|
|
2381
|
+
NSLog(@"RCTPushy -- native sync skipped: JS already queried in this process");
|
|
2374
2382
|
return;
|
|
2375
2383
|
}
|
|
2376
2384
|
[self startRoundWithDeadline:0];
|
|
@@ -2392,7 +2400,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2392
2400
|
rolledBack:(NSString *)launchRolledBackVersion {
|
|
2393
2401
|
[self prepareProcess:launchRolledBackVersion];
|
|
2394
2402
|
if (![self hasRunnableConfig]) {
|
|
2395
|
-
RCTLogWarn(@"RCTPushy -- version %@ was purged and no native
|
|
2403
|
+
RCTLogWarn(@"RCTPushy -- version %@ was purged and no native sync is "
|
|
2396
2404
|
@"configured; launching the packaged bundle", purgedVersion);
|
|
2397
2405
|
return NO;
|
|
2398
2406
|
}
|
|
@@ -2425,11 +2433,11 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2425
2433
|
}
|
|
2426
2434
|
#endif
|
|
2427
2435
|
|
|
2428
|
-
+ (NSDictionary *)
|
|
2436
|
+
+ (NSDictionary *)prepareBundle {
|
|
2429
2437
|
#if DEBUG
|
|
2430
2438
|
return PushyHostResult(@"skipped", @"debug", nil, NO);
|
|
2431
2439
|
#else
|
|
2432
|
-
if (!
|
|
2440
|
+
if (!pushySyncReady.load()) {
|
|
2433
2441
|
return PushyHostResult(@"skipped", @"not_initialized", nil, NO);
|
|
2434
2442
|
}
|
|
2435
2443
|
NSString *configJson = [PushyDefaults() stringForKey:keyNativeConfig];
|
|
@@ -2468,7 +2476,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2468
2476
|
#endif
|
|
2469
2477
|
}
|
|
2470
2478
|
|
|
2471
|
-
+ (void)
|
|
2479
|
+
+ (void)recordJsRound:(NSString *)config {
|
|
2472
2480
|
@synchronized (RCTPushyOrchestrator.class) {
|
|
2473
2481
|
pushyJsCompletedConfig = [config copy];
|
|
2474
2482
|
}
|
|
@@ -2478,7 +2486,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2478
2486
|
// exact config the native round would use (§10.3): the delayed round is
|
|
2479
2487
|
// then a duplicate request. Only the scheduled round asks — the crash-rescue
|
|
2480
2488
|
// path still runs, JS is dead by then.
|
|
2481
|
-
+ (BOOL)
|
|
2489
|
+
+ (BOOL)hasJsRound {
|
|
2482
2490
|
NSString *jsConfig;
|
|
2483
2491
|
@synchronized (RCTPushyOrchestrator.class) {
|
|
2484
2492
|
jsConfig = pushyJsCompletedConfig;
|
|
@@ -2503,10 +2511,10 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2503
2511
|
return;
|
|
2504
2512
|
}
|
|
2505
2513
|
@try {
|
|
2506
|
-
[self runOnce:
|
|
2514
|
+
[self runOnce:pushyLaunchRolledBackForHold deadline:deadlineUptime];
|
|
2507
2515
|
} @catch (NSException *exception) {
|
|
2508
2516
|
// The rescue path must never take the app down with it.
|
|
2509
|
-
RCTLogWarn(@"RCTPushy -- native
|
|
2517
|
+
RCTLogWarn(@"RCTPushy -- native sync crashed: %@", exception.reason);
|
|
2510
2518
|
pushyHostRoundResult = PushyHostResult(@"failed", @"internal_error", nil, NO);
|
|
2511
2519
|
} @finally {
|
|
2512
2520
|
pushyRoundCompleted.store(true);
|
|
@@ -2520,8 +2528,8 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2520
2528
|
// process's round runs to completion within the budget, then activates a
|
|
2521
2529
|
// downloaded-but-unactivated version if one exists — the last chance before
|
|
2522
2530
|
// the process is gone.
|
|
2523
|
-
+ (void)
|
|
2524
|
-
|
|
2531
|
+
+ (void)runHoldRoundWithDeadline:(NSTimeInterval)deadlineUptime {
|
|
2532
|
+
pushyCrashHoldActive.store(true);
|
|
2525
2533
|
[self startRoundWithDeadline:deadlineUptime];
|
|
2526
2534
|
if (pushyRoundStarted.load() && !pushyRoundCompleted.load()) {
|
|
2527
2535
|
NSTimeInterval remaining = deadlineUptime - PushyMonotonicNow();
|
|
@@ -2552,7 +2560,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2552
2560
|
// catch below anyway, or by cleanup).
|
|
2553
2561
|
NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash];
|
|
2554
2562
|
if (!PushyHasCompletedVersionAtPath(versionDir, hash)) {
|
|
2555
|
-
NSLog(@"RCTPushy -- crash
|
|
2563
|
+
NSLog(@"RCTPushy -- crash hold: version %@ no longer on disk, dropping activation", hash);
|
|
2556
2564
|
return;
|
|
2557
2565
|
}
|
|
2558
2566
|
NSDictionary *hashInfoEntry = nil;
|
|
@@ -2562,7 +2570,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2562
2570
|
[NSJSONSerialization JSONObjectWithData:existingData options:0 error:nil];
|
|
2563
2571
|
if ([parsed isKindOfClass:[NSDictionary class]]) {
|
|
2564
2572
|
NSMutableDictionary *merged = [parsed mutableCopy];
|
|
2565
|
-
merged[
|
|
2573
|
+
merged[PushyInfoCrashHold] = @YES;
|
|
2566
2574
|
hashInfoEntry = @{@"hash": hash, @"info": merged};
|
|
2567
2575
|
}
|
|
2568
2576
|
BOOL committed = [self commitRoundWithGeneration:generation
|
|
@@ -2577,9 +2585,9 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2577
2585
|
@synchronized (self) {
|
|
2578
2586
|
pushyUnactivatedHash = nil;
|
|
2579
2587
|
}
|
|
2580
|
-
NSLog(@"RCTPushy -- crash
|
|
2588
|
+
NSLog(@"RCTPushy -- crash hold: activated downloaded version %@", hash);
|
|
2581
2589
|
} else {
|
|
2582
|
-
NSLog(@"RCTPushy -- crash
|
|
2590
|
+
NSLog(@"RCTPushy -- crash hold: reset since download, dropping activation");
|
|
2583
2591
|
}
|
|
2584
2592
|
}
|
|
2585
2593
|
|
|
@@ -2691,28 +2699,28 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2691
2699
|
// shared with JS getBundleHash) on the ordinary delayed round.
|
|
2692
2700
|
input.Set("bundleHash",
|
|
2693
2701
|
flowjson::Value::String(PushyToStdString(
|
|
2694
|
-
PushyBundleHashSync(!
|
|
2702
|
+
PushyBundleHashSync(!pushyCrashHoldActive.load()))));
|
|
2695
2703
|
|
|
2696
2704
|
std::string bodyJson =
|
|
2697
|
-
flowjson::Stringify(
|
|
2705
|
+
flowjson::Stringify(flowcore::BuildRequestBody(input));
|
|
2698
2706
|
NSString *body = [NSString stringWithUTF8String:bodyJson.c_str()];
|
|
2699
2707
|
if (body == nil) {
|
|
2700
|
-
RCTLogWarn(@"RCTPushy -- native
|
|
2708
|
+
RCTLogWarn(@"RCTPushy -- native sync: request body is not valid UTF-8");
|
|
2701
2709
|
pushyHostRoundResult = PushyHostResult(@"failed", @"invalid_request", nil, NO);
|
|
2702
2710
|
return;
|
|
2703
2711
|
}
|
|
2704
2712
|
|
|
2705
|
-
NSString *responseText = [self
|
|
2713
|
+
NSString *responseText = [self runQueryRequest:config
|
|
2706
2714
|
appKey:appKey
|
|
2707
2715
|
body:body
|
|
2708
2716
|
deadline:deadlineUptime];
|
|
2709
2717
|
if (responseText == nil) {
|
|
2710
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
2718
|
+
RCTLogInfo(@"RCTPushy -- native sync: no endpoint reachable, giving up until next launch");
|
|
2711
2719
|
return;
|
|
2712
2720
|
}
|
|
2713
2721
|
long long responseAtSeconds = (long long)[[NSDate date] timeIntervalSince1970];
|
|
2714
2722
|
|
|
2715
|
-
flowjson::Value decision =
|
|
2723
|
+
flowjson::Value decision = flowcore::HandleResponse(
|
|
2716
2724
|
PushyToStdString(responseText), identity, false,
|
|
2717
2725
|
config.Get("afterDownload").AsString());
|
|
2718
2726
|
if (decision.Get("action").AsString() != "download") {
|
|
@@ -2725,9 +2733,9 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2725
2733
|
responseAt:responseAtSeconds
|
|
2726
2734
|
activated:NULL];
|
|
2727
2735
|
pushyHostRoundResult = committed
|
|
2728
|
-
? PushyHostResult(
|
|
2736
|
+
? PushyHostResult(PushyStatusNone, PushyFromStdString(decision.Get("reason").AsString()), nil, NO)
|
|
2729
2737
|
: PushyHostResult(@"cancelled", @"reset", nil, NO);
|
|
2730
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
2738
|
+
RCTLogInfo(@"RCTPushy -- native sync: nothing to do (%s)",
|
|
2731
2739
|
decision.Get("reason").AsString().c_str());
|
|
2732
2740
|
return;
|
|
2733
2741
|
}
|
|
@@ -2774,10 +2782,10 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2774
2782
|
// survives to markSuccess. Only the server-sent directive counts — a
|
|
2775
2783
|
// silent-strategy activation is ordinary delivery.
|
|
2776
2784
|
if (info.Get("config").Get("forceBoot").Truthy()) {
|
|
2777
|
-
versionInfo[
|
|
2785
|
+
versionInfo[PushyInfoForceBoot] = @YES;
|
|
2778
2786
|
}
|
|
2779
|
-
if (
|
|
2780
|
-
versionInfo[
|
|
2787
|
+
if (pushyCrashHoldActive.load()) {
|
|
2788
|
+
versionInfo[PushyInfoCrashHold] = @YES;
|
|
2781
2789
|
}
|
|
2782
2790
|
// Silent strategies or a server-marked forceBoot version (per-version
|
|
2783
2791
|
// remote override — the brick rescue) activate for the next launch;
|
|
@@ -2786,7 +2794,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2786
2794
|
// forever (§11.3). Or the launch is blocked on reinstalling a version
|
|
2787
2795
|
// tvOS purged: JS has not started, and the whole point is to boot it —
|
|
2788
2796
|
// commitRoundWithGeneration drops that one once the restore window closed.
|
|
2789
|
-
BOOL activate = decision.Get("activate").Truthy() ||
|
|
2797
|
+
BOOL activate = decision.Get("activate").Truthy() || pushyCrashHoldActive.load()
|
|
2790
2798
|
|| pushyPurgeRestoreActive.load();
|
|
2791
2799
|
BOOL activated = NO;
|
|
2792
2800
|
BOOL committed = [self commitRoundWithGeneration:resetGeneration
|
|
@@ -2799,12 +2807,12 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2799
2807
|
activated:&activated];
|
|
2800
2808
|
activate = activated;
|
|
2801
2809
|
if (!committed) {
|
|
2802
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
2810
|
+
RCTLogInfo(@"RCTPushy -- native sync: reset during round, dropping result");
|
|
2803
2811
|
} else if (activate) {
|
|
2804
2812
|
@synchronized (self) {
|
|
2805
2813
|
pushyUnactivatedHash = nil;
|
|
2806
2814
|
}
|
|
2807
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
2815
|
+
RCTLogInfo(@"RCTPushy -- native sync: downloaded %@ and set for next launch", hash);
|
|
2808
2816
|
} else {
|
|
2809
2817
|
// Remembered so a crash later in this process can still activate it
|
|
2810
2818
|
// (activatePendingVersion) — JS never will.
|
|
@@ -2812,7 +2820,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2812
2820
|
pushyUnactivatedHash = [hash copy];
|
|
2813
2821
|
pushyUnactivatedGeneration = resetGeneration;
|
|
2814
2822
|
}
|
|
2815
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
2823
|
+
RCTLogInfo(@"RCTPushy -- native sync: downloaded %@, activation left to JS", hash);
|
|
2816
2824
|
}
|
|
2817
2825
|
pushyHostRoundResult = committed
|
|
2818
2826
|
? PushyHostResult(@"downloaded", @"", hash, activate)
|
|
@@ -2863,7 +2871,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2863
2871
|
marked[@"purgeRestore"] = @YES;
|
|
2864
2872
|
info = marked;
|
|
2865
2873
|
}
|
|
2866
|
-
} else if (!
|
|
2874
|
+
} else if (!pushyCrashHoldActive.load()) {
|
|
2867
2875
|
activation = nil;
|
|
2868
2876
|
}
|
|
2869
2877
|
}
|
|
@@ -2899,20 +2907,20 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
2899
2907
|
// more round. No hedged race on purpose — this path is latency-insensitive.
|
|
2900
2908
|
// Per-request timeout, capped to the crash-rescue budget when one is active.
|
|
2901
2909
|
// <= 0 means the budget is gone and the round must stop issuing requests.
|
|
2902
|
-
static NSTimeInterval
|
|
2910
|
+
static NSTimeInterval PushyQueryRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
2903
2911
|
if (deadlineUptime <= 0) {
|
|
2904
2912
|
return 10;
|
|
2905
2913
|
}
|
|
2906
2914
|
return MIN(10, deadlineUptime - PushyMonotonicNow());
|
|
2907
2915
|
}
|
|
2908
2916
|
|
|
2909
|
-
+ (NSString *)
|
|
2917
|
+
+ (NSString *)runQueryRequest:(const flowjson::Value &)config
|
|
2910
2918
|
appKey:(NSString *)appKey
|
|
2911
2919
|
body:(NSString *)body
|
|
2912
2920
|
deadline:(NSTimeInterval)deadlineUptime {
|
|
2913
2921
|
double sample = arc4random() / 4294967296.0;
|
|
2914
2922
|
flowjson::Value ordered =
|
|
2915
|
-
|
|
2923
|
+
flowcore::OrderEndpointCandidates(config.Get("endpoints"), sample);
|
|
2916
2924
|
NSMutableSet<NSString *> *tried = [NSMutableSet set];
|
|
2917
2925
|
const NSUInteger maxHttpAttempts = 8;
|
|
2918
2926
|
NSUInteger httpAttempts = 0;
|
|
@@ -2926,14 +2934,14 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
2926
2934
|
return nil;
|
|
2927
2935
|
}
|
|
2928
2936
|
[tried addObject:base];
|
|
2929
|
-
NSTimeInterval timeout =
|
|
2937
|
+
NSTimeInterval timeout = PushyQueryRequestTimeout(deadlineUptime);
|
|
2930
2938
|
if (timeout <= 0) {
|
|
2931
2939
|
return nil;
|
|
2932
2940
|
}
|
|
2933
2941
|
NSString *response = PushyHttpRequest(
|
|
2934
|
-
[NSString stringWithFormat:@"
|
|
2942
|
+
[NSString stringWithFormat:@"%@%@%@", base, RCTPushyQueryPath(), appKey],
|
|
2935
2943
|
@"POST", body, timeout);
|
|
2936
|
-
if (
|
|
2944
|
+
if (PushyIsValidResponse(response)) {
|
|
2937
2945
|
return response;
|
|
2938
2946
|
}
|
|
2939
2947
|
}
|
|
@@ -2945,7 +2953,7 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
2945
2953
|
if (httpAttempts++ >= maxHttpAttempts) {
|
|
2946
2954
|
return nil;
|
|
2947
2955
|
}
|
|
2948
|
-
NSTimeInterval listTimeout =
|
|
2956
|
+
NSTimeInterval listTimeout = PushyQueryRequestTimeout(deadlineUptime);
|
|
2949
2957
|
if (listTimeout <= 0) {
|
|
2950
2958
|
return nil;
|
|
2951
2959
|
}
|
|
@@ -2968,14 +2976,14 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
2968
2976
|
return nil;
|
|
2969
2977
|
}
|
|
2970
2978
|
[tried addObject:base];
|
|
2971
|
-
NSTimeInterval timeout =
|
|
2979
|
+
NSTimeInterval timeout = PushyQueryRequestTimeout(deadlineUptime);
|
|
2972
2980
|
if (timeout <= 0) {
|
|
2973
2981
|
return nil;
|
|
2974
2982
|
}
|
|
2975
2983
|
NSString *response = PushyHttpRequest(
|
|
2976
|
-
[NSString stringWithFormat:@"
|
|
2984
|
+
[NSString stringWithFormat:@"%@%@%@", base, RCTPushyQueryPath(), appKey],
|
|
2977
2985
|
@"POST", body, timeout);
|
|
2978
|
-
if (
|
|
2986
|
+
if (PushyIsValidResponse(response)) {
|
|
2979
2987
|
return response;
|
|
2980
2988
|
}
|
|
2981
2989
|
}
|
|
@@ -2987,12 +2995,12 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
2987
2995
|
+ (BOOL)performAttempts:(const flowjson::Value &)attempts
|
|
2988
2996
|
hash:(NSString *)hash
|
|
2989
2997
|
originHash:(NSString *)originHash
|
|
2990
|
-
deadline:(NSTimeInterval)
|
|
2998
|
+
deadline:(NSTimeInterval)holdDeadline {
|
|
2991
2999
|
RCTPushy *engine = [self engine];
|
|
2992
3000
|
// Crash-rescue budget caps every phase; 0 keeps the normal 10min windows.
|
|
2993
3001
|
NSTimeInterval incrementalDeadline = PushyMonotonicNow() + 600;
|
|
2994
|
-
if (
|
|
2995
|
-
incrementalDeadline = MIN(incrementalDeadline,
|
|
3002
|
+
if (holdDeadline > 0) {
|
|
3003
|
+
incrementalDeadline = MIN(incrementalDeadline, holdDeadline);
|
|
2996
3004
|
}
|
|
2997
3005
|
NSTimeInterval fullDeadline = 0;
|
|
2998
3006
|
for (const auto &attempt : attempts.elements()) {
|
|
@@ -3007,8 +3015,8 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
3007
3015
|
if (isFullAttempt && fullDeadline == 0) {
|
|
3008
3016
|
// diff/pdiff cannot starve the last-resort full download.
|
|
3009
3017
|
fullDeadline = PushyMonotonicNow() + 600;
|
|
3010
|
-
if (
|
|
3011
|
-
fullDeadline = MIN(fullDeadline,
|
|
3018
|
+
if (holdDeadline > 0) {
|
|
3019
|
+
fullDeadline = MIN(fullDeadline, holdDeadline);
|
|
3012
3020
|
}
|
|
3013
3021
|
}
|
|
3014
3022
|
NSTimeInterval deadline = isFullAttempt ? fullDeadline : incrementalDeadline;
|
|
@@ -3026,7 +3034,7 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
3026
3034
|
}
|
|
3027
3035
|
NSMutableDictionary *options =
|
|
3028
3036
|
[@{
|
|
3029
|
-
|
|
3037
|
+
PushyOptionUrlKey: url,
|
|
3030
3038
|
@"hash": hash,
|
|
3031
3039
|
@"deadlineUptime": @(deadline),
|
|
3032
3040
|
} mutableCopy];
|
|
@@ -3035,13 +3043,13 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
3035
3043
|
}
|
|
3036
3044
|
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
|
3037
3045
|
__block NSError *resultError = nil;
|
|
3038
|
-
[engine
|
|
3046
|
+
[engine performFetch:pushyType options:options callback:^(NSError *error) {
|
|
3039
3047
|
resultError = error;
|
|
3040
3048
|
dispatch_semaphore_signal(sem);
|
|
3041
3049
|
}];
|
|
3042
3050
|
if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW,
|
|
3043
3051
|
(int64_t)(remaining * NSEC_PER_SEC))) != 0) {
|
|
3044
|
-
RCTLogWarn(@"RCTPushy -- native
|
|
3052
|
+
RCTLogWarn(@"RCTPushy -- native sync: %s attempt timed out", type.c_str());
|
|
3045
3053
|
if (isFullAttempt) {
|
|
3046
3054
|
return NO;
|
|
3047
3055
|
}
|
|
@@ -3050,7 +3058,7 @@ static NSTimeInterval PushyCheckRequestTimeout(NSTimeInterval deadlineUptime) {
|
|
|
3050
3058
|
if (resultError == nil) {
|
|
3051
3059
|
return YES;
|
|
3052
3060
|
}
|
|
3053
|
-
RCTLogInfo(@"RCTPushy -- native
|
|
3061
|
+
RCTLogInfo(@"RCTPushy -- native sync: %s attempt failed: %@",
|
|
3054
3062
|
type.c_str(), resultError.localizedDescription);
|
|
3055
3063
|
}
|
|
3056
3064
|
}
|