react-native-update 10.43.2 → 10.44.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/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +24 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +23 -8
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +64 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +2 -2
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +8 -8
- package/cpp/patch_core/archive_patch_core.cpp +16 -0
- package/cpp/patch_core/archive_patch_core.h +5 -0
- package/cpp/patch_core/jni_util.h +56 -0
- package/cpp/patch_core/patch_core_android.cpp +4 -37
- package/cpp/patch_core/state_ops.h +24 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +32 -2
- package/cpp/patch_core/update_core_android.cpp +43 -69
- package/harmony/pushy/src/main/cpp/pushy.cpp +162 -35
- package/harmony/pushy/src/main/ets/DownloadTask.ts +59 -9
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -2
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +52 -8
- package/harmony/pushy/src/main/ets/UpdateContext.ts +26 -9
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
- package/package.json +2 -1
- package/src/client.ts +130 -75
- package/src/provider.tsx +40 -26
- package/src/utils.ts +2 -2
- package/harmony/pushy/src/main/cpp/pushy.c +0 -117
- package/harmony/pushy/src/main/cpp/pushy.h +0 -8
- package/src/__tests__/setup.ts +0 -44
|
@@ -68,7 +68,7 @@ export class UpdateContext {
|
|
|
68
68
|
*/
|
|
69
69
|
private trace(point: string): void {
|
|
70
70
|
const snap = this.getStateSnapshot();
|
|
71
|
-
logger.
|
|
71
|
+
logger.debug(
|
|
72
72
|
'UpdateContext',
|
|
73
73
|
`trace id=${this.instanceId} ${point}` +
|
|
74
74
|
` pkg=${snap.packageVersion} bt=${snap.buildTime}` +
|
|
@@ -91,7 +91,12 @@ export class UpdateContext {
|
|
|
91
91
|
name: 'update',
|
|
92
92
|
});
|
|
93
93
|
} catch (e) {
|
|
94
|
+
// Fail fast: a missing preferences store means no state can be persisted,
|
|
95
|
+
// which disables rollback protection. Rethrow so the failure surfaces at
|
|
96
|
+
// construction time instead of later as an unrelated TypeError on the
|
|
97
|
+
// undefined `preferences` handle.
|
|
94
98
|
console.error('Failed to init preferences:', e);
|
|
99
|
+
throw e;
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
@@ -179,17 +184,23 @@ export class UpdateContext {
|
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
private flushPreferences(reason: string): void {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
const flushablePreferences = this.preferences as FlushablePreferences;
|
|
188
|
+
if (typeof flushablePreferences.flushSync === 'function') {
|
|
189
|
+
try {
|
|
185
190
|
flushablePreferences.flushSync();
|
|
186
191
|
return;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`Failed to flushSync preferences for ${reason}:`, error);
|
|
194
|
+
// fall through to async flush rather than failing the whole operation
|
|
187
195
|
}
|
|
188
|
-
throw Error('preferences.flushSync is not available');
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error(`Failed to flush preferences for ${reason}:`, error);
|
|
191
|
-
throw error;
|
|
192
196
|
}
|
|
197
|
+
// flushSync unavailable or failed: writes are already applied in memory via
|
|
198
|
+
// putSync/deleteSync; persist asynchronously as a best-effort so the state
|
|
199
|
+
// operation still succeeds instead of throwing (which is worse than a
|
|
200
|
+
// slightly delayed persist).
|
|
201
|
+
this.preferences.flush().catch((error: Object) => {
|
|
202
|
+
console.error(`Failed to flush preferences for ${reason}:`, error);
|
|
203
|
+
});
|
|
193
204
|
}
|
|
194
205
|
|
|
195
206
|
private putNullableString(key: string, value?: string): void {
|
|
@@ -509,12 +520,18 @@ export class UpdateContext {
|
|
|
509
520
|
|
|
510
521
|
public cleanUp(): void {
|
|
511
522
|
const state = this.getStateSnapshot();
|
|
523
|
+
// cleanupOldEntries now runs on a native worker thread (returns a Promise).
|
|
524
|
+
// Cleanup is best-effort background maintenance and no caller depends on its
|
|
525
|
+
// completion, so fire-and-forget it off the UI thread and just log failures
|
|
526
|
+
// instead of blocking the state operation (or cold start) on disk I/O.
|
|
512
527
|
NativePatchCore.cleanupOldEntries(
|
|
513
528
|
this.rootDir,
|
|
514
529
|
state.currentVersion || '',
|
|
515
530
|
state.lastVersion || '',
|
|
516
531
|
3,
|
|
517
|
-
)
|
|
532
|
+
).catch((error: Object) => {
|
|
533
|
+
console.error('cleanupOldEntries failed:', error);
|
|
534
|
+
});
|
|
518
535
|
}
|
|
519
536
|
|
|
520
537
|
public getIsUsingBundleUrl(): boolean {
|
package/harmony/pushy.har
CHANGED
|
Binary file
|
|
@@ -10,6 +10,8 @@ static NSString *const RCTPushyDownloaderErrorDomain = @"cn.reactnative.pushy";
|
|
|
10
10
|
@property (copy) NSString *savePath;
|
|
11
11
|
@property (nonatomic, strong) NSError *fileError;
|
|
12
12
|
@property (nonatomic, assign) BOOL finished;
|
|
13
|
+
@property (nonatomic, assign) int lastReportedPercentage;
|
|
14
|
+
@property (nonatomic, assign) long long lastReportedBytes;
|
|
13
15
|
@end
|
|
14
16
|
|
|
15
17
|
@implementation RCTPushyDownloader
|
|
@@ -42,6 +44,10 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
47
|
+
// Avoid hanging forever on a stalled connection (default resource timeout
|
|
48
|
+
// is 7 days). These are generous enough for large bundles on slow networks.
|
|
49
|
+
sessionConfig.timeoutIntervalForRequest = 30;
|
|
50
|
+
sessionConfig.timeoutIntervalForResource = 300;
|
|
45
51
|
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
|
|
46
52
|
delegate:self
|
|
47
53
|
delegateQueue:nil];
|
|
@@ -77,14 +83,47 @@ completionHandler:(void (^)(NSString *path, NSError *error))completionHandler
|
|
|
77
83
|
totalBytesWritten:(int64_t)totalBytesWritten
|
|
78
84
|
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
|
79
85
|
{
|
|
80
|
-
if (self.progressHandler) {
|
|
81
|
-
|
|
86
|
+
if (!self.progressHandler) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Normalize an unknown total (NSURLSessionTransferSizeUnknown == -1) to 0 so
|
|
90
|
+
// the JS side does not compute a negative/NaN percentage.
|
|
91
|
+
long long total = totalBytesExpectedToWrite > 0 ? totalBytesExpectedToWrite : 0;
|
|
92
|
+
if (total > 0) {
|
|
93
|
+
int percentage = (int)((totalBytesWritten * 100.0 / total) + 0.5);
|
|
94
|
+
if (percentage <= self.lastReportedPercentage) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
self.lastReportedPercentage = percentage;
|
|
98
|
+
} else {
|
|
99
|
+
// Total unknown: throttle by bytes to avoid flooding the bridge.
|
|
100
|
+
if (totalBytesWritten - self.lastReportedBytes < 256 * 1024) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
self.lastReportedBytes = totalBytesWritten;
|
|
82
104
|
}
|
|
105
|
+
self.progressHandler(totalBytesWritten, total);
|
|
83
106
|
}
|
|
84
107
|
|
|
85
108
|
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
|
86
109
|
didFinishDownloadingToURL:(NSURL *)location
|
|
87
110
|
{
|
|
111
|
+
// A completed transfer does not imply success: 404/500 pages, CDN error
|
|
112
|
+
// bodies and captive-portal HTML all arrive here. Reject non-2xx responses
|
|
113
|
+
// before touching savePath so an existing valid package is not destroyed.
|
|
114
|
+
NSURLResponse *response = downloadTask.response;
|
|
115
|
+
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
116
|
+
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
|
117
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
118
|
+
self.fileError = [NSError errorWithDomain:RCTPushyDownloaderErrorDomain
|
|
119
|
+
code:statusCode
|
|
120
|
+
userInfo:@{
|
|
121
|
+
NSLocalizedDescriptionKey: [NSString stringWithFormat:@"unexpected http status code %ld", (long)statusCode],
|
|
122
|
+
}];
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
88
127
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
89
128
|
[fileManager removeItemAtPath:self.savePath error:nil];
|
|
90
129
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.44.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
5
|
"main": "src/index",
|
|
6
|
+
"types": "src/index.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"prepublishOnly": "NODE_ENV=production bun scripts/prepublish.ts",
|
|
8
9
|
"publish:local": "SKIP_NATIVE_BUILD=1 npm publish",
|
package/src/client.ts
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
assertWeb,
|
|
31
31
|
computeProgress,
|
|
32
32
|
DEFAULT_FETCH_TIMEOUT_MS,
|
|
33
|
-
emptyObj,
|
|
34
33
|
fetchWithTimeout,
|
|
35
34
|
info,
|
|
36
35
|
joinUrls,
|
|
@@ -87,6 +86,7 @@ const defaultClientOptions: ClientOptions = {
|
|
|
87
86
|
|
|
88
87
|
export const sharedState: {
|
|
89
88
|
progressHandlers: Record<string, EmitterSubscription>;
|
|
89
|
+
downloadingTasks: Record<string, Promise<string | undefined>>;
|
|
90
90
|
downloadedHash?: string;
|
|
91
91
|
toHash?: string;
|
|
92
92
|
apkStatus: 'downloading' | 'downloaded' | null;
|
|
@@ -94,6 +94,7 @@ export const sharedState: {
|
|
|
94
94
|
applyingUpdate: boolean;
|
|
95
95
|
} = {
|
|
96
96
|
progressHandlers: {},
|
|
97
|
+
downloadingTasks: {},
|
|
97
98
|
downloadedHash: undefined,
|
|
98
99
|
apkStatus: null,
|
|
99
100
|
marked: false,
|
|
@@ -102,6 +103,7 @@ export const sharedState: {
|
|
|
102
103
|
|
|
103
104
|
const assertHash = (hash: string) => {
|
|
104
105
|
if (!sharedState.downloadedHash) {
|
|
106
|
+
log(`no downloaded hash yet, ignore switch to ${hash}`);
|
|
105
107
|
return;
|
|
106
108
|
}
|
|
107
109
|
if (hash !== sharedState.downloadedHash) {
|
|
@@ -113,7 +115,7 @@ const assertHash = (hash: string) => {
|
|
|
113
115
|
|
|
114
116
|
// for China users
|
|
115
117
|
export class Pushy {
|
|
116
|
-
options = defaultClientOptions;
|
|
118
|
+
options = { ...defaultClientOptions };
|
|
117
119
|
clientType: 'Pushy' | 'Cresc' = 'Pushy';
|
|
118
120
|
lastChecking?: number;
|
|
119
121
|
lastRespJson?: Promise<CheckResult>;
|
|
@@ -134,7 +136,9 @@ export class Pushy {
|
|
|
134
136
|
this.clientType = clientType || 'Pushy';
|
|
135
137
|
this.options.server = cloneServerConfig(SERVER_PRESETS[this.clientType]);
|
|
136
138
|
|
|
137
|
-
i18n.setLocale(
|
|
139
|
+
i18n.setLocale(
|
|
140
|
+
options.locale ?? (this.clientType === 'Pushy' ? 'zh' : 'en'),
|
|
141
|
+
);
|
|
138
142
|
|
|
139
143
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
140
144
|
if (!options.appKey) {
|
|
@@ -348,7 +352,14 @@ export class Pushy {
|
|
|
348
352
|
sharedState.applyingUpdate = false;
|
|
349
353
|
throw e;
|
|
350
354
|
}
|
|
351
|
-
|
|
355
|
+
try {
|
|
356
|
+
return await PushyModule.reloadUpdate({ hash });
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// reloadUpdate can reject (e.g. bundle missing); reset the flag so a
|
|
359
|
+
// later retry is not permanently blocked by a stuck applyingUpdate.
|
|
360
|
+
sharedState.applyingUpdate = false;
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
352
363
|
}
|
|
353
364
|
};
|
|
354
365
|
|
|
@@ -437,7 +448,10 @@ export class Pushy {
|
|
|
437
448
|
});
|
|
438
449
|
this.notifyAfterCheckUpdate({ status: 'error', error: e });
|
|
439
450
|
this.throwIfEnabled(e);
|
|
440
|
-
|
|
451
|
+
// Fall back to the previous successful response if we have one; otherwise
|
|
452
|
+
// return undefined so callers can distinguish "check failed" from a real
|
|
453
|
+
// empty result and avoid overwriting the last good updateInfo.
|
|
454
|
+
return previousRespJson ? await previousRespJson : undefined;
|
|
441
455
|
}
|
|
442
456
|
};
|
|
443
457
|
getBackupEndpoints = async () => {
|
|
@@ -455,16 +469,7 @@ export class Pushy {
|
|
|
455
469
|
updateInfo: CheckResult,
|
|
456
470
|
onDownloadProgress?: (data: ProgressData) => void,
|
|
457
471
|
) => {
|
|
458
|
-
const {
|
|
459
|
-
hash,
|
|
460
|
-
diff,
|
|
461
|
-
pdiff,
|
|
462
|
-
full,
|
|
463
|
-
paths = [],
|
|
464
|
-
name,
|
|
465
|
-
description = '',
|
|
466
|
-
metaInfo,
|
|
467
|
-
} = updateInfo;
|
|
472
|
+
const { hash } = updateInfo;
|
|
468
473
|
if (
|
|
469
474
|
this.options.beforeDownloadUpdate &&
|
|
470
475
|
(await this.options.beforeDownloadUpdate(updateInfo)) === false
|
|
@@ -487,6 +492,39 @@ export class Pushy {
|
|
|
487
492
|
log(`duplicated downloaded hash ${sharedState.downloadedHash}, ignored`);
|
|
488
493
|
return sharedState.downloadedHash;
|
|
489
494
|
}
|
|
495
|
+
// Deduplicate concurrent downloads of the same hash regardless of whether a
|
|
496
|
+
// progress callback was passed: all callers await the single in-flight
|
|
497
|
+
// promise instead of triggering parallel native downloads.
|
|
498
|
+
const existingTask = sharedState.downloadingTasks[hash];
|
|
499
|
+
if (existingTask) {
|
|
500
|
+
log(`download for hash ${hash} already in progress, reusing it`);
|
|
501
|
+
return existingTask;
|
|
502
|
+
}
|
|
503
|
+
const task = this.performDownload(updateInfo, onDownloadProgress);
|
|
504
|
+
sharedState.downloadingTasks[hash] = task;
|
|
505
|
+
try {
|
|
506
|
+
return await task;
|
|
507
|
+
} finally {
|
|
508
|
+
delete sharedState.downloadingTasks[hash];
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
private performDownload = async (
|
|
512
|
+
updateInfo: CheckResult,
|
|
513
|
+
onDownloadProgress?: (data: ProgressData) => void,
|
|
514
|
+
) => {
|
|
515
|
+
const {
|
|
516
|
+
hash,
|
|
517
|
+
diff,
|
|
518
|
+
pdiff,
|
|
519
|
+
full,
|
|
520
|
+
paths = [],
|
|
521
|
+
name,
|
|
522
|
+
description = '',
|
|
523
|
+
metaInfo,
|
|
524
|
+
} = updateInfo;
|
|
525
|
+
if (!hash) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
490
528
|
if (sharedState.progressHandlers[hash]) {
|
|
491
529
|
return;
|
|
492
530
|
}
|
|
@@ -525,6 +563,59 @@ export class Pushy {
|
|
|
525
563
|
let lastError: any;
|
|
526
564
|
const errorMessages: string[] = [];
|
|
527
565
|
|
|
566
|
+
// Ordered download strategies, tried in sequence until one succeeds. Each
|
|
567
|
+
// resolves its candidate URL lazily (testUrls) and runs the matching native
|
|
568
|
+
// download. diff/pdiff are incremental and skipped entirely in dev; full is
|
|
569
|
+
// attempted whenever a URL exists, and in dev with no URL it is treated as a
|
|
570
|
+
// no-op success so the flow can proceed.
|
|
571
|
+
type DownloadStrategy = {
|
|
572
|
+
name: string;
|
|
573
|
+
candidate: string | undefined;
|
|
574
|
+
errorKey: 'error_diff_failed' | 'error_pdiff_failed' | 'error_full_patch_failed';
|
|
575
|
+
skipInDev: boolean;
|
|
576
|
+
devNoopWhenNoUrl: boolean;
|
|
577
|
+
run: (url: string) => Promise<void>;
|
|
578
|
+
};
|
|
579
|
+
const strategies: DownloadStrategy[] = [
|
|
580
|
+
{
|
|
581
|
+
name: 'diff',
|
|
582
|
+
candidate: diff,
|
|
583
|
+
errorKey: 'error_diff_failed',
|
|
584
|
+
skipInDev: true,
|
|
585
|
+
devNoopWhenNoUrl: false,
|
|
586
|
+
run: url =>
|
|
587
|
+
PushyModule.downloadPatchFromPpk({
|
|
588
|
+
updateUrl: url,
|
|
589
|
+
hash,
|
|
590
|
+
originHash: currentVersion,
|
|
591
|
+
}),
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: 'pdiff',
|
|
595
|
+
candidate: pdiff,
|
|
596
|
+
errorKey: 'error_pdiff_failed',
|
|
597
|
+
skipInDev: true,
|
|
598
|
+
devNoopWhenNoUrl: false,
|
|
599
|
+
run: url =>
|
|
600
|
+
PushyModule.downloadPatchFromPackage({
|
|
601
|
+
updateUrl: url,
|
|
602
|
+
hash,
|
|
603
|
+
}),
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: 'full',
|
|
607
|
+
candidate: full,
|
|
608
|
+
errorKey: 'error_full_patch_failed',
|
|
609
|
+
skipInDev: false,
|
|
610
|
+
devNoopWhenNoUrl: true,
|
|
611
|
+
run: url =>
|
|
612
|
+
PushyModule.downloadFullUpdate({
|
|
613
|
+
updateUrl: url,
|
|
614
|
+
hash,
|
|
615
|
+
}),
|
|
616
|
+
},
|
|
617
|
+
];
|
|
618
|
+
|
|
528
619
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
529
620
|
if (attempt > 0) {
|
|
530
621
|
const backoffMs = Math.min(1000 * 2 ** (attempt - 1), 10000);
|
|
@@ -541,66 +632,27 @@ export class Pushy {
|
|
|
541
632
|
attempt,
|
|
542
633
|
},
|
|
543
634
|
});
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
try {
|
|
548
|
-
await PushyModule.downloadPatchFromPpk({
|
|
549
|
-
updateUrl: diffUrl,
|
|
550
|
-
hash,
|
|
551
|
-
originHash: currentVersion,
|
|
552
|
-
});
|
|
553
|
-
succeeded = 'diff';
|
|
554
|
-
} catch (e: any) {
|
|
555
|
-
const errorMessage = this.t('error_diff_failed', {
|
|
556
|
-
message: e.message,
|
|
557
|
-
});
|
|
558
|
-
errorMessages.push(errorMessage);
|
|
559
|
-
lastError = Error(errorMessage);
|
|
560
|
-
log(errorMessage);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
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
|
-
}
|
|
635
|
+
for (const strategy of strategies) {
|
|
636
|
+
if (succeeded) {
|
|
637
|
+
break;
|
|
581
638
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (fullUrl) {
|
|
586
|
-
log('downloading full patch');
|
|
639
|
+
const url = await testUrls(joinUrls(paths, strategy.candidate));
|
|
640
|
+
if (url && !(strategy.skipInDev && __DEV__)) {
|
|
641
|
+
log(`downloading ${strategy.name}`);
|
|
587
642
|
try {
|
|
588
|
-
await
|
|
589
|
-
|
|
590
|
-
hash,
|
|
591
|
-
});
|
|
592
|
-
succeeded = 'full';
|
|
643
|
+
await strategy.run(url);
|
|
644
|
+
succeeded = strategy.name;
|
|
593
645
|
} catch (e: any) {
|
|
594
|
-
const errorMessage = this.t(
|
|
646
|
+
const errorMessage = this.t(strategy.errorKey, {
|
|
595
647
|
message: e.message,
|
|
596
648
|
});
|
|
597
649
|
errorMessages.push(errorMessage);
|
|
598
650
|
lastError = Error(errorMessage);
|
|
599
651
|
log(errorMessage);
|
|
600
652
|
}
|
|
601
|
-
} else if (__DEV__) {
|
|
653
|
+
} else if (!url && strategy.devNoopWhenNoUrl && __DEV__) {
|
|
602
654
|
log(this.t('dev_incremental_update_disabled'));
|
|
603
|
-
succeeded =
|
|
655
|
+
succeeded = strategy.name;
|
|
604
656
|
}
|
|
605
657
|
}
|
|
606
658
|
if (succeeded) {
|
|
@@ -697,19 +749,22 @@ export class Pushy {
|
|
|
697
749
|
},
|
|
698
750
|
);
|
|
699
751
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
752
|
+
try {
|
|
753
|
+
await PushyModule.downloadAndInstallApk({
|
|
754
|
+
url,
|
|
755
|
+
target: 'update.apk',
|
|
756
|
+
hash: progressKey,
|
|
757
|
+
});
|
|
758
|
+
sharedState.apkStatus = 'downloaded';
|
|
759
|
+
} catch {
|
|
705
760
|
sharedState.apkStatus = null;
|
|
706
761
|
this.report({ type: 'errorDownloadAndInstallApk' });
|
|
707
762
|
this.throwIfEnabled(Error('errorDownloadAndInstallApk'));
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
763
|
+
} finally {
|
|
764
|
+
if (sharedState.progressHandlers[progressKey]) {
|
|
765
|
+
sharedState.progressHandlers[progressKey].remove();
|
|
766
|
+
delete sharedState.progressHandlers[progressKey];
|
|
767
|
+
}
|
|
713
768
|
}
|
|
714
769
|
};
|
|
715
770
|
restartApp = async () => {
|
package/src/provider.tsx
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import { UpdateContext } from './context';
|
|
28
28
|
import { URL } from 'react-native-url-polyfill';
|
|
29
29
|
import { resolveCheckResult } from './resolveCheckResult';
|
|
30
|
-
import { assertWeb, log } from './utils';
|
|
30
|
+
import { assertWeb, log, noop } from './utils';
|
|
31
31
|
|
|
32
32
|
export const UpdateProvider = ({
|
|
33
33
|
client,
|
|
@@ -108,7 +108,6 @@ export const UpdateProvider = ({
|
|
|
108
108
|
if (!hash) {
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
111
|
-
stateListener.current && stateListener.current.remove();
|
|
112
111
|
|
|
113
112
|
if (
|
|
114
113
|
options.afterDownloadUpdate &&
|
|
@@ -170,7 +169,7 @@ export const UpdateProvider = ({
|
|
|
170
169
|
lastChecking.current = now;
|
|
171
170
|
let rootInfo: CheckResult | undefined;
|
|
172
171
|
try {
|
|
173
|
-
rootInfo =
|
|
172
|
+
rootInfo = await client.checkUpdate(extra);
|
|
174
173
|
} catch (e: any) {
|
|
175
174
|
setLastError(e);
|
|
176
175
|
alertError(client.t('error_update_check_failed'), e.message);
|
|
@@ -178,6 +177,8 @@ export const UpdateProvider = ({
|
|
|
178
177
|
return;
|
|
179
178
|
}
|
|
180
179
|
if (!rootInfo) {
|
|
180
|
+
// Check was skipped or failed with no cached result; keep the last
|
|
181
|
+
// known updateInfo instead of overwriting it with an empty object.
|
|
181
182
|
return;
|
|
182
183
|
}
|
|
183
184
|
const info = resolveCheckResult(rootInfo);
|
|
@@ -198,7 +199,7 @@ export const UpdateProvider = ({
|
|
|
198
199
|
if (downloadUrl && sharedState.apkStatus === null) {
|
|
199
200
|
if (options.updateStrategy === 'silentAndNow') {
|
|
200
201
|
if (Platform.OS === 'android' && downloadUrl.endsWith('.apk')) {
|
|
201
|
-
downloadAndInstallApk(downloadUrl);
|
|
202
|
+
downloadAndInstallApk(downloadUrl).catch(noop);
|
|
202
203
|
} else {
|
|
203
204
|
Linking.openURL(downloadUrl);
|
|
204
205
|
}
|
|
@@ -215,7 +216,7 @@ export const UpdateProvider = ({
|
|
|
215
216
|
Platform.OS === 'android' &&
|
|
216
217
|
downloadUrl.endsWith('.apk')
|
|
217
218
|
) {
|
|
218
|
-
downloadAndInstallApk(downloadUrl);
|
|
219
|
+
downloadAndInstallApk(downloadUrl).catch(noop);
|
|
219
220
|
} else {
|
|
220
221
|
Linking.openURL(downloadUrl);
|
|
221
222
|
}
|
|
@@ -229,7 +230,7 @@ export const UpdateProvider = ({
|
|
|
229
230
|
options.updateStrategy === 'silentAndNow' ||
|
|
230
231
|
options.updateStrategy === 'silentAndLater'
|
|
231
232
|
) {
|
|
232
|
-
downloadUpdate(info);
|
|
233
|
+
downloadUpdate(info).catch(noop);
|
|
233
234
|
return info;
|
|
234
235
|
}
|
|
235
236
|
alertUpdate(
|
|
@@ -244,7 +245,7 @@ export const UpdateProvider = ({
|
|
|
244
245
|
text: client.t('alert_confirm'),
|
|
245
246
|
style: 'default',
|
|
246
247
|
onPress: () => {
|
|
247
|
-
downloadUpdate();
|
|
248
|
+
downloadUpdate().catch(noop);
|
|
248
249
|
},
|
|
249
250
|
},
|
|
250
251
|
],
|
|
@@ -272,7 +273,7 @@ export const UpdateProvider = ({
|
|
|
272
273
|
if (!assertWeb()) {
|
|
273
274
|
return;
|
|
274
275
|
}
|
|
275
|
-
const { checkStrategy,
|
|
276
|
+
const { checkStrategy, autoMarkSuccess } = options;
|
|
276
277
|
if (autoMarkSuccess) {
|
|
277
278
|
setTimeout(() => {
|
|
278
279
|
markSuccess();
|
|
@@ -283,26 +284,35 @@ export const UpdateProvider = ({
|
|
|
283
284
|
'change',
|
|
284
285
|
nextAppState => {
|
|
285
286
|
if (nextAppState === 'active') {
|
|
286
|
-
checkUpdate();
|
|
287
|
+
checkUpdate().catch(noop);
|
|
287
288
|
}
|
|
288
289
|
},
|
|
289
290
|
);
|
|
290
291
|
}
|
|
291
292
|
if (checkStrategy === 'both' || checkStrategy === 'onAppStart') {
|
|
292
|
-
checkUpdate();
|
|
293
|
-
}
|
|
294
|
-
let dismissErrorTimer: ReturnType<typeof setTimeout>;
|
|
295
|
-
if (typeof dismissErrorAfter === 'number' && dismissErrorAfter > 0) {
|
|
296
|
-
dismissErrorTimer = setTimeout(() => {
|
|
297
|
-
dismissError();
|
|
298
|
-
}, dismissErrorAfter);
|
|
293
|
+
checkUpdate().catch(noop);
|
|
299
294
|
}
|
|
300
295
|
return () => {
|
|
301
296
|
stateListener.current && stateListener.current.remove();
|
|
302
|
-
clearTimeout(dismissErrorTimer);
|
|
303
297
|
};
|
|
304
298
|
}, [checkUpdate, options, dismissError, markSuccess, client]);
|
|
305
299
|
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
const { dismissErrorAfter } = options;
|
|
302
|
+
if (
|
|
303
|
+
lastError &&
|
|
304
|
+
typeof dismissErrorAfter === 'number' &&
|
|
305
|
+
dismissErrorAfter > 0
|
|
306
|
+
) {
|
|
307
|
+
const dismissErrorTimer = setTimeout(() => {
|
|
308
|
+
dismissError();
|
|
309
|
+
}, dismissErrorAfter);
|
|
310
|
+
return () => {
|
|
311
|
+
clearTimeout(dismissErrorTimer);
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}, [lastError, options, dismissError]);
|
|
315
|
+
|
|
306
316
|
const parseTestPayload = useCallback(
|
|
307
317
|
(payload: UpdateTestPayload) => {
|
|
308
318
|
if (payload && payload.type && payload.type.startsWith('__rnPushy')) {
|
|
@@ -314,15 +324,19 @@ export const UpdateProvider = ({
|
|
|
314
324
|
if (payload.type === '__rnPushyVersionHash') {
|
|
315
325
|
const toHash = payload.data;
|
|
316
326
|
sharedState.toHash = toHash;
|
|
317
|
-
checkUpdate({ extra: { toHash } })
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
327
|
+
checkUpdate({ extra: { toHash } })
|
|
328
|
+
.then(() => {
|
|
329
|
+
if (updateInfoRef.current && updateInfoRef.current.upToDate) {
|
|
330
|
+
Alert.alert(
|
|
331
|
+
client.t('alert_info'),
|
|
332
|
+
client.t('alert_no_update_wait'),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
.catch(noop)
|
|
337
|
+
.finally(() => {
|
|
338
|
+
options.logger = logger;
|
|
339
|
+
});
|
|
326
340
|
}
|
|
327
341
|
return true;
|
|
328
342
|
}
|
package/src/utils.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function joinUrls(paths: string[], fileName?: string) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export const testUrls = async (urls?: string[]) => {
|
|
87
|
+
export const testUrls = async (urls?: string[]): Promise<string | null> => {
|
|
88
88
|
if (!urls?.length) {
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
@@ -93,7 +93,7 @@ export const testUrls = async (urls?: string[]) => {
|
|
|
93
93
|
const ret = await promiseAny(urls.map(ping));
|
|
94
94
|
if (ret) {
|
|
95
95
|
log('ping success, use url:', ret);
|
|
96
|
-
return ret;
|
|
96
|
+
return ret as string;
|
|
97
97
|
}
|
|
98
98
|
} catch {}
|
|
99
99
|
log('all ping failed, use first url:', urls[0]);
|