react-native-update 10.59.1 → 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 +11 -12
- 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 +19 -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 -2628
- 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 +74 -83
- 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
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class DownloadTaskParams {
|
|
5
5
|
// 任务类型常量
|
|
6
|
-
static readonly TASK_TYPE_CLEANUP: number = 0; // 保留hash和originHash
|
|
7
6
|
static readonly TASK_TYPE_PATCH_FULL: number = 1; // 全量补丁
|
|
8
7
|
static readonly TASK_TYPE_PATCH_FROM_APP: number = 2; // 从APP补丁
|
|
9
8
|
static readonly TASK_TYPE_PATCH_FROM_PPK: number = 3; // 从PPK补丁
|
|
10
|
-
static readonly TASK_TYPE_PLAIN_DOWNLOAD: number = 4; // 普通下载
|
|
11
9
|
|
|
12
|
-
type: number = DownloadTaskParams.
|
|
10
|
+
type: number = DownloadTaskParams.TASK_TYPE_PATCH_FULL; // 任务类型
|
|
13
11
|
url: string = ''; // 下载URL
|
|
14
12
|
hash: string = ''; // 文件哈希值
|
|
15
13
|
originHash: string = ''; // 原始文件哈希值
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ArkTS mirror of cpp/patch_core/error_codes.h — keep in sync by hand
|
|
2
|
-
// (
|
|
2
|
+
// (scripts/check-constant-parity.js asserts the values match).
|
|
3
3
|
// 与 Android/iOS 共用的稳定错误码:JS 层(src/error.ts)按 code 聚合遥测,
|
|
4
4
|
// 消息文案各平台可不同,只有码是契约。
|
|
5
5
|
//
|
|
@@ -62,16 +62,3 @@ export function toPushyError(error: unknown, defaultCode: string): PushyError {
|
|
|
62
62
|
}
|
|
63
63
|
return new PushyError(defaultCode, getErrorMessage(error));
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
/** 从 `[CODE] message` 形式的消息里解析错误码;无前缀返回空串。 */
|
|
67
|
-
export function parseErrorCodePrefix(message: string): string {
|
|
68
|
-
if (typeof message !== 'string' || !message.startsWith('[')) {
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
|
-
const end = message.indexOf('] ');
|
|
72
|
-
if (end <= 1) {
|
|
73
|
-
return '';
|
|
74
|
-
}
|
|
75
|
-
const code = message.substring(1, end);
|
|
76
|
-
return /^[A-Z][A-Z0-9_]*$/.test(code) ? code : '';
|
|
77
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The subset of RNOH's RNInstance the SDK needs: device events for download
|
|
2
|
-
// progress. Typed structurally so
|
|
2
|
+
// progress. Typed structurally so tests can pass a mock.
|
|
3
3
|
export interface DeviceEventEmitter {
|
|
4
4
|
emitDeviceEvent(eventName: string, payload: Object): void;
|
|
5
5
|
}
|
|
@@ -1,187 +1,88 @@
|
|
|
1
|
-
import fileIo from '@ohos.file.fs';
|
|
2
|
-
import { util } from '@kit.ArkTS';
|
|
3
1
|
import NativePatchCore from './NativePatchCore';
|
|
4
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ERROR_DELTA_FAILED,
|
|
4
|
+
ERROR_SWITCH_VERSION_FAILED,
|
|
5
|
+
createPushyError,
|
|
6
|
+
getErrorMessage,
|
|
7
|
+
} from './ErrorCodes';
|
|
5
8
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
export const INSTALL_RECORD_SCHEMA = 1;
|
|
12
|
-
export const INSTALL_RECORD_FILE_NAME = '.pushy-complete';
|
|
9
|
+
// 两阶段安装的完成记录(cpp/patch_core/install_record.h,三端共用的唯一
|
|
10
|
+
// 实现):解压/打补丁全在 staging 目录里进行,最后写入记录,再一次 rename 成
|
|
11
|
+
// 版本目录;激活前重新校验 bundle 摘要。这里只拼路径、把原生结果映射成
|
|
12
|
+
// 稳定错误码。
|
|
13
|
+
// STAGING_SUFFIX 镜像 kStagingSuffix(scripts/check-constant-parity.js 断言)。
|
|
13
14
|
export const STAGING_SUFFIX = '.staging';
|
|
14
|
-
// 归档不得携带任何 `.pushy-` 前缀的条目(任意深度):这些名字保留给 SDK 自己
|
|
15
|
-
// 写的记录,与 Android 的规则一致。
|
|
16
|
-
export const RESERVED_ENTRY_PREFIX = '.pushy-';
|
|
17
15
|
export const HARMONY_BUNDLE_FILE_NAME = 'bundle.harmony.js';
|
|
18
16
|
|
|
19
|
-
export interface InstallRecordData {
|
|
20
|
-
schema?: number;
|
|
21
|
-
versionHash?: string;
|
|
22
|
-
bundleSha256?: string;
|
|
23
|
-
artifactSha256?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
export function stagingDirectoryFor(versionDir: string): string {
|
|
27
18
|
return `${versionDir}${STAGING_SUFFIX}`;
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export function
|
|
21
|
+
/**
|
|
22
|
+
* 在 native 工作线程计算 staging 里 bundle 的摘要并原子写入完成记录
|
|
23
|
+
* (schema 1 的 bundleSha256 必填:算不出摘要就安装失败)。失败 = PATCH_FAILED。
|
|
24
|
+
*/
|
|
25
|
+
export async function sealInstallRecord(
|
|
26
|
+
stagingDir: string,
|
|
35
27
|
versionHash: string,
|
|
36
|
-
bundleSha256: string,
|
|
37
28
|
artifactSha256: string,
|
|
38
|
-
): string {
|
|
39
|
-
const record: InstallRecordData = {
|
|
40
|
-
schema: INSTALL_RECORD_SCHEMA,
|
|
41
|
-
versionHash,
|
|
42
|
-
};
|
|
43
|
-
if (bundleSha256) {
|
|
44
|
-
record.bundleSha256 = bundleSha256;
|
|
45
|
-
}
|
|
46
|
-
if (artifactSha256) {
|
|
47
|
-
record.artifactSha256 = artifactSha256;
|
|
48
|
-
}
|
|
49
|
-
return JSON.stringify(record);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** 写入并 fsync:紧随其后的 rename 必须能在盘上看到它。 */
|
|
53
|
-
export async function writeInstallRecord(
|
|
54
|
-
versionDir: string,
|
|
55
|
-
recordJson: string,
|
|
56
29
|
): Promise<void> {
|
|
57
|
-
const path = `${versionDir}/${INSTALL_RECORD_FILE_NAME}`;
|
|
58
|
-
if (fileIo.accessSync(path)) {
|
|
59
|
-
await fileIo.unlink(path);
|
|
60
|
-
}
|
|
61
|
-
const file = await fileIo.open(
|
|
62
|
-
path,
|
|
63
|
-
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
|
64
|
-
);
|
|
65
|
-
try {
|
|
66
|
-
const bytes = new util.TextEncoder().encodeInto(recordJson);
|
|
67
|
-
await fileIo.write(file.fd, bytes.buffer as ArrayBuffer);
|
|
68
|
-
await fileIo.fsync(file.fd);
|
|
69
|
-
} finally {
|
|
70
|
-
await fileIo.close(file);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 解析记录文本:空串(历史标记)返回 {};畸形/非对象返回 null。纯函数,
|
|
76
|
-
* 供 readInstallRecord 与单测共用。
|
|
77
|
-
*/
|
|
78
|
-
export function parseInstallRecord(text: string): InstallRecordData | null {
|
|
79
|
-
if (text.length === 0) {
|
|
80
|
-
return {};
|
|
81
|
-
}
|
|
82
30
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
31
|
+
await NativePatchCore.sealInstallRecord(
|
|
32
|
+
stagingDir,
|
|
33
|
+
versionHash,
|
|
34
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
35
|
+
artifactSha256,
|
|
36
|
+
);
|
|
87
37
|
} catch (e) {
|
|
88
|
-
|
|
38
|
+
throw createPushyError(ERROR_DELTA_FAILED, getErrorMessage(e));
|
|
89
39
|
}
|
|
90
40
|
}
|
|
91
41
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!fileIo.accessSync(path)) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
42
|
+
/** staging rename 成版本目录(调用方先删掉已有的版本目录)。失败 = PATCH_FAILED。 */
|
|
43
|
+
export async function promoteStaging(
|
|
44
|
+
stagingDir: string,
|
|
45
|
+
versionDir: string,
|
|
46
|
+
): Promise<void> {
|
|
100
47
|
try {
|
|
101
|
-
|
|
102
|
-
if (stat.size === 0) {
|
|
103
|
-
return {};
|
|
104
|
-
}
|
|
105
|
-
if (stat.size > 64 * 1024) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
return parseInstallRecord(fileIo.readTextSync(path));
|
|
48
|
+
await NativePatchCore.promoteStaging(stagingDir, versionDir);
|
|
109
49
|
} catch (e) {
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** 记录是否宣告本版本完整安装(历史空记录视为是)。纯函数。 */
|
|
115
|
-
export function isRecordForVersion(
|
|
116
|
-
record: InstallRecordData | null,
|
|
117
|
-
versionHash: string,
|
|
118
|
-
): boolean {
|
|
119
|
-
if (record === null) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
if (Object.keys(record).length === 0) {
|
|
123
|
-
return true;
|
|
50
|
+
throw createPushyError(ERROR_DELTA_FAILED, getErrorMessage(e));
|
|
124
51
|
}
|
|
125
|
-
return (
|
|
126
|
-
record.schema === INSTALL_RECORD_SCHEMA && record.versionHash === versionHash
|
|
127
|
-
);
|
|
128
52
|
}
|
|
129
53
|
|
|
130
|
-
/** 存在性判定(启动/去重路径,不算摘要)
|
|
54
|
+
/** 存在性判定(启动/去重路径,不算摘要)。 */
|
|
131
55
|
export function isInstallComplete(versionDir: string, versionHash: string): boolean {
|
|
132
|
-
return
|
|
56
|
+
return NativePatchCore.isInstallComplete(
|
|
57
|
+
versionDir,
|
|
58
|
+
versionHash,
|
|
59
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
60
|
+
);
|
|
133
61
|
}
|
|
134
62
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
*/
|
|
139
|
-
export function expectedBundleSha256ForActivation(
|
|
140
|
-
record: InstallRecordData | null,
|
|
141
|
-
versionHash: string,
|
|
142
|
-
): string {
|
|
143
|
-
if (record === null) {
|
|
144
|
-
throw createPushyError(
|
|
145
|
-
ERROR_SWITCH_VERSION_FAILED,
|
|
146
|
-
`Bundle version ${versionHash} has no valid completion record.`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
if (Object.keys(record).length === 0) {
|
|
150
|
-
return '';
|
|
151
|
-
}
|
|
152
|
-
if (
|
|
153
|
-
record.schema !== INSTALL_RECORD_SCHEMA ||
|
|
154
|
-
record.versionHash !== versionHash
|
|
155
|
-
) {
|
|
156
|
-
throw createPushyError(
|
|
157
|
-
ERROR_SWITCH_VERSION_FAILED,
|
|
158
|
-
`Bundle version ${versionHash} completion record mismatch.`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return record.bundleSha256 ?? '';
|
|
63
|
+
/** 记录里的 bundleSha256(崩溃归因);没有时为空串。 */
|
|
64
|
+
export function recordedBundleSha256(versionDir: string): string {
|
|
65
|
+
return NativePatchCore.recordedBundleSha256(versionDir);
|
|
162
66
|
}
|
|
163
67
|
|
|
164
68
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
69
|
+
* 激活前校验:bundle 在盘上,且记录的摘要与盘上字节一致(整包几十 MB,在
|
|
70
|
+
* native 工作线程算)。无记录文件的目录只有是 current/last(grandfathered,
|
|
71
|
+
* 标记机制之前就激活过)时放行。不通过抛 SWITCH_VERSION_FAILED。
|
|
167
72
|
*/
|
|
168
73
|
export async function verifyInstallForActivation(
|
|
169
74
|
versionDir: string,
|
|
170
75
|
versionHash: string,
|
|
171
|
-
|
|
76
|
+
grandfathered: boolean,
|
|
172
77
|
): Promise<void> {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
const actual = await NativePatchCore.sha256HexFileAsync(bundlePath);
|
|
181
|
-
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
182
|
-
throw createPushyError(
|
|
183
|
-
ERROR_SWITCH_VERSION_FAILED,
|
|
184
|
-
`Bundle version ${versionHash} bundle digest mismatch.`,
|
|
78
|
+
try {
|
|
79
|
+
await NativePatchCore.checkActivation(
|
|
80
|
+
versionDir,
|
|
81
|
+
versionHash,
|
|
82
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
83
|
+
grandfathered,
|
|
185
84
|
);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw createPushyError(ERROR_SWITCH_VERSION_FAILED, getErrorMessage(e));
|
|
186
87
|
}
|
|
187
88
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import hilog from '@ohos.hilog';
|
|
2
2
|
|
|
3
|
+
type HilogFunction = (
|
|
4
|
+
domain: number,
|
|
5
|
+
tag: string,
|
|
6
|
+
format: string,
|
|
7
|
+
...args: string[]
|
|
8
|
+
) => void;
|
|
9
|
+
|
|
3
10
|
class Logger {
|
|
4
11
|
private domain: number;
|
|
5
12
|
private prefix: string;
|
|
@@ -24,54 +31,33 @@ class Logger {
|
|
|
24
31
|
this.isDebug = enabled;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
// args[0] is the tag, the rest is joined into the message.
|
|
35
|
+
private log(fn: HilogFunction, args: string[]): void {
|
|
36
|
+
fn(
|
|
37
|
+
this.domain,
|
|
38
|
+
this.prefix,
|
|
39
|
+
this.format,
|
|
40
|
+
args.length > 0 ? args[0] : '',
|
|
41
|
+
args.length > 1 ? args.slice(1).join(' ') : '',
|
|
42
|
+
);
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
debug(...args: string[]): void {
|
|
36
46
|
if (this.isDebug) {
|
|
37
|
-
hilog.debug
|
|
38
|
-
this.domain,
|
|
39
|
-
this.prefix,
|
|
40
|
-
this.format,
|
|
41
|
-
this.tagOf(args),
|
|
42
|
-
this.messageOf(args),
|
|
43
|
-
);
|
|
47
|
+
this.log(hilog.debug, args);
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
info(...args: string[]): void {
|
|
48
|
-
hilog.info
|
|
49
|
-
this.domain,
|
|
50
|
-
this.prefix,
|
|
51
|
-
this.format,
|
|
52
|
-
this.tagOf(args),
|
|
53
|
-
this.messageOf(args),
|
|
54
|
-
);
|
|
52
|
+
this.log(hilog.info, args);
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
warn(...args: string[]): void {
|
|
58
|
-
hilog.warn
|
|
59
|
-
this.domain,
|
|
60
|
-
this.prefix,
|
|
61
|
-
this.format,
|
|
62
|
-
this.tagOf(args),
|
|
63
|
-
this.messageOf(args),
|
|
64
|
-
);
|
|
56
|
+
this.log(hilog.warn, args);
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
error(...args: string[]): void {
|
|
68
|
-
hilog.error
|
|
69
|
-
this.domain,
|
|
70
|
-
this.prefix,
|
|
71
|
-
this.format,
|
|
72
|
-
this.tagOf(args),
|
|
73
|
-
this.messageOf(args),
|
|
74
|
-
);
|
|
60
|
+
this.log(hilog.error, args);
|
|
75
61
|
}
|
|
76
62
|
}
|
|
77
63
|
|
|
@@ -3,11 +3,9 @@ import NativeCore from 'librnpushy.so';
|
|
|
3
3
|
export const STATE_OP_SWITCH_VERSION = 1;
|
|
4
4
|
export const STATE_OP_MARK_SUCCESS = 2;
|
|
5
5
|
export const STATE_OP_ROLLBACK = 3;
|
|
6
|
-
export const STATE_OP_CLEAR_FIRST_TIME = 4;
|
|
7
6
|
export const STATE_OP_CLEAR_ROLLBACK_MARK = 5;
|
|
8
7
|
export const STATE_OP_RESOLVE_LAUNCH = 6;
|
|
9
8
|
|
|
10
|
-
export const ARCHIVE_PATCH_TYPE_FULL = 1;
|
|
11
9
|
export const ARCHIVE_PATCH_TYPE_FROM_PACKAGE = 2;
|
|
12
10
|
export const ARCHIVE_PATCH_TYPE_FROM_PPK = 3;
|
|
13
11
|
|
|
@@ -26,29 +24,43 @@ export interface StateCoreResult {
|
|
|
26
24
|
consumedFirstTime?: boolean;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
export interface ArchivePatchPlanResult {
|
|
30
|
-
mergeSourceSubdir?: string;
|
|
31
|
-
enableMerge: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
27
|
export interface CopyGroupResult {
|
|
35
28
|
from: string;
|
|
36
29
|
toPaths: string[];
|
|
30
|
+
/** 源内容必须匹配的 CRC32(__diff.json copiesCrc);未声明时缺省 */
|
|
31
|
+
expectedCrc?: number;
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
/**
|
|
35
|
+
* 一次 patch 安装的原生部分(cpp/patch_core/archive_patch_core.h
|
|
36
|
+
* ApplyArchivePatch):校验条目、解析并校验 __diff.json、重建 bundle;
|
|
37
|
+
* hostCopiesResources 为 false 时同时完成拷贝/删除/合并。
|
|
38
|
+
*/
|
|
39
|
+
export interface ArchivePatchRequest {
|
|
40
|
+
archiveType: number;
|
|
41
|
+
/** staging 目录里解压出的条目名 */
|
|
42
|
+
entryNames: string[];
|
|
43
|
+
/** __diff.json 原文;归档里没有时为空串 */
|
|
44
|
+
manifestJson: string;
|
|
45
|
+
bundleDeltaEntry: string;
|
|
46
|
+
/** 从拷贝源开头剥掉的前缀(包内 rawfile 路径) */
|
|
47
|
+
copySourcePrefix?: string;
|
|
43
48
|
sourceRoot: string;
|
|
44
49
|
targetRoot: string;
|
|
45
50
|
originBundlePath: string;
|
|
46
51
|
bundleDeltaPath: string;
|
|
47
52
|
bundleOutputPath: string;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
/** 资源不在 sourceRoot 下(rawfile):只重建 bundle,拷贝分组交回调用方 */
|
|
54
|
+
hostCopiesResources?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface FreeSpaceProbePlan {
|
|
58
|
+
/** 本次写入前是否需要探测可用空间。 */
|
|
59
|
+
probe: boolean;
|
|
60
|
+
/** 探测时要求的字节数(不含 FREE_DISK_MARGIN_BYTES)。 */
|
|
61
|
+
reserve: number;
|
|
62
|
+
/** 计划后的"已预留到"位置(相对本次响应体起点)。 */
|
|
63
|
+
reservedUntil: number;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
interface NativePatchCoreBindings {
|
|
@@ -64,16 +76,8 @@ interface NativePatchCoreBindings {
|
|
|
64
76
|
flagA?: boolean,
|
|
65
77
|
flagB?: boolean,
|
|
66
78
|
): StateCoreResult;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
entryNames: string[],
|
|
70
|
-
copyFroms: string[],
|
|
71
|
-
copyTos: string[],
|
|
72
|
-
deletes: string[],
|
|
73
|
-
bundlePatchEntryName?: string,
|
|
74
|
-
): ArchivePatchPlanResult;
|
|
75
|
-
buildCopyGroups(copyFroms: string[], copyTos: string[]): CopyGroupResult[];
|
|
76
|
-
applyDeltaFromSource(options: FileSourcePatchRequest): Promise<void>;
|
|
79
|
+
/** 工作线程执行;resolve 为调用方仍需完成的拷贝(hostCopiesResources 之外为空) */
|
|
80
|
+
applyArchiveDelta(request: ArchivePatchRequest): Promise<CopyGroupResult[]>;
|
|
77
81
|
/**
|
|
78
82
|
* 删除 rootDir 下超过 maxAgeDays 且名字不在 keepNames 里的条目(native
|
|
79
83
|
* 工作线程)。keepNames 必须包含所有可能仍在使用的版本:持久化的
|
|
@@ -84,34 +88,104 @@ interface NativePatchCoreBindings {
|
|
|
84
88
|
keepNames: string[],
|
|
85
89
|
maxAgeDays: number,
|
|
86
90
|
): Promise<void>;
|
|
87
|
-
|
|
88
|
-
sha256Hex(data: Uint8Array): string;
|
|
89
|
-
// 文件流式 SHA-256(小写 hex;不可读时空串)。同步,仅限小文件——整包/
|
|
90
|
-
// 整 bundle 用下面的异步版本,避免冻结 UI 线程。
|
|
91
|
-
sha256HexFile(path: string): string;
|
|
92
|
-
// 同上,在 libuv 工作线程计算;文件不可读时 reject。
|
|
91
|
+
// 文件流式 SHA-256(小写 hex),在 libuv 工作线程计算;文件不可读时 reject。
|
|
93
92
|
sha256HexFileAsync(path: string): Promise<string>;
|
|
93
|
+
// fd 上 [offset, offset + length) 区间的 SHA-256(小写 hex),工作线程
|
|
94
|
+
// pread 流式计算(内嵌 bundle 走 resourceManager.getRawFd)。fd 由调用方
|
|
95
|
+
// 持有,promise 落定前不得关闭;读失败/提前 EOF 时 reject。
|
|
96
|
+
sha256HexRawFdAsync(fd: number, offset: number, length: number): Promise<string>;
|
|
97
|
+
// 完成记录(cpp/patch_core/install_record.h,与 Android/iOS 共用)。
|
|
98
|
+
/** 存在性判定(启动/去重路径,不算摘要):bundle 在盘上且记录指向本版本 */
|
|
99
|
+
isInstallComplete(
|
|
100
|
+
versionDir: string,
|
|
101
|
+
versionHash: string,
|
|
102
|
+
bundleFileName: string,
|
|
103
|
+
): boolean;
|
|
104
|
+
/** 记录里的 bundleSha256(崩溃归因);没有时为空串 */
|
|
105
|
+
recordedBundleSha256(versionDir: string): string;
|
|
106
|
+
/** 工作线程上计算 staging 里 bundle 的摘要并原子写入完成记录 */
|
|
107
|
+
sealInstallRecord(
|
|
108
|
+
stagingDir: string,
|
|
109
|
+
versionHash: string,
|
|
110
|
+
bundleFileName: string,
|
|
111
|
+
artifactSha256: string,
|
|
112
|
+
): Promise<void>;
|
|
113
|
+
/** 把已写完记录的 staging 目录 rename 成版本目录(调用方先删掉旧目录) */
|
|
114
|
+
promoteStaging(stagingDir: string, versionDir: string): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* 激活前校验(工作线程重算 bundle 摘要);不可激活时以原因 reject。
|
|
117
|
+
* grandfathered = 该版本是持久化的 current/last。
|
|
118
|
+
*/
|
|
119
|
+
checkActivation(
|
|
120
|
+
versionDir: string,
|
|
121
|
+
versionHash: string,
|
|
122
|
+
bundleFileName: string,
|
|
123
|
+
grandfathered: boolean,
|
|
124
|
+
): Promise<void>;
|
|
125
|
+
// 解压资源上限(cpp/patch_core/archive_limits.h,与 Android/iOS 共用)。
|
|
126
|
+
/** 整包解压量与整包压缩比;超限时返回原因,否则 undefined */
|
|
127
|
+
checkUncompressedSize(
|
|
128
|
+
archiveBytes: number,
|
|
129
|
+
uncompressedBytes: number,
|
|
130
|
+
): string | undefined;
|
|
131
|
+
/** 工作线程遍历刚解压的目录(不跟随符号链接),超限/保留名/符号链接时以原因 reject */
|
|
132
|
+
checkExtractedDirectory(directory: string, archiveBytes: number): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* 未知长度下载每个 chunk 写入前的磁盘探测计划。writtenBefore 是本次响应体
|
|
135
|
+
* 在该 chunk 之前已收到的字节数;knownTotal > 0 时从不探测。
|
|
136
|
+
*/
|
|
137
|
+
planFreeSpaceProbe(
|
|
138
|
+
knownTotal: number,
|
|
139
|
+
writtenBefore: number,
|
|
140
|
+
chunkBytes: number,
|
|
141
|
+
reservedUntil: number,
|
|
142
|
+
): FreeSpaceProbePlan;
|
|
94
143
|
/** CRC32(zip/zlib 多项式)。pdiff 拷贝前与 copiesCrc 比对用 */
|
|
95
144
|
crc32(data: Uint8Array | ArrayBuffer): number;
|
|
96
145
|
/** 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道) */
|
|
97
146
|
getSupportedDiffVersion(): number;
|
|
98
147
|
|
|
99
|
-
//
|
|
100
|
-
// JSON
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
148
|
+
// 原生冷启动轮次(cpp/update_flow_core/native_round.h,与 Android/iOS 共用):
|
|
149
|
+
// JSON 字符串进出。返回 undefined = 内核拒收了输入,本轮失败。
|
|
150
|
+
// envJson 描述本次启动(字段见 native_round.h)。
|
|
151
|
+
/** {"body": 请求体文本, "query": 首个查询状态};配置不可运行时 undefined */
|
|
152
|
+
prepareRound(
|
|
153
|
+
configJson: string,
|
|
154
|
+
envJson: string,
|
|
105
155
|
randomSample: number,
|
|
106
156
|
): string | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* 下一步查询状态:带 "url"(及 "post")= 发这个请求后再调;"found" = 刚传入
|
|
159
|
+
* 的文本就是有效响应;都没有 = 查询结束。responseText 为 undefined 表示请求
|
|
160
|
+
* 失败或尚未发出。
|
|
161
|
+
*/
|
|
162
|
+
nextQueryStep(
|
|
163
|
+
stateJson: string,
|
|
164
|
+
responseText: string | undefined,
|
|
165
|
+
): string | undefined;
|
|
166
|
+
/** 决策(已按本机规划的 attempts)、要持久化的 hashInfo 与响应缓存文本 */
|
|
107
167
|
handleResponse(
|
|
108
168
|
responseText: string,
|
|
109
|
-
|
|
110
|
-
|
|
169
|
+
configJson: string,
|
|
170
|
+
envJson: string,
|
|
171
|
+
requestBody: string,
|
|
172
|
+
responseAtSeconds: number,
|
|
111
173
|
): string | undefined;
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
|
|
174
|
+
|
|
175
|
+
// 持久化的原生配置(cpp/update_flow_core/native_config.h,与 Android/iOS
|
|
176
|
+
// 共用)。undefined 视同空串:缺省配置。
|
|
177
|
+
/** C1 配置等价:同一配置的不同序列化。任一缺省即不等价。 */
|
|
178
|
+
configsEquivalent(a: string | undefined, b: string | undefined): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* 轮次门槛:可运行时为 {"appKey": 去空白后的值},否则
|
|
181
|
+
* {"status": "skipped" | "failed", "reason": ...}。
|
|
182
|
+
*/
|
|
183
|
+
runnableConfig(configJson: string | undefined): string;
|
|
184
|
+
/**
|
|
185
|
+
* 校验宿主 configure() 的选项(JSON 对象文本),返回要持久化的配置;
|
|
186
|
+
* 不合法时抛出 Error("Invalid native configuration: ...")。
|
|
187
|
+
*/
|
|
188
|
+
normalizeConfiguration(optionsJson: string | undefined): string;
|
|
115
189
|
}
|
|
116
190
|
|
|
117
191
|
export default NativeCore as unknown as NativePatchCoreBindings;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NativePatchCore from './NativePatchCore';
|
|
2
2
|
|
|
3
3
|
/** Options accepted by PushyFileJSBundleProvider.configure(). */
|
|
4
4
|
export interface PushyConfiguration {
|
|
@@ -16,89 +16,12 @@ export interface PushyConfiguration {
|
|
|
16
16
|
rn?: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
packageVersion?: string;
|
|
26
|
-
rnu: string;
|
|
27
|
-
rn: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const DEFAULT_ENDPOINTS: string[] = [
|
|
31
|
-
revealText('3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5b3d5b9817b592947e5cdefbc8a7e'),
|
|
32
|
-
revealText('3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5f0daac9c644a620ae88ca1ad93'),
|
|
33
|
-
];
|
|
34
|
-
const DEFAULT_QUERY_URLS: string[] = [
|
|
35
|
-
revealText('3203e0c1bdd1270a253608fcd3fd9362476817f4f0d5a1996342631be3c2a3a9d779552507fdcde8928a6f512f5ce2ccbdc869404d2f1de79daa826d562c0913eec4fa9b7d4426'),
|
|
36
|
-
revealText('3203e0c1bdd1270a213b12b7dca09468462e12f3b0d5bd813d482446f4c6a1be8e79552507fdcda68cd06e5c3710e480a4867048483e55e0c2ab8d7d43030d1ce9c3b183214e2601f2f0d5b782601e2719e8ca'),
|
|
37
|
-
];
|
|
38
|
-
const CONFIG_KEYS: string[] = [
|
|
39
|
-
'appKey', 'endpoints', 'queryUrls', 'afterDownload', 'disabled',
|
|
40
|
-
'packageVersion', 'rnu', 'rn',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
function configString(value: string, name: string, allowEmpty: boolean): string {
|
|
44
|
-
if (typeof value !== 'string' || (!allowEmpty && value.trim().length === 0)) {
|
|
45
|
-
throw new Error(`Invalid native configuration: ${name} must be a string${allowEmpty ? '' : ' and must not be blank'}`);
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function configUrls(values: string[], name: string, base: boolean): string[] {
|
|
51
|
-
if (!Array.isArray(values) || (base && values.length === 0)) {
|
|
52
|
-
throw new Error(`Invalid native configuration: ${name} must be ${base ? 'a non-empty' : 'an'} array`);
|
|
53
|
-
}
|
|
54
|
-
const result: string[] = [];
|
|
55
|
-
for (const value of values) {
|
|
56
|
-
const url = configString(value, name, false).trim();
|
|
57
|
-
// Restrict the authority and scheme, without relying on browser URL globals
|
|
58
|
-
// unavailable in ArkTS. Platform networking performs the final URL parsing.
|
|
59
|
-
if (!/^https?:\/\/(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9.-]+)(:[0-9]+)?([/?#]|$)/.test(url)
|
|
60
|
-
|| /\s|\\/.test(url) || (base && /[?#]/.test(url))) {
|
|
61
|
-
throw new Error(`Invalid native configuration: ${name} requires absolute HTTP(S) URLs without credentials${base ? ', queries or fragments' : ''}`);
|
|
62
|
-
}
|
|
63
|
-
const normalized = base ? url.replace(/\/+$/, '') : url;
|
|
64
|
-
if (!result.includes(normalized)) {
|
|
65
|
-
result.push(normalized);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Pure validation, before any native state is touched. Does not mutate options. */
|
|
19
|
+
/**
|
|
20
|
+
* Pure validation, before any native state is touched. Does not mutate options.
|
|
21
|
+
* The rules and defaults are the shared core's (cpp/update_flow_core
|
|
22
|
+
* NormalizeConfiguration, also used by Android and iOS); it throws
|
|
23
|
+
* Error("Invalid native configuration: ...") for options the host must fix.
|
|
24
|
+
*/
|
|
72
25
|
export function normalizePushyConfiguration(options: PushyConfiguration): string {
|
|
73
|
-
|
|
74
|
-
throw new Error('Invalid native configuration: expected an object');
|
|
75
|
-
}
|
|
76
|
-
for (const key of Object.keys(options)) {
|
|
77
|
-
if (!CONFIG_KEYS.includes(key)) {
|
|
78
|
-
throw new Error(`Invalid native configuration: unknown option ${key}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const appKey = configString(options.appKey, 'appKey', false);
|
|
82
|
-
const customEndpoints = options.endpoints !== undefined;
|
|
83
|
-
const endpoints = configUrls(customEndpoints ? options.endpoints! : DEFAULT_ENDPOINTS, 'endpoints', true);
|
|
84
|
-
const queryUrls = configUrls(
|
|
85
|
-
options.queryUrls !== undefined ? options.queryUrls : (customEndpoints ? [] : DEFAULT_QUERY_URLS),
|
|
86
|
-
'queryUrls', false,
|
|
87
|
-
);
|
|
88
|
-
const afterDownload = options.afterDownload === undefined ? 'none' : options.afterDownload;
|
|
89
|
-
if (afterDownload !== 'none' && afterDownload !== POLICY_NEXT_LAUNCH) {
|
|
90
|
-
throw new Error(`Invalid native configuration: afterDownload must be none or ${POLICY_NEXT_LAUNCH}`);
|
|
91
|
-
}
|
|
92
|
-
if (options.disabled !== undefined && typeof options.disabled !== 'boolean') {
|
|
93
|
-
throw new Error('Invalid native configuration: disabled must be a boolean');
|
|
94
|
-
}
|
|
95
|
-
const result: PersistedPushyConfiguration = {
|
|
96
|
-
appKey, endpoints, queryUrls, afterDownload, disabled: options.disabled ?? false,
|
|
97
|
-
rnu: options.rnu === undefined ? '' : configString(options.rnu, 'rnu', true),
|
|
98
|
-
rn: options.rn === undefined ? '' : configString(options.rn, 'rn', true),
|
|
99
|
-
};
|
|
100
|
-
if (options.packageVersion !== undefined) {
|
|
101
|
-
result.packageVersion = configString(options.packageVersion, 'packageVersion', false);
|
|
102
|
-
}
|
|
103
|
-
return JSON.stringify(result);
|
|
26
|
+
return NativePatchCore.normalizeConfiguration(JSON.stringify(options));
|
|
104
27
|
}
|