react-native-update 10.59.2 → 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 +2 -7
- 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 +17 -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 -2634
- 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 +73 -82
- 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
|
@@ -6,10 +6,12 @@ import type { UpdateContext } from './UpdateContext';
|
|
|
6
6
|
import { BundlePreparationRound, bundlePreparationResult } from './BundlePreparationResult';
|
|
7
7
|
import type { BundlePreparationResult } from './BundlePreparationResult';
|
|
8
8
|
import { isSafePathComponent } from './PathUtils';
|
|
9
|
-
import {
|
|
9
|
+
import { STATUS_NONE } from './Texts';
|
|
10
10
|
import { monotonicNowMs } from './MonotonicClock';
|
|
11
11
|
import {
|
|
12
|
+
ERROR_DELTA_FAILED,
|
|
12
13
|
ERROR_DOWNLOAD_FAILED,
|
|
14
|
+
PushyError,
|
|
13
15
|
createPushyError,
|
|
14
16
|
getErrorMessage,
|
|
15
17
|
} from './ErrorCodes';
|
|
@@ -38,38 +40,26 @@ const DOWNLOAD_PHASE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
|
38
40
|
const DOWNLOAD_TYPE_DIFF = 'diff';
|
|
39
41
|
const DOWNLOAD_TYPE_PDIFF = 'pdiff';
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
packageVersion?: string;
|
|
44
|
-
endpoints?: string[];
|
|
45
|
-
queryUrls?: string[];
|
|
46
|
-
afterDownload?: string;
|
|
47
|
-
rnu?: string;
|
|
48
|
-
rn?: string;
|
|
49
|
-
disabled?: boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface FlowIdentity {
|
|
43
|
+
// 本次启动的事实,交给共享内核(cpp/update_flow_core/native_round.h)。
|
|
44
|
+
interface RoundEnv {
|
|
53
45
|
packageVersion: string;
|
|
54
|
-
currentVersion
|
|
46
|
+
currentVersion: string;
|
|
55
47
|
uuid: string;
|
|
56
|
-
rolledBackVersion
|
|
48
|
+
rolledBackVersion: string;
|
|
49
|
+
buildTime: string;
|
|
50
|
+
os: string;
|
|
51
|
+
bundleHash: string;
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
interface
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
os: string;
|
|
63
|
-
uuid: string;
|
|
54
|
+
interface PreparedRound {
|
|
55
|
+
body: string;
|
|
56
|
+
query: Object;
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
interface
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
cInfo: FlowCInfo;
|
|
71
|
-
supportedDiffVersion: number;
|
|
72
|
-
bundleHash: string;
|
|
59
|
+
interface QueryStep {
|
|
60
|
+
url?: string;
|
|
61
|
+
post?: boolean;
|
|
62
|
+
found?: boolean;
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
interface DecisionAttempt {
|
|
@@ -77,35 +67,16 @@ interface DecisionAttempt {
|
|
|
77
67
|
urls?: string[];
|
|
78
68
|
}
|
|
79
69
|
|
|
80
|
-
interface DecisionInfoConfig {
|
|
81
|
-
forceBoot?: boolean;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface DecisionInfo {
|
|
85
|
-
name?: string;
|
|
86
|
-
description?: string;
|
|
87
|
-
metaInfo?: string;
|
|
88
|
-
// 服务端按版本下发的配置(forceBoot = 救砖指令)。
|
|
89
|
-
config?: DecisionInfoConfig;
|
|
90
|
-
// 写进 hash 信息:这个版本是被救援通道送进来的,JS 在 markSuccess 时据此
|
|
91
|
-
// 上报 forceBootRescue 回执(与 Android/iOS 一致)。
|
|
92
|
-
forceBootRescue?: boolean;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
70
|
interface Decision {
|
|
96
71
|
action?: string;
|
|
97
72
|
reason?: string;
|
|
98
73
|
hash?: string;
|
|
99
74
|
attempts?: DecisionAttempt[];
|
|
100
75
|
activate?: boolean;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
ts: number;
|
|
106
|
-
body: string;
|
|
107
|
-
request: string;
|
|
108
|
-
config: string;
|
|
76
|
+
// 要与版本一起持久化的元信息(JS 侧 setLocalHashInfo;forceBootRescue 由内核标记)。
|
|
77
|
+
hashInfo?: Object;
|
|
78
|
+
// 供 JS 复用的响应缓存文本(§10.3)。
|
|
79
|
+
cache?: string;
|
|
109
80
|
}
|
|
110
81
|
|
|
111
82
|
let scheduled = false;
|
|
@@ -137,28 +108,37 @@ function startNativeRound(
|
|
|
137
108
|
});
|
|
138
109
|
}
|
|
139
110
|
|
|
111
|
+
interface ConfigRead {
|
|
112
|
+
json: string;
|
|
113
|
+
/** Set when the persisted configuration cannot start a round. */
|
|
114
|
+
rejection?: BundlePreparationResult;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The single configuration gate shared by the preflight, the host API and
|
|
118
|
+
// the round itself, so the three can never disagree about what is runnable.
|
|
119
|
+
// The verdict is the shared core's (cpp/update_flow_core ParseRunnableConfig,
|
|
120
|
+
// also used by Android and iOS).
|
|
121
|
+
interface RunnableVerdict {
|
|
122
|
+
status?: string;
|
|
123
|
+
reason?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readNativeConfig(context: UpdateContext): ConfigRead {
|
|
127
|
+
const json = context.getKv(KEY_CONFIG);
|
|
128
|
+
const verdict = JSON.parse(NativePatchCore.runnableConfig(json)) as RunnableVerdict;
|
|
129
|
+
if (verdict.status !== undefined) {
|
|
130
|
+
return {
|
|
131
|
+
json,
|
|
132
|
+
rejection: bundlePreparationResult(verdict.status, verdict.reason ?? ''),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { json };
|
|
136
|
+
}
|
|
137
|
+
|
|
140
138
|
// A preflight skip must not consume the process's only round: configuration
|
|
141
139
|
// may arrive after the delayed startup timer on a first-ever launch.
|
|
142
140
|
function configurationError(context: UpdateContext): BundlePreparationResult | undefined {
|
|
143
|
-
|
|
144
|
-
if (!json) {
|
|
145
|
-
return bundlePreparationResult('skipped', 'not_configured');
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
const config = JSON.parse(json) as NativeConfig;
|
|
149
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
150
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
151
|
-
}
|
|
152
|
-
if (config.disabled) {
|
|
153
|
-
return bundlePreparationResult('skipped', 'disabled');
|
|
154
|
-
}
|
|
155
|
-
if (typeof config.appKey !== 'string' || config.appKey.trim().length === 0) {
|
|
156
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
157
|
-
}
|
|
158
|
-
} catch (e) {
|
|
159
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
160
|
-
}
|
|
161
|
-
return undefined;
|
|
141
|
+
return readNativeConfig(context).rejection;
|
|
162
142
|
}
|
|
163
143
|
|
|
164
144
|
export async function prepareBundleNative(
|
|
@@ -167,24 +147,11 @@ export async function prepareBundleNative(
|
|
|
167
147
|
if (scheduledContext !== context) {
|
|
168
148
|
return bundlePreparationResult('skipped', 'not_initialized');
|
|
169
149
|
}
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const config = JSON.parse(configJson) as NativeConfig;
|
|
176
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
177
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
178
|
-
}
|
|
179
|
-
if (config.disabled) {
|
|
180
|
-
return bundlePreparationResult('skipped', 'disabled');
|
|
181
|
-
}
|
|
182
|
-
if (typeof config.appKey !== 'string' || !config.appKey) {
|
|
183
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
184
|
-
}
|
|
185
|
-
} catch (e) {
|
|
186
|
-
return bundlePreparationResult('failed', 'invalid_config');
|
|
150
|
+
const read = readNativeConfig(context);
|
|
151
|
+
if (read.rejection !== undefined) {
|
|
152
|
+
return read.rejection;
|
|
187
153
|
}
|
|
154
|
+
const configJson = read.json;
|
|
188
155
|
const result = await startNativeRound(context, scheduledRollback);
|
|
189
156
|
if (roundConfigGeneration !== context.getNativeConfigGeneration()
|
|
190
157
|
|| configJson !== roundConfigJson || configJson !== context.getKv(KEY_CONFIG)) {
|
|
@@ -205,13 +172,11 @@ export function recordJsRound(config: string): void {
|
|
|
205
172
|
jsCompletedConfig = config;
|
|
206
173
|
}
|
|
207
174
|
|
|
208
|
-
// JS
|
|
175
|
+
// JS 已用与原生等价的配置(C1:规范形式相同,而非逐字相同——宿主持有的配置
|
|
176
|
+
// 与 JS 序列化格式不同)拿到有效响应(§10.3):延迟轮次就是一次重复请求。
|
|
209
177
|
// 只有计划内的轮次会问这个问题——JS 没起来、启动即崩、检查失败时都不会有信号。
|
|
210
178
|
function hasJsRound(context: UpdateContext): boolean {
|
|
211
|
-
return (
|
|
212
|
-
jsCompletedConfig !== undefined &&
|
|
213
|
-
jsCompletedConfig === context.getKv(KEY_CONFIG)
|
|
214
|
-
);
|
|
179
|
+
return !!jsCompletedConfig && context.isStoredNativeConfig(jsCompletedConfig);
|
|
215
180
|
}
|
|
216
181
|
|
|
217
182
|
export function scheduleNativeSync(
|
|
@@ -252,33 +217,13 @@ async function runOnce(
|
|
|
252
217
|
roundGeneration = resetGeneration;
|
|
253
218
|
roundConfigGeneration = context.getNativeConfigGeneration();
|
|
254
219
|
roundResult = bundlePreparationResult('failed', 'check_failed');
|
|
255
|
-
const
|
|
256
|
-
roundConfigJson =
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
roundResult = bundlePreparationResult('skipped', 'not_configured');
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
let config: NativeConfig;
|
|
263
|
-
try {
|
|
264
|
-
config = JSON.parse(configJson) as NativeConfig;
|
|
265
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
266
|
-
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
} catch (e) {
|
|
270
|
-
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (config.disabled) {
|
|
274
|
-
roundResult = bundlePreparationResult('skipped', 'disabled');
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const appKey = config.appKey ?? '';
|
|
278
|
-
if (!appKey) {
|
|
279
|
-
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
220
|
+
const read = readNativeConfig(context);
|
|
221
|
+
roundConfigJson = read.json;
|
|
222
|
+
if (read.rejection !== undefined) {
|
|
223
|
+
roundResult = read.rejection;
|
|
280
224
|
return;
|
|
281
225
|
}
|
|
226
|
+
const configJson = read.json;
|
|
282
227
|
// 从这里起本轮开始做真实工作:留下面包屑,死于轮中时下次启动零延迟续传。
|
|
283
228
|
logger.info(
|
|
284
229
|
TAG,
|
|
@@ -296,8 +241,6 @@ async function runOnce(
|
|
|
296
241
|
launchRolledBackVersion,
|
|
297
242
|
resetGeneration,
|
|
298
243
|
configJson,
|
|
299
|
-
config,
|
|
300
|
-
appKey,
|
|
301
244
|
);
|
|
302
245
|
} finally {
|
|
303
246
|
try {
|
|
@@ -309,103 +252,84 @@ async function runOnce(
|
|
|
309
252
|
}
|
|
310
253
|
}
|
|
311
254
|
|
|
312
|
-
// 配置端点全为 https 时,网络下发的端点列表/制品 URL 一律拒绝明文 http:
|
|
313
|
-
// @ohos.net.http 内部跟随重定向且不暴露最终 URL,这是鸿蒙侧唯一能做的
|
|
314
|
-
// https→http 降级防线(SR-1);其余依赖宿主 network_config.json 的
|
|
315
|
-
// cleartextTrafficPermitted:false(见 harmony/pushy/src/README.md)。
|
|
316
|
-
function isHttpsOnly(config: NativeConfig): boolean {
|
|
317
|
-
const endpoints = config.endpoints ?? [];
|
|
318
|
-
return (
|
|
319
|
-
endpoints.length > 0 &&
|
|
320
|
-
endpoints.every(endpoint => isHttpsUrl(endpoint))
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function isHttpsUrl(url: string): boolean {
|
|
325
|
-
return typeof url === 'string' && url.toLowerCase().startsWith('https://');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function isCleartextUrl(url: string): boolean {
|
|
329
|
-
return typeof url === 'string' && url.toLowerCase().startsWith('http://');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
255
|
async function runConfiguredRound(
|
|
333
256
|
context: UpdateContext,
|
|
334
257
|
launchRolledBackVersion: string,
|
|
335
258
|
resetGeneration: number,
|
|
336
259
|
configJson: string,
|
|
337
|
-
config: NativeConfig,
|
|
338
|
-
appKey: string,
|
|
339
260
|
): Promise<void> {
|
|
340
261
|
const currentVersion = context.getCurrentVersion();
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const uuid = context.getKv('uuid') ?? '';
|
|
345
|
-
const packageVersion = config.packageVersion || context.getPackageVersion();
|
|
346
|
-
|
|
347
|
-
const identity: FlowIdentity = {
|
|
348
|
-
packageVersion,
|
|
262
|
+
const env: RoundEnv = {
|
|
263
|
+
packageVersion: context.getPackageVersion(),
|
|
264
|
+
// 与 JS 常量同形(无版本时为 ''),JS 才能复用这次请求的响应缓存。
|
|
349
265
|
currentVersion,
|
|
350
|
-
uuid,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const cInfo: FlowCInfo = {
|
|
357
|
-
rnu: config.rnu ?? '',
|
|
358
|
-
rn: config.rn ?? '',
|
|
266
|
+
uuid: context.getKv('uuid') ?? '',
|
|
267
|
+
// getConstants consumes the persisted rollback marker during startup; use
|
|
268
|
+
// the launch-path snapshot captured before that happens.
|
|
269
|
+
rolledBackVersion: launchRolledBackVersion,
|
|
270
|
+
buildTime: context.getBuildTime(),
|
|
359
271
|
// RNOH's Platform.Version is osFullName; use the same value so JS can
|
|
360
272
|
// reuse the response cache produced by this native request.
|
|
361
273
|
os: `harmony ${deviceInfo.osFullName}`,
|
|
362
|
-
uuid,
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
const input: FlowInput = {
|
|
366
|
-
packageVersion: identity.packageVersion,
|
|
367
|
-
currentVersion,
|
|
368
|
-
buildTime: context.getBuildTime(),
|
|
369
|
-
cInfo,
|
|
370
|
-
supportedDiffVersion: NativePatchCore.getSupportedDiffVersion(),
|
|
371
274
|
bundleHash: await context.getBundleHash(),
|
|
372
275
|
};
|
|
373
|
-
const
|
|
374
|
-
|
|
276
|
+
const envJson = JSON.stringify(env);
|
|
277
|
+
const preparedJson = NativePatchCore.prepareRound(configJson, envJson, Math.random());
|
|
278
|
+
if (!preparedJson) {
|
|
375
279
|
roundResult = bundlePreparationResult('failed', 'invalid_request');
|
|
376
280
|
return;
|
|
377
281
|
}
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
282
|
+
const prepared = JSON.parse(preparedJson) as PreparedRound;
|
|
283
|
+
const responseText = await runQueryRequest(
|
|
284
|
+
JSON.stringify(prepared.query),
|
|
285
|
+
prepared.body,
|
|
286
|
+
);
|
|
381
287
|
if (!responseText) {
|
|
382
288
|
logger.warn(TAG, 'no endpoint reachable, giving up until next launch');
|
|
383
289
|
return;
|
|
384
290
|
}
|
|
385
|
-
//
|
|
386
|
-
const responseAtSeconds = Math.floor(Date.now() / 1000);
|
|
387
|
-
|
|
291
|
+
// 规划好的 attempts、hashInfo 与响应缓存(时间戳取响应到达时)都来自共享内核。
|
|
388
292
|
const decisionJson = NativePatchCore.handleResponse(
|
|
389
293
|
responseText,
|
|
390
|
-
|
|
391
|
-
|
|
294
|
+
configJson,
|
|
295
|
+
envJson,
|
|
296
|
+
prepared.body,
|
|
297
|
+
Math.floor(Date.now() / 1000),
|
|
392
298
|
);
|
|
393
299
|
if (!decisionJson) {
|
|
394
300
|
roundResult = bundlePreparationResult('failed', 'invalid_response');
|
|
395
301
|
return;
|
|
396
302
|
}
|
|
397
303
|
const decision = JSON.parse(decisionJson) as Decision;
|
|
398
|
-
|
|
304
|
+
const responseCacheJson = decision.cache ?? '';
|
|
305
|
+
// Commits the round's persistent outcome (see commitSyncResult) and maps
|
|
306
|
+
// it to the round result; a reset during the round wins over `result`.
|
|
307
|
+
const commit = async (
|
|
308
|
+
hash: string,
|
|
309
|
+
hashInfoJson: string,
|
|
310
|
+
activate: boolean,
|
|
311
|
+
result: BundlePreparationResult,
|
|
312
|
+
): Promise<boolean> => {
|
|
399
313
|
const committed = await context.commitSyncResult(
|
|
400
314
|
resetGeneration,
|
|
315
|
+
hash,
|
|
316
|
+
hashInfoJson,
|
|
317
|
+
activate,
|
|
318
|
+
responseCacheJson,
|
|
319
|
+
);
|
|
320
|
+
roundResult = committed
|
|
321
|
+
? result
|
|
322
|
+
: bundlePreparationResult('cancelled', 'reset');
|
|
323
|
+
return committed;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (decision.action !== 'download') {
|
|
327
|
+
await commit(
|
|
401
328
|
'',
|
|
402
329
|
'',
|
|
403
330
|
false,
|
|
404
|
-
|
|
331
|
+
bundlePreparationResult(STATUS_NONE, decision.reason ?? ''),
|
|
405
332
|
);
|
|
406
|
-
roundResult = committed
|
|
407
|
-
? bundlePreparationResult(STATUS_NONE, decision.reason ?? '')
|
|
408
|
-
: bundlePreparationResult('cancelled', 'reset');
|
|
409
333
|
logger.info(TAG, `nothing to do (${decision.reason ?? ''})`);
|
|
410
334
|
return;
|
|
411
335
|
}
|
|
@@ -426,46 +350,15 @@ async function runConfiguredRound(
|
|
|
426
350
|
decision.attempts ?? [],
|
|
427
351
|
hash,
|
|
428
352
|
currentVersion,
|
|
429
|
-
httpsOnly,
|
|
430
353
|
);
|
|
431
354
|
}
|
|
432
355
|
if (!downloaded) {
|
|
433
356
|
logger.warn(TAG, `all download attempts for ${hash} failed`);
|
|
434
|
-
|
|
435
|
-
resetGeneration,
|
|
436
|
-
'',
|
|
437
|
-
'',
|
|
438
|
-
false,
|
|
439
|
-
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds),
|
|
440
|
-
);
|
|
441
|
-
roundResult = committed
|
|
442
|
-
? bundlePreparationResult('failed', 'download_failed')
|
|
443
|
-
: bundlePreparationResult('cancelled', 'reset');
|
|
357
|
+
await commit('', '', false, bundlePreparationResult('failed', 'download_failed'));
|
|
444
358
|
return;
|
|
445
359
|
}
|
|
446
360
|
|
|
447
|
-
|
|
448
|
-
const info = decision.info;
|
|
449
|
-
let hashInfoJson = '';
|
|
450
|
-
if (info) {
|
|
451
|
-
const hashInfo: DecisionInfo = {};
|
|
452
|
-
if (typeof info.name === 'string') {
|
|
453
|
-
hashInfo.name = info.name;
|
|
454
|
-
}
|
|
455
|
-
if (typeof info.description === 'string') {
|
|
456
|
-
hashInfo.description = info.description;
|
|
457
|
-
}
|
|
458
|
-
if (typeof info.metaInfo === 'string') {
|
|
459
|
-
hashInfo.metaInfo = info.metaInfo;
|
|
460
|
-
}
|
|
461
|
-
// forceBoot 激活就是救砖路径:记进持久化元信息,这个版本活到 markSuccess
|
|
462
|
-
// 时 JS 上报 forceBootRescue。只认服务端下发的指令——静默策略的激活是
|
|
463
|
-
// 普通投递(Android/iOS 同一规则)。
|
|
464
|
-
if (info.config && info.config.forceBoot) {
|
|
465
|
-
hashInfo.forceBootRescue = true;
|
|
466
|
-
}
|
|
467
|
-
hashInfoJson = JSON.stringify(hashInfo);
|
|
468
|
-
}
|
|
361
|
+
const hashInfoJson = JSON.stringify(decision.hashInfo);
|
|
469
362
|
|
|
470
363
|
// 版本元信息、激活与响应缓存一次性原子提交(见 commitSyncResult);
|
|
471
364
|
// 缓存只在原生文件/状态工作结束后公开,避免 JS 观察到响应后并发下载。
|
|
@@ -473,12 +366,11 @@ async function runConfiguredRound(
|
|
|
473
366
|
const activate = decision.activate === true;
|
|
474
367
|
let committed = false;
|
|
475
368
|
try {
|
|
476
|
-
committed = await
|
|
477
|
-
resetGeneration,
|
|
369
|
+
committed = await commit(
|
|
478
370
|
hash,
|
|
479
371
|
hashInfoJson,
|
|
480
372
|
activate,
|
|
481
|
-
|
|
373
|
+
bundlePreparationResult('downloaded', '', hash, activate),
|
|
482
374
|
);
|
|
483
375
|
} catch (e) {
|
|
484
376
|
logger.error(TAG, `commit failed: ${getErrorMessage(e)}`);
|
|
@@ -492,35 +384,6 @@ async function runConfiguredRound(
|
|
|
492
384
|
} else {
|
|
493
385
|
logger.info(TAG, `downloaded ${hash}, activation left to JS`);
|
|
494
386
|
}
|
|
495
|
-
roundResult = committed
|
|
496
|
-
? bundlePreparationResult('downloaded', '', hash, activate)
|
|
497
|
-
: bundlePreparationResult('cancelled', 'reset');
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function buildResponseCacheJson(
|
|
501
|
-
configJson: string,
|
|
502
|
-
requestBody: string,
|
|
503
|
-
responseText: string,
|
|
504
|
-
responseAtSeconds: number,
|
|
505
|
-
): string {
|
|
506
|
-
const cacheEntry: RespCacheEntry = {
|
|
507
|
-
ts: responseAtSeconds,
|
|
508
|
-
body: responseText,
|
|
509
|
-
request: requestBody,
|
|
510
|
-
config: configJson,
|
|
511
|
-
};
|
|
512
|
-
return JSON.stringify(cacheEntry);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function isValidResponse(responseText: string | undefined): boolean {
|
|
516
|
-
if (responseText === undefined) {
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
try {
|
|
520
|
-
return NativePatchCore.isValidResponse(responseText);
|
|
521
|
-
} catch (e) {
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
387
|
}
|
|
525
388
|
|
|
526
389
|
async function httpRequest(
|
|
@@ -568,92 +431,32 @@ async function httpRequest(
|
|
|
568
431
|
}
|
|
569
432
|
}
|
|
570
433
|
|
|
571
|
-
// 顺序回退(§5.1)
|
|
572
|
-
// queryUrls
|
|
573
|
-
//
|
|
434
|
+
// 顺序回退(§5.1):按共享内核给出的下一步逐个请求,单请求超时
|
|
435
|
+
// (NativePatchCore.nextQueryStep:候选序、queryUrls 发现、远程端点的 https
|
|
436
|
+
// 门槛、请求预算、响应 schema)。刻意不做 hedged race——该路径对延迟不敏感。
|
|
574
437
|
async function runQueryRequest(
|
|
575
|
-
|
|
576
|
-
appKey: string,
|
|
438
|
+
query: string,
|
|
577
439
|
body: string,
|
|
578
|
-
httpsOnly: boolean,
|
|
579
440
|
): Promise<string | undefined> {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
const ordered = JSON.parse(orderedJson) as string[];
|
|
588
|
-
const tried = new Set<string>();
|
|
589
|
-
let httpAttempts = 0;
|
|
590
|
-
for (const rawBase of ordered) {
|
|
591
|
-
const base = normalizeEndpointBase(rawBase);
|
|
592
|
-
if (!base || tried.has(base)) {
|
|
593
|
-
continue;
|
|
441
|
+
let state: string | undefined = query;
|
|
442
|
+
let text: string | undefined = undefined;
|
|
443
|
+
while (state !== undefined) {
|
|
444
|
+
state = NativePatchCore.nextQueryStep(state, text);
|
|
445
|
+
if (state === undefined) {
|
|
446
|
+
break;
|
|
594
447
|
}
|
|
595
|
-
|
|
596
|
-
|
|
448
|
+
const step = JSON.parse(state) as QueryStep;
|
|
449
|
+
if (step.found) {
|
|
450
|
+
return text;
|
|
597
451
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (isValidResponse(response)) {
|
|
601
|
-
return response;
|
|
452
|
+
if (!step.url) {
|
|
453
|
+
break;
|
|
602
454
|
}
|
|
603
|
-
|
|
604
|
-
for (const listUrl of config.queryUrls ?? []) {
|
|
605
|
-
if (!listUrl) {
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
609
|
-
return undefined;
|
|
610
|
-
}
|
|
611
|
-
const listText = await httpRequest(listUrl);
|
|
612
|
-
if (listText === undefined) {
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
let remote: string[];
|
|
616
|
-
try {
|
|
617
|
-
const parsed = JSON.parse(listText) as Object;
|
|
618
|
-
if (!Array.isArray(parsed)) {
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
remote = parsed as string[];
|
|
622
|
-
} catch (e) {
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
for (const rawBase of remote) {
|
|
626
|
-
if (typeof rawBase !== 'string') {
|
|
627
|
-
continue;
|
|
628
|
-
}
|
|
629
|
-
const base = normalizeEndpointBase(rawBase);
|
|
630
|
-
if (!base || tried.has(base)) {
|
|
631
|
-
continue;
|
|
632
|
-
}
|
|
633
|
-
if (httpsOnly && !isHttpsUrl(base)) {
|
|
634
|
-
// 远程注入的端点不得把 https 配置降级成明文。
|
|
635
|
-
logger.warn(TAG, `ignoring non-https remote endpoint ${base}`);
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
639
|
-
return undefined;
|
|
640
|
-
}
|
|
641
|
-
tried.add(base);
|
|
642
|
-
const response = await httpRequest(`${base}${QUERY_PATH}${appKey}`, body);
|
|
643
|
-
if (isValidResponse(response)) {
|
|
644
|
-
return response;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
// 拉到一份可解析的远程列表就够了。
|
|
648
|
-
break;
|
|
455
|
+
text = await httpRequest(step.url, step.post ? body : undefined);
|
|
649
456
|
}
|
|
650
457
|
return undefined;
|
|
651
458
|
}
|
|
652
459
|
|
|
653
|
-
function normalizeEndpointBase(base: string): string {
|
|
654
|
-
return base.replace(/\/+$/, '');
|
|
655
|
-
}
|
|
656
|
-
|
|
657
460
|
async function runWithinDeadline(
|
|
658
461
|
start: () => Promise<void>,
|
|
659
462
|
deadlineUptimeMs: number,
|
|
@@ -691,16 +494,11 @@ async function performAttempts(
|
|
|
691
494
|
attempts: DecisionAttempt[],
|
|
692
495
|
hash: string,
|
|
693
496
|
originHash: string,
|
|
694
|
-
httpsOnly: boolean,
|
|
695
497
|
): Promise<boolean> {
|
|
696
498
|
const incrementalDeadline = monotonicNowMs() + DOWNLOAD_PHASE_TIMEOUT_MS;
|
|
697
499
|
let fullDeadline = 0;
|
|
698
500
|
for (const attempt of attempts) {
|
|
699
501
|
const type = attempt.type ?? '';
|
|
700
|
-
if (type === DOWNLOAD_TYPE_DIFF && !originHash) {
|
|
701
|
-
// diff 以当前运行版本为源;没有运行中的热更版本就跳过。
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
502
|
const isFullAttempt =
|
|
705
503
|
type !== DOWNLOAD_TYPE_DIFF && type !== DOWNLOAD_TYPE_PDIFF;
|
|
706
504
|
if (isFullAttempt && fullDeadline === 0) {
|
|
@@ -710,14 +508,6 @@ async function performAttempts(
|
|
|
710
508
|
}
|
|
711
509
|
const deadline = isFullAttempt ? fullDeadline : incrementalDeadline;
|
|
712
510
|
for (const url of attempt.urls ?? []) {
|
|
713
|
-
if (!url) {
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
if (httpsOnly && isCleartextUrl(url)) {
|
|
717
|
-
// 决策里的明文制品 URL 不得把 https 配置降级(SR-1)。
|
|
718
|
-
logger.warn(TAG, `ignoring cleartext ${type} url ${url}`);
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
511
|
if (monotonicNowMs() >= deadline) {
|
|
722
512
|
if (isFullAttempt) {
|
|
723
513
|
return false;
|
|
@@ -744,8 +534,19 @@ async function performAttempts(
|
|
|
744
534
|
return true;
|
|
745
535
|
} catch (e) {
|
|
746
536
|
logger.warn(TAG, `${type} attempt failed: ${getErrorMessage(e)}`);
|
|
537
|
+
if (!isMirrorRetryable(e)) {
|
|
538
|
+
// The bytes arrived but could not be applied: every mirror serves
|
|
539
|
+
// the same artifact, so move on to the next strategy instead of
|
|
540
|
+
// re-downloading it (update_flow_core IsMirrorRetryableCode, JS
|
|
541
|
+
// client.ts isMirrorRetryableCode).
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
747
544
|
}
|
|
748
545
|
}
|
|
749
546
|
}
|
|
750
547
|
return false;
|
|
751
548
|
}
|
|
549
|
+
|
|
550
|
+
function isMirrorRetryable(error: unknown): boolean {
|
|
551
|
+
return !(error instanceof PushyError && error.code === ERROR_DELTA_FAILED);
|
|
552
|
+
}
|
|
@@ -13,12 +13,8 @@ export function revealText(hex: string): string {
|
|
|
13
13
|
return text;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// Request path appended to an endpoint base.
|
|
17
|
-
export const QUERY_PATH: string = revealText('7514fcd4ad805d55263e08fc99');
|
|
18
|
-
|
|
19
16
|
// Protocol values, version-info flags and storage names, stored encoded.
|
|
20
17
|
export const STATUS_NONE: string = revealText('3418c1c1aa8a7c40');
|
|
21
|
-
export const POLICY_NEXT_LAUNCH: string = revealText('2912e0ffab8e6c70323b1dedd3');
|
|
22
18
|
export const STORAGE_DIR_NAME: string = revealText('0502e4d5af9f6d');
|
|
23
19
|
export const PREFERENCES_NAME: string = revealText('2f07f0d0ba8e');
|
|
24
20
|
export const PPK_DELTA_SUFFIX: string = revealText('7407e4dae09b69512137');
|