react-native-update 10.58.0 → 10.59.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 CHANGED
@@ -172,3 +172,10 @@ Hermes 字节码对通用二进制 diff 极不友好,我们在两个环节同
172
172
  本组件由[React Native 中文网](https://reactnative.cn/)独家发布,如有定制需求可以[联系我们](https://reactnative.cn/about.html#content)。
173
173
 
174
174
  关于此插件发现任何问题,可以前往[Issues](https://github.com/reactnativecn/react-native-update/issues)发帖提问。
175
+
176
+ ## 原生宿主接口
177
+
178
+ 原生宿主入口统一为 `prepareBundle`:Android 使用 `PushyRuntime`,
179
+ Apple 平台使用 `RCTPushy`,Harmony 使用已有的 `PushyFileJSBundleProvider`。
180
+ 请在正常启动 bundle 解析后调用。改名不改变 JS 桥接契约,但原生接入代码需要迁移并重新构建。
181
+ 详见[原生接口迁移说明](NATIVE_HOST_API_MIGRATION.md)。
package/README.md CHANGED
@@ -153,3 +153,11 @@ Since 10.52.1 every cold start runs one background update check that **does not
153
153
  | **Technical Support** | ✅ Paid dedicated support | ⚠️ Community support | ❌ **Discontinued** |
154
154
  | **Server Deployment** | ✅ Hosted service or paid private deployment | ✅ Hosted by Expo (EAS Update) | ❌ **Discontinued** |
155
155
  | **Bandwidth Usage** | ⭐⭐⭐⭐⭐ Very low (incremental) | ⭐⭐⭐ Higher (full bundle) | ❌ **Discontinued** |
156
+
157
+ ## Native host APIs
158
+
159
+ Bridge-free hosts use `PushyRuntime.prepareBundle` (Android),
160
+ `RCTPushy.prepareBundle` (Swift), or `PushyFileJSBundleProvider.prepareBundle`
161
+ (HarmonyOS), after normal launch bundle resolution. See the
162
+ [native host API migration guide](NATIVE_HOST_API_MIGRATION.md) for the
163
+ source-breaking rename, unchanged JS bridge contract, and native rebuild steps.
@@ -4,7 +4,7 @@ package cn.reactnative.modules.update;
4
4
  * Snapshot of a native check-and-update round. A downloaded result does not
5
5
  * mean that the running React Native instance has been reloaded.
6
6
  */
7
- public final class NativeUpdateResult {
7
+ public final class BundlePreparationResult {
8
8
  public static final String SKIPPED = "skipped";
9
9
  public static final String NO_UPDATE = "noUpdate";
10
10
  public static final String DOWNLOADED = "downloaded";
@@ -16,19 +16,19 @@ public final class NativeUpdateResult {
16
16
  private final String hash;
17
17
  private final boolean activated;
18
18
 
19
- private NativeUpdateResult(String status, String reason, String hash, boolean activated) {
19
+ private BundlePreparationResult(String status, String reason, String hash, boolean activated) {
20
20
  this.status = status;
21
21
  this.reason = reason;
22
22
  this.hash = hash;
23
23
  this.activated = activated;
24
24
  }
25
25
 
26
- static NativeUpdateResult of(String status, String reason) {
27
- return new NativeUpdateResult(status, reason, "", false);
26
+ static BundlePreparationResult of(String status, String reason) {
27
+ return new BundlePreparationResult(status, reason, "", false);
28
28
  }
29
29
 
30
- static NativeUpdateResult downloaded(String hash, boolean activated) {
31
- return new NativeUpdateResult(DOWNLOADED, "", hash, activated);
30
+ static BundlePreparationResult downloaded(String hash, boolean activated) {
31
+ return new BundlePreparationResult(DOWNLOADED, "", hash, activated);
32
32
  }
33
33
 
34
34
  public String getStatus() {
@@ -73,46 +73,46 @@ final class NativeCheckOrchestrator {
73
73
  private static volatile String sJsCompletedConfig;
74
74
  // Published after the launch rollback snapshot, before host calls are accepted.
75
75
  private static volatile boolean nativeReady;
76
- private static volatile NativeUpdateResult roundResult =
77
- NativeUpdateResult.of(NativeUpdateResult.FAILED, "check_failed");
76
+ private static volatile BundlePreparationResult roundResult =
77
+ BundlePreparationResult.of(BundlePreparationResult.FAILED, "check_failed");
78
78
  private static volatile long roundGeneration = -1;
79
79
  private static volatile long roundConfigGeneration = -1;
80
80
  private static volatile String roundConfigJson;
81
81
 
82
82
  /** Blocking only on the host API's worker; never call on the UI thread. */
83
- static NativeUpdateResult checkAndUpdate(UpdateContext context) throws InterruptedException {
83
+ static BundlePreparationResult prepareBundle(UpdateContext context) throws InterruptedException {
84
84
  if (UpdateContext.DEBUG) {
85
- return NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "debug");
85
+ return BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "debug");
86
86
  }
87
87
  if (!nativeReady || sContext != context || !context.getIsUsingBundleUrl()) {
88
- return NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "not_initialized");
88
+ return BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "not_initialized");
89
89
  }
90
90
  String configJson = context.getKv(KEY_CONFIG);
91
91
  if (configJson == null || configJson.isEmpty()) {
92
- return NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "not_configured");
92
+ return BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "not_configured");
93
93
  }
94
94
  try {
95
95
  JSONObject config = new JSONObject(configJson);
96
96
  if (config.optBoolean("disabled", false)) {
97
- return NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "disabled");
97
+ return BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "disabled");
98
98
  }
99
99
  if (config.optString("appKey", "").isEmpty()) {
100
- return NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_config");
100
+ return BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_config");
101
101
  }
102
102
  } catch (JSONException e) {
103
- return NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_config");
103
+ return BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_config");
104
104
  }
105
105
  startRound(0);
106
106
  if (!roundStarted.get()) {
107
- return NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "config_changed");
107
+ return BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "config_changed");
108
108
  }
109
109
  roundDone.await();
110
110
  if (roundConfigGeneration != UpdateContext.getNativeConfigGeneration()
111
111
  || !configJson.equals(roundConfigJson) || !configJson.equals(context.getKv(KEY_CONFIG))) {
112
- return NativeUpdateResult.of(NativeUpdateResult.CANCELLED, "config_changed");
112
+ return BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "config_changed");
113
113
  }
114
114
  if (roundGeneration != UpdateContext.getResetGeneration()) {
115
- return NativeUpdateResult.of(NativeUpdateResult.CANCELLED, "reset");
115
+ return BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "reset");
116
116
  }
