react-native-update 10.42.4 → 10.42.5

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.
@@ -75,7 +75,7 @@ export class PushyTurboModule extends UITurboModule {
75
75
  const currentVersionInfo = currentVersion
76
76
  ? this.context.getKv(`hash_${currentVersion}`)
77
77
  : '';
78
- const isFirstTime = this.context.isFirstTime();
78
+ const isFirstTime = this.context.consumeFirstLoadMarker();
79
79
  const rolledBackVersion = this.context.rolledBackVersion();
80
80
  const uuid = this.context.getKv('uuid');
81
81
  const isUsingBundleUrl = this.context.getIsUsingBundleUrl();
@@ -25,6 +25,7 @@ export class UpdateContext {
25
25
  private preferences!: preferences.Preferences;
26
26
  private static DEBUG: boolean = false;
27
27
  private static isUsingBundleUrl: boolean = false;
28
+ private static ignoreRollback: boolean = false;
28
29
 
29
30
  constructor(context: common.UIAbilityContext) {
30
31
  this.context = context;
@@ -72,9 +73,8 @@ export class UpdateContext {
72
73
 
73
74
  public getBuildTime(): string {
74
75
  try {
75
- const content = this.context.resourceManager.getRawFileContentSync(
76
- 'meta.json',
77
- );
76
+ const content =
77
+ this.context.resourceManager.getRawFileContentSync('meta.json');
78
78
  const metaData = JSON.parse(
79
79
  new util.TextDecoder().decodeToString(content),
80
80
  ) as Record<string, string | number | boolean | null | undefined>;
@@ -182,6 +182,8 @@ export class UpdateContext {
182
182
  clearExisting?: boolean;
183
183
  removeStaleHash?: boolean;
184
184
  cleanUp?: boolean;
185
+ markFirstLoadMarker?: boolean;
186
+ clearFirstLoadMarker?: boolean;
185
187
  } = {},
186
188
  ): void {
187
189
  if (options.clearExisting) {
@@ -191,6 +193,12 @@ export class UpdateContext {
191
193
  if (options.removeStaleHash && state.staleVersionToDelete) {
192
194
  this.preferences.deleteSync(`hash_${state.staleVersionToDelete}`);
193
195
  }
196
+ if (options.markFirstLoadMarker) {
197
+ this.preferences.putSync('firstLoadMarked', 'true');
198
+ }
199
+ if (options.clearFirstLoadMarker) {
200
+ this.preferences.deleteSync('firstLoadMarked');
201
+ }
194
202
  this.flushPreferences('persist state');
195
203
  if (options.cleanUp) {
196
204
  this.cleanUp();
@@ -203,6 +211,7 @@ export class UpdateContext {
203
211
  options: {
204
212
  removeStaleHash?: boolean;
205
213
  cleanUp?: boolean;
214
+ clearFirstLoadMarker?: boolean;
206
215
  } = {},
207
216
  ): StateCoreResult {
208
217
  const nextState = NativePatchCore.runStateCore(
@@ -245,6 +254,7 @@ export class UpdateContext {
245
254
  return;
246
255
  }
247
256
 
257
+ UpdateContext.ignoreRollback = false;
248
258
  this.cleanUp();
249
259
  this.persistState(nextState, { clearExisting: true });
250
260
  }
@@ -278,7 +288,10 @@ export class UpdateContext {
278
288
  }
279
289
 
280
290
  public clearFirstTime(): void {
281
- this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', { cleanUp: true });
291
+ this.runStateOperation(STATE_OP_CLEAR_FIRST_TIME, '', {
292
+ cleanUp: true,
293
+ clearFirstLoadMarker: true,
294
+ });
282
295
  }
283
296
 
284
297
  public clearRollbackMark(): void {
@@ -361,23 +374,38 @@ export class UpdateContext {
361
374
  }
362
375
 
363
376
  this.runStateOperation(STATE_OP_SWITCH_VERSION, hash);
377
+ UpdateContext.ignoreRollback = false;
364
378
  } catch (e) {
365
379
  console.error('Failed to switch version:', e);
366
380
  throw e;
367
381
  }
368
382
  }
369
383
 
384
+ public consumeFirstLoadMarker(): boolean {
385
+ const marked = this.readString('firstLoadMarked') === 'true';
386
+ if (marked) {
387
+ this.preferences.deleteSync('firstLoadMarked');
388
+ this.flushPreferences('clear first load marker');
389
+ }
390
+ return marked;
391
+ }
392
+
370
393
  public getBundleUrl() {
371
394
  UpdateContext.isUsingBundleUrl = true;
372
395
  const launchState = NativePatchCore.runStateCore(
373
396
  STATE_OP_RESOLVE_LAUNCH,
374
397
  this.getStateSnapshot(),
375
398
  '',
376
- false,
377
- false,
399
+ UpdateContext.ignoreRollback,
400
+ true,
378
401
  );
379
402
  if (launchState.didRollback || launchState.consumedFirstTime) {
380
- this.persistState(launchState);
403
+ this.persistState(launchState, {
404
+ markFirstLoadMarker: launchState.consumedFirstTime,
405
+ });
406
+ }
407
+ if (launchState.consumedFirstTime) {
408
+ UpdateContext.ignoreRollback = true;
381
409
  }
382
410
 
383
411
  let version = launchState.loadVersion || '';
package/harmony/pushy.har CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.42.4",
3
+ "version": "10.42.5",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
package/src/client.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  } from './type';
29
29
  import {
30
30
  assertWeb,
31
+ computeProgress,
31
32
  DEFAULT_FETCH_TIMEOUT_MS,
32
33
  emptyObj,
33
34
  fetchWithTimeout,
@@ -491,13 +492,19 @@ export class Pushy {
491
492
  }
492
493
  const patchStartTime = Date.now();
493
494
  if (onDownloadProgress) {
495
+ const wrapProgress = (data: ProgressData) => {
496
+ onDownloadProgress({
497
+ ...data,
498
+ progress: computeProgress(data.received, data.total),
499
+ });
500
+ };
494
501
  // @ts-expect-error harmony not in existing platforms
495
502
  if (Platform.OS === 'harmony') {
496
503
  sharedState.progressHandlers[hash] = DeviceEventEmitter.addListener(
497
504
  'RCTPushyDownloadProgress',
498
- progressData => {
505
+ (progressData: ProgressData) => {
499
506
  if (progressData.hash === hash) {
500
- onDownloadProgress(progressData);
507
+ wrapProgress(progressData);
501
508
  }
502
509
  },
503
510
  );
@@ -505,54 +512,47 @@ export class Pushy {
505
512
  sharedState.progressHandlers[hash] =
506
513
  pushyNativeEventEmitter.addListener(
507
514
  'RCTPushyDownloadProgress',
508
- progressData => {
515
+ (progressData: ProgressData) => {
509
516
  if (progressData.hash === hash) {
510
- onDownloadProgress(progressData);
517
+ wrapProgress(progressData);
511
518
  }
512
519
  },
513
520
  );
514
521
  }
515
522
  }
523
+ const maxRetries = Math.max(0, Math.floor(this.options.maxRetries ?? 3));
516
524
  let succeeded = '';
517
- this.report({
518
- type: 'downloading',
519
- data: {
520
- newVersion: hash,
521
- },
522
- });
523
525
  let lastError: any;
524
526
  const errorMessages: string[] = [];
525
- const diffUrl = await testUrls(joinUrls(paths, diff));
526
- if (diffUrl && !__DEV__) {
527
- log('downloading diff');
528
- try {
529
- await PushyModule.downloadPatchFromPpk({
530
- updateUrl: diffUrl,
531
- hash,
532
- originHash: currentVersion,
533
- });
534
- succeeded = 'diff';
535
- } catch (e: any) {
536
- const errorMessage = this.t('error_diff_failed', {
537
- message: e.message,
538
- });
539
- errorMessages.push(errorMessage);
540
- lastError = Error(errorMessage);
541
- log(errorMessage);
527
+
528
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
529
+ if (attempt > 0) {
530
+ const backoffMs = Math.min(1000 * 2 ** (attempt - 1), 10000);
531
+ log(`retry attempt ${attempt}/${maxRetries}, waiting ${backoffMs}ms`);
532
+ await new Promise(r => setTimeout(r, backoffMs));
533
+ errorMessages.length = 0;
534
+ lastError = undefined;
535
+ succeeded = '';
542
536
  }
543
- }
544
- if (!succeeded) {
545
- const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
546
- if (pdiffUrl && !__DEV__) {
547
- log('downloading pdiff');
537
+ this.report({
538
+ type: 'downloading',
539
+ data: {
540
+ newVersion: hash,
541
+ attempt,
542
+ },
543
+ });
544
+ const diffUrl = await testUrls(joinUrls(paths, diff));
545
+ if (diffUrl && !__DEV__) {
546
+ log('downloading diff');
548
547
  try {
549
- await PushyModule.downloadPatchFromPackage({
550
- updateUrl: pdiffUrl,
548
+ await PushyModule.downloadPatchFromPpk({
549
+ updateUrl: diffUrl,
551
550
  hash,
551
+ originHash: currentVersion,
552
552
  });
553
- succeeded = 'pdiff';
553
+ succeeded = 'diff';
554
554
  } catch (e: any) {
555
- const errorMessage = this.t('error_pdiff_failed', {
555
+ const errorMessage = this.t('error_diff_failed', {
556
556
  message: e.message,
557
557
  });
558
558
  errorMessages.push(errorMessage);
@@ -560,28 +560,51 @@ export class Pushy {
560
560
  log(errorMessage);
561
561
  }
562
562
  }
563
- }
564
- if (!succeeded) {
565
- const fullUrl = await testUrls(joinUrls(paths, full));
566
- if (fullUrl) {
567
- log('downloading full patch');
568
- try {
569
- await PushyModule.downloadFullUpdate({
570
- updateUrl: fullUrl,
571
- hash,
572
- });
563
+ if (!succeeded) {
564
+ const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
565
+ if (pdiffUrl && !__DEV__) {
566
+ log('downloading pdiff');
567
+ try {
568
+ await PushyModule.downloadPatchFromPackage({
569
+ updateUrl: pdiffUrl,
570
+ hash,
571
+ });
572
+ succeeded = 'pdiff';
573
+ } catch (e: any) {
574
+ const errorMessage = this.t('error_pdiff_failed', {
575
+ message: e.message,
576
+ });
577
+ errorMessages.push(errorMessage);
578
+ lastError = Error(errorMessage);
579
+ log(errorMessage);
580
+ }
581
+ }
582
+ }
583
+ if (!succeeded) {
584
+ const fullUrl = await testUrls(joinUrls(paths, full));
585
+ if (fullUrl) {
586
+ log('downloading full patch');
587
+ try {
588
+ await PushyModule.downloadFullUpdate({
589
+ updateUrl: fullUrl,
590
+ hash,
591
+ });
592
+ succeeded = 'full';
593
+ } catch (e: any) {
594
+ const errorMessage = this.t('error_full_patch_failed', {
595
+ message: e.message,
596
+ });
597
+ errorMessages.push(errorMessage);
598
+ lastError = Error(errorMessage);
599
+ log(errorMessage);
600
+ }
601
+ } else if (__DEV__) {
602
+ log(this.t('dev_incremental_update_disabled'));
573
603
  succeeded = 'full';
574
- } catch (e: any) {
575
- const errorMessage = this.t('error_full_patch_failed', {
576
- message: e.message,
577
- });
578
- errorMessages.push(errorMessage);
579
- lastError = Error(errorMessage);
580
- log(errorMessage);
581
604
  }
582
- } else if (__DEV__) {
583
- log(this.t('dev_incremental_update_disabled'));
584
- succeeded = 'full';
605
+ }
606
+ if (succeeded) {
607
+ break;
585
608
  }
586
609
  }
587
610
  if (sharedState.progressHandlers[hash]) {
@@ -0,0 +1,2 @@
1
+ /** React Native dev mode flag, injected by Metro bundler */
2
+ declare const __DEV__: boolean;
package/src/type.ts CHANGED
@@ -33,6 +33,8 @@ export interface ProgressData {
33
33
  hash: string;
34
34
  received: number;
35
35
  total: number;
36
+ /** Download progress percentage (0-100), computed from received / total and clamped to range. Only populated in downloadUpdate callbacks. */
37
+ progress?: number;
36
38
  }
37
39
 
38
40
  // 用于描述一次检查结束后的最终状态,便于业务侧感知成功、跳过或失败
@@ -119,6 +121,8 @@ export interface ClientOptions {
119
121
  ) => Promise<boolean | void> | boolean | void;
120
122
  onPackageExpired?: (info: CheckResult) => Promise<boolean> | boolean;
121
123
  overridePackageVersion?: string;
124
+ /** Maximum number of retry attempts for failed downloads (default: 3) */
125
+ maxRetries?: number;
122
126
  }
123
127
 
124
128
  export interface UpdateTestPayload {
package/src/utils.ts CHANGED
@@ -108,6 +108,11 @@ export const assertWeb = () => {
108
108
  return true;
109
109
  };
110
110
 
111
+ export const computeProgress = (received: number, total: number): number =>
112
+ total > 0
113
+ ? Math.min(100, Math.max(0, Math.floor((received / total) * 100)))
114
+ : 0;
115
+
111
116
  export const fetchWithTimeout = (
112
117
  url: string,
113
118
  params: Parameters<typeof fetch>[1],