react-native-update 10.49.0 → 10.51.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/BUNDLEHASH_MIGRATION.md +22 -2
- 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/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/BundledResourceCopier.java +42 -9
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -22
- 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/PatchFailedException.java +16 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +196 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +56 -3
- 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/patch_core/digest.cpp +53 -0
- package/cpp/patch_core/digest.h +18 -0
- package/cpp/patch_core/patch_core.cpp +17 -1
- package/cpp/patch_core/patch_core.h +8 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +99 -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 +150 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +133 -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 +16 -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 +152 -52
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +840 -68
- 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 +439 -122
- package/src/endpoint.ts +105 -80
- package/src/error.ts +4 -0
- package/src/locales/en.ts +4 -0
- package/src/locales/zh.ts +4 -0
- package/src/provider.tsx +74 -9
- package/src/telemetry.ts +8 -0
- package/src/type.ts +31 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +14 -12
- package/src/isInRollout.ts +0 -86
- package/src/resolveCheckResult.ts +0 -30
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import preferences from '@ohos.data.preferences';
|
|
2
2
|
import fileIo from '@ohos.file.fs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DownloadTask,
|
|
5
|
+
VERSION_COMPLETE_FILE_NAME,
|
|
6
|
+
} from './DownloadTask';
|
|
4
7
|
import common from '@ohos.app.ability.common';
|
|
5
8
|
import { DownloadTaskParams } from './DownloadTaskParams';
|
|
6
9
|
import { bundleManager } from '@kit.AbilityKit';
|
|
7
10
|
import { util } from '@kit.ArkTS';
|
|
8
11
|
import logger from './Logger';
|
|
12
|
+
import {
|
|
13
|
+
KEY_RESP_CACHE,
|
|
14
|
+
scheduleNativeCheck,
|
|
15
|
+
} from './NativeCheckOrchestrator';
|
|
9
16
|
import NativePatchCore, {
|
|
10
17
|
STATE_OP_CLEAR_FIRST_TIME,
|
|
11
18
|
STATE_OP_CLEAR_ROLLBACK_MARK,
|
|
@@ -15,6 +22,9 @@ import NativePatchCore, {
|
|
|
15
22
|
STATE_OP_SWITCH_VERSION,
|
|
16
23
|
StateCoreResult,
|
|
17
24
|
} from './NativePatchCore';
|
|
25
|
+
import { assertSafePathComponent } from './PathUtils';
|
|
26
|
+
|
|
27
|
+
export { isSafePathComponent } from './PathUtils';
|
|
18
28
|
|
|
19
29
|
type FlushablePreferences = preferences.Preferences & {
|
|
20
30
|
flushSync?: () => void;
|
|
@@ -31,6 +41,10 @@ export class UpdateContext {
|
|
|
31
41
|
// resetToPackagedBundle 不能删它的目录:热更包内的图片等资源是运行时按需
|
|
32
42
|
// 读盘的,静默(不重启)reset 若删掉会导致后续所有未加载过的资源失败。
|
|
33
43
|
private static launchVersion: string = '';
|
|
44
|
+
// 由 resetToPackagedBundle 递增。原生冷启动检测可能跑数分钟并已握有决策,
|
|
45
|
+
// 期间发生的 reset 必须赢:编排器采样该值,发现变化即放弃激活与响应缓存,
|
|
46
|
+
// 在飞的救援不会把刚被重置掉的版本装回去。
|
|
47
|
+
private static resetGeneration: number = 0;
|
|
34
48
|
private static cachedPackageVersion: string = '';
|
|
35
49
|
private static cachedBuildTime: string = '';
|
|
36
50
|
// 单例:确保 bundle provider 与 TurboModule 共用同一份 preferences 内存状态,
|
|
@@ -296,7 +310,7 @@ export class UpdateContext {
|
|
|
296
310
|
const params = new DownloadTaskParams();
|
|
297
311
|
params.type = type;
|
|
298
312
|
params.url = url;
|
|
299
|
-
params.hash = hash;
|
|
313
|
+
params.hash = assertSafePathComponent(hash);
|
|
300
314
|
return params;
|
|
301
315
|
}
|
|
302
316
|
|
|
@@ -317,6 +331,15 @@ export class UpdateContext {
|
|
|
317
331
|
|
|
318
332
|
private async executeTask(params: DownloadTaskParams): Promise<void> {
|
|
319
333
|
await this.enqueueSerialTask(() => {
|
|
334
|
+
const isPatchTask =
|
|
335
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL ||
|
|
336
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP ||
|
|
337
|
+
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
|
|
338
|
+
// Re-check only when this queued job actually starts. A JS download may
|
|
339
|
+
// have completed while the cold-start duplicate waited behind it.
|
|
340
|
+
if (isPatchTask && this.hasDownloadedVersion(params.hash)) {
|
|
341
|
+
return Promise.resolve();
|
|
342
|
+
}
|
|
320
343
|
const downloadTask = new DownloadTask(this.context);
|
|
321
344
|
return downloadTask.execute(params);
|
|
322
345
|
});
|
|
@@ -430,7 +453,16 @@ export class UpdateContext {
|
|
|
430
453
|
console.error('Failed to clear hash info on reset:', e);
|
|
431
454
|
}
|
|
432
455
|
}
|
|
456
|
+
// 先让在飞的原生检测轮次失效,再清理状态:随后在同一(单线程)执行序里
|
|
457
|
+
// 提交的轮次会看到新代数并整轮丢弃。
|
|
458
|
+
UpdateContext.resetGeneration += 1;
|
|
433
459
|
this.persistState(resetState, { clearFirstLoadMarker: true });
|
|
460
|
+
// 缓存里的响应仍在宣告本次 reset 刚删掉的版本,一并丢弃,避免 JS 侧复用。
|
|
461
|
+
try {
|
|
462
|
+
this.preferences.deleteSync(KEY_RESP_CACHE);
|
|
463
|
+
} catch (e: any) {
|
|
464
|
+
console.error('Failed to clear native check cache on reset:', e);
|
|
465
|
+
}
|
|
434
466
|
UpdateContext.ignoreRollback = false;
|
|
435
467
|
|
|
436
468
|
// maxAgeDays=0:删除下载目录内容,仅保留当前运行版本的目录(残留目录由
|
|
@@ -448,7 +480,44 @@ export class UpdateContext {
|
|
|
448
480
|
this.trace('resetToPackagedBundle:after');
|
|
449
481
|
}
|
|
450
482
|
|
|
451
|
-
|
|
483
|
+
/** 供原生检测编排器采样/比对的 reset 代数(见 resetGeneration 注释)。 */
|
|
484
|
+
public getResetGeneration(): number {
|
|
485
|
+
return UpdateContext.resetGeneration;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 一次性提交原生检测轮次的全部持久化结果(版本元信息、激活、响应缓存):
|
|
490
|
+
* 先复核 reset 代数,再落所有写入。ArkTS 单线程 + 本方法内无 await,因此
|
|
491
|
+
* 校验与写入之间不存在可插入 reset 的窗口(iOS/Android 用锁达到同一效果)。
|
|
492
|
+
* 返回是否提交成功。
|
|
493
|
+
*/
|
|
494
|
+
public commitNativeCheckResult(
|
|
495
|
+
expectedGeneration: number,
|
|
496
|
+
hash: string,
|
|
497
|
+
hashInfoJson: string,
|
|
498
|
+
activate: boolean,
|
|
499
|
+
responseCacheJson: string,
|
|
500
|
+
): boolean {
|
|
501
|
+
if (UpdateContext.resetGeneration !== expectedGeneration) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
if (hash && hashInfoJson) {
|
|
505
|
+
this.setKv(`hash_${hash}`, hashInfoJson);
|
|
506
|
+
}
|
|
507
|
+
if (activate && hash) {
|
|
508
|
+
this.switchVersion(hash);
|
|
509
|
+
}
|
|
510
|
+
if (responseCacheJson) {
|
|
511
|
+
this.setKv(KEY_RESP_CACHE, responseCacheJson);
|
|
512
|
+
}
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public async downloadFullUpdate(
|
|
517
|
+
url: string,
|
|
518
|
+
hash: string,
|
|
519
|
+
deadlineUptimeMs: number = 0,
|
|
520
|
+
): Promise<void> {
|
|
452
521
|
try {
|
|
453
522
|
const params = this.createTaskParams(
|
|
454
523
|
DownloadTaskParams.TASK_TYPE_PATCH_FULL,
|
|
@@ -457,6 +526,7 @@ export class UpdateContext {
|
|
|
457
526
|
);
|
|
458
527
|
params.targetFile = `${this.rootDir}/${hash}.ppk`;
|
|
459
528
|
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
|
529
|
+
params.deadlineUptimeMs = deadlineUptimeMs;
|
|
460
530
|
await this.executeTask(params);
|
|
461
531
|
} catch (e) {
|
|
462
532
|
console.error('Failed to download full update:', e);
|
|
@@ -474,7 +544,7 @@ export class UpdateContext {
|
|
|
474
544
|
url,
|
|
475
545
|
hash,
|
|
476
546
|
);
|
|
477
|
-
params.targetFile = this.rootDir + '/' + fileName;
|
|
547
|
+
params.targetFile = this.rootDir + '/' + assertSafePathComponent(fileName);
|
|
478
548
|
await this.executeTask(params);
|
|
479
549
|
}
|
|
480
550
|
|
|
@@ -482,22 +552,25 @@ export class UpdateContext {
|
|
|
482
552
|
url: string,
|
|
483
553
|
hash: string,
|
|
484
554
|
originHash: string,
|
|
555
|
+
deadlineUptimeMs: number = 0,
|
|
485
556
|
): Promise<void> {
|
|
486
557
|
const params = this.createTaskParams(
|
|
487
558
|
DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK,
|
|
488
559
|
url,
|
|
489
560
|
hash,
|
|
490
561
|
);
|
|
491
|
-
params.originHash = originHash;
|
|
562
|
+
params.originHash = assertSafePathComponent(originHash);
|
|
492
563
|
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
|
|
493
564
|
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
|
494
565
|
params.originDirectory = `${this.rootDir}/${params.originHash}`;
|
|
566
|
+
params.deadlineUptimeMs = deadlineUptimeMs;
|
|
495
567
|
await this.executeTask(params);
|
|
496
568
|
}
|
|
497
569
|
|
|
498
570
|
public async downloadPatchFromPackage(
|
|
499
571
|
url: string,
|
|
500
572
|
hash: string,
|
|
573
|
+
deadlineUptimeMs: number = 0,
|
|
501
574
|
): Promise<void> {
|
|
502
575
|
try {
|
|
503
576
|
const params = this.createTaskParams(
|
|
@@ -507,6 +580,7 @@ export class UpdateContext {
|
|
|
507
580
|
);
|
|
508
581
|
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
|
|
509
582
|
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
|
583
|
+
params.deadlineUptimeMs = deadlineUptimeMs;
|
|
510
584
|
return await this.executeTask(params);
|
|
511
585
|
} catch (e) {
|
|
512
586
|
console.error('Failed to download package patch:', e);
|
|
@@ -514,9 +588,23 @@ export class UpdateContext {
|
|
|
514
588
|
}
|
|
515
589
|
}
|
|
516
590
|
|
|
591
|
+
// 原生冷启动检测(NativeCheckOrchestrator)用来跳过已就绪版本的重复下载
|
|
592
|
+
// ——alert 类策略下版本已下载但未激活,若不判在这里会每次冷启动重下一遍。
|
|
593
|
+
public hasDownloadedVersion(hash: string): boolean {
|
|
594
|
+
try {
|
|
595
|
+
const safeHash = assertSafePathComponent(hash);
|
|
596
|
+
return fileIo.accessSync(this.getBundlePath(safeHash))
|
|
597
|
+
&& fileIo.accessSync(
|
|
598
|
+
`${this.rootDir}/${safeHash}/${VERSION_COMPLETE_FILE_NAME}`,
|
|
599
|
+
);
|
|
600
|
+
} catch (e) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
517
605
|
public switchVersion(hash: string): void {
|
|
518
606
|
try {
|
|
519
|
-
const bundlePath = this.getBundlePath(hash);
|
|
607
|
+
const bundlePath = this.getBundlePath(assertSafePathComponent(hash));
|
|
520
608
|
if (!fileIo.accessSync(bundlePath)) {
|
|
521
609
|
throw Error(`Bundle version ${hash} not found.`);
|
|
522
610
|
}
|
|
@@ -544,58 +632,70 @@ export class UpdateContext {
|
|
|
544
632
|
public getBundleUrl() {
|
|
545
633
|
UpdateContext.isUsingBundleUrl = true;
|
|
546
634
|
this.trace('getBundleUrl:enter');
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
635
|
+
let nativeCheckRolledBackVersion = '';
|
|
636
|
+
try {
|
|
637
|
+
const stateBeforeLaunch = this.getStateSnapshot();
|
|
638
|
+
const launchState = NativePatchCore.runStateCore(
|
|
639
|
+
STATE_OP_RESOLVE_LAUNCH,
|
|
640
|
+
stateBeforeLaunch,
|
|
641
|
+
'',
|
|
642
|
+
UpdateContext.ignoreRollback,
|
|
643
|
+
true,
|
|
644
|
+
);
|
|
645
|
+
nativeCheckRolledBackVersion = launchState.rolledBackVersion || '';
|
|
646
|
+
if (launchState.didRollback) {
|
|
647
|
+
// The crash-protection rollback: the new version never called
|
|
648
|
+
// markSuccess. Keep this visible in release logs.
|
|
649
|
+
console.error(
|
|
650
|
+
`Version ${stateBeforeLaunch.currentVersion} was not marked as successful,` +
|
|
651
|
+
` rolled back to ${launchState.currentVersion}`,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if (launchState.didRollback || launchState.consumedFirstTime) {
|
|
655
|
+
this.persistState(launchState, {
|
|
656
|
+
markFirstLoadMarker: launchState.consumedFirstTime,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (launchState.consumedFirstTime) {
|
|
660
|
+
UpdateContext.ignoreRollback = true;
|
|
661
|
+
}
|
|
662
|
+
this.trace(
|
|
663
|
+
`getBundleUrl:load=${launchState.loadVersion}` +
|
|
664
|
+
` consumed=${launchState.consumedFirstTime}` +
|
|
665
|
+
` rollback=${launchState.didRollback}`,
|
|
561
666
|
);
|
|
562
|
-
}
|
|
563
|
-
if (launchState.didRollback || launchState.consumedFirstTime) {
|
|
564
|
-
this.persistState(launchState, {
|
|
565
|
-
markFirstLoadMarker: launchState.consumedFirstTime,
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
if (launchState.consumedFirstTime) {
|
|
569
|
-
UpdateContext.ignoreRollback = true;
|
|
570
|
-
}
|
|
571
|
-
this.trace(
|
|
572
|
-
`getBundleUrl:load=${launchState.loadVersion}` +
|
|
573
|
-
` consumed=${launchState.consumedFirstTime}` +
|
|
574
|
-
` rollback=${launchState.didRollback}`,
|
|
575
|
-
);
|
|
576
667
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
668
|
+
let version = launchState.loadVersion || '';
|
|
669
|
+
// Guard the rollback chain against cycles: a corrupted state returning an
|
|
670
|
+
// already-visited version would otherwise spin this loop forever during
|
|
671
|
+
// startup (Android has the same guard).
|
|
672
|
+
const visitedVersions = new Set<string>();
|
|
673
|
+
while (version && !visitedVersions.has(version)) {
|
|
674
|
+
visitedVersions.add(version);
|
|
675
|
+
const bundleFile = this.getBundlePath(version);
|
|
676
|
+
try {
|
|
677
|
+
if (!fileIo.accessSync(bundleFile)) {
|
|
678
|
+
console.error(`Bundle version ${version} not found.`);
|
|
679
|
+
version = this.rollBack();
|
|
680
|
+
nativeCheckRolledBackVersion = this.rolledBackVersion();
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
UpdateContext.launchVersion = version;
|
|
684
|
+
nativeCheckRolledBackVersion = this.rolledBackVersion();
|
|
685
|
+
return bundleFile;
|
|
686
|
+
} catch (e) {
|
|
687
|
+
console.error('Failed to access bundle file:', e);
|
|
588
688
|
version = this.rollBack();
|
|
589
|
-
|
|
689
|
+
nativeCheckRolledBackVersion = this.rolledBackVersion();
|
|
590
690
|
}
|
|
591
|
-
UpdateContext.launchVersion = version;
|
|
592
|
-
return bundleFile;
|
|
593
|
-
} catch (e) {
|
|
594
|
-
console.error('Failed to access bundle file:', e);
|
|
595
|
-
version = this.rollBack();
|
|
596
691
|
}
|
|
692
|
+
nativeCheckRolledBackVersion = this.rolledBackVersion();
|
|
693
|
+
return '';
|
|
694
|
+
} finally {
|
|
695
|
+
// State corruption is exactly when the native rescue check is needed;
|
|
696
|
+
// schedule even if state parsing/rollback throws before a normal exit.
|
|
697
|
+
scheduleNativeCheck(this, nativeCheckRolledBackVersion);
|
|
597
698
|
}
|
|
598
|
-
return '';
|
|
599
699
|
}
|
|
600
700
|
|
|
601
701
|
public getCurrentVersion(): string {
|
package/harmony/pushy.har
CHANGED
|
Binary file
|