react-native-update 10.55.1 → 10.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README-CN.md +35 -0
  2. package/README.md +37 -0
  3. package/android/build.gradle +53 -27
  4. package/android/jni/Application.mk +6 -1
  5. package/android/jni/hpatch.c +59 -4
  6. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  7. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  8. package/android/lib/x86/librnupdate.so +0 -0
  9. package/android/lib/x86_64/librnupdate.so +0 -0
  10. package/android/proguard.pro +70 -14
  11. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
  12. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
  13. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
  14. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
  16. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
  17. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
  18. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
  19. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
  20. package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
  21. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
  22. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
  23. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
  24. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
  25. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
  26. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
  27. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
  28. package/cpp/patch_core/archive_limits.h +6 -0
  29. package/cpp/patch_core/jni_util.h +6 -2
  30. package/cpp/patch_core/patch_core.cpp +237 -84
  31. package/cpp/patch_core/patch_core.h +11 -0
  32. package/cpp/patch_core/patch_core_android.cpp +49 -33
  33. package/cpp/patch_core/update_core_android.cpp +134 -103
  34. package/cpp/update_flow_core/flow_json.cpp +150 -17
  35. package/cpp/update_flow_core/flow_json.h +13 -1
  36. package/cpp/update_flow_core/update_flow_core.cpp +78 -8
  37. package/cpp/update_flow_core/update_flow_core.h +9 -0
  38. package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
  39. package/harmony/hvigor-plugin.ts +168 -21
  40. package/harmony/pushy/src/README.md +74 -0
  41. package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
  42. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
  43. package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
  44. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
  45. package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
  46. package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
  47. package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
  48. package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
  49. package/harmony/pushy/src/main/ets/Logger.ts +42 -16
  50. package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
  51. package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
  52. package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
  53. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
  54. package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
  55. package/harmony/pushy/src/main/module.json5 +5 -1
  56. package/harmony/pushy.har +0 -0
  57. package/ios/PrivacyInfo.xcprivacy +79 -0
  58. package/ios/RCTPushy/RCTPushy.mm +399 -170
  59. package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
  60. package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
  61. package/package.json +14 -24
  62. package/react-native-update.podspec +43 -3
  63. package/src/client.ts +236 -82
  64. package/src/context.ts +40 -27
  65. package/src/core.ts +41 -16
  66. package/src/endpoint.ts +2 -1
  67. package/src/error.ts +49 -11
  68. package/src/index.ts +14 -0
  69. package/src/locales/en.ts +1 -23
  70. package/src/locales/zh.ts +1 -22
  71. package/src/provider.tsx +159 -111
  72. package/src/type.ts +0 -2
  73. package/src/updateFlowCore.ts +10 -2
  74. package/src/utils.ts +61 -4
  75. package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
  76. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
@@ -13,24 +13,53 @@ import {
13
13
  markJsCheckCompleted,
14
14
  KEY_RESP_CACHE,
15
15
  } from './NativeCheckOrchestrator';
16
+ import {
17
+ ERROR_FILE_OPERATION_FAILED,
18
+ ERROR_INVALID_HASH_INFO,
19
+ ERROR_INVALID_OPTIONS,
20
+ ERROR_MARK_SUCCESS_FAILED,
21
+ ERROR_RESET_FAILED,
22
+ ERROR_RESTART_FAILED,
23
+ ERROR_SWITCH_VERSION_FAILED,
24
+ ERROR_UNSUPPORTED_PLATFORM,
25
+ createUpdateError,
26
+ getErrorMessage,
27
+ toUpdateError,
28
+ } from './ErrorCodes';
29
+
30
+ export { getErrorMessage } from './ErrorCodes';
16
31
 
17
32
  const TAG = 'PushyTurboModule';
18
33
 
19
- export function getErrorMessage(error: any): string {
20
- if (error && typeof error === 'object' && 'message' in error) {
21
- return String(error.message);
22
- }
23
- return String(error);
34
+ interface RestartWant {
35
+ bundleName: string;
36
+ abilityName?: string;
37
+ }
38
+
39
+ // applicationContext.restartApp 是 API 12 才有的可选能力,按结构探测。
40
+ interface RestartableApplicationContext {
41
+ restartApp?: (want: RestartWant) => void;
42
+ }
43
+
44
+ interface ReloadEventEmitter {
45
+ emit(event: string, payload: Object): void;
46
+ }
47
+
48
+ // RNOH 的 devToolsController 不在公开的 UITurboModuleContext 类型里。
49
+ interface DevToolsControllerHolder {
50
+ devToolsController?: { eventEmitter: ReloadEventEmitter };
24
51
  }
25
52
 
26
53
  export function validateHashInfo(info: string): void {
54
+ let valid = false;
27
55
  try {
28
- const parsed = JSON.parse(info);
29
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
30
- throw Error('invalid json string');
31
- }
32
- } catch {
33
- throw Error('invalid json string');
56
+ const parsed = JSON.parse(info) as Object | null;
57
+ valid = !!parsed && typeof parsed === 'object' && !Array.isArray(parsed);
58
+ } catch (e) {
59
+ valid = false;
60
+ }
61
+ if (!valid) {
62
+ throw createUpdateError(ERROR_INVALID_HASH_INFO, 'invalid json string');
34
63
  }
35
64
  }
