react-native-update 10.50.0 → 10.51.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/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
- package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
- package/README-CN.md +14 -1
- package/README.md +14 -1
- package/android/build.gradle +10 -0
- package/android/jni/Android.mk +5 -1
- 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/DownloadTask.java +76 -21
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +160 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +44 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/cpp/update_flow_core/flow_json.cpp +522 -0
- package/cpp/update_flow_core/flow_json.h +103 -0
- package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
- package/cpp/update_flow_core/update_flow_core.cpp +301 -0
- package/cpp/update_flow_core/update_flow_core.h +77 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +48 -9
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
- package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +14 -0
- package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +148 -69
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +779 -61
- package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
- package/package.json +3 -1
- package/react-native-update.podspec +2 -0
- package/src/NativePushy.ts +14 -0
- package/src/client.ts +341 -118
- package/src/endpoint.ts +7 -29
- package/src/provider.tsx +38 -2
- package/src/type.ts +26 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +1 -12
- package/src/isInRollout.ts +0 -87
- package/src/resolveCheckResult.ts +0 -30
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// JNI surface of the update-flow decision layer for the Android orchestrator
|
|
2
|
+
// (NativeCheckOrchestrator.java). Pure string-in/string-out: every payload is
|
|
3
|
+
// JSON, matching the decision layer's own boundary. A null return means
|
|
4
|
+
// "input did not parse" and the caller skips the check round.
|
|
5
|
+
#include <jni.h>
|
|
6
|
+
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <limits>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
#include "../patch_core/jni_util.h"
|
|
13
|
+
#include "flow_json.h"
|
|
14
|
+
#include "update_flow_core.h"
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
jstring ToJString(JNIEnv* env, const std::string& value) {
|
|
19
|
+
// flow_json emits standard UTF-8, while NewStringUTF expects JNI's modified
|
|
20
|
+
// UTF-8 and corrupts supplementary code points. Decode explicitly and pass
|
|
21
|
+
// UTF-16 code units to NewString. Accept encoded surrogate code points too:
|
|
22
|
+
// strings originating in Java may have entered through modified UTF-8.
|
|
23
|
+
std::vector<jchar> utf16;
|
|
24
|
+
utf16.reserve(value.size());
|
|
25
|
+
size_t i = 0;
|
|
26
|
+
while (i < value.size()) {
|
|
27
|
+
const unsigned char first = static_cast<unsigned char>(value[i]);
|
|
28
|
+
uint32_t codePoint = 0xfffd;
|
|
29
|
+
size_t width = 1;
|
|
30
|
+
if (first < 0x80) {
|
|
31
|
+
codePoint = first;
|
|
32
|
+
} else if (first >= 0xc2 && first <= 0xdf && i + 1 < value.size()) {
|
|
33
|
+
const unsigned char b1 = static_cast<unsigned char>(value[i + 1]);
|
|
34
|
+
if ((b1 & 0xc0) == 0x80) {
|
|
35
|
+
codePoint = ((first & 0x1f) << 6) | (b1 & 0x3f);
|
|
36
|
+
width = 2;
|
|
37
|
+
}
|
|
38
|
+
} else if (first == 0xc0 && i + 1 < value.size() &&
|
|
39
|
+
static_cast<unsigned char>(value[i + 1]) == 0x80) {
|
|
40
|
+
codePoint = 0; // modified UTF-8 encoding of U+0000
|
|
41
|
+
width = 2;
|
|
42
|
+
} else if (first >= 0xe0 && first <= 0xef && i + 2 < value.size()) {
|
|
43
|
+
const unsigned char b1 = static_cast<unsigned char>(value[i + 1]);
|
|
44
|
+
const unsigned char b2 = static_cast<unsigned char>(value[i + 2]);
|
|
45
|
+
if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 &&
|
|
46
|
+
!(first == 0xe0 && b1 < 0xa0)) {
|
|
47
|
+
codePoint = ((first & 0x0f) << 12) | ((b1 & 0x3f) << 6) |
|
|
48
|
+
(b2 & 0x3f);
|
|
49
|
+
width = 3;
|
|
50
|
+
}
|
|
51
|
+
} else if (first >= 0xf0 && first <= 0xf4 && i + 3 < value.size()) {
|
|
52
|
+
const unsigned char b1 = static_cast<unsigned char>(value[i + 1]);
|
|
53
|
+
const unsigned char b2 = static_cast<unsigned char>(value[i + 2]);
|
|
54
|
+
const unsigned char b3 = static_cast<unsigned char>(value[i + 3]);
|
|
55
|
+
if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 &&
|
|
56
|
+
(b3 & 0xc0) == 0x80 && !(first == 0xf0 && b1 < 0x90) &&
|
|
57
|
+
!(first == 0xf4 && b1 > 0x8f)) {
|
|
58
|
+
codePoint = ((first & 0x07) << 18) | ((b1 & 0x3f) << 12) |
|
|
59
|
+
((b2 & 0x3f) << 6) | (b3 & 0x3f);
|
|
60
|
+
width = 4;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
i += width;
|
|
64
|
+
if (codePoint <= 0xffff) {
|
|
65
|
+
utf16.push_back(static_cast<jchar>(codePoint));
|
|
66
|
+
} else {
|
|
67
|
+
codePoint -= 0x10000;
|
|
68
|
+
utf16.push_back(static_cast<jchar>(0xd800 + (codePoint >> 10)));
|
|
69
|
+
utf16.push_back(static_cast<jchar>(0xdc00 + (codePoint & 0x3ff)));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (utf16.size() > static_cast<size_t>(std::numeric_limits<jsize>::max())) {
|
|
73
|
+
return nullptr;
|
|
74
|
+
}
|
|
75
|
+
return env->NewString(utf16.empty() ? nullptr : utf16.data(),
|
|
76
|
+
static_cast<jsize>(utf16.size()));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
} // namespace
|
|
80
|
+
|
|
81
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
82
|
+
Java_cn_reactnative_modules_update_NativeUpdateFlow_buildCheckRequestBody(
|
|
83
|
+
JNIEnv* env, jclass, jstring inputJson) {
|
|
84
|
+
bool ok = false;
|
|
85
|
+
flowjson::Value input = flowjson::Parse(
|
|
86
|
+
pushy::jni_util::JStringToString(env, inputJson), &ok);
|
|
87
|
+
if (!ok || !input.IsObject()) {
|
|
88
|
+
return nullptr;
|
|
89
|
+
}
|
|
90
|
+
return ToJString(
|
|
91
|
+
env, flowjson::Stringify(updateflow::BuildCheckRequestBody(input)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
95
|
+
Java_cn_reactnative_modules_update_NativeUpdateFlow_orderEndpointCandidates(
|
|
96
|
+
JNIEnv* env, jclass, jstring endpointsJson, jdouble randomSample) {
|
|
97
|
+
bool ok = false;
|
|
98
|
+
flowjson::Value endpoints = flowjson::Parse(
|
|
99
|
+
pushy::jni_util::JStringToString(env, endpointsJson), &ok);
|
|
100
|
+
if (!ok || !endpoints.IsArray()) {
|
|
101
|
+
return nullptr;
|
|
102
|
+
}
|
|
103
|
+
return ToJString(env, flowjson::Stringify(updateflow::OrderEndpointCandidates(
|
|
104
|
+
endpoints, randomSample)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
108
|
+
Java_cn_reactnative_modules_update_NativeUpdateFlow_handleCheckResponse(
|
|
109
|
+
JNIEnv* env, jclass, jstring responseText, jstring identityJson,
|
|
110
|
+
jstring afterDownload) {
|
|
111
|
+
bool ok = false;
|
|
112
|
+
flowjson::Value identity = flowjson::Parse(
|
|
113
|
+
pushy::jni_util::JStringToString(env, identityJson), &ok);
|
|
114
|
+
if (!ok || !identity.IsObject()) {
|
|
115
|
+
return nullptr;
|
|
116
|
+
}
|
|
117
|
+
return ToJString(env, flowjson::Stringify(updateflow::HandleCheckResponse(
|
|
118
|
+
pushy::jni_util::JStringToString(env, responseText),
|
|
119
|
+
identity, false,
|
|
120
|
+
pushy::jni_util::JStringToString(env, afterDownload))));
|
|
121
|
+
}
|
|
@@ -17,6 +17,10 @@ set(PATCH_CORE_DIR ${REPO_ROOT}/cpp/patch_core)
|
|
|
17
17
|
if(NOT EXISTS ${PATCH_CORE_DIR}/patch_core.cpp)
|
|
18
18
|
set(PATCH_CORE_DIR ${STAGED_NATIVE_DIR}/patch_core)
|
|
19
19
|
endif()
|
|
20
|
+
set(UPDATE_FLOW_CORE_DIR ${REPO_ROOT}/cpp/update_flow_core)
|
|
21
|
+
if(NOT EXISTS ${UPDATE_FLOW_CORE_DIR}/update_flow_core.cpp)
|
|
22
|
+
set(UPDATE_FLOW_CORE_DIR ${STAGED_NATIVE_DIR}/update_flow_core)
|
|
23
|
+
endif()
|
|
20
24
|
|
|
21
25
|
# Always compile from source. A prebuilt-import branch used to live here, but
|
|
22
26
|
# it had no symbol verification: a stale libs/<abi>/librnupdate.so missing a
|
|
@@ -32,6 +36,8 @@ set(HDP_SOURCES
|
|
|
32
36
|
${PATCH_CORE_DIR}/hbc_transform_wire.cpp
|
|
33
37
|
${PATCH_CORE_DIR}/patch_core.cpp
|
|
34
38
|
${PATCH_CORE_DIR}/state_core.cpp
|
|
39
|
+
${UPDATE_FLOW_CORE_DIR}/flow_json.cpp
|
|
40
|
+
${UPDATE_FLOW_CORE_DIR}/update_flow_core.cpp
|
|
35
41
|
${ANDROID_JNI_DIR}/hpatch.c
|
|
36
42
|
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch/patch.c
|
|
37
43
|
${HDIFFPATCH_DIR}/file_for_patch.c
|
|
@@ -46,6 +52,7 @@ add_library(rnupdate SHARED
|
|
|
46
52
|
target_include_directories(rnupdate PRIVATE
|
|
47
53
|
${CMAKE_CURRENT_SOURCE_DIR}
|
|
48
54
|
${PATCH_CORE_DIR}
|
|
55
|
+
${UPDATE_FLOW_CORE_DIR}
|
|
49
56
|
${ANDROID_JNI_DIR}
|
|
50
57
|
${HDIFFPATCH_DIR}
|
|
51
58
|
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#include <napi/native_api.h>
|
|
2
|
+
|
|
3
|
+
#include "flow_json.h"
|
|
4
|
+
#include "update_flow_core.h"
|
|
2
5
|
#include <js_native_api.h>
|
|
3
6
|
#include <js_native_api_types.h>
|
|
4
7
|
|
|
@@ -992,6 +995,102 @@ static napi_value GetSupportedDiffVersion(napi_env env, napi_callback_info) {
|
|
|
992
995
|
return result;
|
|
993
996
|
}
|
|
994
997
|
|
|
998
|
+
// ---- update-flow decision layer (NATIVE_CHECKUPDATE_DESIGN §10) ----
|
|
999
|
+
// String-in/string-out JSON, matching the decision layer's own boundary.
|
|
1000
|
+
// Returning undefined (nullptr without a pending exception) means "input did
|
|
1001
|
+
// not parse"; the ArkTS orchestrator skips the check round.
|
|
1002
|
+
|
|
1003
|
+
static napi_value MakeUtf8String(napi_env env, const std::string& value) {
|
|
1004
|
+
napi_value result = nullptr;
|
|
1005
|
+
if (napi_create_string_utf8(env, value.c_str(), value.size(), &result) !=
|
|
1006
|
+
napi_ok) {
|
|
1007
|
+
ThrowError(env, "Failed to create string");
|
|
1008
|
+
return nullptr;
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
static napi_value FlowBuildCheckRequestBody(napi_env env,
|
|
1014
|
+
napi_callback_info info) {
|
|
1015
|
+
size_t argc = 1;
|
|
1016
|
+
napi_value args[1] = {nullptr};
|
|
1017
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
1018
|
+
ThrowError(env, "buildCheckRequestBody: missing input argument");
|
|
1019
|
+
return nullptr;
|
|
1020
|
+
}
|
|
1021
|
+
bool ok = false;
|
|
1022
|
+
std::string input_json = GetString(env, args[0], &ok);
|
|
1023
|
+
if (!ok) {
|
|
1024
|
+
return nullptr;
|
|
1025
|
+
}
|
|
1026
|
+
bool parsed = false;
|
|
1027
|
+
flowjson::Value input = flowjson::Parse(input_json, &parsed);
|
|
1028
|
+
if (!parsed || !input.IsObject()) {
|
|
1029
|
+
return nullptr;
|
|
1030
|
+
}
|
|
1031
|
+
return MakeUtf8String(
|
|
1032
|
+
env, flowjson::Stringify(updateflow::BuildCheckRequestBody(input)));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
static napi_value FlowOrderEndpointCandidates(napi_env env,
|
|
1036
|
+
napi_callback_info info) {
|
|
1037
|
+
size_t argc = 2;
|
|
1038
|
+
napi_value args[2] = {nullptr, nullptr};
|
|
1039
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 2) {
|
|
1040
|
+
ThrowError(env, "orderEndpointCandidates: missing arguments");
|
|
1041
|
+
return nullptr;
|
|
1042
|
+
}
|
|
1043
|
+
bool ok = false;
|
|
1044
|
+
std::string endpoints_json = GetString(env, args[0], &ok);
|
|
1045
|
+
if (!ok) {
|
|
1046
|
+
return nullptr;
|
|
1047
|
+
}
|
|
1048
|
+
double sample = 0;
|
|
1049
|
+
if (napi_get_value_double(env, args[1], &sample) != napi_ok) {
|
|
1050
|
+
ThrowError(env, "orderEndpointCandidates: expected number sample");
|
|
1051
|
+
return nullptr;
|
|
1052
|
+
}
|
|
1053
|
+
bool parsed = false;
|
|
1054
|
+
flowjson::Value endpoints = flowjson::Parse(endpoints_json, &parsed);
|
|
1055
|
+
if (!parsed || !endpoints.IsArray()) {
|
|
1056
|
+
return nullptr;
|
|
1057
|
+
}
|
|
1058
|
+
return MakeUtf8String(env,
|
|
1059
|
+
flowjson::Stringify(updateflow::OrderEndpointCandidates(
|
|
1060
|
+
endpoints, sample)));
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
static napi_value FlowHandleCheckResponse(napi_env env,
|
|
1064
|
+
napi_callback_info info) {
|
|
1065
|
+
size_t argc = 3;
|
|
1066
|
+
napi_value args[3] = {nullptr, nullptr, nullptr};
|
|
1067
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 3) {
|
|
1068
|
+
ThrowError(env, "handleCheckResponse: missing arguments");
|
|
1069
|
+
return nullptr;
|
|
1070
|
+
}
|
|
1071
|
+
bool ok = false;
|
|
1072
|
+
std::string response_text = GetString(env, args[0], &ok);
|
|
1073
|
+
if (!ok) {
|
|
1074
|
+
return nullptr;
|
|
1075
|
+
}
|
|
1076
|
+
std::string identity_json = GetString(env, args[1], &ok);
|
|
1077
|
+
if (!ok) {
|
|
1078
|
+
return nullptr;
|
|
1079
|
+
}
|
|
1080
|
+
std::string after_download = GetString(env, args[2], &ok);
|
|
1081
|
+
if (!ok) {
|
|
1082
|
+
return nullptr;
|
|
1083
|
+
}
|
|
1084
|
+
bool parsed = false;
|
|
1085
|
+
flowjson::Value identity = flowjson::Parse(identity_json, &parsed);
|
|
1086
|
+
if (!parsed || !identity.IsObject()) {
|
|
1087
|
+
return nullptr;
|
|
1088
|
+
}
|
|
1089
|
+
return MakeUtf8String(env,
|
|
1090
|
+
flowjson::Stringify(updateflow::HandleCheckResponse(
|
|
1091
|
+
response_text, identity, false, after_download)));
|
|
1092
|
+
}
|
|
1093
|
+
|
|
995
1094
|
napi_value Init(napi_env env, napi_value exports) {
|
|
996
1095
|
if (!ExportFunction(env, exports, "syncStateWithBinaryVersion", SyncStateWithBinaryVersion) ||
|
|
997
1096
|
!ExportFunction(env, exports, "runStateCore", RunStateCore) ||
|
|
@@ -1001,7 +1100,10 @@ napi_value Init(napi_env env, napi_value exports) {
|
|
|
1001
1100
|
!ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries) ||
|
|
1002
1101
|
!ExportFunction(env, exports, "sha256Hex", Sha256Hex) ||
|
|
1003
1102
|
!ExportFunction(env, exports, "crc32", Crc32) ||
|
|
1004
|
-
!ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion)
|
|
1103
|
+
!ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion) ||
|
|
1104
|
+
!ExportFunction(env, exports, "buildCheckRequestBody", FlowBuildCheckRequestBody) ||
|
|
1105
|
+
!ExportFunction(env, exports, "orderEndpointCandidates", FlowOrderEndpointCandidates) ||
|
|
1106
|
+
!ExportFunction(env, exports, "handleCheckResponse", FlowHandleCheckResponse)) {
|
|
1005
1107
|
return nullptr;
|
|
1006
1108
|
}
|
|
1007
1109
|
return exports;
|
|
@@ -11,6 +11,9 @@ import NativePatchCore, {
|
|
|
11
11
|
ARCHIVE_PATCH_TYPE_FROM_PPK,
|
|
12
12
|
CopyGroupResult,
|
|
13
13
|
} from './NativePatchCore';
|
|
14
|
+
import { monotonicNowMs } from './MonotonicClock';
|
|
15
|
+
|
|
16
|
+
export const VERSION_COMPLETE_FILE_NAME = '.pushy-complete';
|
|
14
17
|
|
|
15
18
|
export interface PatchManifestArrays {
|
|
16
19
|
copyFroms: string[];
|
|
@@ -95,6 +98,7 @@ const DIFF_MANIFEST_ENTRY = '__diff.json';
|
|
|
95
98
|
const HARMONY_BUNDLE_PATCH_ENTRY = 'bundle.harmony.js.patch';
|
|
96
99
|
const TEMP_ORIGIN_BUNDLE_ENTRY = '.origin.bundle.harmony.js';
|
|
97
100
|
const FILE_COPY_BUFFER_SIZE = 64 * 1024;
|
|
101
|
+
const DOWNLOAD_CALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
98
102
|
|
|
99
103
|
export class DownloadTask {
|
|
100
104
|
private context: common.Context;
|
|
@@ -343,6 +347,12 @@ export class DownloadTask {
|
|
|
343
347
|
let writeQueue = Promise.resolve();
|
|
344
348
|
let lastReportedPercentage = -1;
|
|
345
349
|
let lastReportedBytes = 0;
|
|
350
|
+
const deadlineUptimeMs = params.deadlineUptimeMs > 0
|
|
351
|
+
? params.deadlineUptimeMs
|
|
352
|
+
: monotonicNowMs() + DOWNLOAD_CALL_TIMEOUT_MS;
|
|
353
|
+
if (deadlineUptimeMs <= monotonicNowMs()) {
|
|
354
|
+
throw Error('Download deadline expired before start');
|
|
355
|
+
}
|
|
346
356
|
|
|
347
357
|
// Emit at most one progress event per whole-percent change (or per 256KB
|
|
348
358
|
// when the total is unknown), and only from the dataReceive handler, so the
|
|
@@ -374,6 +384,7 @@ export class DownloadTask {
|
|
|
374
384
|
// Promise (and the JS caller) forever.
|
|
375
385
|
const INACTIVITY_TIMEOUT_MS = 60000;
|
|
376
386
|
let watchdogTimer: number | null = null;
|
|
387
|
+
let deadlineTimer: number | null = null;
|
|
377
388
|
let inactivityReject: ((error: Error) => void) | null = null;
|
|
378
389
|
const clearWatchdog = () => {
|
|
379
390
|
if (watchdogTimer !== null) {
|
|
@@ -482,14 +493,22 @@ export class DownloadTask {
|
|
|
482
493
|
},
|
|
483
494
|
);
|
|
484
495
|
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
header: {
|
|
490
|
-
'Content-Type': 'application/octet-stream',
|
|
491
|
-
},
|
|
496
|
+
const deadlinePromise = new Promise<never>((_, reject) => {
|
|
497
|
+
deadlineTimer = setTimeout(() => {
|
|
498
|
+
reject(Error('Download exceeded its whole-call deadline'));
|
|
499
|
+
}, Math.max(1, deadlineUptimeMs - monotonicNowMs()));
|
|
492
500
|
});
|
|
501
|
+
const responseCode = await Promise.race([
|
|
502
|
+
httpRequest.requestInStream(params.url, {
|
|
503
|
+
method: http.RequestMethod.GET,
|
|
504
|
+
readTimeout: 60000,
|
|
505
|
+
connectTimeout: 60000,
|
|
506
|
+
header: {
|
|
507
|
+
'Content-Type': 'application/octet-stream',
|
|
508
|
+
},
|
|
509
|
+
}),
|
|
510
|
+
deadlinePromise,
|
|
511
|
+
]);
|
|
493
512
|
if (responseCode > 299) {
|
|
494
513
|
throw Error(`Server error: ${responseCode}`);
|
|
495
514
|
}
|
|
@@ -499,7 +518,7 @@ export class DownloadTask {
|
|
|
499
518
|
// 时 reject(unhandled rejection),且 promise 一经 reject 无法复活——
|
|
500
519
|
// 即使随后数据正常流入,race 也必然以 "Download stalled" 失败。
|
|
501
520
|
refreshWatchdog();
|
|
502
|
-
await Promise.race([dataEndPromise, inactivityPromise]);
|
|
521
|
+
await Promise.race([dataEndPromise, inactivityPromise, deadlinePromise]);
|
|
503
522
|
const stats = await fileIo.stat(params.targetFile);
|
|
504
523
|
const fileSize = stats.size;
|
|
505
524
|
if (contentLength > 0 && fileSize !== contentLength) {
|
|
@@ -512,6 +531,9 @@ export class DownloadTask {
|
|
|
512
531
|
throw error;
|
|
513
532
|
} finally {
|
|
514
533
|
clearWatchdog();
|
|
534
|
+
if (deadlineTimer !== null) {
|
|
535
|
+
clearTimeout(deadlineTimer);
|
|
536
|
+
}
|
|
515
537
|
try {
|
|
516
538
|
await closeWriter();
|
|
517
539
|
} catch (closeError) {
|
|
@@ -798,6 +820,10 @@ export class DownloadTask {
|
|
|
798
820
|
}
|
|
799
821
|
|
|
800
822
|
public async execute(params: DownloadTaskParams): Promise<void> {
|
|
823
|
+
const isPatchTask =
|
|
824
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL ||
|
|
825
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP ||
|
|
826
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
|
|
801
827
|
try {
|
|
802
828
|
switch (params.type) {
|
|
803
829
|
case DownloadTaskParams.TASK_TYPE_PATCH_FULL:
|
|
@@ -818,13 +844,26 @@ export class DownloadTask {
|
|
|
818
844
|
default:
|
|
819
845
|
throw Error(`Unknown task type: ${params.type}`);
|
|
820
846
|
}
|
|
847
|
+
if (isPatchTask) {
|
|
848
|
+
await this.writeFileContent(
|
|
849
|
+
`${params.unzipDirectory}/${VERSION_COMPLETE_FILE_NAME}`,
|
|
850
|
+
new Uint8Array(0),
|
|
851
|
+
);
|
|
852
|
+
}
|
|
821
853
|
} catch (error: any) {
|
|
822
854
|
console.error('Task execution failed:', error.message);
|
|
823
855
|
if (params.type !== DownloadTaskParams.TASK_TYPE_CLEANUP) {
|
|
824
856
|
try {
|
|
825
857
|
if (params.type === DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD) {
|
|
826
858
|
await fileIo.unlink(params.targetFile);
|
|
827
|
-
} else
|
|
859
|
+
} else if (
|
|
860
|
+
!fileIo.accessSync(
|
|
861
|
+
`${params.unzipDirectory}/${VERSION_COMPLETE_FILE_NAME}`,
|
|
862
|
+
) ||
|
|
863
|
+
!fileIo.accessSync(`${params.unzipDirectory}/bundle.harmony.js`)
|
|
864
|
+
) {
|
|
865
|
+
// Never let a failed duplicate task remove an install already
|
|
866
|
+
// handed off by an earlier task via marker + bundle.
|
|
828
867
|
await this.removeDirectory(params.unzipDirectory);
|
|
829
868
|
}
|
|
830
869
|
} catch (cleanupError: any) {
|
|
@@ -16,4 +16,7 @@ export class DownloadTaskParams {
|
|
|
16
16
|
targetFile: string = ''; // 目标文件路径
|
|
17
17
|
unzipDirectory: string = ''; // 解压目录路径
|
|
18
18
|
originDirectory: string = ''; // 原始文件目录路径
|
|
19
|
+
// Native cold-start orchestrator's absolute monotonic-uptime deadline. Zero
|
|
20
|
+
// uses the normal public download API's 10-minute whole-call cap.
|
|
21
|
+
deadlineUptimeMs: number = 0;
|
|
19
22
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { systemDateTime } from '@kit.BasicServicesKit';
|
|
2
|
+
|
|
3
|
+
// Absolute deadlines must not use Date.now(): automatic time synchronization
|
|
4
|
+
// can move the wall clock while the cold-start rescue round is running.
|
|
5
|
+
//
|
|
6
|
+
// TimeType.ACTIVE (not STARTUP) is the semantic match for the other two
|
|
7
|
+
// platforms: iOS NSProcessInfo.systemUptime and Android System.nanoTime both
|
|
8
|
+
// stop during deep sleep, so a device that sleeps mid-download resumes with
|
|
9
|
+
// its budget intact. STARTUP keeps counting through sleep and would abort a
|
|
10
|
+
// rescue that the same network completes on iOS/Android.
|
|
11
|
+
export function monotonicNowMs(): number {
|
|
12
|
+
return systemDateTime.getUptime(systemDateTime.TimeType.ACTIVE);
|
|
13
|
+
}
|