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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import preferences from '@ohos.data.preferences';
|
|
2
2
|
import fileIo from '@ohos.file.fs';
|
|
3
3
|
import {
|
|
4
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
4
5
|
isInstallComplete,
|
|
5
|
-
|
|
6
|
+
recordedBundleSha256,
|
|
6
7
|
verifyInstallForActivation,
|
|
7
8
|
} from './InstallRecord';
|
|
8
9
|
import { DownloadTask } from './DownloadTask';
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
scheduleNativeSync,
|
|
19
20
|
} from './SyncCoordinator';
|
|
20
21
|
import NativePatchCore, {
|
|
21
|
-
STATE_OP_CLEAR_FIRST_TIME,
|
|
22
22
|
STATE_OP_CLEAR_ROLLBACK_MARK,
|
|
23
23
|
STATE_OP_MARK_SUCCESS,
|
|
24
24
|
STATE_OP_RESOLVE_LAUNCH,
|
|
@@ -27,9 +27,9 @@ import NativePatchCore, {
|
|
|
27
27
|
StateCoreResult,
|
|
28
28
|
} from './NativePatchCore';
|
|
29
29
|
import { assertSafePathComponent } from './PathUtils';
|
|
30
|
+
import { withRawFd } from './SaveFile';
|
|
30
31
|
import {
|
|
31
32
|
ERROR_SWITCH_VERSION_FAILED,
|
|
32
|
-
createPushyError,
|
|
33
33
|
getErrorMessage,
|
|
34
34
|
toPushyError,
|
|
35
35
|
} from './ErrorCodes';
|
|
@@ -41,11 +41,18 @@ const TAG = 'PushyContext';
|
|
|
41
41
|
// 常规清理保留最近 3 天内触碰过的条目(续传 partial、staging 同样按 mtime)。
|
|
42
42
|
const CLEANUP_MAX_AGE_DAYS = 3;
|
|
43
43
|
|
|
44
|
-
// Preferences.flushSync 是 API 14
|
|
45
|
-
//
|
|
44
|
+
// Preferences.flushSync 是 API 14 才有:HAR 兼容 API 12,按结构化视图探测
|
|
45
|
+
// (不用交叉类型——ArkTS 不支持)。
|
|
46
46
|
interface OptionalPreferencesApi {
|
|
47
47
|
flushSync?: () => void;
|
|
48
|
-
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// C1 配置等价(cpp/update_flow_core ConfigsEquivalent,与 Android/iOS 共用):
|
|
51
|
+
// 缺省键补默认值、端点/发现地址去尾斜杠并保序去重、忽略键序。等价的配置不算
|
|
52
|
+
// "变更"——宿主重复 configure、或 JS 同步一份只差格式的同一配置,都不应取消
|
|
53
|
+
// 在飞轮次或丢弃响应缓存。任一缺省即不等价。
|
|
54
|
+
function nativeConfigsEquivalent(a: string | undefined, b: string | undefined): boolean {
|
|
55
|
+
return NativePatchCore.configsEquivalent(a, b);
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
interface PersistOptions {
|
|
@@ -320,7 +327,7 @@ export class UpdateContext {
|
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
private getBundlePath(hash: string): string {
|
|
323
|
-
return `${this.rootDir}/${hash}
|
|
330
|
+
return `${this.rootDir}/${hash}/${HARMONY_BUNDLE_FILE_NAME}`;
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
private getStateSnapshot(): StateCoreResult {
|
|
@@ -415,13 +422,9 @@ export class UpdateContext {
|
|
|
415
422
|
|
|
416
423
|
private async executeTask(params: DownloadTaskParams): Promise<void> {
|
|
417
424
|
await this.enqueueSerialTask(() => {
|
|
418
|
-
const isPatchTask =
|
|
419
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL ||
|
|
420
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP ||
|
|
421
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
|
|
422
425
|
// Re-check only when this queued job actually starts. A JS download may
|
|
423
426
|
// have completed while the cold-start duplicate waited behind it.
|
|
424
|
-
if (
|
|
427
|
+
if (this.hasDownloadedVersion(params.hash)) {
|
|
425
428
|
return Promise.resolve();
|
|
426
429
|
}
|
|
427
430
|
const downloadTask = new DownloadTask(this.context);
|
|
@@ -460,19 +463,29 @@ export class UpdateContext {
|
|
|
460
463
|
this.flushInBackground('sync binary version');
|
|
461
464
|
}
|
|
462
465
|
|
|
463
|
-
/** 写入并落盘;flushSync 不可用时以 flush() 的结果拒绝。 */
|
|
464
|
-
/** Shared JS/native configuration writer; all mutations precede the first await. */
|
|
465
466
|
private static nativeConfigGeneration: number = 0;
|
|
466
467
|
|
|
467
468
|
public getNativeConfigGeneration(): number {
|
|
468
469
|
return UpdateContext.nativeConfigGeneration;
|
|
469
470
|
}
|
|
470
471
|
|
|
472
|
+
/** 与持久化的配置是否等价(C1)。 */
|
|
473
|
+
public isStoredNativeConfig(config: string | undefined): boolean {
|
|
474
|
+
return nativeConfigsEquivalent(config, this.getKv(KEY_CONFIG));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Shared JS/native configuration writer; all mutations precede the first
|
|
479
|
+
* await. An equivalent configuration (C1) is not a change: the stored string,
|
|
480
|
+
* response cache and generations stay as they are.
|
|
481
|
+
*/
|
|
471
482
|
public setNativeConfig(config: string): Promise<void> {
|
|
472
483
|
if (!this.getKv('uuid')) {
|
|
473
484
|
this.preferences.putSync('uuid', util.generateRandomUUID());
|
|
474
485
|
}
|
|
475
|
-
|
|
486
|
+
const current = this.getKv(KEY_CONFIG);
|
|
487
|
+
// Byte-identical is trivially no change, even for a value C1 cannot parse.
|
|
488
|
+
if (!current || (current !== config && !nativeConfigsEquivalent(current, config))) {
|
|
476
489
|
// Also guards A -> B -> A replacements and late download commits.
|
|
477
490
|
UpdateContext.resetGeneration += 1;
|
|
478
491
|
UpdateContext.nativeConfigGeneration += 1;
|
|
@@ -484,6 +497,7 @@ export class UpdateContext {
|
|
|
484
497
|
return this.flushPreferences('persist configuration');
|
|
485
498
|
}
|
|
486
499
|
|
|
500
|
+
/** 写入并落盘;flushSync 不可用时以 flush() 的结果拒绝。 */
|
|
487
501
|
public setKv(key: string, value: string): Promise<void> {
|
|
488
502
|
this.preferences.putSync(key, value);
|
|
489
503
|
return this.flushPreferences(`set key ${key}`);
|
|
@@ -498,10 +512,6 @@ export class UpdateContext {
|
|
|
498
512
|
return this.flushPreferences(`remove key ${key}`);
|
|
499
513
|
}
|
|
500
514
|
|
|
501
|
-
public isFirstTime(): boolean {
|
|
502
|
-
return this.getStateSnapshot().firstTime;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
515
|
public rolledBackVersion(): string {
|
|
506
516
|
return this.getStateSnapshot().rolledBackVersion || '';
|
|
507
517
|
}
|
|
@@ -518,14 +528,6 @@ export class UpdateContext {
|
|
|
518
528
|
await this.flushPreferences('mark success');
|
|
519
529
|
}
|
|
520
530
|
|
|
521
|
-
public async clearFirstTime(): Promise<void> {
|
|
522
|
-
this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', {
|
|
523
|
-
cleanUp: true,
|
|
524
|
-
clearFirstLoadMarker: true,
|
|
525
|
-
});
|
|
526
|
-
await this.flushPreferences('clear first time');
|
|
527
|
-
}
|
|
528
|
-
|
|
529
531
|
/** getConstants(同步)里调用:落盘失败只记日志。 */
|
|
530
532
|
public clearRollbackMark(): void {
|
|
531
533
|
this.runStateOperation(STATE_OP_CLEAR_ROLLBACK_MARK, '', {
|
|
@@ -561,23 +563,20 @@ export class UpdateContext {
|
|
|
561
563
|
let flushed: Promise<void> = Promise.resolve();
|
|
562
564
|
try {
|
|
563
565
|
// 删除已下载版本的 hash_* 元信息(不走 clear():它是异步的,且会连带清掉
|
|
564
|
-
// uuid —— 见 syncStateWithBinaryVersion
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (key.startsWith('hash_')) {
|
|
572
|
-
this.preferences.deleteSync(key);
|
|
573
|
-
}
|
|
566
|
+
// uuid —— 见 syncStateWithBinaryVersion 的注释)。读取失败时残留的
|
|
567
|
+
// hash_* 只是无害孤儿数据,不影响 reset 语义。
|
|
568
|
+
try {
|
|
569
|
+
const all = this.preferences.getAllSync();
|
|
570
|
+
for (const key of Object.keys(all)) {
|
|
571
|
+
if (key.startsWith('hash_')) {
|
|
572
|
+
this.preferences.deleteSync(key);
|
|
574
573
|
}
|
|
575
|
-
} catch (e) {
|
|
576
|
-
logger.error(
|
|
577
|
-
TAG,
|
|
578
|
-
`Failed to clear hash info on reset: ${getErrorMessage(e)}`,
|
|
579
|
-
);
|
|
580
574
|
}
|
|
575
|
+
} catch (e) {
|
|
576
|
+
logger.error(
|
|
577
|
+
TAG,
|
|
578
|
+
`Failed to clear hash info on reset: ${getErrorMessage(e)}`,
|
|
579
|
+
);
|
|
581
580
|
}
|
|
582
581
|
this.writeState(resetState, { clearFirstLoadMarker: true });
|
|
583
582
|
// 缓存里的响应仍在宣告本次 reset 刚删掉的版本,一并丢弃,避免 JS 侧复用。
|
|
@@ -658,20 +657,15 @@ export class UpdateContext {
|
|
|
658
657
|
hash: string,
|
|
659
658
|
deadlineUptimeMs: number = 0,
|
|
660
659
|
): Promise<void> {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
await this.executeTask(params);
|
|
671
|
-
} catch (e) {
|
|
672
|
-
logger.error(TAG, `Failed to download full package: ${getErrorMessage(e)}`);
|
|
673
|
-
throw e;
|
|
674
|
-
}
|
|
660
|
+
const params = this.createTaskParams(
|
|
661
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FULL,
|
|
662
|
+
url,
|
|
663
|
+
hash,
|
|
664
|
+
);
|
|
665
|
+
params.targetFile = `${this.rootDir}/${hash}.ppk`;
|
|
666
|
+
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
|
667
|
+
params.deadlineUptimeMs = deadlineUptimeMs;
|
|
668
|
+
await this.executeTask(params);
|
|
675
669
|
}
|
|
676
670
|
|
|
677
671
|
public async downloadPatchFromPpk(
|
|
@@ -698,23 +692,15 @@ export class UpdateContext {
|
|
|
698
692
|
hash: string,
|
|
699
693
|
deadlineUptimeMs: number = 0,
|
|
700
694
|
): Promise<void> {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return await this.executeTask(params);
|
|
711
|
-
} catch (e) {
|
|
712
|
-
logger.error(
|
|
713
|
-
TAG,
|
|
714
|
-
`Failed to download package delta: ${getErrorMessage(e)}`,
|
|
715
|
-
);
|
|
716
|
-
throw e;
|
|
717
|
-
}
|
|
695
|
+
const params = this.createTaskParams(
|
|
696
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP,
|
|
697
|
+
url,
|
|
698
|
+
hash,
|
|
699
|
+
);
|
|
700
|
+
params.targetFile = `${this.rootDir}/${hash}${APP_DELTA_SUFFIX}`;
|
|
701
|
+
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
|
702
|
+
params.deadlineUptimeMs = deadlineUptimeMs;
|
|
703
|
+
await this.executeTask(params);
|
|
718
704
|
}
|
|
719
705
|
|
|
720
706
|
// 运行中热更版本安装记录里的 bundleSha256(崩溃归因用);内置 bundle /
|
|
@@ -724,10 +710,9 @@ export class UpdateContext {
|
|
|
724
710
|
return '';
|
|
725
711
|
}
|
|
726
712
|
try {
|
|
727
|
-
|
|
713
|
+
return recordedBundleSha256(
|
|
728
714
|
`${this.rootDir}/${assertSafePathComponent(hash)}`,
|
|
729
715
|
);
|
|
730
|
-
return record?.bundleSha256 ?? '';
|
|
731
716
|
} catch (e) {
|
|
732
717
|
return '';
|
|
733
718
|
}
|
|
@@ -738,51 +723,26 @@ export class UpdateContext {
|
|
|
738
723
|
public hasDownloadedVersion(hash: string): boolean {
|
|
739
724
|
try {
|
|
740
725
|
const safeHash = assertSafePathComponent(hash);
|
|
741
|
-
return
|
|
742
|
-
&& isInstallComplete(`${this.rootDir}/${safeHash}`, safeHash);
|
|
726
|
+
return isInstallComplete(`${this.rootDir}/${safeHash}`, safeHash);
|
|
743
727
|
} catch (e) {
|
|
744
728
|
return false;
|
|
745
729
|
}
|
|
746
730
|
}
|
|
747
731
|
|
|
748
732
|
/**
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
* (current/last
|
|
752
|
-
*
|
|
753
|
-
*/
|
|
754
|
-
private assertActivatable(safeHash: string): boolean {
|
|
755
|
-
if (!fileIo.accessSync(this.getBundlePath(safeHash))) {
|
|
756
|
-
throw createPushyError(
|
|
757
|
-
ERROR_SWITCH_VERSION_FAILED,
|
|
758
|
-
`Bundle version ${safeHash} not found.`,
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
const legacyActivated =
|
|
762
|
-
this.readString('currentVersion') === safeHash ||
|
|
763
|
-
this.readString('lastVersion') === safeHash;
|
|
764
|
-
if (!this.hasDownloadedVersion(safeHash) && !legacyActivated) {
|
|
765
|
-
throw createPushyError(
|
|
766
|
-
ERROR_SWITCH_VERSION_FAILED,
|
|
767
|
-
`Bundle version ${safeHash} is incomplete.`,
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
return !legacyActivated;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* 完整的激活校验(含 native 工作线程上的 bundle 摘要复核),不写状态。
|
|
775
|
-
* 记录里的 bundle 摘要必须与盘上字节一致,才能把下次启动指向它。
|
|
733
|
+
* 完整的激活校验(含 native 工作线程上的 bundle 摘要复核),不写状态:
|
|
734
|
+
* bundle 在盘上,且记录里的 bundle 摘要与盘上字节一致,才能把下次启动指向
|
|
735
|
+
* 它。标记机制之前就激活过、目录里没有记录文件的历史版本(current/last
|
|
736
|
+
* 里有它)免检;有记录文件的目录即便是 current/last 也照常复核。
|
|
776
737
|
*/
|
|
777
738
|
public async verifyActivation(hash: string): Promise<void> {
|
|
778
739
|
const safeHash = assertSafePathComponent(hash);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
this.
|
|
784
|
-
|
|
785
|
-
}
|
|
740
|
+
await verifyInstallForActivation(
|
|
741
|
+
`${this.rootDir}/${safeHash}`,
|
|
742
|
+
safeHash,
|
|
743
|
+
this.readString('currentVersion') === safeHash ||
|
|
744
|
+
this.readString('lastVersion') === safeHash,
|
|
745
|
+
);
|
|
786
746
|
}
|
|
787
747
|
|
|
788
748
|
/** 内存里切换状态(不落盘);调用方负责 flush。 */
|
|
@@ -952,29 +912,56 @@ export class UpdateContext {
|
|
|
952
912
|
}
|
|
953
913
|
}
|
|
954
914
|
|
|
915
|
+
// 进行中的摘要计算:JS getBundleHash 与原生轮次可能同时要它,共用一次。
|
|
916
|
+
private bundleHashTask: Promise<string> | null = null;
|
|
917
|
+
|
|
955
918
|
/**
|
|
956
919
|
* bundleHash = 二进制内嵌 JS bundle 的 sha256 —— 标识二进制本身,与当前运行
|
|
957
920
|
* 的热更版本无关。哈希对象与 pdiff 的源读取完全一致:rawfile 的
|
|
958
921
|
* bundle.harmony.js(DownloadTask.doPatchFromApp 同一读法)。懒计算 + 缓存;
|
|
959
922
|
* 永不失败——空串表示"未知",服务端回退 buildTime 启发式。
|
|
960
923
|
*/
|
|
961
|
-
public
|
|
924
|
+
public getBundleHash(): Promise<string> {
|
|
962
925
|
if (UpdateContext.DEBUG) {
|
|
963
926
|
// debug 下 bundle 由 metro 提供,与 dev 删 buildTime 的行为对齐。
|
|
964
|
-
return '';
|
|
927
|
+
return Promise.resolve('');
|
|
965
928
|
}
|
|
966
929
|
const cachePrefix = `${this.getPackageVersion()}|${this.getBundleModifiedTime()}|`;
|
|
967
930
|
const cached = this.readString(UpdateContext.KEY_BUNDLE_HASH_CACHE);
|
|
968
931
|
if (cached.startsWith(cachePrefix)) {
|
|
969
|
-
return cached.slice(cachePrefix.length);
|
|
932
|
+
return Promise.resolve(cached.slice(cachePrefix.length));
|
|
933
|
+
}
|
|
934
|
+
if (!this.bundleHashTask) {
|
|
935
|
+
const task = this.computeBundleHash(cachePrefix);
|
|
936
|
+
this.bundleHashTask = task;
|
|
937
|
+
task.then(
|
|
938
|
+
() => {
|
|
939
|
+
this.bundleHashTask = null;
|
|
940
|
+
},
|
|
941
|
+
() => {
|
|
942
|
+
this.bundleHashTask = null;
|
|
943
|
+
},
|
|
944
|
+
);
|
|
970
945
|
}
|
|
946
|
+
return this.bundleHashTask;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private async computeBundleHash(cachePrefix: string): Promise<string> {
|
|
971
950
|
let hash = '';
|
|
972
951
|
try {
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
952
|
+
// 内嵌 bundle 有几 MB:不读进内存,也不在 UI 线程(TurboModule 所在)
|
|
953
|
+
// 哈希——native 工作线程按 rawfile descriptor 的区间 pread 流式计算。
|
|
954
|
+
// fd 在 promise 落定后才关闭(withRawFd)。
|
|
955
|
+
hash = await withRawFd(
|
|
956
|
+
this.context.resourceManager,
|
|
957
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
958
|
+
descriptor =>
|
|
959
|
+
NativePatchCore.sha256HexRawFdAsync(
|
|
960
|
+
descriptor.fd,
|
|
961
|
+
descriptor.offset,
|
|
962
|
+
descriptor.length,
|
|
963
|
+
),
|
|
964
|
+
);
|
|
978
965
|
} catch (error) {
|
|
979
966
|
// rawfile 缺 bundle(未打包 release bundle)是合法的"未知",不是错误。
|
|
980
967
|
logger.info(TAG, `Cannot hash embedded bundle: ${getErrorMessage(error)}`);
|
package/harmony/pushy.har
CHANGED
|
Binary file
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Launch bundle resolution and the identity of the running binary: which
|
|
2
|
+
// bundle this launch loads, where downloaded versions live, and the
|
|
3
|
+
// packageVersion/buildTime pair that detects a rebuilt binary.
|
|
4
|
+
#import "RCTPushyInternal.h"
|
|
5
|
+
|
|
6
|
+
// The version whose bundle this process actually loaded (resolved in
|
|
7
|
+
// +bundleURL). resetToPackagedBundle must not delete its directory: update
|
|
8
|
+
// assets (images/fonts) are read from it on demand at runtime, so wiping it
|
|
9
|
+
// under a silent (no-restart) reset would break every image the running app
|
|
10
|
+
// has not loaded yet. Guarded by the state lock.
|
|
11
|
+
PUSHY_HIDDEN NSString *pushyLaunchVersion = nil;
|
|
12
|
+
|
|
13
|
+
#if !DEBUG
|
|
14
|
+
static NSString *PushyReadBuildTime(void) {
|
|
15
|
+
// The podspec's s.resource lands in the app bundle for a static
|
|
16
|
+
// library but inside this pod's own framework bundle under
|
|
17
|
+
// use_frameworks!; look in both.
|
|
18
|
+
NSString *stampPath =
|
|
19
|
+
[[NSBundle mainBundle] pathForResource:@"pushy_build_time" ofType:@"txt"]
|
|
20
|
+
?: [[NSBundle bundleForClass:[RCTPushy class]] pathForResource:@"pushy_build_time"
|
|
21
|
+
ofType:@"txt"];
|
|
22
|
+
NSString *stamp = stampPath == nil ? nil
|
|
23
|
+
: [[NSString stringWithContentsOfFile:stampPath encoding:NSUTF8StringEncoding error:nil]
|
|
24
|
+
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
25
|
+
if (stamp.length == 0) {
|
|
26
|
+
// Never empty: with "" SyncBinaryVersion would miss a rebuilt
|
|
27
|
+
// binary that kept its CFBundleShortVersionString, and a stale
|
|
28
|
+
// hot update would outlive an App Store update. Every build
|
|
29
|
+
// rewrites Info.plist, so its mtime (integer seconds) stands in.
|
|
30
|
+
RCTLogWarn(@"RCTPushy -- pushy_build_time.txt %@; using the Info.plist mtime as buildTime",
|
|
31
|
+
stampPath == nil ? @"not found in the main or framework bundle" : @"is empty");
|
|
32
|
+
NSString *infoPlist =
|
|
33
|
+
[[NSBundle mainBundle].bundlePath stringByAppendingPathComponent:@"Info.plist"];
|
|
34
|
+
NSDate *modified = [[NSFileManager defaultManager] attributesOfItemAtPath:infoPlist
|
|
35
|
+
error:nil].fileModificationDate;
|
|
36
|
+
stamp = [NSString stringWithFormat:@"%lld", (long long)[modified timeIntervalSince1970]];
|
|
37
|
+
}
|
|
38
|
+
return stamp;
|
|
39
|
+
}
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
@implementation RCTPushy (Launch)
|
|
43
|
+
|
|
44
|
+
// Resolves the bundle to launch from the persisted state; nil means the
|
|
45
|
+
// packaged bundle. Consumes the launch's one-shot state (first_time), so a
|
|
46
|
+
// second call is only valid after something re-armed it (a switchVersion).
|
|
47
|
+
// purgedVersion is set on tvOS when the installed version's files are gone.
|
|
48
|
+
+ (NSURL *)resolveLaunchBundleURL:(NSString **)launchRolledBackVersionOut
|
|
49
|
+
purgedVersion:(NSString **)purgedVersionOut
|
|
50
|
+
{
|
|
51
|
+
__block NSURL *resolvedURL = nil;
|
|
52
|
+
__block NSString *launchRolledBackVersion = nil;
|
|
53
|
+
__block NSString *purgedVersion = nil;
|
|
54
|
+
@try {
|
|
55
|
+
PushyWithStateLock(^{
|
|
56
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
57
|
+
|
|
58
|
+
NSString *curPackageVersion = [RCTPushy packageVersion];
|
|
59
|
+
NSString *curBuildTime = [RCTPushy buildTime];
|
|
60
|
+
|
|
61
|
+
pushy::state::State state = PushyStateFromDefaults(defaults);
|
|
62
|
+
pushy::state::BinaryVersionSyncResult sync = pushy::state::SyncBinaryVersion(
|
|
63
|
+
state,
|
|
64
|
+
PushyToStdString(curPackageVersion),
|
|
65
|
+
PushyToStdString(curBuildTime)
|
|
66
|
+
);
|
|
67
|
+
if (sync.changed) {
|
|
68
|
+
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked()];
|
|
69
|
+
state = sync.state;
|
|
70
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!state.current_version.empty()) {
|
|
74
|
+
std::string const versionBeforeLaunch = state.current_version;
|
|
75
|
+
pushy::state::LaunchDecision decision = pushy::state::ResolveLaunchState(
|
|
76
|
+
state,
|
|
77
|
+
ignoreRollback.load(),
|
|
78
|
+
true
|
|
79
|
+
);
|
|
80
|
+
state = decision.state;
|
|
81
|
+
|
|
82
|
+
if (decision.did_rollback) {
|
|
83
|
+
// The crash-protection rollback: the new version never called
|
|
84
|
+
// markSuccess. Keep this visible in release logs.
|
|
85
|
+
RCTLogWarn(@"RCTPushy -- version %@ was not marked as successful, rolled back to %@",
|
|
86
|
+
PushyFromStdString(versionBeforeLaunch),
|
|
87
|
+
PushyFromStdString(state.current_version));
|
|
88
|
+
}
|
|
89
|
+
if (decision.did_rollback || decision.consumed_first_time) {
|
|
90
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
91
|
+
}
|
|
92
|
+
if (decision.consumed_first_time) {
|
|
93
|
+
// bundleURL may be called many times, ignore rollbacks before process restarted again.
|
|
94
|
+
ignoreRollback = true;
|
|
95
|
+
[defaults setObject:@(YES) forKey:keyFirstLoadMarked];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
NSString *loadVersion = PushyFromStdString(decision.load_version);
|
|
99
|
+
// Guard the rollback chain against cycles: a corrupted state
|
|
100
|
+
// returning an already-visited version would otherwise spin this
|
|
101
|
+
// loop forever during startup (Android has the same guard).
|
|
102
|
+
NSMutableSet<NSString *> *visitedVersions = [NSMutableSet set];
|
|
103
|
+
while (loadVersion.length && ![visitedVersions containsObject:loadVersion]) {
|
|
104
|
+
[visitedVersions addObject:loadVersion];
|
|
105
|
+
NSString *bundlePath = [PushyVersionDir(loadVersion) stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
106
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) {
|
|
107
|
+
pushyLaunchVersion = loadVersion;
|
|
108
|
+
resolvedURL = [NSURL fileURLWithPath:bundlePath];
|
|
109
|
+
break;
|
|
110
|
+
} else {
|
|
111
|
+
#if TARGET_OS_TV
|
|
112
|
+
// Caches is purgeable on tvOS: a missing bundle is the OS
|
|
113
|
+
// reclaiming space, not a bad release. Everything under
|
|
114
|
+
// rctpushy went with it, so walking back to last_version
|
|
115
|
+
// is pointless, and a rollback mark would make this
|
|
116
|
+
// launch's check refuse to reinstall the version.
|
|
117
|
+
RCTLogWarn(@"RCTPushy -- bundle version %@ was purged by the system", loadVersion);
|
|
118
|
+
purgedVersion = loadVersion;
|
|
119
|
+
state = pushy::state::ForgetPurgedVersions(state);
|
|
120
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
121
|
+
if (decision.consumed_first_time) {
|
|
122
|
+
// The first load being consumed never happens: the
|
|
123
|
+
// packaged bundle must not report isFirstTime.
|
|
124
|
+
ignoreRollback = false;
|
|
125
|
+
[defaults removeObjectForKey:keyFirstLoadMarked];
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
#else
|
|
129
|
+
RCTLogError(@"RCTPushy -- bundle version %@ not found, rolling back", loadVersion);
|
|
130
|
+
state = pushy::state::Rollback(state);
|
|
131
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
132
|
+
loadVersion = PushyFromStdString(state.current_version);
|
|
133
|
+
#endif
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Capture before constantsToExport consumes this one-shot marker.
|
|
138
|
+
// The delayed native check must never forceBoot the version that this
|
|
139
|
+
// launch just rolled back.
|
|
140
|
+
launchRolledBackVersion = PushyFromStdString(state.rolled_back_version);
|
|
141
|
+
});
|
|
142
|
+
} @finally {
|
|
143
|
+
*launchRolledBackVersionOut = launchRolledBackVersion;
|
|
144
|
+
*purgedVersionOut = purgedVersion;
|
|
145
|
+
}
|
|
146
|
+
return resolvedURL;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
+ (NSString *)downloadDir
|
|
150
|
+
{
|
|
151
|
+
#if TARGET_OS_TV
|
|
152
|
+
// tvOS apps cannot write Application Support; Caches is the only
|
|
153
|
+
// writable, non-temporary location, and the system may purge it while
|
|
154
|
+
// the app is not running (see the purged-version path in +bundleURL).
|
|
155
|
+
NSSearchPathDirectory base = NSCachesDirectory;
|
|
156
|
+
#else
|
|
157
|
+
NSSearchPathDirectory base = NSApplicationSupportDirectory;
|
|
158
|
+
#endif
|
|
159
|
+
NSString *directory = [NSSearchPathForDirectoriesInDomains(base, NSUserDomainMask, YES) firstObject];
|
|
160
|
+
return [directory stringByAppendingPathComponent:@"rctpushy"];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
+ (NSURL *)binaryBundleURL
|
|
164
|
+
{
|
|
165
|
+
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
+ (NSString *)packageVersion
|
|
169
|
+
{
|
|
170
|
+
static NSString *version =
|
|
171
|
+
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
|
172
|
+
return version;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
+ (NSString *)buildTime
|
|
176
|
+
{
|
|
177
|
+
#if DEBUG
|
|
178
|
+
return @"0";
|
|
179
|
+
#else
|
|
180
|
+
static NSString *buildTime = PushyReadBuildTime();
|
|
181
|
+
return buildTime;
|
|
182
|
+
#endif
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@end
|
package/ios/RCTPushy/RCTPushy.h
CHANGED
|
@@ -26,8 +26,20 @@ typedef void (^RCTPushyBundlePreparationCompletion)(NSDictionary<NSString *, id>
|
|
|
26
26
|
* Result keys: status (skipped/noUpdate/downloaded/failed/cancelled), reason,
|
|
27
27
|
* hash (empty when nothing was installed), activated (selected for NEXT
|
|
28
28
|
* launch, not a reload of the running instance).
|
|
29
|
+
*
|
|
30
|
+
* Waits at most 30 minutes for the round (longer than any round takes).
|
|
29
31
|
*/
|
|
30
32
|
+ (void)prepareBundleWithCompletion:(RCTPushyBundlePreparationCompletion _Nullable)completion
|
|
31
33
|
NS_SWIFT_NAME(prepareBundle(completion:));
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Same as prepareBundleWithCompletion:, but the completion runs after at most
|
|
37
|
+
* `timeout` seconds. On expiry the result is status failed, reason timeout;
|
|
38
|
+
* the round keeps running in the background and its result still applies to
|
|
39
|
+
* the next launch.
|
|
40
|
+
*/
|
|
41
|
+
+ (void)prepareBundleWithTimeout:(NSTimeInterval)timeout
|
|
42
|
+
completion:(RCTPushyBundlePreparationCompletion _Nullable)completion
|
|
43
|
+
NS_SWIFT_NAME(prepareBundle(timeout:completion:));
|
|
44
|
+
|
|
33
45
|
@end
|