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.
Files changed (125) hide show
  1. package/README-CN.md +7 -7
  2. package/README.md +7 -6
  3. package/android/build.gradle +25 -33
  4. package/android/jni/Android.mk +16 -24
  5. package/android/lib/arm64-v8a/librnpushy.so +0 -0
  6. package/android/lib/armeabi-v7a/librnpushy.so +0 -0
  7. package/android/lib/x86/librnpushy.so +0 -0
  8. package/android/lib/x86_64/librnpushy.so +0 -0
  9. package/android/proguard.pro +9 -7
  10. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +2 -7
  11. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
  12. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
  13. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
  14. package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
  16. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
  17. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
  18. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
  19. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
  20. package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
  21. package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
  22. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
  23. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
  24. package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
  25. package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
  26. package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
  27. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
  28. package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
  29. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
  30. package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
  31. package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
  32. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
  33. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
  34. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
  35. package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
  36. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
  37. package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
  38. package/cpp/android_jni/archive_limits_jni.cpp +83 -0
  39. package/cpp/android_jni/install_record_jni.cpp +89 -0
  40. package/cpp/android_jni/jni_log.h +31 -0
  41. package/cpp/android_jni/jni_natives.h +40 -0
  42. package/cpp/android_jni/jni_registration.cpp +133 -0
  43. package/cpp/android_jni/jni_util.h +160 -0
  44. package/cpp/android_jni/update_core_android.cpp +450 -0
  45. package/cpp/android_jni/update_flow_jni.cpp +133 -0
  46. package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
  47. package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
  48. package/cpp/patch_core/archive_limits.cpp +194 -0
  49. package/cpp/patch_core/archive_limits.h +99 -10
  50. package/cpp/patch_core/archive_patch_core.cpp +96 -12
  51. package/cpp/patch_core/archive_patch_core.h +49 -0
  52. package/cpp/patch_core/digest.h +5 -4
  53. package/cpp/patch_core/error_codes.h +5 -3
  54. package/cpp/patch_core/install_record.cpp +430 -0
  55. package/cpp/patch_core/install_record.h +163 -9
  56. package/cpp/patch_core/patch_core.cpp +62 -16
  57. package/cpp/patch_core/patch_core.h +27 -7
  58. package/cpp/patch_core/patch_manifest.cpp +201 -0
  59. package/cpp/patch_core/patch_manifest.h +74 -0
  60. package/cpp/patch_core/state_ops.h +3 -3
  61. package/cpp/update_flow_core/flow_json.cpp +82 -8
  62. package/cpp/update_flow_core/flow_json.h +21 -0
  63. package/cpp/update_flow_core/native_config.cpp +456 -0
  64. package/cpp/update_flow_core/native_config.h +84 -0
  65. package/cpp/update_flow_core/native_round.cpp +326 -0
  66. package/cpp/update_flow_core/native_round.h +90 -0
  67. package/cpp/update_flow_core/update_flow_core.h +2 -2
  68. package/harmony/hvigor-plugin.ts +11 -19
  69. package/harmony/pushy/src/main/cpp/CMakeLists.txt +17 -26
  70. package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
  71. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
  72. package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
  73. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
  74. package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
  75. package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
  76. package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
  77. package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
  78. package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
  79. package/harmony/pushy/src/main/ets/Logger.ts +20 -34
  80. package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
  81. package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
  82. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
  83. package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
  84. package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
  85. package/harmony/pushy/src/main/ets/Texts.ts +0 -4
  86. package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
  87. package/harmony/pushy.har +0 -0
  88. package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
  89. package/ios/RCTPushy/RCTPushy.h +12 -0
  90. package/ios/RCTPushy/RCTPushy.mm +87 -2634
  91. package/ios/RCTPushy/RCTPushyCommon.h +34 -0
  92. package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
  93. package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
  94. package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
  95. package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
  96. package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
  97. package/ios/RCTPushy/RCTPushyInternal.h +144 -0
  98. package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
  99. package/ios/RCTPushy/RCTPushyState.mm +314 -0
  100. package/package.json +25 -23
  101. package/react-native-update.podspec +73 -82
  102. package/src/client.ts +361 -451
  103. package/src/context.ts +1 -1
  104. package/src/core.ts +35 -8
  105. package/src/endpoint.ts +0 -2
  106. package/src/error.ts +13 -32
  107. package/src/i18n.ts +18 -83
  108. package/src/locales/en.ts +0 -4
  109. package/src/locales/zh.ts +0 -4
  110. package/src/provider.tsx +60 -53
  111. package/src/telemetry.ts +0 -13
  112. package/src/type.ts +0 -15
  113. package/src/updateFlowCore.ts +116 -10
  114. package/src/utils.ts +49 -42
  115. package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
  116. package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
  117. package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
  118. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
  119. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
  120. package/cpp/patch_core/jni_natives.h +0 -27
  121. package/cpp/patch_core/jni_registration.cpp +0 -103
  122. package/cpp/patch_core/jni_util.h +0 -60
  123. package/cpp/patch_core/patch_core_android.cpp +0 -97
  124. package/cpp/patch_core/update_core_android.cpp +0 -485
  125. 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.TASK_TYPE_CLEANUP; // 任务类型
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
- // (harmony/pushy/src/test/check-constant-parity.js asserts the values match).
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 the hypium tests can pass a mock.
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 { ERROR_SWITCH_VERSION_FAILED, createPushyError } from './ErrorCodes';
2
+ import {
3
+ ERROR_DELTA_FAILED,
4
+ ERROR_SWITCH_VERSION_FAILED,
5
+ createPushyError,
6
+ getErrorMessage,
7
+ } from './ErrorCodes';
5
8
 