36
65
 
@@ -42,36 +71,61 @@ export class PushyTurboModule extends UITurboModule {
42
71
 
43
72
  constructor(protected ctx: UITurboModuleContext) {
44
73
  super(ctx);
74
+ if (ctx.isDebugModeEnabled) {
75
+ // 宿主处于 RN 调试模式:打开 debug 级日志,方便接入排查。
76
+ logger.setDebug(true);
77
+ }
45
78
  logger.debug(TAG, ',PushyTurboModule constructor');
46
79
  this.mUiCtx = ctx.uiAbilityContext;
47
- this.context = UpdateContext.getInstance(this.mUiCtx);
80
+ this.context = UpdateContext.getInstance(
81
+ this.mUiCtx,
82
+ ctx.isDebugModeEnabled,
83
+ );
48
84
  EventHub.getInstance().setRNInstance(ctx.rnInstance);
49
85
  }
50
86
 
87
+ // RNOH 销毁 TurboModule 时的钩子:释放对已销毁 RNInstance 的引用(只在
88
+ // EventHub 仍指向它时,见 EventHub.clearRNInstance)。
89
+ __onDestroy__(): void {
90
+ EventHub.getInstance().clearRNInstance(this.ctx.rnInstance);
91
+ }
92
+
51
93
  private getBundleFlags(): bundleManager.BundleFlag {
52
94
  return bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
53
95
  }
54
96
 
55
97
  private requireHash(hash: string, methodName: string): string {
56
98
  if (!hash) {
57
- throw Error(`${methodName}: empty hash`);
99
+ throw createUpdateError(
100
+ ERROR_INVALID_OPTIONS,
101
+ `${methodName}: empty hash`,
102
+ );
58
103
  }
59
104
  return hash;
60
105
  }
61
106
 
107
+ private softReload(): void {
108
+ const holder = this.ctx as unknown as DevToolsControllerHolder;
109
+ const devToolsController = holder.devToolsController;
110
+ if (devToolsController) {
111
+ devToolsController.eventEmitter.emit('RELOAD', { reason: 'HotReload2' });
112
+ }
113
+ }
114
+
62
115
  private async restartAbility(): Promise<void> {
63
116
  const bundleInfo = await bundleManager.getBundleInfoForSelf(
64
117
  this.getBundleFlags(),
65
118
  );
66
- const want = {
119
+ const want: RestartWant = {
67
120
  bundleName: bundleInfo.name,
68
121
  abilityName: this.mUiCtx.abilityInfo?.name,
69
122
  };
70
123
  try {
71
- const applicationContext = this.mUiCtx.getApplicationContext();
72
- if (applicationContext && typeof (applicationContext as any).restartApp === 'function') {
124
+ const applicationContext =
125
+ this.mUiCtx.getApplicationContext() as unknown as RestartableApplicationContext;
126
+ if (applicationContext && typeof applicationContext.restartApp === 'function') {
73
127
  logger.debug(TAG, 'restartAbility via applicationContext.restartApp');
74
- (applicationContext as any).restartApp(want);
128
+ applicationContext.restartApp(want);
75
129
  return;
76
130
  }
77
131
  } catch (e) {
@@ -93,10 +147,7 @@ export class PushyTurboModule extends UITurboModule {
93
147
  private async reloadBridge(): Promise<void> {
94
148
  if (this.ctx.isDebugModeEnabled) {
95
149
  logger.debug(TAG, 'reloadBridge via devToolsController RELOAD (debug mode)');
96
- const devToolsController = (this.ctx as Record<string, any>).devToolsController;
97
- if (devToolsController) {
98
- devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
99
- }
150
+ this.softReload();
100
151
  } else {
101
152
  logger.debug(TAG, 'reloadBridge via restartAbility (release mode)');
102
153
  // If the process truly restarts, this timer dies with it. It only fires
@@ -107,10 +158,7 @@ export class PushyTurboModule extends UITurboModule {
107
158
  // path, only in the catch branch where the soft reload runs immediately.
108
159
  const fallbackTimer = setTimeout(() => {
109
160
  logger.warn(TAG, 'restartAbility did not restart the app within 1.5s, triggering soft reload fallback');
110
- const devToolsController = (this.ctx as Record<string, any>).devToolsController;
111
- if (devToolsController) {
112
- devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
113
- }
161
+ this.softReload();
114
162
  }, 1500);
115
163
 
116
164
  try {
@@ -118,10 +166,7 @@ export class PushyTurboModule extends UITurboModule {
118
166
  } catch (error) {
119
167
  clearTimeout(fallbackTimer);
120
168
  logger.error(TAG, `restartAbility failed: ${getErrorMessage(error)}, triggering soft reload fallback`);
121
- const devToolsController = (this.ctx as Record<string, any>).devToolsController;
122
- if (devToolsController) {
123
- devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
124
- }
169
+ this.softReload();
125
170
  }
126
171
  }
127
172
  }
@@ -175,22 +220,41 @@ export class PushyTurboModule extends UITurboModule {
175
220
  return result;
176
221
  }
177
222
 
178
- setLocalHashInfo(hash: string, info: string): boolean {
223
+ // 以下方法在 JS spec(src/NativePushy.ts)里都返回 Promise,C++ 方法表
224
+ // (PushyTurboModule.cpp)相应用 PUSHY_ASYNC_METHOD 注册:同步注册会把
225
+ // ArkTS 的 Promise 变成空对象、把 reject 丢成 ArkTS 侧的 unhandled rejection。
226
+ // 每个拒绝都带稳定错误码(ErrorCodes.ts):`code` 属性 + `[CODE] ` 消息前缀。
227
+
228
+ async setLocalHashInfo(hash: string, info: string): Promise<void> {
179
229
  logger.debug(TAG, ',call setLocalHashInfo');
230
+ this.requireHash(hash, 'setLocalHashInfo');
180
231
  validateHashInfo(info);
181
- this.context.setKv(`hash_${hash}`, info);
182
- return true;
232
+ try {
233
+ await this.context.setKv(`hash_${hash}`, info);
234
+ } catch (error) {
235
+ throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
236
+ }
183
237
  }
184
238
 
185
- getLocalHashInfo(hash: string): string {
239
+ // 没有记录时返回 ''(iOS 同样 resolve nil):JS 侧把空值当 {}。只有存在
240
+ // 但不是 JSON 对象的记录才是 INVALID_HASH_INFO。
241
+ async getLocalHashInfo(hash: string): Promise<string> {
242
+ this.requireHash(hash, 'getLocalHashInfo');
186
243
  const value = this.context.getKv(`hash_${hash}`);
244
+ if (!value) {
245
+ return '';
246
+ }
187
247
  validateHashInfo(value);
188
248
  return value;
189
249
  }
190
250
 
191
251
  async setUuid(uuid: string): Promise<void> {
192
252
  logger.debug(TAG, ',call setUuid');
193
- this.context.setKv('uuid', uuid);
253
+ try {
254
+ await this.context.setKv('uuid', uuid);
255
+ } catch (error) {
256
+ throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
257
+ }
194
258
  }
195
259
 
196
260
  // Provisioning for the native cold-start update check
@@ -200,15 +264,29 @@ export class PushyTurboModule extends UITurboModule {
200
264
  // native check forever with no signal.
201
265
  async syncNativeConfig(config: string): Promise<void> {
202
266
  logger.debug(TAG, ',call syncNativeConfig');
203
- JSON.parse(config);
204
- this.context.setKv(KEY_CONFIG, config);
267
+ try {
268
+ JSON.parse(config);
269
+ } catch (e) {
270
+ throw createUpdateError(
271
+ ERROR_INVALID_OPTIONS,
272
+ `syncNativeConfig: config is not valid JSON: ${getErrorMessage(e)}`,
273
+ );
274
+ }
275
+ try {
276
+ await this.context.setKv(KEY_CONFIG, config);
277
+ } catch (error) {
278
+ throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
279
+ }
205
280
  }
206
281
 
207
282
  // JS 本进程已拿到有效检查响应:延迟的原生轮次据此跳过重复请求(§10.3)。
208
283
  async markJsCheckCompleted(config: string): Promise<void> {
209
284
  logger.debug(TAG, ',call markJsCheckCompleted');
210
285
  if (typeof config !== 'string' || config.length === 0) {
211
- throw Error('config must be a non-empty string');
286
+ throw createUpdateError(
287
+ ERROR_INVALID_OPTIONS,
288
+ 'config must be a non-empty string',
289
+ );
212
290
  }
213
291
  markJsCheckCompleted(config);
214
292
  }
@@ -224,12 +302,19 @@ export class PushyTurboModule extends UITurboModule {
224
302
  logger.debug(TAG, ',call reloadUpdate');
225
303
  const hash = this.requireHash(options.hash, 'reloadUpdate');
226
304
 
305
+ // 切换必须真正落盘(switchVersion 内 await flush)后才重启:重启会立刻
306
+ // 杀进程,未落盘的切换就是"重启回旧 bundle"。
307
+ try {
308
+ await this.context.switchVersion(hash);
309
+ } catch (error) {
310
+ logger.error(TAG, `reloadUpdate switch failed: ${getErrorMessage(error)}`);
311
+ throw toUpdateError(error, ERROR_SWITCH_VERSION_FAILED);
312
+ }
227
313
  try {
228
- this.context.switchVersion(hash);
229
314
  await this.reloadBridge();
230
315
  } catch (error) {
231
- logger.error(TAG, `reloadUpdate failed: ${getErrorMessage(error)}`);
232
- throw Error(`switchVersion failed ${getErrorMessage(error)}`);
316
+ logger.error(TAG, `reloadUpdate restart failed: ${getErrorMessage(error)}`);
317
+ throw toUpdateError(error, ERROR_RESTART_FAILED);
233
318
  }
234
319
  }
235
320
 
@@ -239,38 +324,36 @@ export class PushyTurboModule extends UITurboModule {
239
324
  await this.reloadBridge();
240
325
  } catch (error) {
241
326
  logger.error(TAG, `restartApp failed: ${getErrorMessage(error)}`);
242
- throw Error(`restartApp failed ${getErrorMessage(error)}`);
327
+ throw toUpdateError(error, ERROR_RESTART_FAILED);
243
328
  }
244
329
  }
245
330
 
246
- async setNeedUpdate(options: { hash: string }): Promise<boolean> {
331
+ async setNeedUpdate(options: { hash: string }): Promise<void> {
247
332
  logger.debug(TAG, ',call setNeedUpdate');
248
333
  const hash = this.requireHash(options.hash, 'setNeedUpdate');
249
334
 
250
335
  try {
251
- this.context.switchVersion(hash);
252
- return true;
336
+ await this.context.switchVersion(hash);
253
337
  } catch (error) {
254
338
  logger.error(TAG, `setNeedUpdate failed: ${getErrorMessage(error)}`);
255
- throw Error(`switchVersionLater failed: ${getErrorMessage(error)}`);
339
+ throw toUpdateError(error, ERROR_SWITCH_VERSION_FAILED);
256
340
  }
257
341
  }
258
342
 
259
- async markSuccess(): Promise<boolean> {
343
+ async markSuccess(): Promise<void> {
260
344
  logger.debug(TAG, ',call markSuccess');
261
345
  try {
262
- this.context.markSuccess();
263
- return true;
346
+ await this.context.markSuccess();
264
347
  } catch (error) {
265
348
  logger.error(TAG, `markSuccess failed: ${getErrorMessage(error)}`);
266
- throw error;
349
+ throw toUpdateError(error, ERROR_MARK_SUCCESS_FAILED);
267
350
  }
268
351
  }
269
352
 
270
353
  async getBundleHash(): Promise<string> {
271
354
  logger.debug(TAG, ',call getBundleHash');
272
355
  try {
273
- return this.context.getBundleHash();
356
+ return await this.context.getBundleHash();
274
357
  } catch (error) {
275
358
  // 空串 = "未知",服务端回退 buildTime 启发式;该方法永不 reject。
276
359
  logger.error(TAG, `getBundleHash failed: ${getErrorMessage(error)}`);
@@ -281,10 +364,10 @@ export class PushyTurboModule extends UITurboModule {
281
364
  async resetToPackagedBundle(): Promise<void> {
282
365
  logger.debug(TAG, ',call resetToPackagedBundle');
283
366
  try {
284
- this.context.resetToPackagedBundle();
367
+ await this.context.resetToPackagedBundle();
285
368
  } catch (error) {
286
369
  logger.error(TAG, `resetToPackagedBundle failed: ${getErrorMessage(error)}`);
287
- throw Error(`resetToPackagedBundle failed: ${getErrorMessage(error)}`);
370
+ throw toUpdateError(error, ERROR_RESET_FAILED);
288
371
  }
289
372
  }
290
373
 
@@ -326,7 +409,10 @@ export class PushyTurboModule extends UITurboModule {
326
409
  hash: string;
327
410
  }): Promise<void> {
328
411
  logger.debug(TAG, ',call downloadAndInstallApk');
329
- throw Error('downloadAndInstallApk is only supported on Android');
412
+ throw createUpdateError(
413
+ ERROR_UNSUPPORTED_PLATFORM,
414
+ 'downloadAndInstallApk is only supported on Android',
415
+ );
330
416
  }
331
417
 
332
418
  addListener(_eventName: string): void {