react-native-update 10.43.3 → 10.45.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/jni/Android.mk +0 -1
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +24 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +33 -9
- 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 +8 -4
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -1
- 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.cpp +16 -1
- package/cpp/patch_core/patch_core.h +6 -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 +109 -2
- package/cpp/patch_core/update_core_android.cpp +43 -69
- package/harmony/pushy/src/main/cpp/pushy.cpp +163 -128
- package/harmony/pushy/src/main/ets/DownloadTask.ts +72 -19
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -6
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +10 -10
- package/harmony/pushy/src/main/ets/UpdateContext.ts +26 -9
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +142 -111
- package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
- package/package.json +7 -3
- package/src/client.ts +161 -101
- package/src/context.ts +21 -7
- package/src/core.ts +5 -1
- package/src/index.ts +7 -1
- package/src/provider.tsx +91 -56
- package/src/utils.ts +58 -18
- package/android/jni/DownloadTask.c +0 -56
- package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
- 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
|
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
#import <React/RCTConvert.h>
|
|
16
16
|
#import <React/RCTLog.h>
|
|
17
|
+
#import <os/lock.h>
|
|
18
|
+
|
|
19
|
+
#include <atomic>
|
|
17
20
|
|
|
18
21
|
static NSString *const keyPushyInfo = @"REACTNATIVECN_PUSHY_INFO_KEY";
|
|
19
22
|
static NSString *const paramPackageVersion = @"packageVersion";
|
|
@@ -52,7 +55,20 @@ typedef NS_ENUM(NSInteger, PushyType) {
|
|
|
52
55
|
//TASK_TYPE_PLAIN_DOWNLOAD=4?
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
static
|
|
58
|
+
static std::atomic<bool> ignoreRollback{false};
|
|
59
|
+
|
|
60
|
+
// Serializes every read-modify-write of the persisted update state. The state
|
|
61
|
+
// machine itself is a pure function (state_core), but callers run on different
|
|
62
|
+
// threads (main thread bundleURL, module method queue, _fileQueue), so the
|
|
63
|
+
// read→transform→write sequence must be atomic to avoid e.g. markSuccess being
|
|
64
|
+
// overwritten by a concurrent bundleURL and the version being rolled back.
|
|
65
|
+
static os_unfair_lock pushyStateLock = OS_UNFAIR_LOCK_INIT;
|
|
66
|
+
|
|
67
|
+
static void PushyWithStateLock(void (NS_NOESCAPE ^block)(void)) {
|
|
68
|
+
os_unfair_lock_lock(&pushyStateLock);
|
|
69
|
+
block();
|
|
70
|
+
os_unfair_lock_unlock(&pushyStateLock);
|
|
71
|
+
}
|
|
56
72
|
|
|
57
73
|
static std::string PushyToStdString(NSString *value) {
|
|
58
74
|
if (value == nil) {
|
|
@@ -221,64 +237,71 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
221
237
|
|
|
222
238
|
+ (NSURL *)bundleURL
|
|
223
239
|
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
pushy::state::BinaryVersionSyncResult sync = pushy::state::SyncBinaryVersion(
|
|
231
|
-
state,
|
|
232
|
-
PushyToStdString(curPackageVersion),
|
|
233
|
-
PushyToStdString(curBuildTime)
|
|
234
|
-
);
|
|
235
|
-
if (sync.changed) {
|
|
236
|
-
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
|
|
237
|
-
state = sync.state;
|
|
238
|
-
PushyApplyStateToDefaults(defaults, state);
|
|
239
|
-
}
|
|
240
|
+
__block NSURL *resolvedURL = nil;
|
|
241
|
+
PushyWithStateLock(^{
|
|
242
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
243
|
+
|
|
244
|
+
NSString *curPackageVersion = [RCTPushy packageVersion];
|
|
245
|
+
NSString *curBuildTime = [RCTPushy buildTime];
|
|
240
246
|
|
|
241
|
-
|
|
242
|
-
pushy::state::
|
|
247
|
+
pushy::state::State state = PushyStateFromDefaults(defaults);
|
|
248
|
+
pushy::state::BinaryVersionSyncResult sync = pushy::state::SyncBinaryVersion(
|
|
243
249
|
state,
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
PushyToStdString(curPackageVersion),
|
|
251
|
+
PushyToStdString(curBuildTime)
|
|
246
252
|
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
253
|
+
if (sync.changed) {
|
|
254
|
+
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
|
|
255
|
+
state = sync.state;
|
|
250
256
|
PushyApplyStateToDefaults(defaults, state);
|
|
251
257
|
}
|
|
252
|
-
if (decision.consumed_first_time) {
|
|
253
|
-
// bundleURL may be called many times, ignore rollbacks before process restarted again.
|
|
254
|
-
ignoreRollback = true;
|
|
255
|
-
[defaults setObject:@(YES) forKey:keyFirstLoadMarked];
|
|
256
|
-
}
|
|
257
258
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
state = pushy::state::Rollback(state);
|
|
259
|
+
if (!state.current_version.empty()) {
|
|
260
|
+
pushy::state::LaunchDecision decision = pushy::state::ResolveLaunchState(
|
|
261
|
+
state,
|
|
262
|
+
ignoreRollback.load(),
|
|
263
|
+
true
|
|
264
|
+
);
|
|
265
|
+
state = decision.state;
|
|
266
|
+
|
|
267
|
+
if (decision.did_rollback || decision.consumed_first_time) {
|
|
268
268
|
PushyApplyStateToDefaults(defaults, state);
|
|
269
|
-
|
|
269
|
+
}
|
|
270
|
+
if (decision.consumed_first_time) {
|
|
271
|
+
// bundleURL may be called many times, ignore rollbacks before process restarted again.
|
|
272
|
+
ignoreRollback = true;
|
|
273
|
+
[defaults setObject:@(YES) forKey:keyFirstLoadMarked];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
NSString *loadVersion = PushyFromStdString(decision.load_version);
|
|
277
|
+
NSString *downloadDir = [RCTPushy downloadDir];
|
|
278
|
+
while (loadVersion.length) {
|
|
279
|
+
NSString *bundlePath = [[downloadDir stringByAppendingPathComponent:loadVersion] stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
280
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) {
|
|
281
|
+
resolvedURL = [NSURL fileURLWithPath:bundlePath];
|
|
282
|
+
return;
|
|
283
|
+
} else {
|
|
284
|
+
RCTLogError(@"RCTPushy -- bundle version %@ not found", loadVersion);
|
|
285
|
+
state = pushy::state::Rollback(state);
|
|
286
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
287
|
+
loadVersion = PushyFromStdString(state.current_version);
|
|
288
|
+
}
|
|
270
289
|
}
|
|
271
290
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return [RCTPushy binaryBundleURL];
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return resolvedURL ?: [RCTPushy binaryBundleURL];
|
|
275
294
|
}
|
|
276
295
|
|
|
277
296
|
+ (NSString *) rollback {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
297
|
+
__block NSString *currentVersion = nil;
|
|
298
|
+
PushyWithStateLock(^{
|
|
299
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
300
|
+
pushy::state::State state = pushy::state::Rollback(PushyStateFromDefaults(defaults));
|
|
301
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
302
|
+
currentVersion = PushyFromStdString(state.current_version);
|
|
303
|
+
});
|
|
304
|
+
return currentVersion;
|
|
282
305
|
}
|
|
283
306
|
|
|
284
307
|
+ (BOOL)requiresMainQueueSetup
|
|
@@ -288,36 +311,37 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
288
311
|
|
|
289
312
|
- (NSDictionary *)constantsToExport
|
|
290
313
|
{
|
|
291
|
-
NSUserDefaults *defaults = PushyDefaults();
|
|
292
|
-
|
|
293
314
|
NSMutableDictionary *ret = [NSMutableDictionary new];
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
PushyWithStateLock(^{
|
|
316
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
317
|
+
|
|
318
|
+
ret[@"downloadRootDir"] = [RCTPushy downloadDir];
|
|
319
|
+
ret[@"packageVersion"] = [RCTPushy packageVersion];
|
|
320
|
+
ret[@"buildTime"] = [RCTPushy buildTime];
|
|
321
|
+
ret[@"rolledBackVersion"] = [defaults objectForKey:keyRolledBackMarked];
|
|
322
|
+
ret[@"isFirstTime"] = [defaults objectForKey:keyFirstLoadMarked];
|
|
323
|
+
ret[@"uuid"] = [defaults objectForKey:keyUuid];
|
|
324
|
+
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
|
|
325
|
+
NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion];
|
|
326
|
+
ret[@"currentVersion"] = currentVersion;
|
|
327
|
+
if (currentVersion != nil) {
|
|
328
|
+
ret[@"currentVersionInfo"] = [defaults objectForKey:PushyHashInfoKey(currentVersion)];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (ret[@"isFirstTime"]) {
|
|
332
|
+
[defaults removeObjectForKey:keyFirstLoadMarked];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (ret[@"rolledBackVersion"] != nil) {
|
|
336
|
+
[defaults removeObjectForKey:keyRolledBackMarked];
|
|
337
|
+
[self clearInvalidFiles];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if ([[defaults objectForKey:KeyPackageUpdatedMarked] boolValue]) {
|
|
341
|
+
[defaults removeObjectForKey:KeyPackageUpdatedMarked];
|
|
342
|
+
[self clearInvalidFiles];
|
|
343
|
+
}
|
|
344
|
+
});
|
|
321
345
|
|
|
322
346
|
return ret;
|
|
323
347
|
}
|
|
@@ -438,13 +462,15 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
438
462
|
resolve(@true);
|
|
439
463
|
#else
|
|
440
464
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
pushy::state::
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
465
|
+
PushyWithStateLock(^{
|
|
466
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
467
|
+
pushy::state::MarkSuccessResult result =
|
|
468
|
+
pushy::state::MarkSuccess(PushyStateFromDefaults(defaults));
|
|
469
|
+
if (!result.stale_version_to_delete.empty()) {
|
|
470
|
+
[defaults removeObjectForKey:PushyHashInfoKey(PushyFromStdString(result.stale_version_to_delete))];
|
|
471
|
+
}
|
|
472
|
+
PushyApplyStateToDefaults(defaults, result.state);
|
|
473
|
+
});
|
|
448
474
|
|
|
449
475
|
[self clearInvalidFiles];
|
|
450
476
|
resolve(@true);
|
|
@@ -674,38 +700,39 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
674
700
|
return NO;
|
|
675
701
|
}
|
|
676
702
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
703
|
+
PushyWithStateLock(^{
|
|
704
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
705
|
+
pushy::state::State next = pushy::state::SwitchVersion(
|
|
706
|
+
PushyStateFromDefaults(defaults),
|
|
707
|
+
PushyToStdString(hash)
|
|
708
|
+
);
|
|
709
|
+
PushyApplyStateToDefaults(defaults, next);
|
|
710
|
+
// Re-enable first-load consumption and rollback checks for the newly selected bundle.
|
|
711
|
+
ignoreRollback = false;
|
|
712
|
+
});
|
|
685
713
|
return YES;
|
|
686
714
|
}
|
|
687
715
|
|
|
688
716
|
- (BOOL)ensureDirectoryExistsAtPath:(NSString *)path
|
|
689
717
|
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
718
|
+
// No _fileQueue hop here: that queue also runs multi-second unzip/patch
|
|
719
|
+
// work, and a dispatch_sync onto it would block the whole module method
|
|
720
|
+
// queue for the duration. createDirectoryAtPath is idempotent and
|
|
721
|
+
// thread-safe, so checking inline is fine.
|
|
722
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
723
|
+
BOOL isDirectory = NO;
|
|
724
|
+
if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory]) {
|
|
725
|
+
return isDirectory;
|
|
726
|
+
}
|
|
699
727
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
728
|
+
NSError *error = nil;
|
|
729
|
+
BOOL success = [fileManager createDirectoryAtPath:path
|
|
730
|
+
withIntermediateDirectories:YES
|
|
731
|
+
attributes:nil
|
|
732
|
+
error:&error];
|
|
733
|
+
if (!success && error != nil) {
|
|
734
|
+
RCTLogWarn(@"Pushy create directory error: %@", error.localizedDescription);
|
|
735
|
+
}
|
|
709
736
|
|
|
710
737
|
return success;
|
|
711
738
|
}
|
|
@@ -741,8 +768,12 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
741
768
|
- (void)clearInvalidFiles
|
|
742
769
|
{
|
|
743
770
|
dispatch_async(_fileQueue, ^{
|
|
744
|
-
|
|
745
|
-
|
|
771
|
+
// Snapshot the state under the lock, but run the (slow) filesystem
|
|
772
|
+
// cleanup outside of it so state operations are not blocked.
|
|
773
|
+
__block pushy::state::State state;
|
|
774
|
+
PushyWithStateLock(^{
|
|
775
|
+
state = PushyStateFromDefaults(PushyDefaults());
|
|
776
|
+
});
|
|
746
777
|
NSString *downloadDir = [RCTPushy downloadDir];
|
|
747
778
|
pushy::patch::Status status = pushy::patch::CleanupOldEntries(
|
|
748
779
|
PushyToStdString(downloadDir),
|
|
@@ -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,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.45.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",
|
|
9
10
|
"postinstall": "node scripts/check-expo-version.js",
|
|
10
11
|
"prepack": "bun submodule && bun lint",
|
|
11
|
-
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
|
|
12
|
+
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit && node scripts/check-harmony-types.js",
|
|
13
|
+
"lint:harmony": "node scripts/check-harmony-types.js",
|
|
12
14
|
"submodule": "git submodule update --init --recursive",
|
|
13
15
|
"test": "bun test src/__tests__",
|
|
14
16
|
"test:patch-core": "./scripts/test-patch-core.sh",
|
|
15
17
|
"build:harmony-har": "node scripts/build-harmony-har.js",
|
|
16
|
-
"build:so": "bun submodule &&
|
|
18
|
+
"build:so": "bun submodule && bash scripts/build-android-so.sh",
|
|
17
19
|
"build:ios-debug": "cd Example/e2etest && bun && detox build --configuration ios.sim.debug",
|
|
18
20
|
"build:ios-release": "cd Example/e2etest && bun && detox build --configuration ios.sim.release",
|
|
19
21
|
"test:ios-debug": "cd Example/e2etest && E2E_PLATFORM=ios detox test --configuration ios.sim.debug",
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
"@types/jest": "^30.0.0",
|
|
68
70
|
"@types/node": "^25.5.0",
|
|
69
71
|
"@types/react": "^19.2.14",
|
|
72
|
+
"@types/react-test-renderer": "18.0.7",
|
|
70
73
|
"detox": "^20.50.1",
|
|
71
74
|
"eslint": "^8.57.0",
|
|
72
75
|
"firebase-tools": "^15.11.0",
|
|
@@ -75,6 +78,7 @@
|
|
|
75
78
|
"prettier": "^2",
|
|
76
79
|
"react": "18.2.0",
|
|
77
80
|
"react-native": "0.73",
|
|
81
|
+
"react-test-renderer": "18.2.0",
|
|
78
82
|
"ts-jest": "^29.4.6",
|
|
79
83
|
"typescript": "^5.6.3"
|
|
80
84
|
}
|