117
117
  return roundResult;
118
118
  }
@@ -226,7 +226,7 @@ final class NativeCheckOrchestrator {
226
226
  runOnce(sContext, sLaunchRolledBackVersion, deadlineNanos);
227
227
  } catch (Throwable e) {
228
228
  Log.w(UpdateContext.TAG, "native check failed: " + e);
229
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "internal_error");
229
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "internal_error");
230
230
  } finally {
231
231
  roundCompleted = true;
232
232
  roundDone.countDown();
@@ -303,27 +303,27 @@ final class NativeCheckOrchestrator {
303
303
  final long resetGeneration = UpdateContext.getResetGeneration();
304
304
  roundGeneration = resetGeneration;
305
305
  roundConfigGeneration = UpdateContext.getNativeConfigGeneration();
306
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "check_failed");
306
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "check_failed");
307
307
  String configJson = context.getKv(KEY_CONFIG);
308
308
  roundConfigJson = configJson;
309
309
  if (configJson == null || configJson.isEmpty()) {
310
- roundResult = NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "not_configured");
310
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "not_configured");
311
311
  return;
312
312
  }
313
313
  JSONObject config;
314
314
  try {
315
315
  config = new JSONObject(configJson);
316
316
  } catch (JSONException e) {
317
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_config");
317
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_config");
318
318
  return;
319
319
  }
320
320
  if (config.optBoolean("disabled", false)) {
321
- roundResult = NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "disabled");
321
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "disabled");
322
322
  return;
323
323
  }
324
324
  String appKey = config.optString("appKey", "");
325
325
  if (appKey.isEmpty()) {
326
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_config");
326
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_config");
327
327
  return;
328
328
  }
329
329
  // Keep the existing interrupted-round breadcrumb and reset generation.
@@ -401,7 +401,7 @@ final class NativeCheckOrchestrator {
401
401
 
402
402
  String body = NativeUpdateFlow.buildCheckRequestBody(input.toString());
403
403
  if (body == null) {
404
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_request");
404
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_request");
405
405
  return;
406
406
  }
407
407
 
@@ -418,7 +418,7 @@ final class NativeCheckOrchestrator {
418
418
  String decisionJson = NativeUpdateFlow.handleCheckResponse(
419
419
  responseText, identity.toString(), config.optString("afterDownload", ""));
420
420
  if (decisionJson == null) {
421
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_response");
421
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_response");
422
422
  return;
423
423
  }
424
424
  JSONObject decision = new JSONObject(decisionJson);
@@ -427,15 +427,15 @@ final class NativeCheckOrchestrator {
427
427
  resetGeneration, null, null, false,
428
428
  buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
429
429
  roundResult = committed
430
- ? NativeUpdateResult.of(NativeUpdateResult.NO_UPDATE, decision.optString("reason"))
431
- : NativeUpdateResult.of(NativeUpdateResult.CANCELLED, "reset");
430
+ ? BundlePreparationResult.of(BundlePreparationResult.NO_UPDATE, decision.optString("reason"))
431
+ : BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "reset");
432
432
  Log.i(UpdateContext.TAG,
433
433
  "native check: nothing to do (" + decision.optString("reason") + ")");
434
434
  return;
435
435
  }
436
436
  String hash = decision.optString("hash", "");
437
437
  if (!UpdateFileUtils.isSafePathComponent(hash)) {
438
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "invalid_response");
438
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_response");
439
439
  return;
440
440
  }
441
441
 
@@ -451,8 +451,8 @@ final class NativeCheckOrchestrator {
451
451
  boolean committed = context.commitNativeCheckResult(
452
452
  resetGeneration, null, null, false,
453
453
  buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
454
- roundResult = NativeUpdateResult.of(
455
- committed ? NativeUpdateResult.FAILED : NativeUpdateResult.CANCELLED,
454
+ roundResult = BundlePreparationResult.of(
455
+ committed ? BundlePreparationResult.FAILED : BundlePreparationResult.CANCELLED,
456
456
  committed ? "download_failed" : "reset");
457
457
  return;
458
458
  }
@@ -499,7 +499,7 @@ final class NativeCheckOrchestrator {
499
499
  buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
500
500
  } catch (Exception e) {
501
501
  Log.w(UpdateContext.TAG, "native check: commit failed: " + e);
502
- roundResult = NativeUpdateResult.of(NativeUpdateResult.FAILED, "commit_failed");
502
+ roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "commit_failed");
503
503
  return;
504
504
  }
505
505
  if (!committed) {
@@ -517,8 +517,8 @@ final class NativeCheckOrchestrator {
517
517
  "native check: downloaded " + hash + ", activation left to JS");
518
518
  }
519
519
  roundResult = committed
520
- ? NativeUpdateResult.downloaded(hash, activate)
521
- : NativeUpdateResult.of(NativeUpdateResult.CANCELLED, "reset");
520
+ ? BundlePreparationResult.downloaded(hash, activate)
521
+ : BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "reset");
522
522
  }
