react-native-update 10.59.1 → 10.60.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 +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- 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/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2628
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +74 -83
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Internal to the RCTPushy pod and Foundation-only: what every translation
|
|
2
|
+
// unit shares, including RCTPushyConfiguration.mm and RCTPushyDownloader.mm,
|
|
3
|
+
// which compile without React Native (the purge-restore test host builds
|
|
4
|
+
// RCTPushyConfiguration.mm on its own). Not a public header.
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#import <Foundation/Foundation.h>
|
|
8
|
+
|
|
9
|
+
// Shared across the pod's translation units, never exported from a dynamic
|
|
10
|
+
// framework.
|
|
11
|
+
#ifndef PUSHY_HIDDEN
|
|
12
|
+
#define PUSHY_HIDDEN __attribute__((visibility("hidden")))
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
// Every NSError this pod creates: the domain, and the userInfo key carrying
|
|
16
|
+
// the stable cross-platform code (cpp/patch_core/error_codes.h).
|
|
17
|
+
static NSString *const RCTPushyErrorDomain = @"cn.reactnative.pushy";
|
|
18
|
+
static NSString *const RCTPushyErrorCodeKey = @"PushyErrorCode";
|
|
19
|
+
|
|
20
|
+
// Text produced by scripts/encode-native-text.ts, decoded with
|
|
21
|
+
// pushy::text::Reveal (cpp/patch_core/obscured_text.h). Keeps service
|
|
22
|
+
// addresses, paths and protocol strings out of static string scans of the
|
|
23
|
+
// binary; not a secret.
|
|
24
|
+
PUSHY_HIDDEN NSString *RCTPushyRevealText(const char *hex);
|
|
25
|
+
|
|
26
|
+
// Whether following a redirect from `from` to `to` would leave https for
|
|
27
|
+
// plaintext http. Every https request (check and artifact) refuses that
|
|
28
|
+
// downgrade before the redirected request is sent.
|
|
29
|
+
PUSHY_HIDDEN BOOL RCTPushyIsHttpsDowngrade(NSURL *from, NSURL *to);
|
|
30
|
+
|
|
31
|
+
// Removes a downloaded archive together with its resume sidecar
|
|
32
|
+
// (RCTPushyResumeSidecarPath): nothing may survive to vouch for bytes that
|
|
33
|
+
// are gone.
|
|
34
|
+
PUSHY_HIDDEN void RCTPushyDiscardArchive(NSString *path);
|
|
@@ -3,11 +3,3 @@
|
|
|
3
3
|
// Validates and snapshots host options without changing any update state.
|
|
4
4
|
FOUNDATION_EXPORT NSString * _Nullable RCTPushyNormalizeConfiguration(
|
|
5
5
|
NSDictionary * _Nonnull options, NSError * _Nullable * _Nullable error);
|
|
6
|
-
|
|
7
|
-
// Decodes text produced by scripts/encode-native-text.ts (byte i XORed with
|
|
8
|
-
// (0x5A + 0x1D * i) & 0xFF), keeping service addresses and paths out of static
|
|
9
|
-
// string scans of the binary. Not a secret.
|
|
10
|
-
FOUNDATION_EXPORT NSString * _Nonnull RCTPushyRevealText(const char * _Nonnull hex);
|
|
11
|
-
|
|
12
|
-
// Request path appended to an endpoint base.
|
|
13
|
-
FOUNDATION_EXPORT NSString * _Nonnull RCTPushyQueryPath(void);
|
|
@@ -1,130 +1,49 @@
|
|
|
1
1
|
#import "RCTPushyConfiguration.h"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
#import "RCTPushyCommon.h"
|
|
3
|
+
#include "../../cpp/patch_core/error_codes.h"
|
|
4
|
+
#include "../../cpp/patch_core/obscured_text.h"
|
|
5
|
+
#include "../../cpp/update_flow_core/native_config.h"
|
|
6
6
|
|
|
7
7
|
NSString *RCTPushyRevealText(const char *hex) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
unsigned char byte = (unsigned char)strtoul(pair, NULL, 16);
|
|
14
|
-
[text appendFormat:@"%c", (char)(byte ^ (unsigned char)(base + 0x1D * i))];
|
|
15
|
-
}
|
|
16
|
-
return text;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
NSString *RCTPushyQueryPath(void) {
|
|
20
|
-
static NSString *path;
|
|
21
|
-
static dispatch_once_t once;
|
|
22
|
-
dispatch_once(&once, ^{
|
|
23
|
-
path = RCTPushyRevealText("7514fcd4ad805d55263e08fc99");
|
|
24
|
-
});
|
|
25
|
-
return path;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static void PushyConfigInvalid(NSString *message) {
|
|
29
|
-
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
|
30
|
-
reason:[@"Invalid native configuration: " stringByAppendingString:message]
|
|
31
|
-
userInfo:nil];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static NSString *PushyConfigString(id value, NSString *name, BOOL allowEmpty) {
|
|
35
|
-
if (![value isKindOfClass:NSString.class]
|
|
36
|
-
|| (!allowEmpty && [[value stringByTrimmingCharactersInSet:
|
|
37
|
-
NSCharacterSet.whitespaceAndNewlineCharacterSet] length] == 0)) {
|
|
38
|
-
PushyConfigInvalid([NSString stringWithFormat:@"%@ must be a string%@",
|
|
39
|
-
name, allowEmpty ? @"" : @" and must not be blank"]);
|
|
40
|
-
}
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static NSArray *PushyConfigUrls(id value, NSString *name, BOOL base) {
|
|
45
|
-
if (![value isKindOfClass:NSArray.class] || (base && [value count] == 0)) {
|
|
46
|
-
PushyConfigInvalid([NSString stringWithFormat:@"%@ must be %@ array",
|
|
47
|
-
name, base ? @"a non-empty" : @"an"]);
|
|
48
|
-
}
|
|
49
|
-
NSMutableArray *result = [NSMutableArray array];
|
|
50
|
-
NSRegularExpression *authority = [NSRegularExpression regularExpressionWithPattern:
|
|
51
|
-
@"^https?://(\\[[0-9a-fA-F:]+\\]|[a-zA-Z0-9.-]+)(:[0-9]+)?([/?#]|$)"
|
|
52
|
-
options:0 error:nil];
|
|
53
|
-
for (id item in value) {
|
|
54
|
-
NSString *url = [PushyConfigString(item, name, NO) stringByTrimmingCharactersInSet:
|
|
55
|
-
NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
|
56
|
-
if ([authority firstMatchInString:url options:0 range:NSMakeRange(0, url.length)] == nil
|
|
57
|
-
|| [url rangeOfCharacterFromSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].location != NSNotFound
|
|
58
|
-
|| [url containsString:@"\\"]
|
|
59
|
-
|| (base && ([url containsString:@"?"] || [url containsString:@"#"]))) {
|
|
60
|
-
PushyConfigInvalid([NSString stringWithFormat:
|
|
61
|
-
@"%@ requires absolute HTTP(S) URLs without credentials%@",
|
|
62
|
-
name, base ? @", queries or fragments" : @""]);
|
|
63
|
-
}
|
|
64
|
-
if (base) {
|
|
65
|
-
while ([url hasSuffix:@"/"]) {
|
|
66
|
-
url = [url substringToIndex:url.length - 1];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (![result containsObject:url]) {
|
|
70
|
-
[result addObject:url];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
8
|
+
// The texts are ASCII (scripts/encode-native-text.ts).
|
|
9
|
+
std::string text = pushy::text::Reveal(hex);
|
|
10
|
+
return [[NSString alloc] initWithBytes:text.data()
|
|
11
|
+
length:text.size()
|
|
12
|
+
encoding:NSUTF8StringEncoding] ?: @"";
|
|
74
13
|
}
|
|
75
14
|
|
|
15
|
+
// Validation and defaults are the shared core's (flowcore::NormalizeConfiguration,
|
|
16
|
+
// also used by Android and Harmony); this only bridges the dictionary.
|
|
76
17
|
NSString *RCTPushyNormalizeConfiguration(NSDictionary *options, NSError **error) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
PushyConfigInvalid([NSString stringWithFormat:@"unknown option %@", key]);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
NSString *appKey = PushyConfigString(options[@"appKey"], @"appKey", NO);
|
|
89
|
-
BOOL customEndpoints = options[@"endpoints"] != nil;
|
|
90
|
-
NSArray *endpoints = PushyConfigUrls(customEndpoints ? options[@"endpoints"]
|
|
91
|
-
: @[RCTPushyRevealText("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5b3d5b9817b592947e5cdefbc8a7e"),
|
|
92
|
-
RCTPushyRevealText("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5f0daac9c644a620ae88ca1ad93")],
|
|
93
|
-
@"endpoints", YES);
|
|
94
|
-
NSArray *queryUrls = PushyConfigUrls(options[@"queryUrls"] ?: (customEndpoints ? @[]
|
|
95
|
-
: @[RCTPushyRevealText("3203e0c1bdd1270a253608fcd3fd9362476817f4f0d5a1996342631be3c2a3a9d779552507fdcde8928a6f512f5ce2ccbdc869404d2f1de79daa826d562c0913eec4fa9b7d4426"),
|
|
96
|
-
RCTPushyRevealText("3203e0c1bdd1270a213b12b7dca09468462e12f3b0d5bd813d482446f4c6a1be8e79552507fdcda68cd06e5c3710e480a4867048483e55e0c2ab8d7d43030d1ce9c3b183214e2601f2f0d5b782601e2719e8ca")]),
|
|
97
|
-
@"queryUrls", NO);
|
|
98
|
-
NSString *afterDownload = options[@"afterDownload"]
|
|
99
|
-
? PushyConfigString(options[@"afterDownload"], @"afterDownload", NO) : @"none";
|
|
100
|
-
NSString *nextLaunch = RCTPushyRevealText("2912e0ffab8e6c70323b1dedd3");
|
|
101
|
-
if (![@[@"none", nextLaunch] containsObject:afterDownload]) {
|
|
102
|
-
PushyConfigInvalid([@"afterDownload must be none or " stringByAppendingString:nextLaunch]);
|
|
18
|
+
// Anything NSJSONSerialization cannot write is not a JSON object; the core
|
|
19
|
+
// reports it as such.
|
|
20
|
+
std::string optionsJson;
|
|
21
|
+
if ([options isKindOfClass:NSDictionary.class]
|
|
22
|
+
&& [NSJSONSerialization isValidJSONObject:options]) {
|
|
23
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:options options:0 error:NULL];
|
|
24
|
+
if (data != nil) {
|
|
25
|
+
optionsJson.assign(static_cast<const char *>(data.bytes), data.length);
|
|
103
26
|
}
|
|
104
|
-
id disabled = options[@"disabled"] ?: @NO;
|
|
105
|
-
if (![disabled isKindOfClass:NSNumber.class] || CFGetTypeID((__bridge CFTypeRef)disabled) != CFBooleanGetTypeID()) {
|
|
106
|
-
PushyConfigInvalid(@"disabled must be a boolean");
|
|
107
|
-
}
|
|
108
|
-
NSMutableDictionary *result = [@{
|
|
109
|
-
@"appKey": appKey, @"endpoints": endpoints, @"queryUrls": queryUrls,
|
|
110
|
-
@"afterDownload": afterDownload, @"disabled": disabled,
|
|
111
|
-
@"rnu": options[@"rnu"] ? PushyConfigString(options[@"rnu"], @"rnu", YES) : @"",
|
|
112
|
-
@"rn": options[@"rn"] ? PushyConfigString(options[@"rn"], @"rn", YES) : @""
|
|
113
|
-
} mutableCopy];
|
|
114
|
-
if (options[@"packageVersion"] != nil) {
|
|
115
|
-
result[@"packageVersion"] = PushyConfigString(options[@"packageVersion"], @"packageVersion", NO);
|
|
116
|
-
}
|
|
117
|
-
// Stable ordering makes repeated identical configure calls idempotent.
|
|
118
|
-
NSData *data = [NSJSONSerialization dataWithJSONObject:result
|
|
119
|
-
options:NSJSONWritingSortedKeys error:error];
|
|
120
|
-
return data == nil ? nil : [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
121
|
-
} @catch (NSException *exception) {
|
|
122
|
-
if (error != NULL) {
|
|
123
|
-
*error = [NSError errorWithDomain:@"cn.reactnative.pushy" code:1 userInfo:@{
|
|
124
|
-
NSLocalizedDescriptionKey: exception.reason ?: @"Invalid native configuration",
|
|
125
|
-
@"PushyErrorCode": @"INVALID_OPTIONS"
|
|
126
|
-
}];
|
|
127
|
-
}
|
|
128
|
-
return nil;
|
|
129
27
|
}
|
|
28
|
+
std::string config;
|
|
29
|
+
std::string message;
|
|
30
|
+
if (flowcore::NormalizeConfiguration(optionsJson, &config, &message)) {
|
|
31
|
+
NSString *result = [[NSString alloc] initWithBytes:config.data()
|
|
32
|
+
length:config.size()
|
|
33
|
+
encoding:NSUTF8StringEncoding];
|
|
34
|
+
if (result != nil) {
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
message = "Invalid native configuration: not UTF-8";
|
|
38
|
+
}
|
|
39
|
+
if (error != NULL) {
|
|
40
|
+
*error = [NSError errorWithDomain:RCTPushyErrorDomain code:1 userInfo:@{
|
|
41
|
+
NSLocalizedDescriptionKey: [[NSString alloc] initWithBytes:message.data()
|
|
42
|
+
length:message.size()
|
|
43
|
+
encoding:NSUTF8StringEncoding]
|
|
44
|
+
?: @"Invalid native configuration",
|
|
45
|
+
RCTPushyErrorCodeKey: @(pushy::error_codes::kInvalidOptions)
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
return nil;
|
|
130
49
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#import "RCTPushyDownloader.h"
|
|
2
|
+
#import "RCTPushyCommon.h"
|
|
2
3
|
#include "../../cpp/patch_core/archive_limits.h"
|
|
3
4
|
|
|
4
|
-
static NSString *const RCTPushyDownloaderErrorDomain = @"cn.reactnative.pushy";
|
|
5
|
-
|
|
6
5
|
// Cross-launch resumable download (NATIVE_CHECKUPDATE_DESIGN §11.4): a data
|
|
7
6
|
// task streams the archive straight into savePath (a download task's
|
|
8
7
|
// temporary file is discarded on failure, which made every partial byte
|
|
@@ -36,6 +35,16 @@ static void RCTPushyDeleteResumeSidecar(NSString *savePath) {
|
|
|
36
35
|
error:nil];
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
void RCTPushyDiscardArchive(NSString *path) {
|
|
39
|
+
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
|
40
|
+
RCTPushyDeleteResumeSidecar(path);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
BOOL RCTPushyIsHttpsDowngrade(NSURL *from, NSURL *to) {
|
|
44
|
+
return [[from.scheme lowercaseString] isEqualToString:@"https"]
|
|
45
|
+
&& [[to.scheme lowercaseString] isEqualToString:@"http"];
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
long long RCTPushyFileSize(NSString *path) {
|
|
40
49
|
NSDictionary *attributes =
|
|
41
50
|
[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
|
|
@@ -174,7 +183,7 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
174
183
|
{
|
|
175
184
|
NSURL *url = [NSURL URLWithString:self.urlString];
|
|
176
185
|
if (url == nil) {
|
|
177
|
-
[self completeWithError:[NSError errorWithDomain:
|
|
186
|
+
[self completeWithError:[NSError errorWithDomain:RCTPushyErrorDomain
|
|
178
187
|
code:-1
|
|
179
188
|
userInfo:@{
|
|
180
189
|
NSLocalizedDescriptionKey: @"invalid download url",
|
|
@@ -263,13 +272,8 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
263
272
|
}
|
|
264
273
|
}
|
|
265
274
|
|
|
266
|
-
- (void)
|
|
275
|
+
- (void)closeFileHandle
|
|
267
276
|
{
|
|
268
|
-
if (self.finished) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
self.finished = YES;
|
|
272
|
-
|
|
273
277
|
if (self.fileHandle != nil) {
|
|
274
278
|
@try {
|
|
275
279
|
[self.fileHandle closeFile];
|
|
@@ -277,9 +281,18 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
277
281
|
}
|
|
278
282
|
self.fileHandle = nil;
|
|
279
283
|
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
- (void)completeWithError:(NSError *)error
|
|
287
|
+
{
|
|
288
|
+
if (self.finished) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
self.finished = YES;
|
|
292
|
+
|
|
293
|
+
[self closeFileHandle];
|
|
280
294
|
if (self.discardPartial) {
|
|
281
|
-
|
|
282
|
-
RCTPushyDeleteResumeSidecar(self.savePath);
|
|
295
|
+
RCTPushyDiscardArchive(self.savePath);
|
|
283
296
|
}
|
|
284
297
|
|
|
285
298
|
void (^completionHandler)(NSString *, NSError *) = self.completionHandler;
|
|
@@ -297,7 +310,7 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
297
310
|
|
|
298
311
|
- (void)failWithDescription:(NSString *)description code:(NSInteger)code
|
|
299
312
|
{
|
|
300
|
-
self.fileError = [NSError errorWithDomain:
|
|
313
|
+
self.fileError = [NSError errorWithDomain:RCTPushyErrorDomain
|
|
301
314
|
code:code
|
|
302
315
|
userInfo:@{NSLocalizedDescriptionKey: description}];
|
|
303
316
|
}
|
|
@@ -317,15 +330,8 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
317
330
|
self.encodedBody = NO;
|
|
318
331
|
self.lastReportedPercentage = 0;
|
|
319
332
|
self.lastReportedBytes = 0;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
[self.fileHandle closeFile];
|
|
323
|
-
} @catch (NSException *exception) {
|
|
324
|
-
}
|
|
325
|
-
self.fileHandle = nil;
|
|
326
|
-
}
|
|
327
|
-
[[NSFileManager defaultManager] removeItemAtPath:self.savePath error:nil];
|
|
328
|
-
RCTPushyDeleteResumeSidecar(self.savePath);
|
|
333
|
+
[self closeFileHandle];
|
|
334
|
+
RCTPushyDiscardArchive(self.savePath);
|
|
329
335
|
[self.session finishTasksAndInvalidate];
|
|
330
336
|
self.session = nil;
|
|
331
337
|
[self startTransfer];
|
|
@@ -346,9 +352,7 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|
|
346
352
|
completionHandler(nil);
|
|
347
353
|
return;
|
|
348
354
|
}
|
|
349
|
-
|
|
350
|
-
NSString *nextScheme = [request.URL.scheme lowercaseString];
|
|
351
|
-
if ([originalScheme isEqualToString:@"https"] && [nextScheme isEqualToString:@"http"]) {
|
|
355
|
+
if (RCTPushyIsHttpsDowngrade([NSURL URLWithString:self.urlString], request.URL)) {
|
|
352
356
|
[self failWithDescription:[NSString stringWithFormat:
|
|
353
357
|
@"https download redirected to plaintext http: %@", request.URL.absoluteString]
|
|
354
358
|
code:-1];
|
|
@@ -523,19 +527,18 @@ didReceiveResponse:(NSURLResponse *)response
|
|
|
523
527
|
[dataTask cancel];
|
|
524
528
|
return;
|
|
525
529
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
NSString *shortfall = RCTPushyFreeSpaceShortfall(self.savePath, reserve);
|
|
530
|
+
// Unknown/encoded length: the response-time check could only reserve the
|
|
531
|
+
// margin, so the disk is probed before any write that would run past the
|
|
532
|
+
// bytes reserved so far (archive_limits::PlanUnknownLengthFreeSpaceProbe)
|
|
533
|
+
// — no write can ever eat into the margin. The partial stays: a later
|
|
534
|
+
// attempt may find the space.
|
|
535
|
+
pushy::archive_limits::FreeSpaceProbe plan =
|
|
536
|
+
pushy::archive_limits::PlanUnknownLengthFreeSpaceProbe(
|
|
537
|
+
self.expectedTotal, self.receivedBytes, (long long)data.length,
|
|
538
|
+
self.freeSpaceReservedUntil);
|
|
539
|
+
self.freeSpaceReservedUntil = plan.reserved_until;
|
|
540
|
+
if (plan.probe) {
|
|
541
|
+
NSString *shortfall = RCTPushyFreeSpaceShortfall(self.savePath, plan.reserve);
|
|
539
542
|
if (shortfall != nil) {
|
|
540
543
|
[self failWithDescription:shortfall code:-1];
|
|
541
544
|
[dataTask cancel];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Version directories and the completion record: where an install lands and
|
|
2
|
+
// how it is recorded as complete. The record itself (format, atomic write,
|
|
3
|
+
// presence and activation checks) is the shared core's
|
|
4
|
+
// (cpp/patch_core/install_record.h); switchVersion:error: calls its
|
|
5
|
+
// activation check directly.
|
|
6
|
+
#import "RCTPushyInternal.h"
|
|
7
|
+
#include "../../cpp/patch_core/error_codes.h"
|
|
8
|
+
#include "../../cpp/patch_core/install_record.h"
|
|
9
|
+
|
|
10
|
+
PUSHY_HIDDEN NSString *const BUNDLE_FILE_NAME = @"index.bundlejs";
|
|
11
|
+
|
|
12
|
+
PUSHY_HIDDEN NSString *PushyVersionDir(NSString *hash) {
|
|
13
|
+
return [[RCTPushy downloadDir] stringByAppendingPathComponent:hash];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Two-phase install: everything lands in <hash>.staging first.
|
|
17
|
+
PUSHY_HIDDEN NSString *PushyStagingDir(NSString *hash) {
|
|
18
|
+
return [PushyVersionDir(hash) stringByAppendingString:@(pushy::install_record::kStagingSuffix)];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Presence check (launch / dedup paths, no digest work): bundle present and
|
|
22
|
+
// the record is the legacy marker or names this version.
|
|
23
|
+
PUSHY_HIDDEN BOOL PushyHasCompletedVersionAtPath(NSString *versionDir, NSString *hash) {
|
|
24
|
+
return pushy::install_record::IsComplete(PushyToStdString(versionDir), PushyToStdString(hash),
|
|
25
|
+
PushyToStdString(BUNDLE_FILE_NAME));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The record's bundleSha256 (crash attribution); @"" when there is none.
|
|
29
|
+
PUSHY_HIDDEN NSString *PushyRecordedBundleSha256(NSString *versionDir) {
|
|
30
|
+
return PushyFromStdString(
|
|
31
|
+
pushy::install_record::RecordedBundleSha256(PushyToStdString(versionDir))) ?: @"";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Second phase of an install: seals <hash>.staging with the completion
|
|
35
|
+
// record (the final bundle's digest is mandatory: a version whose bundle
|
|
36
|
+
// could not be hashed is never recorded as installed) and renames it over
|
|
37
|
+
// <hash> in one atomic step. nil on success. Runs on the file queue. The
|
|
38
|
+
// caller drops the staging directory on failure.
|
|
39
|
+
PUSHY_HIDDEN NSError *PushyPromoteStagingDir(NSString *hash, NSString *artifactSha256) {
|
|
40
|
+
std::string staging = PushyToStdString(PushyStagingDir(hash));
|
|
41
|
+
pushy::install_record::SealResult sealed = pushy::install_record::Seal(
|
|
42
|
+
staging, PushyToStdString(hash), PushyToStdString(BUNDLE_FILE_NAME),
|
|
43
|
+
PushyToStdString(artifactSha256), std::string());
|
|
44
|
+
if (!sealed.status.ok) {
|
|
45
|
+
return PushyErrorWithCode(sealed.file_failure ? pushy::error_codes::kFileOperationFailed
|
|
46
|
+
: pushy::error_codes::kDeltaFailed,
|
|
47
|
+
PushyFromStdString(sealed.status.message));
|
|
48
|
+
}
|
|
49
|
+
NSString *versionDir = PushyVersionDir(hash);
|
|
50
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
51
|
+
if ([fileManager fileExistsAtPath:versionDir]) {
|
|
52
|
+
[fileManager removeItemAtPath:versionDir error:nil];
|
|
53
|
+
}
|
|
54
|
+
pushy::install_record::Status promoted =
|
|
55
|
+
pushy::install_record::Promote(staging, PushyToStdString(versionDir));
|
|
56
|
+
if (!promoted.ok) {
|
|
57
|
+
return PushyErrorWithCode(pushy::error_codes::kFileOperationFailed,
|
|
58
|
+
PushyFromStdString(promoted.message));
|
|
59
|
+
}
|
|
60
|
+
return nil;
|
|
61
|
+
}
|