react-native-update 10.53.0 → 10.54.0
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 +31 -31
- package/README.md +31 -31
- package/android/jni/HDiffPatch/.travis.yml +33 -0
- package/android/jni/HDiffPatch/builds/xcode/bestParams.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/android/jni/HDiffPatch/builds/xcode/hpatchz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/lz4.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/lzma.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/android/jni/HDiffPatch/builds/xcode/unitTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/HDiffPatch/builds/xcode/zstd.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/android/jni/lzma/DOC/7zC.txt +187 -0
- package/android/jni/lzma/DOC/7zFormat.txt +469 -0
- package/android/jni/lzma/DOC/Methods.txt +173 -0
- package/android/jni/lzma/DOC/installer.txt +166 -0
- package/android/jni/lzma/DOC/lzma-history.txt +446 -0
- package/android/jni/lzma/DOC/lzma-sdk.txt +357 -0
- package/android/jni/lzma/DOC/lzma-specification.txt +1176 -0
- package/android/jni/lzma/DOC/lzma.txt +328 -0
- package/android/jni/lzma/bin/7zS2.sfx +0 -0
- package/android/jni/lzma/bin/7zS2con.sfx +0 -0
- package/android/jni/lzma/bin/7zSD.sfx +0 -0
- package/android/jni/lzma/bin/7zdec.exe +0 -0
- package/android/jni/lzma/bin/7zr.exe +0 -0
- package/android/jni/lzma/bin/installer/config.txt +5 -0
- package/android/jni/lzma/bin/installer/cr.bat +5 -0
- package/android/jni/lzma/bin/lzma.exe +0 -0
- package/android/jni/lzma/bin/x64/7zr.exe +0 -0
- 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/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +62 -30
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +44 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +124 -40
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +125 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +43 -11
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +2 -0
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +90 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +79 -19
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +21 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +56 -17
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +1 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
- package/cpp/patch_core/archive_limits.h +38 -0
- package/cpp/patch_core/install_record.h +36 -0
- package/cpp/update_flow_core/flow_json.h +1 -0
- package/cpp/update_flow_core/update_flow_core.cpp +41 -7
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +12 -0
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +2 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +37 -0
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +83 -0
- package/harmony/pushy/src/main/ets/DownloadTask.ts +126 -30
- package/harmony/pushy/src/main/ets/InstallRecord.ts +129 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +24 -2
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +5 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +12 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +78 -15
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +384 -25
- package/ios/RCTPushy/RCTPushyDownloader.mm +32 -0
- package/package.json +25 -2
- package/src/NativePushy.ts +13 -0
- package/src/client.ts +160 -43
- package/src/core.ts +17 -3
- package/src/error.ts +3 -0
- package/src/index.ts +8 -0
- package/src/locales/en.ts +3 -0
- package/src/locales/zh.ts +2 -0
- package/src/metadata.ts +166 -0
- package/src/provider.tsx +52 -17
- package/src/type.ts +22 -0
- package/src/updateFlowCore.ts +95 -4
- package/src/utils.ts +31 -19
- package/.gitmodules +0 -6
- package/BUNDLEHASH_MIGRATION.md +0 -355
- package/NATIVE_CHECKUPDATE_DESIGN.md +0 -539
- package/NATIVE_CHECK_FOLLOWUPS.md +0 -514
- package/android/jni/HDiffPatch/test/_private_searchBestParams.cpp +0 -301
- package/android/jni/HDiffPatch/test/hpatchz_test.cpp +0 -292
- package/android/jni/HDiffPatch/test/testHashClash.cpp +0 -436
- package/android/jni/HDiffPatch/test/unit_test.cpp +0 -417
- package/biome.json +0 -59
- package/cpp/patch_core/tests/fixtures/rawstored.lzma2label.patch.bin +0 -0
- package/cpp/patch_core/tests/fixtures/rawstored.new.bin +0 -0
- package/cpp/patch_core/tests/fixtures/rawstored.old.bin +0 -0
- package/cpp/patch_core/tests/fixtures/v96.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v96.meta.json +0 -1
- package/cpp/patch_core/tests/fixtures/v96.streampatch.bin +0 -0
- package/cpp/patch_core/tests/fixtures/v96.t.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v96.tpatch.bin +0 -0
- package/cpp/patch_core/tests/fixtures/v96.tstreampatch.bin +0 -0
- package/cpp/patch_core/tests/fixtures/v96b.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v98.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v98.t.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v98b.hbc +0 -0
- package/cpp/patch_core/tests/fixtures/v98b.t.hbc +0 -0
- package/cpp/patch_core/tests/hbc_transform_test.cpp +0 -384
- package/cpp/patch_core/tests/patch_core_test.cpp +0 -970
- package/cpp/update_flow_core/tests/flow_vectors.json +0 -1401
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +0 -317
- package/scripts/check-expo-version.js +0 -92
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#import "RCTPushy.h"
|
|
2
2
|
#import "RCTPushyDownloader.h"
|
|
3
3
|
#import "ZipArchive.h"
|
|
4
|
+
#include "../../cpp/patch_core/archive_limits.h"
|
|
4
5
|
#include "../../cpp/patch_core/archive_patch_core.h"
|
|
5
6
|
#include "../../cpp/patch_core/digest.h"
|
|
6
7
|
#include "../../cpp/patch_core/hbc_transform_wire.h"
|
|
8
|
+
#include "../../cpp/patch_core/install_record.h"
|
|
7
9
|
#include "../../cpp/patch_core/error_codes.h"
|
|
8
10
|
#include "../../cpp/patch_core/patch_core.h"
|
|
9
11
|
#include "../../cpp/patch_core/state_core.h"
|
|
@@ -57,7 +59,8 @@ static NSString *const PushyErrorDomain = @"cn.reactnative.pushy";
|
|
|
57
59
|
static NSString * const BUNDLE_FILE_NAME = @"index.bundlejs";
|
|
58
60
|
static NSString * const SOURCE_PATCH_NAME = @"__diff.json";
|
|
59
61
|
static NSString * const BUNDLE_PATCH_NAME = @"index.bundlejs.patch";
|
|
60
|
-
|
|
62
|
+
#define VERSION_COMPLETE_FILE_NAME_LITERAL ".pushy-complete"
|
|
63
|
+
static NSString * const VERSION_COMPLETE_FILE_NAME = @VERSION_COMPLETE_FILE_NAME_LITERAL;
|
|
61
64
|
|
|
62
65
|
// error def — messages are human-readable; the stable cross-platform codes
|
|
63
66
|
// live in cpp/patch_core/error_codes.h and travel in PushyErrorCodeKey.
|
|
@@ -79,13 +82,177 @@ static NSTimeInterval PushyMonotonicNow(void) {
|
|
|
79
82
|
return [NSProcessInfo processInfo].systemUptime;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
static
|
|
85
|
+
static std::string PushyToStdString(NSString *value);
|
|
86
|
+
|
|
87
|
+
static NSString *PushyStagingDirForVersionDir(NSString *versionDir) {
|
|
88
|
+
return [versionDir stringByAppendingString:@(pushy::install_record::kStagingSuffix)];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Parsed completion record (cpp/patch_core/install_record.h): nil when the
|
|
92
|
+
// file is absent or malformed; an empty dictionary for the legacy empty
|
|
93
|
+
// marker written by SDK < 10.53.
|
|
94
|
+
static NSDictionary *PushyReadInstallRecord(NSString *versionDir) {
|
|
95
|
+
NSString *path = [versionDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME];
|
|
96
|
+
NSData *data = [NSData dataWithContentsOfFile:path];
|
|
97
|
+
if (data == nil) {
|
|
98
|
+
return nil;
|
|
99
|
+
}
|
|
100
|
+
if (data.length == 0) {
|
|
101
|
+
return @{};
|
|
102
|
+
}
|
|
103
|
+
if (data.length > 64 * 1024) {
|
|
104
|
+
return nil;
|
|
105
|
+
}
|
|
106
|
+
id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
107
|
+
return [object isKindOfClass:[NSDictionary class]] ? (NSDictionary *)object : nil;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Presence check (launch / dedup paths, no digest work): bundle present and
|
|
111
|
+
// the record exists and, unless legacy-empty, names this version.
|
|
112
|
+
static BOOL PushyHasCompletedVersionAtPath(NSString *versionDir, NSString *hash) {
|
|
83
113
|
NSString *bundlePath = [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
|
|
115
|
+
return NO;
|
|
116
|
+
}
|
|
117
|
+
NSDictionary *record = PushyReadInstallRecord(versionDir);
|
|
118
|
+
if (record == nil) {
|
|
119
|
+
return NO;
|
|
120
|
+
}
|
|
121
|
+
if (record.count == 0) {
|
|
122
|
+
return YES;
|
|
123
|
+
}
|
|
124
|
+
return [record[@"schema"] isKindOfClass:[NSNumber class]]
|
|
125
|
+
&& [record[@"schema"] intValue] == pushy::install_record::kSchema
|
|
126
|
+
&& [record[@"versionHash"] isKindOfClass:[NSString class]]
|
|
127
|
+
&& [record[@"versionHash"] isEqualToString:hash];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Activation check: re-hashes the bundle when the record carries a digest.
|
|
131
|
+
// Returns nil when the directory may be activated, the reason otherwise.
|
|
132
|
+
static NSString *PushyVerifyInstallForActivation(NSString *versionDir, NSString *hash) {
|
|
133
|
+
NSDictionary *record = PushyReadInstallRecord(versionDir);
|
|
134
|
+
if (record == nil) {
|
|
135
|
+
return [NSString stringWithFormat:@"Bundle version %@ has no valid completion record.", hash];
|
|
136
|
+
}
|
|
137
|
+
if (record.count == 0) {
|
|
138
|
+
return nil;
|
|
139
|
+
}
|
|
140
|
+
if (![record[@"schema"] isKindOfClass:[NSNumber class]]
|
|
141
|
+
|| [record[@"schema"] intValue] != pushy::install_record::kSchema
|
|
142
|
+
|| ![record[@"versionHash"] isKindOfClass:[NSString class]]
|
|
143
|
+
|| ![record[@"versionHash"] isEqualToString:hash]) {
|
|
144
|
+
return [NSString stringWithFormat:@"Bundle version %@ completion record mismatch.", hash];
|
|
145
|
+
}
|
|
146
|
+
NSString *expected = record[@"bundleSha256"];
|
|
147
|
+
if (![expected isKindOfClass:[NSString class]] || expected.length == 0) {
|
|
148
|
+
return nil;
|
|
149
|
+
}
|
|
150
|
+
std::string actual = pushy::digest::Sha256File(
|
|
151
|
+
PushyToStdString([versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]));
|
|
152
|
+
if ([[expected lowercaseString] isEqualToString:[NSString stringWithUTF8String:actual.c_str()]]) {
|
|
153
|
+
return nil;
|
|
154
|
+
}
|
|
155
|
+
return [NSString stringWithFormat:@"Bundle version %@ bundle digest mismatch.", hash];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// SHA-256 of each in-flight download's archive, keyed by hash; consumed by
|
|
159
|
+
// the completion record write. Guarded by @synchronized on the table.
|
|
160
|
+
static NSMutableDictionary<NSString *, NSString *> *PushyArtifactDigests(void) {
|
|
161
|
+
static NSMutableDictionary *table;
|
|
162
|
+
static dispatch_once_t once;
|
|
163
|
+
dispatch_once(&once, ^{
|
|
164
|
+
table = [NSMutableDictionary dictionary];
|
|
165
|
+
});
|
|
166
|
+
return table;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static NSError *PushyErrorWithCode(const char *code, NSString *message);
|
|
170
|
+
|
|
171
|
+
static long long PushyFileSizeAtPath(NSString *path) {
|
|
172
|
+
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
|
|
173
|
+
NSNumber *size = attributes[NSFileSize];
|
|
174
|
+
return size == nil ? -1 : size.longLongValue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Free space on the volume holding `path` (walks up to an existing ancestor);
|
|
178
|
+
// -1 when unknown.
|
|
179
|
+
static long long PushyFreeDiskSpaceForPath(NSString *path) {
|
|
180
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
181
|
+
NSString *probe = path;
|
|
182
|
+
while (probe.length > 1 && ![fileManager fileExistsAtPath:probe]) {
|
|
183
|
+
probe = [probe stringByDeletingLastPathComponent];
|
|
184
|
+
}
|
|
185
|
+
NSDictionary *attributes = [fileManager attributesOfFileSystemForPath:probe error:nil];
|
|
186
|
+
NSNumber *free = attributes[NSFileSystemFreeSize];
|
|
187
|
+
return free == nil ? -1 : free.longLongValue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// nil when the volume can take `bytesToWrite` plus the safety margin (or the
|
|
191
|
+
// free space is unknown); an error otherwise.
|
|
192
|
+
static NSError *PushyEnsureFreeSpace(NSString *path, long long bytesToWrite) {
|
|
193
|
+
long long free = PushyFreeDiskSpaceForPath(path);
|
|
194
|
+
if (free < 0) {
|
|
195
|
+
return nil;
|
|
196
|
+
}
|
|
197
|
+
long long needed = MAX(0LL, bytesToWrite) + pushy::archive_limits::kFreeDiskMarginBytes;
|
|
198
|
+
if (free < needed) {
|
|
199
|
+
return PushyErrorWithCode(
|
|
200
|
+
pushy::error_codes::kPatchFailed,
|
|
201
|
+
[NSString stringWithFormat:@"insufficient disk space: need %lld bytes, have %lld",
|
|
202
|
+
needed, free]);
|
|
203
|
+
}
|
|
204
|
+
return nil;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// SSZipArchive delegate enforcing cpp/patch_core/archive_limits.h while the
|
|
208
|
+
// archive is walked: entry count, per-entry size, total size, compression
|
|
209
|
+
// ratio. SSZipArchive silently skips an entry the delegate refuses, so the
|
|
210
|
+
// first violation is recorded and turned into a hard failure afterwards.
|
|
211
|
+
@interface PushyUnzipGuard : NSObject <SSZipArchiveDelegate>
|
|
212
|
+
@property (nonatomic, copy) NSString *violation;
|
|
213
|
+
@property (nonatomic, assign) long long totalUncompressed;
|
|
214
|
+
@end
|
|
215
|
+
|
|
216
|
+
@implementation PushyUnzipGuard
|
|
217
|
+
|
|
218
|
+
- (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo {
|
|
219
|
+
if ((long long)zipInfo.number_entry > pushy::archive_limits::kMaxEntries) {
|
|
220
|
+
self.violation = [NSString stringWithFormat:@"archive has too many entries (%lu)",
|
|
221
|
+
(unsigned long)zipInfo.number_entry];
|
|
222
|
+
}
|
|
87
223
|
}
|
|
88
224
|
|
|
225
|
+
- (BOOL)zipArchiveShouldUnzipFileAtIndex:(NSInteger)fileIndex
|
|
226
|
+
totalFiles:(NSInteger)totalFiles
|
|
227
|
+
archivePath:(NSString *)archivePath
|
|
228
|
+
fileInfo:(unz_file_info)fileInfo {
|
|
229
|
+
if (self.violation != nil) {
|
|
230
|
+
return NO;
|
|
231
|
+
}
|
|
232
|
+
long long size = (long long)fileInfo.uncompressed_size;
|
|
233
|
+
long long compressed = (long long)fileInfo.compressed_size;
|
|
234
|
+
if (size > pushy::archive_limits::kMaxEntryBytes) {
|
|
235
|
+
self.violation = [NSString stringWithFormat:@"archive entry #%ld too large (%lld bytes)",
|
|
236
|
+
(long)fileIndex, size];
|
|
237
|
+
return NO;
|
|
238
|
+
}
|
|
239
|
+
self.totalUncompressed += size;
|
|
240
|
+
if (self.totalUncompressed > pushy::archive_limits::kMaxTotalUncompressedBytes) {
|
|
241
|
+
self.violation = [NSString stringWithFormat:@"archive expands beyond %lld bytes",
|
|
242
|
+
pushy::archive_limits::kMaxTotalUncompressedBytes];
|
|
243
|
+
return NO;
|
|
244
|
+
}
|
|
245
|
+
if (compressed > 0 && size > pushy::archive_limits::kRatioCheckMinBytes
|
|
246
|
+
&& size / compressed > pushy::archive_limits::kMaxCompressionRatio) {
|
|
247
|
+
self.violation = [NSString stringWithFormat:@"archive entry #%ld compression ratio too high",
|
|
248
|
+
(long)fileIndex];
|
|
249
|
+
return NO;
|
|
250
|
+
}
|
|
251
|
+
return YES;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@end
|
|
255
|
+
|
|
89
256
|
static NSError *PushyDownloadDeadlineExpiredError(void) {
|
|
90
257
|
return [NSError errorWithDomain:PushyErrorDomain
|
|
91
258
|
code:-1
|
|
@@ -475,6 +642,7 @@ static void PushySwitchVersionLocked(NSString *hash) {
|
|
|
475
642
|
// launch. Decisions come from cpp/update_flow_core; this class is IO glue.
|
|
476
643
|
@interface RCTPushyOrchestrator : NSObject
|
|
477
644
|
+ (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion;
|
|
645
|
+
+ (void)markJsCheckCompleted:(NSString *)config;
|
|
478
646
|
+ (void)startRoundWithDeadline:(NSTimeInterval)deadlineUptime;
|
|
479
647
|
+ (void)runOnce:(NSString *)launchRolledBackVersion deadline:(NSTimeInterval)deadlineUptime;
|
|
480
648
|
+ (void)runRescueWithDeadline:(NSTimeInterval)deadlineUptime;
|
|
@@ -510,6 +678,10 @@ static uint64_t pushyUnactivatedGeneration = 0;
|
|
|
510
678
|
static NSUncaughtExceptionHandler *pushyPreviousExceptionHandler = NULL;
|
|
511
679
|
// ≈ process start: scheduleFromColdStart runs during the first bundleURL.
|
|
512
680
|
static NSTimeInterval pushyProcessAnchorUptime = 0;
|
|
681
|
+
// Config JSON for which JS reported a completed check in this process
|
|
682
|
+
// (markJsCheckCompleted). Process-scoped by design. Guarded by
|
|
683
|
+
// @synchronized (RCTPushyOrchestrator class).
|
|
684
|
+
static NSString *pushyJsCompletedConfig = nil;
|
|
513
685
|
|
|
514
686
|
static const NSTimeInterval kPushyRescueTriggerUptime = 60;
|
|
515
687
|
static const NSTimeInterval kPushyRescueBudgetBackgroundThread = 10;
|
|
@@ -743,6 +915,16 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
743
915
|
ret[@"currentVersion"] = currentVersion;
|
|
744
916
|
if (currentVersion != nil) {
|
|
745
917
|
ret[@"currentVersionInfo"] = [defaults objectForKey:PushyHashInfoKey(currentVersion)];
|
|
918
|
+
// bundleSha256 from the install record, for crash-report attribution.
|
|
919
|
+
NSString *bundleSha256 = @"";
|
|
920
|
+
if (PushyIsSafePathComponent(currentVersion)) {
|
|
921
|
+
NSDictionary *record = PushyReadInstallRecord(
|
|
922
|
+
[[RCTPushy downloadDir] stringByAppendingPathComponent:currentVersion]);
|
|
923
|
+
if ([record[@"bundleSha256"] isKindOfClass:[NSString class]]) {
|
|
924
|
+
bundleSha256 = record[@"bundleSha256"];
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
ret[@"currentBundleSha256"] = bundleSha256;
|
|
746
928
|
}
|
|
747
929
|
|
|
748
930
|
if (ret[@"isFirstTime"]) {
|
|
@@ -824,6 +1006,18 @@ RCT_EXPORT_METHOD(getNativeCheckCache:(RCTPromiseResolveBlock)resolve
|
|
|
824
1006
|
resolve([PushyDefaults() stringForKey:keyNativeCheckCache] ?: @"");
|
|
825
1007
|
}
|
|
826
1008
|
|
|
1009
|
+
RCT_EXPORT_METHOD(markJsCheckCompleted:(NSString *)config
|
|
1010
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1011
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1012
|
+
{
|
|
1013
|
+
if (PushyStringIsBlank(config)) {
|
|
1014
|
+
PushyRejectError(reject, PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
[RCTPushyOrchestrator markJsCheckCompleted:config];
|
|
1018
|
+
resolve(@true);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
827
1021
|
RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash
|
|
828
1022
|
value:(NSString *)value resolver:(RCTPromiseResolveBlock)resolve
|
|
829
1023
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -1094,7 +1288,7 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1094
1288
|
}
|
|
1095
1289
|
|
|
1096
1290
|
NSString *unzipDir = [dir stringByAppendingPathComponent:hash];
|
|
1097
|
-
if (PushyHasCompletedVersionAtPath(unzipDir)) {
|
|
1291
|
+
if (PushyHasCompletedVersionAtPath(unzipDir, hash)) {
|
|
1098
1292
|
callback(nil);
|
|
1099
1293
|
return;
|
|
1100
1294
|
}
|
|
@@ -1142,20 +1336,59 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1142
1336
|
// completion marker write has run on the process-wide file queue.
|
|
1143
1337
|
dispatch_async(self->_fileQueue, ^{
|
|
1144
1338
|
NSError *finalError = error;
|
|
1339
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
1340
|
+
NSString *staging = PushyStagingDirForVersionDir(unzipDir);
|
|
1341
|
+
NSString *artifactSha256 = nil;
|
|
1342
|
+
@synchronized (PushyArtifactDigests()) {
|
|
1343
|
+
artifactSha256 = PushyArtifactDigests()[hash];
|
|
1344
|
+
[PushyArtifactDigests() removeObjectForKey:hash];
|
|
1345
|
+
}
|
|
1145
1346
|
if (finalError == nil) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
if (!
|
|
1152
|
-
finalError =
|
|
1153
|
-
|
|
1154
|
-
|
|
1347
|
+
// Two-phase install (cpp/patch_core/install_record.h): the
|
|
1348
|
+
// completion record with the final bundle's digest goes into
|
|
1349
|
+
// the staging directory, which is then renamed over the
|
|
1350
|
+
// version directory in one atomic step.
|
|
1351
|
+
NSString *bundlePath = [staging stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
1352
|
+
if (![fileManager fileExistsAtPath:bundlePath]) {
|
|
1353
|
+
finalError = PushyErrorWithCode(pushy::error_codes::kPatchFailed,
|
|
1354
|
+
@"bundle missing after install");
|
|
1355
|
+
} else {
|
|
1356
|
+
std::string bundleSha256 = pushy::digest::Sha256File(PushyToStdString(bundlePath));
|
|
1357
|
+
NSMutableDictionary *record = [NSMutableDictionary dictionary];
|
|
1358
|
+
record[@"schema"] = @(pushy::install_record::kSchema);
|
|
1359
|
+
record[@"versionHash"] = hash;
|
|
1360
|
+
if (!bundleSha256.empty()) {
|
|
1361
|
+
record[@"bundleSha256"] = [NSString stringWithUTF8String:bundleSha256.c_str()];
|
|
1362
|
+
}
|
|
1363
|
+
if (artifactSha256.length > 0) {
|
|
1364
|
+
record[@"artifactSha256"] = artifactSha256;
|
|
1365
|
+
}
|
|
1366
|
+
NSError *recordError = nil;
|
|
1367
|
+
NSData *recordData = [NSJSONSerialization dataWithJSONObject:record options:0 error:&recordError];
|
|
1368
|
+
NSString *marker = [staging stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME];
|
|
1369
|
+
if (recordData == nil || ![recordData writeToFile:marker
|
|
1370
|
+
options:NSDataWritingAtomic
|
|
1371
|
+
error:&recordError]) {
|
|
1372
|
+
finalError = recordError ?: PushyErrorWithCode(
|
|
1373
|
+
pushy::error_codes::kFileOperationFailed,
|
|
1374
|
+
@"failed to write completion record");
|
|
1375
|
+
} else {
|
|
1376
|
+
if ([fileManager fileExistsAtPath:unzipDir]) {
|
|
1377
|
+
[fileManager removeItemAtPath:unzipDir error:nil];
|
|
1378
|
+
}
|
|
1379
|
+
NSError *moveError = nil;
|
|
1380
|
+
if (![fileManager moveItemAtPath:staging toPath:unzipDir error:&moveError]) {
|
|
1381
|
+
finalError = moveError ?: PushyErrorWithCode(
|
|
1382
|
+
pushy::error_codes::kFileOperationFailed,
|
|
1383
|
+
@"failed to promote staging directory");
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1155
1386
|
}
|
|
1156
1387
|
}
|
|
1157
1388
|
if (finalError != nil) {
|
|
1158
|
-
|
|
1389
|
+
// Only the staging directory is ours to drop: the final
|
|
1390
|
+
// version directory is never touched by a failed install.
|
|
1391
|
+
[fileManager removeItemAtPath:staging error:nil];
|
|
1159
1392
|
}
|
|
1160
1393
|
PushyFinishDownload(hash, finalError);
|
|
1161
1394
|
});
|
|
@@ -1192,7 +1425,17 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1192
1425
|
callback:(void (^)(NSError *error))callback
|
|
1193
1426
|
{
|
|
1194
1427
|
RCTLogInfo(@"RCTPushy -- unzip file %@", zipFilePath);
|
|
1195
|
-
|
|
1428
|
+
// Everything lands in <hash>.staging; the completion block promotes it.
|
|
1429
|
+
NSString *unzipFilePath = PushyStagingDirForVersionDir(
|
|
1430
|
+
[[RCTPushy downloadDir] stringByAppendingPathComponent:hash]);
|
|
1431
|
+
dispatch_async(_fileQueue, ^{
|
|
1432
|
+
// Archive digest for the completion record, taken before the unzip
|
|
1433
|
+
// consumes the file (same serial queue, so it runs first).
|
|
1434
|
+
std::string digest = pushy::digest::Sha256File(PushyToStdString(zipFilePath));
|
|
1435
|
+
@synchronized (PushyArtifactDigests()) {
|
|
1436
|
+
PushyArtifactDigests()[hash] = [NSString stringWithUTF8String:digest.c_str()];
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1196
1439
|
[self unzipFileAtPath:zipFilePath
|
|
1197
1440
|
toDestination:unzipFilePath
|
|
1198
1441
|
completionHandler:^(NSError *error) {
|
|
@@ -1240,11 +1483,19 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1240
1483
|
source:(NSString *)sourceOrigin
|
|
1241
1484
|
callback:(void (^)(NSError *error))callback
|
|
1242
1485
|
{
|
|
1243
|
-
|
|
1486
|
+
// Patch work happens in the staging directory (two-phase install).
|
|
1487
|
+
NSString *unzipDir = PushyStagingDirForVersionDir(
|
|
1488
|
+
[[RCTPushy downloadDir] stringByAppendingPathComponent:hash]);
|
|
1244
1489
|
NSString *sourcePatch = [unzipDir stringByAppendingPathComponent:SOURCE_PATCH_NAME];
|
|
1245
1490
|
NSString *bundlePatch = [unzipDir stringByAppendingPathComponent:BUNDLE_PATCH_NAME];
|
|
1246
1491
|
|
|
1247
1492
|
NSString *destination = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
1493
|
+
long long manifestBytes = PushyFileSizeAtPath(sourcePatch);
|
|
1494
|
+
if (manifestBytes > pushy::archive_limits::kMaxManifestBytes) {
|
|
1495
|
+
callback(PushyErrorWithCode(pushy::error_codes::kPatchFailed,
|
|
1496
|
+
[NSString stringWithFormat:@"patch manifest too large: %lld bytes", manifestBytes]));
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1248
1499
|
NSData *data = [NSData dataWithContentsOfFile:sourcePatch];
|
|
1249
1500
|
if (data == nil) {
|
|
1250
1501
|
callback(PushyErrorWithCode(pushy::error_codes::kPatchFailed, @"missing patch manifest"));
|
|
@@ -1336,9 +1587,43 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1336
1587
|
return NO;
|
|
1337
1588
|
}
|
|
1338
1589
|
|
|
1590
|
+
__block NSError *switchError = nil;
|
|
1339
1591
|
PushyWithStateLock(^{
|
|
1592
|
+
// Same rule as Android: only a version this SDK recorded as completely
|
|
1593
|
+
// installed (bundle + marker) may be activated. Versions activated
|
|
1594
|
+
// before markers existed are grandfathered through current/last
|
|
1595
|
+
// state; any other markerless directory may be a crash-left partial
|
|
1596
|
+
// install or a directory something else put there.
|
|
1597
|
+
NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash];
|
|
1598
|
+
pushy::state::State state = PushyStateFromDefaults(PushyDefaults());
|
|
1599
|
+
std::string hashStd = PushyToStdString(hash);
|
|
1600
|
+
BOOL legacyActivated = state.current_version == hashStd || state.last_version == hashStd;
|
|
1601
|
+
if (!PushyHasCompletedVersionAtPath(versionDir, hash) && !legacyActivated) {
|
|
1602
|
+
BOOL hasBundle = [[NSFileManager defaultManager] fileExistsAtPath:
|
|
1603
|
+
[versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]];
|
|
1604
|
+
switchError = PushyErrorWithCode(
|
|
1605
|
+
pushy::error_codes::kSwitchVersionFailed,
|
|
1606
|
+
[NSString stringWithFormat:@"Bundle version %@ %@", hash,
|
|
1607
|
+
hasBundle ? @"is incomplete." : @"not found."]);
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
if (!legacyActivated) {
|
|
1611
|
+
// The record's bundle digest must match the bytes on disk before
|
|
1612
|
+
// the next launch is pointed at them.
|
|
1613
|
+
NSString *reason = PushyVerifyInstallForActivation(versionDir, hash);
|
|
1614
|
+
if (reason != nil) {
|
|
1615
|
+
switchError = PushyErrorWithCode(pushy::error_codes::kSwitchVersionFailed, reason);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1340
1619
|
PushySwitchVersionLocked(hash);
|
|
1341
1620
|
});
|
|
1621
|
+
if (switchError != nil) {
|
|
1622
|
+
if (error != NULL) {
|
|
1623
|
+
*error = switchError;
|
|
1624
|
+
}
|
|
1625
|
+
return NO;
|
|
1626
|
+
}
|
|
1342
1627
|
return YES;
|
|
1343
1628
|
}
|
|
1344
1629
|
|
|
@@ -1397,8 +1682,37 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1397
1682
|
[fileManager removeItemAtPath:destination error:nil];
|
|
1398
1683
|
}
|
|
1399
1684
|
|
|
1685
|
+
// Resource caps before the first byte is extracted
|
|
1686
|
+
// (cpp/patch_core/archive_limits.h). The central directory is not
|
|
1687
|
+
// readable up front through SSZipArchive, so the disk check uses a
|
|
1688
|
+
// 2x-archive heuristic; the delegate below enforces the exact caps
|
|
1689
|
+
// entry by entry.
|
|
1690
|
+
long long archiveBytes = PushyFileSizeAtPath(path);
|
|
1691
|
+
NSError *preflight = nil;
|
|
1692
|
+
if (archiveBytes > pushy::archive_limits::kMaxArchiveBytes) {
|
|
1693
|
+
preflight = PushyErrorWithCode(pushy::error_codes::kPatchFailed,
|
|
1694
|
+
[NSString stringWithFormat:@"archive too large: %lld bytes", archiveBytes]);
|
|
1695
|
+
} else {
|
|
1696
|
+
preflight = PushyEnsureFreeSpace(destination, MAX(0LL, archiveBytes) * 2);
|
|
1697
|
+
}
|
|
1698
|
+
if (preflight != nil) {
|
|
1699
|
+
[fileManager removeItemAtPath:path error:nil];
|
|
1700
|
+
[fileManager removeItemAtPath:[path stringByAppendingString:@".resume"] error:nil];
|
|
1701
|
+
if (completionHandler != nil) {
|
|
1702
|
+
completionHandler(preflight);
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
PushyUnzipGuard *guard = [PushyUnzipGuard new];
|
|
1400
1708
|
[SSZipArchive unzipFileAtPath:path
|
|
1401
1709
|
toDestination:destination
|
|
1710
|
+
preserveAttributes:YES
|
|
1711
|
+
overwrite:YES
|
|
1712
|
+
nestedZipLevel:0
|
|
1713
|
+
password:nil
|
|
1714
|
+
error:nil
|
|
1715
|
+
delegate:guard
|
|
1402
1716
|
progressHandler:nil
|
|
1403
1717
|
completionHandler:^(NSString *archivePath, BOOL succeeded, NSError *error) {
|
|
1404
1718
|
[fileManager removeItemAtPath:archivePath error:nil];
|
|
@@ -1412,8 +1726,20 @@ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
|
|
|
1412
1726
|
}
|
|
1413
1727
|
|
|
1414
1728
|
NSError *unzipError = error;
|
|
1415
|
-
if (
|
|
1729
|
+
if (guard.violation != nil) {
|
|
1730
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kPatchFailed, guard.violation);
|
|
1731
|
+
} else if (!succeeded && unzipError == nil) {
|
|
1416
1732
|
unzipError = PushyErrorWithCode(pushy::error_codes::kPatchFailed, @"unzip failed");
|
|
1733
|
+
} else if (succeeded && unzipError == nil
|
|
1734
|
+
&& [fileManager fileExistsAtPath:
|
|
1735
|
+
[destination stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME]]) {
|
|
1736
|
+
// The completion marker is written by this SDK only, after the
|
|
1737
|
+
// whole install succeeded. An archive that ships its own would
|
|
1738
|
+
// be trusted as a finished install even if the patch step
|
|
1739
|
+
// that follows fails — reject the package outright (the
|
|
1740
|
+
// caller removes the half-unpacked directory).
|
|
1741
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kPatchFailed,
|
|
1742
|
+
@"archive contains reserved entry " VERSION_COMPLETE_FILE_NAME_LITERAL);
|
|
1417
1743
|
} else if (unzipError != nil && unzipError.userInfo[PushyErrorCodeKey] == nil) {
|
|
1418
1744
|
// SSZipArchive's own NSError (corrupt zip, bad magic, ...) has
|
|
1419
1745
|
// no stable code; without one, downloadUpdate's fallback would
|
|
@@ -1540,7 +1866,12 @@ static NSString *PushyHttpRequest(NSString *urlString, NSString *method,
|
|
|
1540
1866
|
? (NSHTTPURLResponse *)response
|
|
1541
1867
|
: nil;
|
|
1542
1868
|
NSInteger status = httpResponse.statusCode;
|
|
1543
|
-
|
|
1869
|
+
// The shared session follows redirects; an https endpoint that
|
|
1870
|
+
// ended up on plaintext http is a failed endpoint, same as the
|
|
1871
|
+
// artifact download.
|
|
1872
|
+
BOOL downgraded = [[url.scheme lowercaseString] isEqualToString:@"https"]
|
|
1873
|
+
&& [[httpResponse.URL.scheme lowercaseString] isEqualToString:@"http"];
|
|
1874
|
+
if (error == nil && httpResponse != nil && !downgraded && status >= 200 &&
|
|
1544
1875
|
status < 300 && data != nil) {
|
|
1545
1876
|
result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
1546
1877
|
}
|
|
@@ -1567,9 +1898,9 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
1567
1898
|
if (responseText == nil) {
|
|
1568
1899
|
return NO;
|
|
1569
1900
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
return
|
|
1901
|
+
// Shared schema rule (update_flow_core::IsValidCheckResponse): a 200 with
|
|
1902
|
+
// `{"error": ...}` is a failed endpoint, not a verdict.
|
|
1903
|
+
return updateflow::IsValidCheckResponse(PushyToStdString(responseText)) ? YES : NO;
|
|
1573
1904
|
}
|
|
1574
1905
|
|
|
1575
1906
|
@implementation RCTPushyOrchestrator
|
|
@@ -1595,12 +1926,40 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
1595
1926
|
[defaults objectForKey:keyNativeCheckIncomplete] != nil ? 0 : 5;
|
|
1596
1927
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delaySeconds * NSEC_PER_SEC),
|
|
1597
1928
|
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
1929
|
+
if ([self isJsCheckCompleted]) {
|
|
1930
|
+
// Not consuming the round: a later crash rescue may still
|
|
1931
|
+
// need it.
|
|
1932
|
+
NSLog(@"RCTPushy -- native check skipped: JS check completed in this process");
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1598
1935
|
[self startRoundWithDeadline:0];
|
|
1599
1936
|
});
|
|
1600
1937
|
});
|
|
1601
1938
|
#endif
|
|
1602
1939
|
}
|
|
1603
1940
|
|
|
1941
|
+
+ (void)markJsCheckCompleted:(NSString *)config {
|
|
1942
|
+
@synchronized (RCTPushyOrchestrator.class) {
|
|
1943
|
+
pushyJsCompletedConfig = [config copy];
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// True when JS already obtained a valid response in this process for the
|
|
1948
|
+
// exact config the native round would use (§10.3): the delayed round is
|
|
1949
|
+
// then a duplicate request. Only the scheduled round asks — the crash-rescue
|
|
1950
|
+
// path still runs, JS is dead by then.
|
|
1951
|
+
+ (BOOL)isJsCheckCompleted {
|
|
1952
|
+
NSString *jsConfig;
|
|
1953
|
+
@synchronized (RCTPushyOrchestrator.class) {
|
|
1954
|
+
jsConfig = pushyJsCompletedConfig;
|
|
1955
|
+
}
|
|
1956
|
+
if (jsConfig == nil) {
|
|
1957
|
+
return NO;
|
|
1958
|
+
}
|
|
1959
|
+
NSString *persisted = [PushyDefaults() stringForKey:keyNativeConfig];
|
|
1960
|
+
return persisted != nil && [persisted isEqualToString:jsConfig];
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1604
1963
|
// Runs the process's single round on the calling thread if nobody has
|
|
1605
1964
|
// started it yet. deadlineUptime > 0 (crash rescue) caps every HTTP call and
|
|
1606
1965
|
// download phase to the remaining budget.
|
|
@@ -1656,7 +2015,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
1656
2015
|
// that are gone (e.g. wiped by a reset whose generation bump we would
|
|
1657
2016
|
// catch below anyway, or by cleanup).
|
|
1658
2017
|
NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash];
|
|
1659
|
-
if (!PushyHasCompletedVersionAtPath(versionDir)) {
|
|
2018
|
+
if (!PushyHasCompletedVersionAtPath(versionDir, hash)) {
|
|
1660
2019
|
NSLog(@"RCTPushy -- crash rescue: version %@ no longer on disk, dropping activation", hash);
|
|
1661
2020
|
return;
|
|
1662
2021
|
}
|
|
@@ -1827,7 +2186,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
|
|
|
1827
2186
|
}
|
|
1828
2187
|
|
|
1829
2188
|
NSString *versionDir = [[RCTPushy downloadDir] stringByAppendingPathComponent:hash];
|
|
1830
|
-
BOOL downloaded = PushyHasCompletedVersionAtPath(versionDir);
|
|
2189
|
+
BOOL downloaded = PushyHasCompletedVersionAtPath(versionDir, hash);
|
|
1831
2190
|
if (!downloaded) {
|
|
1832
2191
|
downloaded = [self performAttempts:decision.Get("attempts")
|
|
1833
2192
|
hash:hash
|
|
@@ -273,6 +273,31 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
273
273
|
|
|
274
274
|
#pragma mark - session delegate
|
|
275
275
|
|
|
276
|
+
// An https artifact URL must stay on https through every redirect: the
|
|
277
|
+
// package is the supply-chain boundary and TLS is what authenticates it.
|
|
278
|
+
// Returning nil delivers the 3xx itself as the response, which the status
|
|
279
|
+
// check below then rejects with the recorded reason.
|
|
280
|
+
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
|
|
281
|
+
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|
282
|
+
newRequest:(NSURLRequest *)request
|
|
283
|
+
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
|
284
|
+
{
|
|
285
|
+
if (session != self.session) {
|
|
286
|
+
completionHandler(nil);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
NSString *originalScheme = [[NSURL URLWithString:self.urlString].scheme lowercaseString];
|
|
290
|
+
NSString *nextScheme = [request.URL.scheme lowercaseString];
|
|
291
|
+
if ([originalScheme isEqualToString:@"https"] && [nextScheme isEqualToString:@"http"]) {
|
|
292
|
+
[self failWithDescription:[NSString stringWithFormat:
|
|
293
|
+
@"https download redirected to plaintext http: %@", request.URL.absoluteString]
|
|
294
|
+
code:-1];
|
|
295
|
+
completionHandler(nil);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
completionHandler(request);
|
|
299
|
+
}
|
|
300
|
+
|
|
276
301
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
|
|
277
302
|
didReceiveResponse:(NSURLResponse *)response
|
|
278
303
|
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
|
|
@@ -286,6 +311,13 @@ didReceiveResponse:(NSURLResponse *)response
|
|
|
286
311
|
[response isKindOfClass:[NSHTTPURLResponse class]] ? (NSHTTPURLResponse *)response : nil;
|
|
287
312
|
NSInteger statusCode = httpResponse.statusCode;
|
|
288
313
|
|
|
314
|
+
if (self.fileError != nil) {
|
|
315
|
+
// A refused redirect (see willPerformHTTPRedirection) already
|
|
316
|
+
// recorded the real reason; the 3xx being delivered here is not it.
|
|
317
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
318
|
+
[self completeWithError:self.fileError];
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
289
321
|
if (statusCode == 416) {
|
|
290
322
|
long long knownTotal = [self.resumeMeta[@"total"] longLongValue];
|
|
291
323
|
completionHandler(NSURLSessionResponseCancel);
|
package/package.json
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.54.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"!src/__tests__",
|
|
10
|
+
"android/src",
|
|
11
|
+
"android/lib",
|
|
12
|
+
"android/jni",
|
|
13
|
+
"!android/jni/HDiffPatch/test",
|
|
14
|
+
"android/build.gradle",
|
|
15
|
+
"android/proguard.pro",
|
|
16
|
+
"ios",
|
|
17
|
+
"cpp/patch_core",
|
|
18
|
+
"cpp/update_flow_core",
|
|
19
|
+
"!cpp/**/tests",
|
|
20
|
+
"harmony/pushy.har",
|
|
21
|
+
"harmony/hvigor-plugin.ts",
|
|
22
|
+
"harmony/pushy/src",
|
|
23
|
+
"!harmony/pushy/src/test",
|
|
24
|
+
"react-native-update.podspec",
|
|
25
|
+
"react-native.config.js",
|
|
26
|
+
"expo-module.config.json",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README-CN.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
7
31
|
"scripts": {
|
|
8
32
|
"prepublishOnly": "NODE_ENV=production bun scripts/prepublish.ts",
|
|
9
33
|
"publish:local": "SKIP_NATIVE_BUILD=1 npm publish",
|
|
10
|
-
"postinstall": "node scripts/check-expo-version.js",
|
|
11
34
|
"prepack": "bun submodule && bun lint",
|
|
12
35
|
"lint": "biome check src && tsc --noEmit && node scripts/check-harmony-types.js",
|
|
13
36
|
"lint:harmony": "node scripts/check-harmony-types.js",
|