523
523
 
524
524
  private static String buildResponseCacheJson(
@@ -9,8 +9,8 @@ import org.json.JSONArray;
9
9
  import org.json.JSONException;
10
10
  import org.json.JSONObject;
11
11
 
12
- /** Internal normalizer for the JSONObject accepted by PushyNativeUpdate.configure. */
13
- final class NativeUpdateConfig {
12
+ /** Internal normalizer for the JSONObject accepted by PushyRuntime.configure. */
13
+ final class PushyConfiguration {
14
14
  private static final Set<String> KEYS = new HashSet<>(Arrays.asList(
15
15
  "appKey", "endpoints", "queryUrls", "afterDownload", "disabled",
16
16
  "packageVersion", "rnu", "rn"));
@@ -24,7 +24,7 @@ final class NativeUpdateConfig {
24
24
  "https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json"
25
25
  };
26
26
 
27
- private NativeUpdateConfig() {}
27
+ private PushyConfiguration() {}
28
28
 
29
29
  static String normalize(String json) throws JSONException {
30
30
  JSONObject options = new JSONObject(json);
@@ -11,10 +11,10 @@ import java.util.concurrent.Executors;
11
11
  import java.util.concurrent.ThreadFactory;
12
12
 
13
13
  /** Bridge-free native configuration and update APIs. */
14
- public final class PushyNativeUpdate {
14
+ public final class PushyRuntime {
15
15
  public interface Callback {
16
16
  /** Always called on the main thread, including skipped and failed checks. */
17
- void onComplete(NativeUpdateResult result);
17
+ void onComplete(BundlePreparationResult result);
18
18
  }
19
19
 
20
20
  // A single waiting worker, not one thread per caller. The actual round is
@@ -46,7 +46,7 @@ public final class PushyNativeUpdate {
46
46
  /**
47
47
  * Validate and persist a complete configuration, even before JS or bundle
48
48
  * resolution. This starts no network work and never resolves a bundle.
49
- * Await the callback before continuing startup/checkAndUpdate. Unless JS
49
+ * Await the callback before continuing startup/prepareBundle. Unless JS
50
50
  * uses nativeConfigSource: 'native', later JS config writes can replace it.
51
51
  */
52
52
  public static void configure(Context context, JSONObject options, final ConfigurationCallback callback) {
@@ -61,7 +61,7 @@ public final class PushyNativeUpdate {
61
61
  public void run() {
62
62
  Exception failure = null;
63
63
  try {
64
- String config = NativeUpdateConfig.normalize(snapshot);
64
+ String config = PushyConfiguration.normalize(snapshot);
65
65
  UpdateContext.getInstance(applicationContext).setNativeConfig(config);
66
66
  } catch (Exception e) {
67
67
  failure = e;
@@ -79,7 +79,7 @@ public final class PushyNativeUpdate {
79
79
  });
80
80
  }
81
81
 
82
- private PushyNativeUpdate() {
82
+ private PushyRuntime() {
83
83
  }
84
84
 
85
85
  /**
@@ -91,7 +91,7 @@ public final class PushyNativeUpdate {
91
91
  * this method: bundle resolution consumes first-load/rollback markers.
92
92
  * Missing persisted configuration and debug builds are reported as skipped.
93
93
  */
94
- public static void checkAndUpdate(Context context, final Callback callback) {
94
+ public static void prepareBundle(Context context, final Callback callback) {
95
95
  if (context == null || callback == null) {
96
96
  throw new IllegalArgumentException("context and callback are required");
97
97
  }
@@ -99,22 +99,22 @@ public final class PushyNativeUpdate {
99
99
  WORKER.execute(new Runnable() {
100
100
  @Override
101
101
  public void run() {
102
- NativeUpdateResult outcome;
102
+ BundlePreparationResult outcome;
103
103
  try {
104
104
  if (BuildConfig.DEBUG) {
105
- outcome = NativeUpdateResult.of(NativeUpdateResult.SKIPPED, "debug");
105
+ outcome = BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "debug");
106
106
  } else {
107
- outcome = NativeCheckOrchestrator.checkAndUpdate(
107
+ outcome = NativeCheckOrchestrator.prepareBundle(
108
108
  UpdateContext.getInstance(applicationContext));
109
109
  }
110
110
  } catch (InterruptedException e) {
111
111
  Thread.currentThread().interrupt();
112
- outcome = NativeUpdateResult.of(NativeUpdateResult.CANCELLED, "interrupted");
112
+ outcome = BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "interrupted");
113
113
  } catch (Exception | LinkageError e) {
114
114
  Log.w("react-native-update", "native host check failed", e);
115
- outcome = NativeUpdateResult.of(NativeUpdateResult.FAILED, "internal_error");
115
+ outcome = BundlePreparationResult.of(BundlePreparationResult.FAILED, "internal_error");
116
116
  }
117
- final NativeUpdateResult result = outcome;
117
+ final BundlePreparationResult result = outcome;
118
118
  new Handler(Looper.getMainLooper()).post(new Runnable() {
119
119
  @Override
120
120
  public void run() {
@@ -766,7 +766,7 @@ public class UpdateContext {
766
766
  editor.putString(NativeCheckOrchestrator.KEY_CONFIG, config);
767
767
  // Persist even an equal value: a previous commit may have updated
768
768
  // SharedPreferences memory but failed to write its file.
769
- persistEditorOrThrow(editor, "configure native update");
769
+ persistEditorOrThrow(editor, "persist configuration");
770
770
  }
771
771
  NativeCheckOrchestrator.onConfigured(this);
772
772
  }
@@ -1,5 +1,5 @@
1
1
  /** A native round snapshot, not the current state of the running RN instance. */
2
- export interface NativeUpdateResult {
2
+ export interface BundlePreparationResult {
3
3
  /** skipped, noUpdate, downloaded, failed, or cancelled. */
4
4
  status: string;
5
5
  reason: string;
@@ -8,20 +8,20 @@ export interface NativeUpdateResult {
8
8
  activated: boolean;
9
9
  }
10
10
 
11
- export function nativeUpdateResult(
11
+ export function bundlePreparationResult(
12
12
  status: string,
13
13
  reason: string = '',
14
14
  hash: string = '',
15
15
  activated: boolean = false,
16
- ): NativeUpdateResult {
16
+ ): BundlePreparationResult {
17
17
  return { status, reason, hash, activated };
18
18
  }
19
19
 
20
20
  /** Internal gate shared by the delayed check and the native host API. */
21
- export class NativeUpdateRound {
22
- private task: Promise<NativeUpdateResult> | undefined;
21
+ export class BundlePreparationRound {
22
+ private task: Promise<BundlePreparationResult> | undefined;
23
23
 
24
- run(operation: () => Promise<NativeUpdateResult>): Promise<NativeUpdateResult> {
24
+ run(operation: () => Promise<BundlePreparationResult>): Promise<BundlePreparationResult> {
25
25
  if (this.task !== undefined) {
26
26
  return this.task;
27
27
  }
@@ -3,8 +3,8 @@ import deviceInfo from '@ohos.deviceInfo';
3
3
  import logger from './Logger';
4
4
  import NativePatchCore from './NativePatchCore';
5
5
  import type { UpdateContext } from './UpdateContext';
6
- import { NativeUpdateRound, nativeUpdateResult } from './NativeUpdateResult';
7
- import type { NativeUpdateResult } from './NativeUpdateResult';
6
+ import { BundlePreparationRound, bundlePreparationResult } from './BundlePreparationResult';
7
+ import type { BundlePreparationResult } from './BundlePreparationResult';
8
8
  import { isSafePathComponent } from './PathUtils';
9
9
  import { monotonicNowMs } from './MonotonicClock';
10
10
  import {
@@ -109,18 +109,18 @@ interface RespCacheEntry {
109
109
 
110
110
  let scheduled = false;
111
111
  // The host and delayed check use one promise, including its settled result.
112
- const hostRound = new NativeUpdateRound();
112
+ const hostRound = new BundlePreparationRound();
113
113
  let scheduledContext: UpdateContext | undefined;
114
114
  let scheduledRollback = '';
115
115
  let roundGeneration = -1;
116
116
  let roundConfigGeneration = -1;
117
117
  let roundConfigJson: string | undefined;
118
- let roundResult = nativeUpdateResult('failed', 'check_failed');
118
+ let roundResult = bundlePreparationResult('failed', 'check_failed');
119
119
 
120
120
  function startNativeRound(
121
121
  context: UpdateContext,
122
122
  launchRolledBackVersion: string,
123
- ): Promise<NativeUpdateResult> {
123
+ ): Promise<BundlePreparationResult> {
124
124
  const preflight = configurationError(context);
125
125
  if (preflight !== undefined) {
126
126
  return Promise.resolve(preflight);
@@ -130,7 +130,7 @@ function startNativeRound(
130
130
  await runOnce(context, launchRolledBackVersion);
131
131
  } catch (e) {
132
132
  logger.error(TAG, `native check failed: ${getErrorMessage(e)}`);
133
- roundResult = nativeUpdateResult('failed', 'internal_error');
133
+ roundResult = bundlePreparationResult('failed', 'internal_error');
134
134
  }
135
135
  return roundResult;
136
136
  });
@@ -138,62 +138,62 @@ function startNativeRound(
138
138
 
139
139
  // A preflight skip must not consume the process's only round: configuration
140
140
  // may arrive after the delayed startup timer on a first-ever launch.
141
- function configurationError(context: UpdateContext): NativeUpdateResult | undefined {
141
+ function configurationError(context: UpdateContext): BundlePreparationResult | undefined {
142
142
  const json = context.getKv(KEY_CONFIG);
143
143
  if (!json) {
144
- return nativeUpdateResult('skipped', 'not_configured');
144
+ return bundlePreparationResult('skipped', 'not_configured');
145
145
  }
146
146
  try {
147
147
  const config = JSON.parse(json) as NativeConfig;
148
148
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
149
- return nativeUpdateResult('failed', 'invalid_config');
149
+ return bundlePreparationResult('failed', 'invalid_config');
150
150
  }
151
151
  if (config.disabled) {
152
- return nativeUpdateResult('skipped', 'disabled');
152
+ return bundlePreparationResult('skipped', 'disabled');
153
153
  }
154
154
  if (typeof config.appKey !== 'string' || config.appKey.trim().length === 0) {
155
- return nativeUpdateResult('failed', 'invalid_config');
155
+ return bundlePreparationResult('failed', 'invalid_config');
156
156
  }
157
157
  } catch (e) {
158
- return nativeUpdateResult('failed', 'invalid_config');
158
+ return bundlePreparationResult('failed', 'invalid_config');
159
159
  }
160
160
  return undefined;
161
161
  }
162
162
 
163
- export async function checkAndUpdateNative(
163
+ export async function prepareBundleNative(
164
164
  context: UpdateContext,
165
- ): Promise<NativeUpdateResult> {
165
+ ): Promise<BundlePreparationResult> {
166
166
  if (scheduledContext !== context) {
167
- return nativeUpdateResult('skipped', 'not_initialized');
167
+ return bundlePreparationResult('skipped', 'not_initialized');
168
168
  }
169
169
  const configJson = context.getKv(KEY_CONFIG);
170
170
  if (!configJson) {
171
- return nativeUpdateResult('skipped', 'not_configured');
171
+ return bundlePreparationResult('skipped', 'not_configured');
172
172
  }
173
173
  try {
174
174
  const config = JSON.parse(configJson) as NativeConfig;
175
175
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
176
- return nativeUpdateResult('failed', 'invalid_config');
176
+ return bundlePreparationResult('failed', 'invalid_config');
177
177
  }
178
178
  if (config.disabled) {
179
- return nativeUpdateResult('skipped', 'disabled');
179
+ return bundlePreparationResult('skipped', 'disabled');
180
180
  }
181
181
  if (typeof config.appKey !== 'string' || !config.appKey) {
182
- return nativeUpdateResult('failed', 'invalid_config');
182
+ return bundlePreparationResult('failed', 'invalid_config');
183
183
  }
184
184
  } catch (e) {
185
- return nativeUpdateResult('failed', 'invalid_config');
185
+ return bundlePreparationResult('failed', 'invalid_config');
186
186
  }
187
187
  const result = await startNativeRound(context, scheduledRollback);
188
188
  if (roundConfigGeneration !== context.getNativeConfigGeneration()
189
189
  || configJson !== roundConfigJson || configJson !== context.getKv(KEY_CONFIG)) {
190
- return nativeUpdateResult('cancelled', 'config_changed');
190
+ return bundlePreparationResult('cancelled', 'config_changed');
191
191
  }
192
192
  if (roundGeneration !== context.getResetGeneration()) {
193
- return nativeUpdateResult('cancelled', 'reset');
193
+ return bundlePreparationResult('cancelled', 'reset');
194
194
  }
195
195
  // Do not let a caller mutate the cached result observed by later callers.
196
- return nativeUpdateResult(result.status, result.reason, result.hash, result.activated);
196
+ return bundlePreparationResult(result.status, result.reason, result.hash, result.activated);
197
197
  }
198
198
 
199
199
  // JS 在本进程内已拿到有效检查响应时对应的配置 JSON(markJsCheckCompleted)。
@@ -250,32 +250,32 @@ async function runOnce(
250
250
  const resetGeneration = context.getResetGeneration();
251
251
  roundGeneration = resetGeneration;
252
252
  roundConfigGeneration = context.getNativeConfigGeneration();
253
- roundResult = nativeUpdateResult('failed', 'check_failed');
253
+ roundResult = bundlePreparationResult('failed', 'check_failed');
254
254
  const configJson = context.getKv(KEY_CONFIG);
255
255
  roundConfigJson = configJson;
256
256
  if (!configJson) {
257
257
  // No persisted configuration: report the rollout gate to native callers.
258
- roundResult = nativeUpdateResult('skipped', 'not_configured');
258
+ roundResult = bundlePreparationResult('skipped', 'not_configured');
259
259
  return;
260
260
  }
261
261
  let config: NativeConfig;
262
262
  try {
263
263
  config = JSON.parse(configJson) as NativeConfig;
264
264
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
265
- roundResult = nativeUpdateResult('failed', 'invalid_config');
265
+ roundResult = bundlePreparationResult('failed', 'invalid_config');
266
266
  return;
267
267
  }
268
268
  } catch (e) {
269
- roundResult = nativeUpdateResult('failed', 'invalid_config');
269
+ roundResult = bundlePreparationResult('failed', 'invalid_config');
270
270
  return;
271
271
  }
272
272
  if (config.disabled) {
273
- roundResult = nativeUpdateResult('skipped', 'disabled');
273
+ roundResult = bundlePreparationResult('skipped', 'disabled');
274
274
  return;
275
275
  }
276
276
  const appKey = config.appKey ?? '';
277
277
  if (!appKey) {
278
- roundResult = nativeUpdateResult('failed', 'invalid_config');
278
+ roundResult = bundlePreparationResult('failed', 'invalid_config');
279
279
  return;
280
280
  }
281
281
  // 从这里起本轮开始做真实工作:留下面包屑,死于轮中时下次启动零延迟续传。
@@ -371,7 +371,7 @@ async function runConfiguredRound(
371
371
  };
372
372
  const body = NativePatchCore.buildCheckRequestBody(JSON.stringify(input));
373
373
  if (!body) {
374
- roundResult = nativeUpdateResult('failed', 'invalid_request');
374
+ roundResult = bundlePreparationResult('failed', 'invalid_request');
375
375
  return;
376
376
  }
377
377
 
@@ -390,7 +390,7 @@ async function runConfiguredRound(
390
390
  config.afterDownload ?? '',
391
391
  );
392
392
  if (!decisionJson) {
393
- roundResult = nativeUpdateResult('failed', 'invalid_response');
393
+ roundResult = bundlePreparationResult('failed', 'invalid_response');
394
394
  return;
395
395
  }
396
396
  const decision = JSON.parse(decisionJson) as Decision;
@@ -403,15 +403,15 @@ async function runConfiguredRound(
403
403
  buildResponseCacheJson(configJson, body, responseText, responseAtSeconds),
404
404
  );
405
405
  roundResult = committed
406
- ? nativeUpdateResult('noUpdate', decision.reason ?? '')
407
- : nativeUpdateResult('cancelled', 'reset');
406
+ ? bundlePreparationResult('noUpdate', decision.reason ?? '')
407
+ : bundlePreparationResult('cancelled', 'reset');
408
408
  logger.info(TAG, `nothing to do (${decision.reason ?? ''})`);
409
409
  return;
410
410
  }
411
411
  const hash = decision.hash ?? '';
412
412
  if (!isSafePathComponent(hash)) {
413
413
  logger.warn(TAG, 'decision carries an unsafe hash, ignoring');
414
- roundResult = nativeUpdateResult('failed', 'invalid_response');
414
+ roundResult = bundlePreparationResult('failed', 'invalid_response');
415
415
  return;
416
416
  }
417
417
 
@@ -438,8 +438,8 @@ async function runConfiguredRound(
438
438
  buildResponseCacheJson(configJson, body, responseText, responseAtSeconds),
439
439
  );
440
440
  roundResult = committed
441
- ? nativeUpdateResult('failed', 'download_failed')
442
- : nativeUpdateResult('cancelled', 'reset');
441
+ ? bundlePreparationResult('failed', 'download_failed')
442
+ : bundlePreparationResult('cancelled', 'reset');
443
443
  return;
444
444
  }
445
445
 
@@ -481,7 +481,7 @@ async function runConfiguredRound(
481
481
  );
482
482
  } catch (e) {
483
483
  logger.error(TAG, `commit failed: ${getErrorMessage(e)}`);
484
- roundResult = nativeUpdateResult('failed', 'commit_failed');
484
+ roundResult = bundlePreparationResult('failed', 'commit_failed');
485
485
  return;
486
486
  }
487
487
  if (!committed) {
@@ -492,8 +492,8 @@ async function runConfiguredRound(
492
492
  logger.info(TAG, `downloaded ${hash}, activation left to JS`);
493
493
  }
494
494
  roundResult = committed
495
- ? nativeUpdateResult('downloaded', '', hash, activate)
496
- : nativeUpdateResult('cancelled', 'reset');
495
+ ? bundlePreparationResult('downloaded', '', hash, activate)
496
+ : bundlePreparationResult('cancelled', 'reset');
497
497
  }
498
498
 
499
499
  function buildResponseCacheJson(
@@ -1,5 +1,5 @@
1
1
  /** Options accepted by PushyFileJSBundleProvider.configure(). */
2
- export interface NativeUpdateConfig {
2
+ export interface PushyConfiguration {
3
3
  appKey: string;
4
4
  /** Omitted: Pushy's built-in endpoints. Custom endpoints never inherit discovery URLs. */
5
5
  endpoints?: string[];
@@ -14,7 +14,7 @@ export interface NativeUpdateConfig {
14
14
  rn?: string;
15
15
  }
16
16
 
17
- interface PersistedNativeUpdateConfig {
17
+ interface PersistedPushyConfiguration {
18
18
  appKey: string;
19
19
  endpoints: string[];
20
20
  queryUrls: string[];
@@ -67,7 +67,7 @@ function configUrls(values: string[], name: string, base: boolean): string[] {
67
67
  }
68
68
 
69
69
  /** Pure validation, before any native state is touched. Does not mutate options. */
70
- export function normalizeNativeUpdateConfig(options: NativeUpdateConfig): string {
70
+ export function normalizePushyConfiguration(options: PushyConfiguration): string {
71
71
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
72
72
  throw new Error('Invalid native configuration: expected an object');
73
73
  }
@@ -90,7 +90,7 @@ export function normalizeNativeUpdateConfig(options: NativeUpdateConfig): string
90
90
  if (options.disabled !== undefined && typeof options.disabled !== 'boolean') {
91
91
  throw new Error('Invalid native configuration: disabled must be a boolean');
92
92
  }
93
- const result: PersistedNativeUpdateConfig = {
93
+ const result: PersistedPushyConfiguration = {
94
94
  appKey, endpoints, queryUrls, afterDownload, disabled: options.disabled ?? false,
95
95
  rnu: options.rnu === undefined ? '' : configString(options.rnu, 'rnu', true),
96
96
  rn: options.rn === undefined ? '' : configString(options.rn, 'rn', true),
@@ -6,10 +6,10 @@ import {
6
6
  import common from '@ohos.app.ability.common';
7
7
  import fs from '@ohos.file.fs';
8
8
  import { UpdateContext } from './UpdateContext';
9
- import { checkAndUpdateNative } from './NativeCheckOrchestrator';
10
- import type { NativeUpdateResult } from './NativeUpdateResult';
11
- import { normalizeNativeUpdateConfig } from './NativeUpdateConfig';
12
- import type { NativeUpdateConfig } from './NativeUpdateConfig';
9
+ import { prepareBundleNative } from './NativeCheckOrchestrator';
10
+ import type { BundlePreparationResult } from './BundlePreparationResult';
11
+ import { normalizePushyConfiguration } from './PushyConfiguration';
12
+ import type { PushyConfiguration } from './PushyConfiguration';
13
13
 
14
14
  export class PushyFileJSBundleProvider extends JSBundleProvider {
15
15
  private updateContext: UpdateContext;
@@ -48,14 +48,14 @@ export class PushyFileJSBundleProvider extends JSBundleProvider {
48
48
  }
49
49
 
50
50
  /** Configure before normal bundle resolution; no JS or network work is required. */
51
- async configure(options: NativeUpdateConfig): Promise<void> {
52
- const config = normalizeNativeUpdateConfig(options);
51
+ async configure(options: PushyConfiguration): Promise<void> {
52
+ const config = normalizePushyConfiguration(options);
53
53
  await this.updateContext.setNativeConfig(config);
54
54
  }
55
55
 
56
56
  /** Call after the host's real bundle resolution; never resolves it again. */
57
- checkAndUpdate(): Promise<NativeUpdateResult> {
58
- return checkAndUpdateNative(this.updateContext);
57
+ prepareBundle(): Promise<BundlePreparationResult> {
58
+ return prepareBundleNative(this.updateContext);
59
59
  }
60
60
 
61
61
  getAppKeys(): string[] {
@@ -480,7 +480,7 @@ export class UpdateContext {
480
480
  markJsCheckCompleted('');
481
481
  }
482
482
  // Flush even an equal value so a retry after a storage error can succeed.
483
- return this.flushPreferences('configure native update');
483
+ return this.flushPreferences('persist configuration');
484
484
  }
485
485
 
486
486
  public setKv(key: string, value: string): Promise<void> {
package/harmony/pushy.har CHANGED
Binary file
@@ -1,9 +1,9 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTEventEmitter.h>
3
3
 
4
- typedef void (^RCTPushyNativeConfigurationCompletion)(NSError * _Nullable error);
4
+ typedef void (^RCTPushyConfigurationCompletion)(NSError * _Nullable error);
5
5
 
6
- typedef void (^RCTPushyNativeUpdateCompletion)(NSDictionary<NSString *, id> * _Nonnull result);
6
+ typedef void (^RCTPushyBundlePreparationCompletion)(NSDictionary<NSString *, id> * _Nonnull result);
7
7
 
8
8
  @interface RCTPushy : RCTEventEmitter<RCTBridgeModule>
9
9
 
@@ -14,20 +14,20 @@ typedef void (^RCTPushyNativeUpdateCompletion)(NSDictionary<NSString *, id> * _N
14
14
  * normal launch bundle resolution for first-install native-only provisioning.
15
15
  */
16
16
  + (void)configure:(NSDictionary<NSString *, id> * _Nonnull)options
17
- completion:(RCTPushyNativeConfigurationCompletion _Nullable)completion
17
+ completion:(RCTPushyConfigurationCompletion _Nullable)completion
18
18
  NS_SWIFT_NAME(configure(_:completion:));
19
19
 
20
20
  /**
21
- * Start, join, or reuse this process's native update round. Call after the
22
- * host's real bundleURL resolution; this method never resolves the bundle
23
- * again, reloads React Native, or displays UI. Configuration is the one
21
+ * Start, join, or reuse this process's native bundle-preparation round. Call
22
+ * after the host's real bundleURL resolution; this method never resolves the
23
+ * bundle again, reloads React Native, or displays UI. Configuration is the one
24
24
  * persisted by the JS SDK. The optional completion runs on the main queue.
25
25
  *
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
29
  */
30
- + (void)checkAndUpdateWithCompletion:(RCTPushyNativeUpdateCompletion _Nullable)completion
31
- NS_SWIFT_NAME(checkAndUpdate(completion:));
30
+ + (void)prepareBundleWithCompletion:(RCTPushyBundlePreparationCompletion _Nullable)completion
31
+ NS_SWIFT_NAME(prepareBundle(completion:));
32
32
 
33
33
  @end
@@ -1,5 +1,5 @@
1
1
  #import "RCTPushy.h"
2
- #import "RCTPushyNativeConfig.h"
2
+ #import "RCTPushyConfiguration.h"
3
3
  #import "RCTPushyDownloader.h"
4
4
  #import "ZipArchive.h"
5
5
  #include "../../cpp/patch_core/archive_limits.h"
@@ -738,7 +738,7 @@ static void PushySwitchVersionLocked(NSString *hash) {
738
738
  @interface RCTPushyOrchestrator : NSObject
739
739
  + (void)persistConfiguration:(NSString *)config;
740
740
  + (BOOL)hasRunnableConfig;
741
- + (NSDictionary *)checkAndUpdate;
741
+ + (NSDictionary *)prepareBundle;
742
742
  + (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion;
743
743
  #if TARGET_OS_TV && !DEBUG
744
744
  + (BOOL)restorePurgedLaunch:(NSString *)purgedVersion
@@ -1104,11 +1104,11 @@ RCT_EXPORT_MODULE(RCTPushy);
1104
1104
  }
1105
1105
 
1106
1106
  + (void)configure:(NSDictionary<NSString *, id> *)options
1107
- completion:(RCTPushyNativeConfigurationCompletion)completion
1107
+ completion:(RCTPushyConfigurationCompletion)completion
1108
1108
  {
1109
1109
  NSError *validationError = nil;
1110
1110
  // Snapshot nested mutable caller values before crossing a queue boundary.
1111
- NSString *config = RCTPushyNormalizeNativeConfig(options, &validationError);
1111
+ NSString *config = RCTPushyNormalizeConfiguration(options, &validationError);
1112
1112
  static dispatch_queue_t configQueue;
1113
1113
  static dispatch_once_t once;
1114
1114
  dispatch_once(&once, ^{
@@ -1132,7 +1132,7 @@ RCT_EXPORT_MODULE(RCTPushy);
1132
1132
  });
1133
1133
  }
1134
1134
 
1135
- + (void)checkAndUpdateWithCompletion:(RCTPushyNativeUpdateCompletion)completion
1135
+ + (void)prepareBundleWithCompletion:(RCTPushyBundlePreparationCompletion)completion
1136
1136
  {
1137
1137
  static dispatch_queue_t hostQueue;
1138
1138
  static dispatch_once_t once;
@@ -1142,7 +1142,7 @@ RCT_EXPORT_MODULE(RCTPushy);
1142
1142
  dispatch_async(hostQueue, ^{
1143
1143
  NSDictionary *result;
1144
1144
  @try {
1145
- result = [RCTPushyOrchestrator checkAndUpdate];
1145
+ result = [RCTPushyOrchestrator prepareBundle];
1146
1146
  } @catch (NSException *exception) {
1147
1147
  RCTLogWarn(@"RCTPushy -- native host check failed: %@", exception.reason);
1148
1148
  result = PushyHostResult(@"failed", @"internal_error", nil, NO);
@@ -2425,7 +2425,7 @@ static BOOL PushyIsValidCheckResponse(NSString *responseText) {
2425
2425
  }
2426
2426
  #endif
2427
2427
 
2428
- + (NSDictionary *)checkAndUpdate {
2428
+ + (NSDictionary *)prepareBundle {
2429
2429
  #if DEBUG
2430
2430
  return PushyHostResult(@"skipped", @"debug", nil, NO);
2431
2431
  #else
@@ -1,5 +1,5 @@
1
1
  #import <Foundation/Foundation.h>
2
2
 
3
3
  // Validates and snapshots host options without changing any update state.
4
- FOUNDATION_EXPORT NSString * _Nullable RCTPushyNormalizeNativeConfig(
4
+ FOUNDATION_EXPORT NSString * _Nullable RCTPushyNormalizeConfiguration(
5
5
  NSDictionary * _Nonnull options, NSError * _Nullable * _Nullable error);
@@ -1,4 +1,4 @@
1
- #import "RCTPushyNativeConfig.h"
1
+ #import "RCTPushyConfiguration.h"
2
2
 
3
3
  static void PushyConfigInvalid(NSString *message) {
4
4
  @throw [NSException exceptionWithName:NSInvalidArgumentException
@@ -48,7 +48,7 @@ static NSArray *PushyConfigUrls(id value, NSString *name, BOOL base) {
48
48
  return result;
49
49
  }
50
50
 
51
- NSString *RCTPushyNormalizeNativeConfig(NSDictionary *options, NSError **error) {
51
+ NSString *RCTPushyNormalizeConfiguration(NSDictionary *options, NSError **error) {
52
52
  @try {
53
53
  if (![options isKindOfClass:NSDictionary.class]) {
54
54
  PushyConfigInvalid(@"expected an object");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.58.0",
3
+ "version": "10.59.0",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "types": "src/index.ts",
package/src/client.ts CHANGED
@@ -742,8 +742,8 @@ export class Pushy {
742
742
  let timer: ReturnType<typeof setTimeout> | undefined;
743
743
  await Promise.race([
744
744
  this.loggerPromise.promise,
745
- new Promise((resolve) => {
746
- timer = setTimeout(resolve, 10 * 1000);
745
+ new Promise<void>((resolve) => {
746
+ timer = setTimeout(() => resolve(), 10 * 1000);
747
747
  }),
748
748
  ]);
749
749
  clearTimeout(timer);
@@ -977,8 +977,9 @@ export class Pushy {
977
977
  ) => {
978
978
  const resp = await fetchWithTimeout(
979
979
  this.getCheckUrl(endpoint),
980
- signal ? { ...fetchPayload, signal } : fetchPayload,
981
- DEFAULT_FETCH_TIMEOUT_MS
980
+ fetchPayload,
981
+ DEFAULT_FETCH_TIMEOUT_MS,
982
+ signal
982
983
  );
983
984
 
984
985
  if (!resp.ok) {
@@ -1532,7 +1533,7 @@ export class Pushy {
1532
1533
  (0.75 + Math.random() * 0.5)
1533
1534
  );
1534
1535
  log(`retry attempt ${attempt}/${maxRetries}, waiting ${backoffMs}ms`);
1535
- await new Promise((r) => setTimeout(r, backoffMs));
1536
+ await new Promise<void>((r) => setTimeout(() => r(), backoffMs));
1536
1537
  errorMessages.length = 0;
1537
1538
  errorMessages.push(...exhaustedStrategies.values());
1538
1539
  lastError = undefined;
package/src/core.ts CHANGED
@@ -3,6 +3,12 @@ import { UpdateError } from './error';
3
3
  import i18n from './i18n';
4
4
  import { emptyModule, error, log } from './utils';
5
5
 
6
+ // RN's JS global object. Declared locally because RN 0.87's own typings no
7
+ // longer define `global` (only @types/node did), and this source is
8
+ // type-checked in consumer projects; the module-scoped declaration also
9
+ // shadows @types/node's without conflicting.
10
+ declare const global: any;
11
+
6
12
  /* eslint-disable @react-native/no-deep-imports */
7
13
  const {
8
14
  version: v,
@@ -10,7 +16,7 @@ const {
10
16
  const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
11
17
  const isTurboModuleEnabled =
12
18
  // https://github.com/facebook/react-native/pull/48362
13
- (global as any).__turboModuleProxy || (global as any).RN$Bridgeless;
19
+ global.__turboModuleProxy || global.RN$Bridgeless;
14
20
 
15
21
  const isWebPlatform = Platform.OS === 'web';
16
22
 
package/src/utils.ts CHANGED
@@ -132,7 +132,11 @@ export const computeProgress = (received: number, total: number): number =>
132
132
  export const fetchWithTimeout = (
133
133
  url: string,
134
134
  params: Parameters<typeof fetch>[1],
135
- timeoutMs = DEFAULT_FETCH_TIMEOUT_MS
135
+ timeoutMs = DEFAULT_FETCH_TIMEOUT_MS,
136
+ // Taken apart from params: merging it into the RequestInit only type-checks
137
+ // when the global AbortSignal and fetch's RequestInit come from the same
138
+ // declarations, which RN 0.87 globals mixed with DOM/Node/bun types break.
139
+ signal?: AbortSignal
136
140
  ): Promise<Response> => {
137
141
  // AbortController landed in the RN fetch polyfill around 0.60; we support
138
142
  // older peers, so fall back to a plain timer race when it is unavailable
@@ -161,7 +165,8 @@ export const fetchWithTimeout = (
161
165
  // The timeout controller replaces params.signal on the fetch call, so a
162
166
  // caller-provided signal (e.g. the hedged endpoint race cancelling losers)
163
167
  // must be chained onto it manually.
164
- const externalSignal = (params as any)?.signal as AbortSignal | undefined;
168
+ const externalSignal =
169
+ signal ?? ((params as any)?.signal as AbortSignal | undefined);
165
170
  const onExternalAbort = () => controller.abort();
166
171
  if (externalSignal?.aborted) {
167
172
  controller.abort();