6
- // ArkTS mirror of cpp/patch_core/install_record.h — keep in sync by hand
7
- // (harmony/pushy/src/test/check-constant-parity.js asserts the values match).
8
- // 两阶段安装的完成记录:作为安装最后一步写进 staging 目录,随后 staging
9
- // 原子 rename 成版本目录;激活前重新校验 bundle 摘要。空文件 = 旧版 SDK
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
- export function isReservedEntryName(name: string): boolean {
31
- return name.startsWith(RESERVED_ENTRY_PREFIX);
32
- }
33
-
34
- export function buildInstallRecord(
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
- const parsed = JSON.parse(text) as InstallRecordData | null;
84
- return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
85
- ? parsed
86
- : null;
31
+ await NativePatchCore.sealInstallRecord(
32
+ stagingDir,
33
+ versionHash,
34
+ HARMONY_BUNDLE_FILE_NAME,
35
+ artifactSha256,
36
+ );
87
37
  } catch (e) {
88
- return null;
38
+ throw createPushyError(ERROR_DELTA_FAILED, getErrorMessage(e));
89
39
  }
90
40
  }
91
41
 
92
- /**
93
- * 读取记录:文件缺失/畸形返回 null;空文件(历史标记)返回 {}。
94
- */
95
- export function readInstallRecord(versionDir: string): InstallRecordData | null {
96
- const path = `${versionDir}/${INSTALL_RECORD_FILE_NAME}`;
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
- const stat = fileIo.statSync(path);
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
- return null;
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 isRecordForVersion(readInstallRecord(versionDir), versionHash);
56
+ return NativePatchCore.isInstallComplete(
57
+ versionDir,
58
+ versionHash,
59
+ HARMONY_BUNDLE_FILE_NAME,
60
+ );
133
61
  }
134
62
 
135
- /**
136
- * 激活前的记录校验(不含摘要):返回需要复核的 bundle 摘要,历史空记录或
137
- * 无摘要记录返回空串;记录缺失/不符抛 SWITCH_VERSION_FAILED。纯函数。
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
- * 激活前校验:记录带摘要时在 native 工作线程重新计算 bundle 摘要(整包
166
- * 几十 MB,不能在 UI 线程同步算);不通过则抛错说明原因。
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
- bundlePath: string,
76
+ grandfathered: boolean,
172
77
  ): Promise<void> {
173
- const expected = expectedBundleSha256ForActivation(
174
- readInstallRecord(versionDir),
175
- versionHash,
176
- );
177
- if (!expected) {
178
- return;
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
- private tagOf(args: string[]): string {
28
- return args.length > 0 ? args[0] : '';
29
- }
30
-
31
- private messageOf(args: string[]): string {
32
- return args.length > 1 ? args.slice(1).join(' ') : '';
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
- export interface FileSourcePatchRequest {
40
- copyFroms: string[];
41
- copyTos: string[];
42
- deletes: string[];
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
- mergeSourceSubdir?: string;
49
- enableMerge?: boolean;
50
- /** __diff.json 中该 bundle patch 条目的 hbcTransform 元数据(原始 JSON) */
51
- bundleHbcTransformMeta?: string;
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
- buildArchivePlan(
68
- patchType: number,
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
- /** sha256(小写 hex)。同步:输入是已在内存的 rawfile bundle,哈希毫秒级 */
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
- // 更新流程决策层(cpp/update_flow_core,NATIVE_CHECKUPDATE_DESIGN §10):
100
- // JSON 字符串进出,与决策层自身的边界一致。返回 undefined = 输入未通过
101
- // 解析,编排器跳过本轮检测。
102
- buildRequestBody(inputJson: string): string | undefined;
103
- orderEndpointCandidates(
104
- endpointsJson: string,
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
- identityJson: string,
110
- afterDownload: string,
169
+ configJson: string,
170
+ envJson: string,
171
+ requestBody: string,
172
+ responseAtSeconds: number,
111
173
  ): string | undefined;
112
- // 响应 schema 门槛(update_flow_core::IsValidResponse):200 但只带
113
- // `{"error":...}` 的节点算失败,不得阻止继续 fallback。
114
- isValidResponse(responseText: string): boolean;
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 { POLICY_NEXT_LAUNCH, revealText } from './Texts';
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
- interface PersistedPushyConfiguration {
20
- appKey: string;
21
- endpoints: string[];
22
- queryUrls: string[];
23
- afterDownload: string;
24
- disabled: boolean;
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
- if (!options || typeof options !== 'object' || Array.isArray(options)) {
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
  }