react-native-update 10.58.1 → 10.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +7 -0
- package/README.md +8 -0
- package/android/build.gradle +46 -14
- package/android/jni/Android.mk +2 -1
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateResult.java → BundlePreparationResult.java} +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/{CrashRescue.java → CrashHold.java} +12 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +28 -24
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateFlow.java → FlowBridge.java} +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +6 -3
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateCore.java → NativeCore.java} +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateConfig.java → PushyConfiguration.java} +13 -8
- package/android/src/main/java/cn/reactnative/modules/update/{PushyNativeUpdate.java → PushyRuntime.java} +15 -15
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +7 -7
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeCheckOrchestrator.java → SyncCoordinator.java} +107 -103
- package/android/src/main/java/cn/reactnative/modules/update/Texts.java +28 -0
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +29 -29
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +12 -9
- package/cpp/patch_core/archive_limits.h +5 -0
- package/cpp/patch_core/archive_patch_core.cpp +26 -19
- package/cpp/patch_core/archive_patch_core.h +17 -8
- package/cpp/patch_core/digest.h +6 -1
- package/cpp/patch_core/error_codes.h +8 -1
- package/cpp/patch_core/hbc_transform.h +5 -0
- package/cpp/patch_core/hbc_transform_wire.h +5 -0
- package/cpp/patch_core/install_record.h +5 -0
- package/cpp/patch_core/jni_natives.h +27 -0
- package/cpp/patch_core/jni_registration.cpp +103 -0
- package/cpp/patch_core/obscured_text.h +44 -0
- package/cpp/patch_core/patch_core.cpp +13 -13
- package/cpp/patch_core/patch_core.h +11 -6
- package/cpp/patch_core/patch_core_android.cpp +10 -10
- package/cpp/patch_core/state_core.h +5 -0
- package/cpp/patch_core/state_ops.h +5 -0
- package/cpp/patch_core/update_core_android.cpp +23 -22
- package/cpp/update_flow_core/flow_json.h +6 -1
- package/cpp/update_flow_core/update_flow_core.cpp +35 -18
- package/cpp/update_flow_core/update_flow_core.h +13 -8
- package/cpp/update_flow_core/update_flow_jni.cpp +11 -13
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +8 -8
- package/harmony/pushy/src/main/cpp/pushy.cpp +33 -33
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +15 -15
- package/harmony/pushy/src/main/ets/{NativeUpdateResult.ts → BundlePreparationResult.ts} +6 -6
- package/harmony/pushy/src/main/ets/DownloadTask.ts +60 -60
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +13 -11
- package/harmony/pushy/src/main/ets/InstallRecord.ts +4 -4
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +9 -9
- package/harmony/pushy/src/main/ets/PathUtils.ts +2 -2
- package/harmony/pushy/src/main/ets/{NativeUpdateConfig.ts → PushyConfiguration.ts} +12 -10
- package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +8 -8
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +42 -41
- package/harmony/pushy/src/main/ets/{NativeCheckOrchestrator.ts → SyncCoordinator.ts} +71 -70
- package/harmony/pushy/src/main/ets/Texts.ts +28 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +32 -31
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.h +8 -8
- package/ios/RCTPushy/RCTPushy.mm +155 -147
- package/ios/RCTPushy/RCTPushyConfiguration.h +13 -0
- package/ios/RCTPushy/{RCTPushyNativeConfig.mm → RCTPushyConfiguration.mm} +34 -7
- package/package.json +1 -1
- package/react-native-update.podspec +15 -3
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/ios/RCTPushy/RCTPushyNativeConfig.h +0 -5
|
@@ -3,13 +3,14 @@ import deviceInfo from '@ohos.deviceInfo';
|
|
|
3
3
|
import logger from './Logger';
|
|
4
4
|
import NativePatchCore from './NativePatchCore';
|
|
5
5
|
import type { UpdateContext } from './UpdateContext';
|
|
6
|
-
import {
|
|
7
|
-
import type {
|
|
6
|
+
import { BundlePreparationRound, bundlePreparationResult } from './BundlePreparationResult';
|
|
7
|
+
import type { BundlePreparationResult } from './BundlePreparationResult';
|
|
8
8
|
import { isSafePathComponent } from './PathUtils';
|
|
9
|
+
import { QUERY_PATH, STATUS_NONE } from './Texts';
|
|
9
10
|
import { monotonicNowMs } from './MonotonicClock';
|
|
10
11
|
import {
|
|
11
12
|
ERROR_DOWNLOAD_FAILED,
|
|
12
|
-
|
|
13
|
+
createPushyError,
|
|
13
14
|
getErrorMessage,
|
|
14
15
|
} from './ErrorCodes';
|
|
15
16
|
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
// 风暴,不拉黑版本。
|
|
21
22
|
// 鸿蒙的 debug 门控由触发点天然承担:dev 走 MetroJSBundleProvider,
|
|
22
23
|
// PushyFileJSBundleProvider.getBundleUrl 不会被调用。
|
|
23
|
-
const TAG = '
|
|
24
|
+
const TAG = 'NativeSync';
|
|
24
25
|
export const KEY_CONFIG = 'nativeConfig';
|
|
25
26
|
// 供 JS 侧复用的原始响应缓存(§10.3),同时记录请求与配置指纹以限定命中范围。
|
|
26
27
|
export const KEY_RESP_CACHE = 'nativeCheckResp';
|
|
@@ -31,7 +32,7 @@ export const KEY_RESP_CACHE = 'nativeCheckResp';
|
|
|
31
32
|
export const KEY_ROUND_INCOMPLETE = 'nativeCheckIncomplete';
|
|
32
33
|
const REQUEST_TIMEOUT_MS = 10000;
|
|
33
34
|
const REQUEST_CALL_TIMEOUT_MS = 15000;
|
|
34
|
-
const
|
|
35
|
+
const MAX_QUERY_HTTP_ATTEMPTS = 8;
|
|
35
36
|
const START_DELAY_MS = 5000;
|
|
36
37
|
const DOWNLOAD_PHASE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
37
38
|
const DOWNLOAD_TYPE_DIFF = 'diff';
|
|
@@ -62,7 +63,7 @@ interface FlowCInfo {
|
|
|
62
63
|
uuid: string;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
interface
|
|
66
|
+
interface FlowInput {
|
|
66
67
|
packageVersion: string;
|
|
67
68
|
currentVersion?: string;
|
|
68
69
|
buildTime: string;
|
|
@@ -109,18 +110,18 @@ interface RespCacheEntry {
|
|
|
109
110
|
|
|
110
111
|
let scheduled = false;
|
|
111
112
|
// The host and delayed check use one promise, including its settled result.
|
|
112
|
-
const hostRound = new
|
|
113
|
+
const hostRound = new BundlePreparationRound();
|
|
113
114
|
let scheduledContext: UpdateContext | undefined;
|
|
114
115
|
let scheduledRollback = '';
|
|
115
116
|
let roundGeneration = -1;
|
|
116
117
|
let roundConfigGeneration = -1;
|
|
117
118
|
let roundConfigJson: string | undefined;
|
|
118
|
-
let roundResult =
|
|
119
|
+
let roundResult = bundlePreparationResult('failed', 'check_failed');
|
|
119
120
|
|
|
120
121
|
function startNativeRound(
|
|
121
122
|
context: UpdateContext,
|
|
122
123
|
launchRolledBackVersion: string,
|
|
123
|
-
): Promise<
|
|
124
|
+
): Promise<BundlePreparationResult> {
|
|
124
125
|
const preflight = configurationError(context);
|
|
125
126
|
if (preflight !== undefined) {
|
|
126
127
|
return Promise.resolve(preflight);
|
|
@@ -129,8 +130,8 @@ function startNativeRound(
|
|
|
129
130
|
try {
|
|
130
131
|
await runOnce(context, launchRolledBackVersion);
|
|
131
132
|
} catch (e) {
|
|
132
|
-
logger.error(TAG, `native
|
|
133
|
-
roundResult =
|
|
133
|
+
logger.error(TAG, `native sync failed: ${getErrorMessage(e)}`);
|
|
134
|
+
roundResult = bundlePreparationResult('failed', 'internal_error');
|
|
134
135
|
}
|
|
135
136
|
return roundResult;
|
|
136
137
|
});
|
|
@@ -138,82 +139,82 @@ function startNativeRound(
|
|
|
138
139
|
|
|
139
140
|
// A preflight skip must not consume the process's only round: configuration
|
|
140
141
|
// may arrive after the delayed startup timer on a first-ever launch.
|
|
141
|
-
function configurationError(context: UpdateContext):
|
|
142
|
+
function configurationError(context: UpdateContext): BundlePreparationResult | undefined {
|
|
142
143
|
const json = context.getKv(KEY_CONFIG);
|
|
143
144
|
if (!json) {
|
|
144
|
-
return
|
|
145
|
+
return bundlePreparationResult('skipped', 'not_configured');
|
|
145
146
|
}
|
|
146
147
|
try {
|
|
147
148
|
const config = JSON.parse(json) as NativeConfig;
|
|
148
149
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
149
|
-
return
|
|
150
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
150
151
|
}
|
|
151
152
|
if (config.disabled) {
|
|
152
|
-
return
|
|
153
|
+
return bundlePreparationResult('skipped', 'disabled');
|
|
153
154
|
}
|
|
154
155
|
if (typeof config.appKey !== 'string' || config.appKey.trim().length === 0) {
|
|
155
|
-
return
|
|
156
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
156
157
|
}
|
|
157
158
|
} catch (e) {
|
|
158
|
-
return
|
|
159
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
159
160
|
}
|
|
160
161
|
return undefined;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
export async function
|
|
164
|
+
export async function prepareBundleNative(
|
|
164
165
|
context: UpdateContext,
|
|
165
|
-
): Promise<
|
|
166
|
+
): Promise<BundlePreparationResult> {
|
|
166
167
|
if (scheduledContext !== context) {
|
|
167
|
-
return
|
|
168
|
+
return bundlePreparationResult('skipped', 'not_initialized');
|
|
168
169
|
}
|
|
169
170
|
const configJson = context.getKv(KEY_CONFIG);
|
|
170
171
|
if (!configJson) {
|
|
171
|
-
return
|
|
172
|
+
return bundlePreparationResult('skipped', 'not_configured');
|
|
172
173
|
}
|
|
173
174
|
try {
|
|
174
175
|
const config = JSON.parse(configJson) as NativeConfig;
|
|
175
176
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
176
|
-
return
|
|
177
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
177
178
|
}
|
|
178
179
|
if (config.disabled) {
|
|
179
|
-
return
|
|
180
|
+
return bundlePreparationResult('skipped', 'disabled');
|
|
180
181
|
}
|
|
181
182
|
if (typeof config.appKey !== 'string' || !config.appKey) {
|
|
182
|
-
return
|
|
183
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
183
184
|
}
|
|
184
185
|
} catch (e) {
|
|
185
|
-
return
|
|
186
|
+
return bundlePreparationResult('failed', 'invalid_config');
|
|
186
187
|
}
|
|
187
188
|
const result = await startNativeRound(context, scheduledRollback);
|
|
188
189
|
if (roundConfigGeneration !== context.getNativeConfigGeneration()
|
|
189
190
|
|| configJson !== roundConfigJson || configJson !== context.getKv(KEY_CONFIG)) {
|
|
190
|
-
return
|
|
191
|
+
return bundlePreparationResult('cancelled', 'config_changed');
|
|
191
192
|
}
|
|
192
193
|
if (roundGeneration !== context.getResetGeneration()) {
|
|
193
|
-
return
|
|
194
|
+
return bundlePreparationResult('cancelled', 'reset');
|
|
194
195
|
}
|
|
195
196
|
// Do not let a caller mutate the cached result observed by later callers.
|
|
196
|
-
return
|
|
197
|
+
return bundlePreparationResult(result.status, result.reason, result.hash, result.activated);
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// JS 在本进程内已拿到有效检查响应时对应的配置 JSON(markJsCheckCompleted)。
|
|
200
201
|
// 进程级:下次启动无信号,冷启动轮次照常运行。
|
|
201
202
|
let jsCompletedConfig: string | undefined;
|
|
202
203
|
|
|
203
|
-
export function
|
|
204
|
+
export function recordJsRound(config: string): void {
|
|
204
205
|
jsCompletedConfig = config;
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
// JS 已用与原生完全相同的配置拿到有效响应(§10.3):延迟轮次就是一次重复请求。
|
|
208
209
|
// 只有计划内的轮次会问这个问题——JS 没起来、启动即崩、检查失败时都不会有信号。
|
|
209
|
-
function
|
|
210
|
+
function hasJsRound(context: UpdateContext): boolean {
|
|
210
211
|
return (
|
|
211
212
|
jsCompletedConfig !== undefined &&
|
|
212
213
|
jsCompletedConfig === context.getKv(KEY_CONFIG)
|
|
213
214
|
);
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
export function
|
|
217
|
+
export function scheduleNativeSync(
|
|
217
218
|
context: UpdateContext,
|
|
218
219
|
launchRolledBackVersion: string,
|
|
219
220
|
): void {
|
|
@@ -227,16 +228,16 @@ export function scheduleNativeCheck(
|
|
|
227
228
|
// 除非上个进程死于轮中(残留标记),那时每一秒启动时间都要用来续传。
|
|
228
229
|
const delayMs = context.getKv(KEY_ROUND_INCOMPLETE) ? 0 : START_DELAY_MS;
|
|
229
230
|
setTimeout(() => {
|
|
230
|
-
if (
|
|
231
|
+
if (hasJsRound(context)) {
|
|
231
232
|
logger.info(
|
|
232
233
|
TAG,
|
|
233
|
-
'native
|
|
234
|
+
'native sync skipped: JS already queried in this process',
|
|
234
235
|
);
|
|
235
236
|
return;
|
|
236
237
|
}
|
|
237
238
|
startNativeRound(context, launchRolledBackVersion).catch((e: Object) => {
|
|
238
239
|
// 救援路径自身绝不能把应用拖垮。
|
|
239
|
-
logger.error(TAG, `native
|
|
240
|
+
logger.error(TAG, `native sync failed: ${getErrorMessage(e)}`);
|
|
240
241
|
});
|
|
241
242
|
}, delayMs);
|
|
242
243
|
}
|
|
@@ -250,32 +251,32 @@ async function runOnce(
|
|
|
250
251
|
const resetGeneration = context.getResetGeneration();
|
|
251
252
|
roundGeneration = resetGeneration;
|
|
252
253
|
roundConfigGeneration = context.getNativeConfigGeneration();
|
|
253
|
-
roundResult =
|
|
254
|
+
roundResult = bundlePreparationResult('failed', 'check_failed');
|
|
254
255
|
const configJson = context.getKv(KEY_CONFIG);
|
|
255
256
|
roundConfigJson = configJson;
|
|
256
257
|
if (!configJson) {
|
|
257
258
|
// No persisted configuration: report the rollout gate to native callers.
|
|
258
|
-
roundResult =
|
|
259
|
+
roundResult = bundlePreparationResult('skipped', 'not_configured');
|
|
259
260
|
return;
|
|
260
261
|
}
|
|
261
262
|
let config: NativeConfig;
|
|
262
263
|
try {
|
|
263
264
|
config = JSON.parse(configJson) as NativeConfig;
|
|
264
265
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
265
|
-
roundResult =
|
|
266
|
+
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
266
267
|
return;
|
|
267
268
|
}
|
|
268
269
|
} catch (e) {
|
|
269
|
-
roundResult =
|
|
270
|
+
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
270
271
|
return;
|
|
271
272
|
}
|
|
272
273
|
if (config.disabled) {
|
|
273
|
-
roundResult =
|
|
274
|
+
roundResult = bundlePreparationResult('skipped', 'disabled');
|
|
274
275
|
return;
|
|
275
276
|
}
|
|
276
277
|
const appKey = config.appKey ?? '';
|
|
277
278
|
if (!appKey) {
|
|
278
|
-
roundResult =
|
|
279
|
+
roundResult = bundlePreparationResult('failed', 'invalid_config');
|
|
279
280
|
return;
|
|
280
281
|
}
|
|
281
282
|
// 从这里起本轮开始做真实工作:留下面包屑,死于轮中时下次启动零延迟续传。
|
|
@@ -361,7 +362,7 @@ async function runConfiguredRound(
|
|
|
361
362
|
uuid,
|
|
362
363
|
};
|
|
363
364
|
|
|
364
|
-
const input:
|
|
365
|
+
const input: FlowInput = {
|
|
365
366
|
packageVersion: identity.packageVersion,
|
|
366
367
|
currentVersion,
|
|
367
368
|
buildTime: context.getBuildTime(),
|
|
@@ -369,14 +370,14 @@ async function runConfiguredRound(
|
|
|
369
370
|
supportedDiffVersion: NativePatchCore.getSupportedDiffVersion(),
|
|
370
371
|
bundleHash: await context.getBundleHash(),
|
|
371
372
|
};
|
|
372
|
-
const body = NativePatchCore.
|
|
373
|
+
const body = NativePatchCore.buildRequestBody(JSON.stringify(input));
|
|
373
374
|
if (!body) {
|
|
374
|
-
roundResult =
|
|
375
|
+
roundResult = bundlePreparationResult('failed', 'invalid_request');
|
|
375
376
|
return;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
const httpsOnly = isHttpsOnly(config);
|
|
379
|
-
const responseText = await
|
|
380
|
+
const responseText = await runQueryRequest(config, appKey, body, httpsOnly);
|
|
380
381
|
if (!responseText) {
|
|
381
382
|
logger.warn(TAG, 'no endpoint reachable, giving up until next launch');
|
|
382
383
|
return;
|
|
@@ -384,18 +385,18 @@ async function runConfiguredRound(
|
|
|
384
385
|
// Anchor cache freshness to response arrival, before download/patch work.
|
|
385
386
|
const responseAtSeconds = Math.floor(Date.now() / 1000);
|
|
386
387
|
|
|
387
|
-
const decisionJson = NativePatchCore.
|
|
388
|
+
const decisionJson = NativePatchCore.handleResponse(
|
|
388
389
|
responseText,
|
|
389
390
|
JSON.stringify(identity),
|
|
390
391
|
config.afterDownload ?? '',
|
|
391
392
|
);
|
|
392
393
|
if (!decisionJson) {
|
|
393
|
-
roundResult =
|
|
394
|
+
roundResult = bundlePreparationResult('failed', 'invalid_response');
|
|
394
395
|
return;
|
|
395
396
|
}
|
|
396
397
|
const decision = JSON.parse(decisionJson) as Decision;
|
|
397
398
|
if (decision.action !== 'download') {
|
|
398
|
-
const committed = await context.
|
|
399
|
+
const committed = await context.commitSyncResult(
|
|
399
400
|
resetGeneration,
|
|
400
401
|
'',
|
|
401
402
|
'',
|
|
@@ -403,15 +404,15 @@ async function runConfiguredRound(
|
|
|
403
404
|
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds),
|
|
404
405
|
);
|
|
405
406
|
roundResult = committed
|
|
406
|
-
?
|
|
407
|
-
:
|
|
407
|
+
? bundlePreparationResult(STATUS_NONE, decision.reason ?? '')
|
|
408
|
+
: bundlePreparationResult('cancelled', 'reset');
|
|
408
409
|
logger.info(TAG, `nothing to do (${decision.reason ?? ''})`);
|
|
409
410
|
return;
|
|
410
411
|
}
|
|
411
412
|
const hash = decision.hash ?? '';
|
|
412
413
|
if (!isSafePathComponent(hash)) {
|
|
413
414
|
logger.warn(TAG, 'decision carries an unsafe hash, ignoring');
|
|
414
|
-
roundResult =
|
|
415
|
+
roundResult = bundlePreparationResult('failed', 'invalid_response');
|
|
415
416
|
return;
|
|
416
417
|
}
|
|
417
418
|
|
|
@@ -430,7 +431,7 @@ async function runConfiguredRound(
|
|
|
430
431
|
}
|
|
431
432
|
if (!downloaded) {
|
|
432
433
|
logger.warn(TAG, `all download attempts for ${hash} failed`);
|
|
433
|
-
const committed = await context.
|
|
434
|
+
const committed = await context.commitSyncResult(
|
|
434
435
|
resetGeneration,
|
|
435
436
|
'',
|
|
436
437
|
'',
|
|
@@ -438,8 +439,8 @@ async function runConfiguredRound(
|
|
|
438
439
|
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds),
|
|
439
440
|
);
|
|
440
441
|
roundResult = committed
|
|
441
|
-
?
|
|
442
|
-
:
|
|
442
|
+
? bundlePreparationResult('failed', 'download_failed')
|
|
443
|
+
: bundlePreparationResult('cancelled', 'reset');
|
|
443
444
|
return;
|
|
444
445
|
}
|
|
445
446
|
|
|
@@ -466,13 +467,13 @@ async function runConfiguredRound(
|
|
|
466
467
|
hashInfoJson = JSON.stringify(hashInfo);
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
// 版本元信息、激活与响应缓存一次性原子提交(见
|
|
470
|
+
// 版本元信息、激活与响应缓存一次性原子提交(见 commitSyncResult);
|
|
470
471
|
// 缓存只在原生文件/状态工作结束后公开,避免 JS 观察到响应后并发下载。
|
|
471
472
|
// 静默策略、或服务端按版本标记的 forceBoot(远程覆盖,救砖指令)才激活。
|
|
472
473
|
const activate = decision.activate === true;
|
|
473
474
|
let committed = false;
|
|
474
475
|
try {
|
|
475
|
-
committed = await context.
|
|
476
|
+
committed = await context.commitSyncResult(
|
|
476
477
|
resetGeneration,
|
|
477
478
|
hash,
|
|
478
479
|
hashInfoJson,
|
|
@@ -481,7 +482,7 @@ async function runConfiguredRound(
|
|
|
481
482
|
);
|
|
482
483
|
} catch (e) {
|
|
483
484
|
logger.error(TAG, `commit failed: ${getErrorMessage(e)}`);
|
|
484
|
-
roundResult =
|
|
485
|
+
roundResult = bundlePreparationResult('failed', 'commit_failed');
|
|
485
486
|
return;
|
|
486
487
|
}
|
|
487
488
|
if (!committed) {
|
|
@@ -492,8 +493,8 @@ async function runConfiguredRound(
|
|
|
492
493
|
logger.info(TAG, `downloaded ${hash}, activation left to JS`);
|
|
493
494
|
}
|
|
494
495
|
roundResult = committed
|
|
495
|
-
?
|
|
496
|
-
:
|
|
496
|
+
? bundlePreparationResult('downloaded', '', hash, activate)
|
|
497
|
+
: bundlePreparationResult('cancelled', 'reset');
|
|
497
498
|
}
|
|
498
499
|
|
|
499
500
|
function buildResponseCacheJson(
|
|
@@ -511,12 +512,12 @@ function buildResponseCacheJson(
|
|
|
511
512
|
return JSON.stringify(cacheEntry);
|
|
512
513
|
}
|
|
513
514
|
|
|
514
|
-
function
|
|
515
|
+
function isValidResponse(responseText: string | undefined): boolean {
|
|
515
516
|
if (responseText === undefined) {
|
|
516
517
|
return false;
|
|
517
518
|
}
|
|
518
519
|
try {
|
|
519
|
-
return NativePatchCore.
|
|
520
|
+
return NativePatchCore.isValidResponse(responseText);
|
|
520
521
|
} catch (e) {
|
|
521
522
|
return false;
|
|
522
523
|
}
|
|
@@ -570,7 +571,7 @@ async function httpRequest(
|
|
|
570
571
|
// 顺序回退(§5.1):按纯层给出的候选序逐个请求,单请求超时;整轮失败后经
|
|
571
572
|
// queryUrls 发现远程候选(排除已试过的)再来一轮。刻意不做 hedged race——
|
|
572
573
|
// 该路径对延迟不敏感。
|
|
573
|
-
async function
|
|
574
|
+
async function runQueryRequest(
|
|
574
575
|
config: NativeConfig,
|
|
575
576
|
appKey: string,
|
|
576
577
|
body: string,
|
|
@@ -591,12 +592,12 @@ async function runCheckRequest(
|
|
|
591
592
|
if (!base || tried.has(base)) {
|
|
592
593
|
continue;
|
|
593
594
|
}
|
|
594
|
-
if (httpAttempts++ >=
|
|
595
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
595
596
|
return undefined;
|
|
596
597
|
}
|
|
597
598
|
tried.add(base);
|
|
598
|
-
const response = await httpRequest(`${base}
|
|
599
|
-
if (
|
|
599
|
+
const response = await httpRequest(`${base}${QUERY_PATH}${appKey}`, body);
|
|
600
|
+
if (isValidResponse(response)) {
|
|
600
601
|
return response;
|
|
601
602
|
}
|
|
602
603
|
}
|
|
@@ -604,7 +605,7 @@ async function runCheckRequest(
|
|
|
604
605
|
if (!listUrl) {
|
|
605
606
|
continue;
|
|
606
607
|
}
|
|
607
|
-
if (httpAttempts++ >=
|
|
608
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
608
609
|
return undefined;
|
|
609
610
|
}
|
|
610
611
|
const listText = await httpRequest(listUrl);
|
|
@@ -634,12 +635,12 @@ async function runCheckRequest(
|
|
|
634
635
|
logger.warn(TAG, `ignoring non-https remote endpoint ${base}`);
|
|
635
636
|
continue;
|
|
636
637
|
}
|
|
637
|
-
if (httpAttempts++ >=
|
|
638
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
638
639
|
return undefined;
|
|
639
640
|
}
|
|
640
641
|
tried.add(base);
|
|
641
|
-
const response = await httpRequest(`${base}
|
|
642
|
-
if (
|
|
642
|
+
const response = await httpRequest(`${base}${QUERY_PATH}${appKey}`, body);
|
|
643
|
+
if (isValidResponse(response)) {
|
|
643
644
|
return response;
|
|
644
645
|
}
|
|
645
646
|
}
|
|
@@ -659,7 +660,7 @@ async function runWithinDeadline(
|
|
|
659
660
|
): Promise<void> {
|
|
660
661
|
const remainingMs = deadlineUptimeMs - monotonicNowMs();
|
|
661
662
|
if (remainingMs <= 0) {
|
|
662
|
-
throw
|
|
663
|
+
throw createPushyError(
|
|
663
664
|
ERROR_DOWNLOAD_FAILED,
|
|
664
665
|
'Download phase deadline expired before start',
|
|
665
666
|
);
|
|
@@ -668,7 +669,7 @@ async function runWithinDeadline(
|
|
|
668
669
|
const deadlinePromise = new Promise<void>((_, reject) => {
|
|
669
670
|
deadlineTimer = setTimeout(() => {
|
|
670
671
|
reject(
|
|
671
|
-
|
|
672
|
+
createPushyError(
|
|
672
673
|
ERROR_DOWNLOAD_FAILED,
|
|
673
674
|
'Download phase deadline exceeded',
|
|
674
675
|
),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Encoded text shared by the Harmony module. No imports, so any file can use it
|
|
2
|
+
// without forming an import cycle.
|
|
3
|
+
|
|
4
|
+
// Decodes text produced by scripts/encode-native-text.ts (byte i XORed with
|
|
5
|
+
// (0x5A + 0x1D * i) & 0xFF), keeping service addresses and paths out of static
|
|
6
|
+
// string scans of the package. Not a secret.
|
|
7
|
+
export function revealText(hex: string): string {
|
|
8
|
+
let text = '';
|
|
9
|
+
for (let i = 0; i < hex.length / 2; i++) {
|
|
10
|
+
const byte = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
11
|
+
text += String.fromCharCode(byte ^ ((0x5a + 0x1d * i) & 0xff));
|
|
12
|
+
}
|
|
13
|
+
return text;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Request path appended to an endpoint base.
|
|
17
|
+
export const QUERY_PATH: string = revealText('7514fcd4ad805d55263e08fc99');
|
|
18
|
+
|
|
19
|
+
// Protocol values, version-info flags and storage names, stored encoded.
|
|
20
|
+
export const STATUS_NONE: string = revealText('3418c1c1aa8a7c40');
|
|
21
|
+
export const POLICY_NEXT_LAUNCH: string = revealText('2912e0ffab8e6c70323b1dedd3');
|
|
22
|
+
export const STORAGE_DIR_NAME: string = revealText('0502e4d5af9f6d');
|
|
23
|
+
export const PREFERENCES_NAME: string = revealText('2f07f0d0ba8e');
|
|
24
|
+
export const PPK_DELTA_SUFFIX: string = revealText('7407e4dae09b69512137');
|
|
25
|
+
export const APP_DELTA_SUFFIX: string = revealText('7416e4c1e09b69512137');
|
|
26
|
+
export const BUNDLE_DELTA_ENTRY: string = revealText('3802fad5a28e264d232d11f6d8aade67596914e0ead8b0');
|
|
27
|
+
export const DEVTOOLS_RESTART_EVENT: string = revealText('0832d8fe8faf');
|
|
28
|
+
export const DEVTOOLS_RESTART_REASON: string = revealText('1218e0e3ab876744266d');
|