react-native-update 10.44.0 → 10.46.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/DownloadTask.java +10 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +24 -0
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +19 -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 +15 -15
- package/cpp/patch_core/error_codes.h +43 -0
- package/cpp/patch_core/patch_core.cpp +16 -1
- package/cpp/patch_core/patch_core.h +6 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +77 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +1 -93
- package/harmony/pushy/src/main/ets/DownloadTask.ts +13 -10
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +20 -2
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +223 -132
- package/ios/RCTPushy/RCTPushyDownloader.mm +22 -1
- package/package.json +7 -3
- package/src/client.ts +151 -53
- package/src/context.ts +21 -7
- package/src/core.ts +8 -2
- package/src/endpoint.ts +4 -2
- package/src/error.ts +68 -0
- package/src/index.ts +10 -1
- package/src/locales/en.ts +5 -1
- package/src/locales/zh.ts +3 -1
- package/src/provider.tsx +101 -54
- package/src/type.ts +6 -0
- package/src/utils.ts +56 -16
- package/android/jni/DownloadTask.c +0 -56
- package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "RCTPushyDownloader.h"
|
|
3
3
|
#import "ZipArchive.h"
|
|
4
4
|
#include "../../cpp/patch_core/archive_patch_core.h"
|
|
5
|
+
#include "../../cpp/patch_core/error_codes.h"
|
|
5
6
|
#include "../../cpp/patch_core/patch_core.h"
|
|
6
7
|
#include "../../cpp/patch_core/state_core.h"
|
|
7
8
|
|
|
@@ -14,6 +15,9 @@
|
|
|
14
15
|
|
|
15
16
|
#import <React/RCTConvert.h>
|
|
16
17
|
#import <React/RCTLog.h>
|
|
18
|
+
#import <os/lock.h>
|
|
19
|
+
|
|
20
|
+
#include <atomic>
|
|
17
21
|
|
|
18
22
|
static NSString *const keyPushyInfo = @"REACTNATIVECN_PUSHY_INFO_KEY";
|
|
19
23
|
static NSString *const paramPackageVersion = @"packageVersion";
|
|
@@ -34,9 +38,15 @@ static NSString * const BUNDLE_FILE_NAME = @"index.bundlejs";
|
|
|
34
38
|
static NSString * const SOURCE_PATCH_NAME = @"__diff.json";
|
|
35
39
|
static NSString * const BUNDLE_PATCH_NAME = @"index.bundlejs.patch";
|
|
36
40
|
|
|
37
|
-
// error def
|
|
41
|
+
// error def — messages are human-readable; the stable cross-platform codes
|
|
42
|
+
// live in cpp/patch_core/error_codes.h and travel in PushyErrorCodeKey.
|
|
38
43
|
static NSString * const ERROR_OPTIONS = @"options error";
|
|
39
44
|
static NSString * const ERROR_FILE_OPERATION = @"file operation error";
|
|
45
|
+
static NSString * const PushyErrorCodeKey = @"PushyErrorCode";
|
|
46
|
+
|
|
47
|
+
static NSString *PushyCode(const char *code) {
|
|
48
|
+
return [NSString stringWithUTF8String:code];
|
|
49
|
+
}
|
|
40
50
|
|
|
41
51
|
// event def
|
|
42
52
|
static NSString * const EVENT_PROGRESS_DOWNLOAD = @"RCTPushyDownloadProgress";
|
|
@@ -52,7 +62,20 @@ typedef NS_ENUM(NSInteger, PushyType) {
|
|
|
52
62
|
//TASK_TYPE_PLAIN_DOWNLOAD=4?
|
|
53
63
|
};
|
|
54
64
|
|
|
55
|
-
static
|
|
65
|
+
static std::atomic<bool> ignoreRollback{false};
|
|
66
|
+
|
|
67
|
+
// Serializes every read-modify-write of the persisted update state. The state
|
|
68
|
+
// machine itself is a pure function (state_core), but callers run on different
|
|
69
|
+
// threads (main thread bundleURL, module method queue, _fileQueue), so the
|
|
70
|
+
// read→transform→write sequence must be atomic to avoid e.g. markSuccess being
|
|
71
|
+
// overwritten by a concurrent bundleURL and the version being rolled back.
|
|
72
|
+
static os_unfair_lock pushyStateLock = OS_UNFAIR_LOCK_INIT;
|
|
73
|
+
|
|
74
|
+
static void PushyWithStateLock(void (NS_NOESCAPE ^block)(void)) {
|
|
75
|
+
os_unfair_lock_lock(&pushyStateLock);
|
|
76
|
+
block();
|
|
77
|
+
os_unfair_lock_unlock(&pushyStateLock);
|
|
78
|
+
}
|
|
56
79
|
|
|
57
80
|
static std::string PushyToStdString(NSString *value) {
|
|
58
81
|
if (value == nil) {
|
|
@@ -64,7 +87,10 @@ static std::string PushyToStdString(NSString *value) {
|
|
|
64
87
|
static NSError *PushyNSErrorFromStatus(const pushy::patch::Status &status) {
|
|
65
88
|
return [NSError errorWithDomain:PushyErrorDomain
|
|
66
89
|
code:-1
|
|
67
|
-
userInfo:@{
|
|
90
|
+
userInfo:@{
|
|
91
|
+
NSLocalizedDescriptionKey: [NSString stringWithUTF8String:status.message.c_str()],
|
|
92
|
+
PushyErrorCodeKey: PushyCode(pushy::error_codes::kPatchFailed),
|
|
93
|
+
}];
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
static NSUserDefaults *PushyDefaults(void) {
|
|
@@ -102,21 +128,24 @@ static BOOL PushyStringIsBlank(NSString *value) {
|
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
static void PushyRejectError(RCTPromiseRejectBlock reject, NSError *error) {
|
|
105
|
-
|
|
131
|
+
// Prefer the stable cross-platform code (error_codes.h); fall back to the
|
|
132
|
+
// numeric NSError code for system errors that were not classified.
|
|
133
|
+
NSString *code = error.userInfo[PushyErrorCodeKey];
|
|
134
|
+
if (code == nil) {
|
|
135
|
+
code = [NSString stringWithFormat:@"%ld", (long)error.code];
|
|
136
|
+
}
|
|
137
|
+
reject(code, error.localizedDescription, error);
|
|
106
138
|
}
|
|
107
139
|
|
|
108
|
-
static NSError *
|
|
140
|
+
static NSError *PushyErrorWithCode(const char *code, NSString *message) {
|
|
109
141
|
return [NSError errorWithDomain:PushyErrorDomain
|
|
110
142
|
code:-1
|
|
111
143
|
userInfo:@{
|
|
112
144
|
NSLocalizedDescriptionKey: message ?: @"unknown error",
|
|
145
|
+
PushyErrorCodeKey: PushyCode(code),
|
|
113
146
|
}];
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
static void PushyRejectMessage(RCTPromiseRejectBlock reject, NSString *message) {
|
|
117
|
-
PushyRejectError(reject, PushyErrorWithMessage(message));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
149
|
static pushy::patch::PatchManifest PushyPatchManifestFromJson(NSDictionary *json) {
|
|
121
150
|
pushy::patch::PatchManifest manifest;
|
|
122
151
|
|
|
@@ -221,64 +250,84 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
221
250
|
|
|
222
251
|
+ (NSURL *)bundleURL
|
|
223
252
|
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
NSString *curBuildTime = [RCTPushy buildTime];
|
|
228
|
-
|
|
229
|
-
pushy::state::State state = PushyStateFromDefaults(defaults);
|
|
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
|
-
}
|
|
253
|
+
__block NSURL *resolvedURL = nil;
|
|
254
|
+
PushyWithStateLock(^{
|
|
255
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
240
256
|
|
|
241
|
-
|
|
242
|
-
|
|
257
|
+
NSString *curPackageVersion = [RCTPushy packageVersion];
|
|
258
|
+
NSString *curBuildTime = [RCTPushy buildTime];
|
|
259
|
+
|
|
260
|
+
pushy::state::State state = PushyStateFromDefaults(defaults);
|
|
261
|
+
pushy::state::BinaryVersionSyncResult sync = pushy::state::SyncBinaryVersion(
|
|
243
262
|
state,
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
PushyToStdString(curPackageVersion),
|
|
264
|
+
PushyToStdString(curBuildTime)
|
|
246
265
|
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
if (sync.changed) {
|
|
267
|
+
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
|
|
268
|
+
state = sync.state;
|
|
250
269
|
PushyApplyStateToDefaults(defaults, state);
|
|
251
270
|
}
|
|
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
271
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
272
|
+
if (!state.current_version.empty()) {
|
|
273
|
+
std::string const versionBeforeLaunch = state.current_version;
|
|
274
|
+
pushy::state::LaunchDecision decision = pushy::state::ResolveLaunchState(
|
|
275
|
+
state,
|
|
276
|
+
ignoreRollback.load(),
|
|
277
|
+
true
|
|
278
|
+
);
|
|
279
|
+
state = decision.state;
|
|
280
|
+
|
|
281
|
+
if (decision.did_rollback) {
|
|
282
|
+
// The crash-protection rollback: the new version never called
|
|
283
|
+
// markSuccess. Keep this visible in release logs.
|
|
284
|
+
RCTLogWarn(@"RCTPushy -- version %@ was not marked as successful, rolled back to %@",
|
|
285
|
+
PushyFromStdString(versionBeforeLaunch),
|
|
286
|
+
PushyFromStdString(state.current_version));
|
|
287
|
+
}
|
|
288
|
+
if (decision.did_rollback || decision.consumed_first_time) {
|
|
268
289
|
PushyApplyStateToDefaults(defaults, state);
|
|
269
|
-
|
|
290
|
+
}
|
|
291
|
+
if (decision.consumed_first_time) {
|
|
292
|
+
// bundleURL may be called many times, ignore rollbacks before process restarted again.
|
|
293
|
+
ignoreRollback = true;
|
|
294
|
+
[defaults setObject:@(YES) forKey:keyFirstLoadMarked];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
NSString *loadVersion = PushyFromStdString(decision.load_version);
|
|
298
|
+
NSString *downloadDir = [RCTPushy downloadDir];
|
|
299
|
+
// Guard the rollback chain against cycles: a corrupted state
|
|
300
|
+
// returning an already-visited version would otherwise spin this
|
|
301
|
+
// loop forever during startup (Android has the same guard).
|
|
302
|
+
NSMutableSet<NSString *> *visitedVersions = [NSMutableSet set];
|
|
303
|
+
while (loadVersion.length && ![visitedVersions containsObject:loadVersion]) {
|
|
304
|
+
[visitedVersions addObject:loadVersion];
|
|
305
|
+
NSString *bundlePath = [[downloadDir stringByAppendingPathComponent:loadVersion] stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
306
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) {
|
|
307
|
+
resolvedURL = [NSURL fileURLWithPath:bundlePath];
|
|
308
|
+
return;
|
|
309
|
+
} else {
|
|
310
|
+
RCTLogError(@"RCTPushy -- bundle version %@ not found, rolling back", loadVersion);
|
|
311
|
+
state = pushy::state::Rollback(state);
|
|
312
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
313
|
+
loadVersion = PushyFromStdString(state.current_version);
|
|
314
|
+
}
|
|
270
315
|
}
|
|
271
316
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return [RCTPushy binaryBundleURL];
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return resolvedURL ?: [RCTPushy binaryBundleURL];
|
|
275
320
|
}
|
|
276
321
|
|
|
277
322
|
+ (NSString *) rollback {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
323
|
+
__block NSString *currentVersion = nil;
|
|
324
|
+
PushyWithStateLock(^{
|
|
325
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
326
|
+
pushy::state::State state = pushy::state::Rollback(PushyStateFromDefaults(defaults));
|
|
327
|
+
PushyApplyStateToDefaults(defaults, state);
|
|
328
|
+
currentVersion = PushyFromStdString(state.current_version);
|
|
329
|
+
});
|
|
330
|
+
return currentVersion;
|
|
282
331
|
}
|
|
283
332
|
|
|
284
333
|
+ (BOOL)requiresMainQueueSetup
|
|
@@ -288,36 +337,37 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
288
337
|
|
|
289
338
|
- (NSDictionary *)constantsToExport
|
|
290
339
|
{
|
|
291
|
-
NSUserDefaults *defaults = PushyDefaults();
|
|
292
|
-
|
|
293
340
|
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
|
-
|
|
341
|
+
PushyWithStateLock(^{
|
|
342
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
343
|
+
|
|
344
|
+
ret[@"downloadRootDir"] = [RCTPushy downloadDir];
|
|
345
|
+
ret[@"packageVersion"] = [RCTPushy packageVersion];
|
|
346
|
+
ret[@"buildTime"] = [RCTPushy buildTime];
|
|
347
|
+
ret[@"rolledBackVersion"] = [defaults objectForKey:keyRolledBackMarked];
|
|
348
|
+
ret[@"isFirstTime"] = [defaults objectForKey:keyFirstLoadMarked];
|
|
349
|
+
ret[@"uuid"] = [defaults objectForKey:keyUuid];
|
|
350
|
+
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
|
|
351
|
+
NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion];
|
|
352
|
+
ret[@"currentVersion"] = currentVersion;
|
|
353
|
+
if (currentVersion != nil) {
|
|
354
|
+
ret[@"currentVersionInfo"] = [defaults objectForKey:PushyHashInfoKey(currentVersion)];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (ret[@"isFirstTime"]) {
|
|
358
|
+
[defaults removeObjectForKey:keyFirstLoadMarked];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (ret[@"rolledBackVersion"] != nil) {
|
|
362
|
+
[defaults removeObjectForKey:keyRolledBackMarked];
|
|
363
|
+
[self clearInvalidFiles];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if ([[defaults objectForKey:KeyPackageUpdatedMarked] boolValue]) {
|
|
367
|
+
[defaults removeObjectForKey:KeyPackageUpdatedMarked];
|
|
368
|
+
[self clearInvalidFiles];
|
|
369
|
+
}
|
|
370
|
+
});
|
|
321
371
|
|
|
322
372
|
return ret;
|
|
323
373
|
}
|
|
@@ -335,7 +385,7 @@ RCT_EXPORT_METHOD(setUuid:(NSString *)uuid resolver:(RCTPromiseResolveBlock)res
|
|
|
335
385
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
336
386
|
{
|
|
337
387
|
if (PushyStringIsBlank(uuid)) {
|
|
338
|
-
PushyRejectError(reject,
|
|
388
|
+
PushyRejectError(reject, PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
339
389
|
return;
|
|
340
390
|
}
|
|
341
391
|
|
|
@@ -349,7 +399,7 @@ RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash
|
|
|
349
399
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
350
400
|
{
|
|
351
401
|
if (PushyStringIsBlank(hash) || PushyStringIsBlank(value)) {
|
|
352
|
-
|
|
402
|
+
PushyRejectError(reject, PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
353
403
|
return;
|
|
354
404
|
}
|
|
355
405
|
|
|
@@ -362,7 +412,9 @@ RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash
|
|
|
362
412
|
|
|
363
413
|
resolve(@true);
|
|
364
414
|
} else {
|
|
365
|
-
PushyRejectError(reject,
|
|
415
|
+
PushyRejectError(reject, PushyErrorWithCode(
|
|
416
|
+
pushy::error_codes::kInvalidHashInfo,
|
|
417
|
+
error != nil ? error.localizedDescription : @"invalid json string"));
|
|
366
418
|
}
|
|
367
419
|
}
|
|
368
420
|
|
|
@@ -397,6 +449,15 @@ RCT_EXPORT_METHOD(downloadPatchFromPpk:(NSDictionary *)options
|
|
|
397
449
|
[self downloadUpdate:PushyTypePatchFromPpk options:options resolver:resolve rejecter:reject];
|
|
398
450
|
}
|
|
399
451
|
|
|
452
|
+
RCT_EXPORT_METHOD(downloadAndInstallApk:(NSDictionary *)options
|
|
453
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
454
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
455
|
+
{
|
|
456
|
+
PushyRejectError(reject, PushyErrorWithCode(
|
|
457
|
+
pushy::error_codes::kUnsupportedPlatform,
|
|
458
|
+
@"downloadAndInstallApk is only supported on Android"));
|
|
459
|
+
}
|
|
460
|
+
|
|
400
461
|
RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
|
|
401
462
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
402
463
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -438,13 +499,15 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
438
499
|
resolve(@true);
|
|
439
500
|
#else
|
|
440
501
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
pushy::state::
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
502
|
+
PushyWithStateLock(^{
|
|
503
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
504
|
+
pushy::state::MarkSuccessResult result =
|
|
505
|
+
pushy::state::MarkSuccess(PushyStateFromDefaults(defaults));
|
|
506
|
+
if (!result.stale_version_to_delete.empty()) {
|
|
507
|
+
[defaults removeObjectForKey:PushyHashInfoKey(PushyFromStdString(result.stale_version_to_delete))];
|
|
508
|
+
}
|
|
509
|
+
PushyApplyStateToDefaults(defaults, result.state);
|
|
510
|
+
});
|
|
448
511
|
|
|
449
512
|
[self clearInvalidFiles];
|
|
450
513
|
resolve(@true);
|
|
@@ -476,6 +539,12 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
476
539
|
{
|
|
477
540
|
[self performUpdate:type options:options callback:^(NSError *error) {
|
|
478
541
|
if (error != nil) {
|
|
542
|
+
if (error.userInfo[PushyErrorCodeKey] == nil) {
|
|
543
|
+
// Unclassified (system/network) errors from the download
|
|
544
|
+
// pipeline; keep the original message.
|
|
545
|
+
error = PushyErrorWithCode(pushy::error_codes::kDownloadFailed,
|
|
546
|
+
error.localizedDescription);
|
|
547
|
+
}
|
|
479
548
|
PushyRejectError(reject, error);
|
|
480
549
|
return;
|
|
481
550
|
}
|
|
@@ -501,24 +570,38 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
501
570
|
NSString *hash = PushyOptionString(options, @"hash");
|
|
502
571
|
|
|
503
572
|
if (PushyStringIsBlank(updateUrl) || PushyStringIsBlank(hash)) {
|
|
504
|
-
callback(
|
|
573
|
+
callback(PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
505
574
|
return;
|
|
506
575
|
}
|
|
507
576
|
NSString *originHash = PushyOptionString(options, @"originHash");
|
|
508
577
|
if (type == PushyTypePatchFromPpk && PushyStringIsBlank(originHash)) {
|
|
509
|
-
callback(
|
|
578
|
+
callback(PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
510
579
|
return;
|
|
511
580
|
}
|
|
512
581
|
|
|
513
582
|
NSString *dir = [RCTPushy downloadDir];
|
|
514
583
|
BOOL success = [self ensureDirectoryExistsAtPath:dir];
|
|
515
584
|
if (!success) {
|
|
516
|
-
callback(
|
|
585
|
+
callback(PushyErrorWithCode(pushy::error_codes::kFileOperationFailed, ERROR_FILE_OPERATION));
|
|
517
586
|
return;
|
|
518
587
|
}
|
|
519
588
|
|
|
520
589
|
NSString *zipFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%@",hash, [self zipExtension:type]]];
|
|
521
590
|
|
|
591
|
+
// On failure, remove the partial version directory like Android/Harmony
|
|
592
|
+
// do: a half-unzipped/half-patched dir leaks disk and could later be
|
|
593
|
+
// mistaken for a complete version. hash is validated non-blank above, so
|
|
594
|
+
// this can never resolve to the download root itself.
|
|
595
|
+
NSString *unzipDir = [dir stringByAppendingPathComponent:hash];
|
|
596
|
+
void (^completion)(NSError *) = ^(NSError *error) {
|
|
597
|
+
if (error != nil) {
|
|
598
|
+
dispatch_async(self->_fileQueue, ^{
|
|
599
|
+
[[NSFileManager defaultManager] removeItemAtPath:unzipDir error:nil];
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
callback(error);
|
|
603
|
+
};
|
|
604
|
+
|
|
522
605
|
RCTLogInfo(@"RCTPushy -- download file %@", updateUrl);
|
|
523
606
|
[RCTPushyDownloader download:updateUrl savePath:zipFilePath progressHandler:^(long long receivedBytes, long long totalBytes) {
|
|
524
607
|
if (self->hasListeners) {
|
|
@@ -530,14 +613,14 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
530
613
|
}
|
|
531
614
|
} completionHandler:^(NSString *path, NSError *error) {
|
|
532
615
|
if (error != nil) {
|
|
533
|
-
|
|
616
|
+
completion(error);
|
|
534
617
|
return;
|
|
535
618
|
}
|
|
536
619
|
[self unzipDownloadedPackage:zipFilePath
|
|
537
620
|
hash:hash
|
|
538
621
|
type:type
|
|
539
622
|
originHash:originHash
|
|
540
|
-
callback:
|
|
623
|
+
callback:completion];
|
|
541
624
|
}];
|
|
542
625
|
}
|
|
543
626
|
|
|
@@ -603,18 +686,21 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
603
686
|
NSString *destination = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
|
|
604
687
|
NSData *data = [NSData dataWithContentsOfFile:sourcePatch];
|
|
605
688
|
if (data == nil) {
|
|
606
|
-
callback(
|
|
689
|
+
callback(PushyErrorWithCode(pushy::error_codes::kPatchFailed, @"missing patch manifest"));
|
|
607
690
|
return;
|
|
608
691
|
}
|
|
609
692
|
|
|
610
693
|
NSError *error = nil;
|
|
611
694
|
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
|
|
612
695
|
if (error != nil) {
|
|
613
|
-
|
|
696
|
+
// Classify as a patch failure like the sibling manifest branches;
|
|
697
|
+
// unclassified errors would otherwise be tagged DOWNLOAD_FAILED by the
|
|
698
|
+
// downloadUpdate fallback even though the download itself succeeded.
|
|
699
|
+
callback(PushyErrorWithCode(pushy::error_codes::kPatchFailed, error.localizedDescription));
|
|
614
700
|
return;
|
|
615
701
|
}
|
|
616
702
|
if (![jsonObject isKindOfClass:[NSDictionary class]]) {
|
|
617
|
-
callback(
|
|
703
|
+
callback(PushyErrorWithCode(pushy::error_codes::kPatchFailed, @"invalid patch manifest"));
|
|
618
704
|
return;
|
|
619
705
|
}
|
|
620
706
|
NSDictionary *json = (NSDictionary *)jsonObject;
|
|
@@ -669,43 +755,44 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
669
755
|
{
|
|
670
756
|
if (PushyStringIsBlank(hash)) {
|
|
671
757
|
if (error != NULL) {
|
|
672
|
-
*error =
|
|
758
|
+
*error = PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS);
|
|
673
759
|
}
|
|
674
760
|
return NO;
|
|
675
761
|
}
|
|
676
762
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
763
|
+
PushyWithStateLock(^{
|
|
764
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
765
|
+
pushy::state::State next = pushy::state::SwitchVersion(
|
|
766
|
+
PushyStateFromDefaults(defaults),
|
|
767
|
+
PushyToStdString(hash)
|
|
768
|
+
);
|
|
769
|
+
PushyApplyStateToDefaults(defaults, next);
|
|
770
|
+
// Re-enable first-load consumption and rollback checks for the newly selected bundle.
|
|
771
|
+
ignoreRollback = false;
|
|
772
|
+
});
|
|
685
773
|
return YES;
|
|
686
774
|
}
|
|
687
775
|
|
|
688
776
|
- (BOOL)ensureDirectoryExistsAtPath:(NSString *)path
|
|
689
777
|
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
778
|
+
// No _fileQueue hop here: that queue also runs multi-second unzip/patch
|
|
779
|
+
// work, and a dispatch_sync onto it would block the whole module method
|
|
780
|
+
// queue for the duration. createDirectoryAtPath is idempotent and
|
|
781
|
+
// thread-safe, so checking inline is fine.
|
|
782
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
783
|
+
BOOL isDirectory = NO;
|
|
784
|
+
if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory]) {
|
|
785
|
+
return isDirectory;
|
|
786
|
+
}
|
|
699
787
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
788
|
+
NSError *error = nil;
|
|
789
|
+
BOOL success = [fileManager createDirectoryAtPath:path
|
|
790
|
+
withIntermediateDirectories:YES
|
|
791
|
+
attributes:nil
|
|
792
|
+
error:&error];
|
|
793
|
+
if (!success && error != nil) {
|
|
794
|
+
RCTLogWarn(@"Pushy create directory error: %@", error.localizedDescription);
|
|
795
|
+
}
|
|
709
796
|
|
|
710
797
|
return success;
|
|
711
798
|
}
|
|
@@ -731,7 +818,7 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
731
818
|
|
|
732
819
|
NSError *unzipError = error;
|
|
733
820
|
if (!succeeded && unzipError == nil) {
|
|
734
|
-
unzipError =
|
|
821
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kPatchFailed, @"unzip failed");
|
|
735
822
|
}
|
|
736
823
|
completionHandler(unzipError);
|
|
737
824
|
}];
|
|
@@ -741,8 +828,12 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
|
741
828
|
- (void)clearInvalidFiles
|
|
742
829
|
{
|
|
743
830
|
dispatch_async(_fileQueue, ^{
|
|
744
|
-
|
|
745
|
-
|
|
831
|
+
// Snapshot the state under the lock, but run the (slow) filesystem
|
|
832
|
+
// cleanup outside of it so state operations are not blocked.
|
|
833
|
+
__block pushy::state::State state;
|
|
834
|
+
PushyWithStateLock(^{
|
|
835
|
+
state = PushyStateFromDefaults(PushyDefaults());
|
|
836
|
+
});
|
|
746
837
|
NSString *downloadDir = [RCTPushy downloadDir];
|
|
747
838
|
pushy::patch::Status status = pushy::patch::CleanupOldEntries(
|
|
748
839
|
PushyToStdString(downloadDir),
|
|
@@ -113,7 +113,8 @@ didFinishDownloadingToURL:(NSURL *)location
|
|
|
113
113
|
// before touching savePath so an existing valid package is not destroyed.
|
|
114
114
|
NSURLResponse *response = downloadTask.response;
|
|
115
115
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
116
|
-
|
|
116
|
+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
117
|
+
NSInteger statusCode = httpResponse.statusCode;
|
|
117
118
|
if (statusCode < 200 || statusCode >= 300) {
|
|
118
119
|
self.fileError = [NSError errorWithDomain:RCTPushyDownloaderErrorDomain
|
|
119
120
|
code:statusCode
|
|
@@ -122,6 +123,26 @@ didFinishDownloadingToURL:(NSURL *)location
|
|
|
122
123
|
}];
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
126
|
+
|
|
127
|
+
// Reject truncated transfers like Android/Harmony do. Skip the check
|
|
128
|
+
// for encoded responses (e.g. gzip): NSURLSession decompresses them
|
|
129
|
+
// transparently, so the on-disk size legitimately differs from the
|
|
130
|
+
// Content-Length of the encoded body.
|
|
131
|
+
NSString *contentEncoding = [httpResponse.allHeaderFields[@"Content-Encoding"] lowercaseString];
|
|
132
|
+
BOOL isEncodedBody = contentEncoding.length > 0 && ![contentEncoding isEqualToString:@"identity"];
|
|
133
|
+
long long expectedLength = httpResponse.expectedContentLength;
|
|
134
|
+
if (!isEncodedBody && expectedLength > 0) {
|
|
135
|
+
NSNumber *fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:location.path
|
|
136
|
+
error:nil][NSFileSize];
|
|
137
|
+
if (fileSize != nil && fileSize.longLongValue != expectedLength) {
|
|
138
|
+
self.fileError = [NSError errorWithDomain:RCTPushyDownloaderErrorDomain
|
|
139
|
+
code:-1
|
|
140
|
+
userInfo:@{
|
|
141
|
+
NSLocalizedDescriptionKey: [NSString stringWithFormat:@"download incomplete: expected %lld bytes, got %lld", expectedLength, fileSize.longLongValue],
|
|
142
|
+
}];
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
125
146
|
}
|
|
126
147
|
|
|
127
148
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.46.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
"publish:local": "SKIP_NATIVE_BUILD=1 npm publish",
|
|
10
10
|
"postinstall": "node scripts/check-expo-version.js",
|
|
11
11
|
"prepack": "bun submodule && bun lint",
|
|
12
|
-
"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",
|
|
13
14
|
"submodule": "git submodule update --init --recursive",
|
|
14
15
|
"test": "bun test src/__tests__",
|
|
15
16
|
"test:patch-core": "./scripts/test-patch-core.sh",
|
|
16
17
|
"build:harmony-har": "node scripts/build-harmony-har.js",
|
|
17
|
-
"build:
|
|
18
|
+
"build:harmony-e2e": "bash Example/e2etest/scripts/build-harmony-e2e.sh",
|
|
19
|
+
"build:so": "bun submodule && bash scripts/build-android-so.sh",
|
|
18
20
|
"build:ios-debug": "cd Example/e2etest && bun && detox build --configuration ios.sim.debug",
|
|
19
21
|
"build:ios-release": "cd Example/e2etest && bun && detox build --configuration ios.sim.release",
|
|
20
22
|
"test:ios-debug": "cd Example/e2etest && E2E_PLATFORM=ios detox test --configuration ios.sim.debug",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"@types/jest": "^30.0.0",
|
|
69
71
|
"@types/node": "^25.5.0",
|
|
70
72
|
"@types/react": "^19.2.14",
|
|
73
|
+
"@types/react-test-renderer": "18.0.7",
|
|
71
74
|
"detox": "^20.50.1",
|
|
72
75
|
"eslint": "^8.57.0",
|
|
73
76
|
"firebase-tools": "^15.11.0",
|
|
@@ -76,6 +79,7 @@
|
|
|
76
79
|
"prettier": "^2",
|
|
77
80
|
"react": "18.2.0",
|
|
78
81
|
"react-native": "0.73",
|
|
82
|
+
"react-test-renderer": "18.2.0",
|
|
79
83
|
"ts-jest": "^29.4.6",
|
|
80
84
|
"typescript": "^5.6.3"
|
|
81
85
|
}
|