@bravemobile/react-native-code-push 13.1.0-beta.4 → 13.1.0-beta.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.
- package/CodePush.podspec +5 -0
- package/README.md +15 -145
- package/android/app/src/main/java/com/microsoft/codepush/react/ArchiveAttemptLog.java +181 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/{BinaryPatchResult.java → ArchiveRestoreResult.java} +17 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +20 -20
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +20 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushErrorCode.java +86 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushHttpException.java +29 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushIncompleteDownloadException.java +17 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +12 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +143 -43
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +31 -31
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushErrorCodeTest.java +90 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +159 -19
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +304 -15
- package/cli/commands/createHistoryCommand/createReleaseHistory.test.ts +133 -0
- package/cli/commands/createHistoryCommand/createReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/addToReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/release.test.ts +3 -3
- package/cli/commands/updateHistoryCommand/updateReleaseHistory.ts +3 -11
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +2 -8
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.test.js +95 -0
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +2 -8
- package/cli/dist/commands/releaseCommand/release.test.js +3 -3
- package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +2 -8
- package/cli/dist/functions/stageReleaseHistoryFile.js +28 -0
- package/cli/functions/stageReleaseHistoryFile.ts +38 -0
- package/ios/CodePush/CodePush.h +6 -3
- package/ios/CodePush/CodePush.mm +10 -10
- package/ios/CodePush/CodePushBinaryPatch.h +18 -8
- package/ios/CodePush/CodePushBinaryPatch.m +23 -22
- package/ios/CodePush/CodePushDownloadHandler.m +2 -1
- package/ios/CodePush/CodePushErrorUtils.m +55 -0
- package/ios/CodePush/CodePushPackage.m +242 -71
- package/package.json +2 -1
- package/src/CodePush.js +28 -18
- package/src/CodePush.test.js +110 -55
- package/src/package-mixins.js +19 -10
- package/typings/react-native-code-push.d.ts +110 -29
- package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +0 -92
|
@@ -10,15 +10,22 @@
|
|
|
10
10
|
|
|
11
11
|
#pragma mark - Private constants
|
|
12
12
|
|
|
13
|
+
static NSString *const AssetDiffDownloadUrlKey = @"assetDiffDownloadUrl";
|
|
14
|
+
static NSString *const UpdateArchiveAssetDiff = @"asset-diff";
|
|
15
|
+
static NSString *const UpdateArchiveBinaryPatch = @"binary-patch";
|
|
13
16
|
static NSString *const BinaryPatchDownloadUrlKey = @"binaryPatchDownloadUrl";
|
|
14
17
|
static NSString *const BinaryPatchFolderName = @"binary-patch";
|
|
15
18
|
// The fields of what a download that tried a patch reports back with. They are read by the
|
|
16
19
|
// app that asked for the download and by nothing else, so they never reach a file.
|
|
17
|
-
static NSString *const
|
|
18
|
-
static NSString *const
|
|
19
|
-
static NSString *const
|
|
20
|
-
static NSString *const
|
|
21
|
-
static NSString *const
|
|
20
|
+
static NSString *const UpdateArchiveAttemptApplyDurationMsKey = @"applyDurationMs";
|
|
21
|
+
static NSString *const UpdateArchiveAttemptDurationMsKey = @"durationMs";
|
|
22
|
+
static NSString *const UpdateArchiveResultArchiveKey = @"archive";
|
|
23
|
+
static NSString *const UpdateArchiveResultAttemptsKey = @"attempts";
|
|
24
|
+
static NSString *const UpdateArchiveResultFallbackReasonKey = @"fallbackReason";
|
|
25
|
+
static NSString *const UpdateArchiveResultStatusKey = @"status";
|
|
26
|
+
static NSString *const UpdateArchiveResultTotalDurationMsKey = @"totalDurationMs";
|
|
27
|
+
static NSString *const UpdateArchiveResultStatusApplied = @"applied";
|
|
28
|
+
static NSString *const UpdateArchiveResultStatusFallback = @"fallback";
|
|
22
29
|
static NSString *const DiffManifestFileName = @"hotcodepush.json";
|
|
23
30
|
static NSString *const DownloadFileName = @"download.zip";
|
|
24
31
|
static NSString *const DownloadUrlKey = @"downloadUrl";
|
|
@@ -58,10 +65,28 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
58
65
|
expectedBundleFileName:(NSString *)expectedBundleFileName
|
|
59
66
|
operationQueue:(dispatch_queue_t)operationQueue
|
|
60
67
|
progressCallback:(void (^)(long long, long long))progressCallback
|
|
61
|
-
doneCallback:(void (^)(NSDictionary *
|
|
68
|
+
doneCallback:(void (^)(NSDictionary *updateArchiveResult))doneCallback
|
|
62
69
|
failCallback:(void (^)(NSError *err))failCallback
|
|
63
70
|
{
|
|
64
|
-
|
|
71
|
+
// A release that was published with a binary patch offers up to three archives of the
|
|
72
|
+
// same update. The asset diff is the smallest and is tried first, the patch archive
|
|
73
|
+
// stands in when the diff fails on its asset side, and the full archive is always
|
|
74
|
+
// there when none of it works out.
|
|
75
|
+
NSMutableArray<NSDictionary *> *archivesToTry = [NSMutableArray array];
|
|
76
|
+
NSString *assetDiffDownloadUrl = updatePackage[AssetDiffDownloadUrlKey];
|
|
77
|
+
if ([assetDiffDownloadUrl isKindOfClass:[NSString class]] && [assetDiffDownloadUrl length] > 0) {
|
|
78
|
+
[archivesToTry addObject:@{ UpdateArchiveResultArchiveKey: UpdateArchiveAssetDiff,
|
|
79
|
+
DownloadUrlKey: assetDiffDownloadUrl }];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
NSString *binaryPatchDownloadUrl = updatePackage[BinaryPatchDownloadUrlKey];
|
|
83
|
+
if ([binaryPatchDownloadUrl isKindOfClass:[NSString class]] && [binaryPatchDownloadUrl length] > 0) {
|
|
84
|
+
[archivesToTry addObject:@{ UpdateArchiveResultArchiveKey: UpdateArchiveBinaryPatch,
|
|
85
|
+
DownloadUrlKey: binaryPatchDownloadUrl }];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if ([archivesToTry count] == 0) {
|
|
89
|
+
// A download that never had a patch to try has nothing to report about one.
|
|
65
90
|
[self downloadAndInstallPackage:updatePackage
|
|
66
91
|
expectedBundleFileName:expectedBundleFileName
|
|
67
92
|
operationQueue:operationQueue
|
|
@@ -69,94 +94,211 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
69
94
|
isBinaryPatchUpdate:NO
|
|
70
95
|
progressCallback:progressCallback
|
|
71
96
|
doneCallback:^{
|
|
72
|
-
doneCallback(
|
|
97
|
+
doneCallback(nil);
|
|
73
98
|
}
|
|
74
99
|
failCallback:failCallback
|
|
75
100
|
patchAppliedCallback:nil
|
|
76
101
|
patchFallbackCallback:nil];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// A release that was published with a binary patch offers two archives of the same
|
|
80
|
-
// update. The patch is worth trying because it is a fraction of the size, and the
|
|
81
|
-
// full archive is always there when it does not work out.
|
|
82
|
-
NSString *binaryPatchDownloadUrl = updatePackage[BinaryPatchDownloadUrlKey];
|
|
83
|
-
if (![binaryPatchDownloadUrl isKindOfClass:[NSString class]] || [binaryPatchDownloadUrl length] == 0) {
|
|
84
|
-
// A download that never had a patch to try has nothing to report about one.
|
|
85
|
-
return downloadFullPackage(nil);
|
|
102
|
+
return;
|
|
86
103
|
}
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
[self tryNextArchive:archivesToTry
|
|
106
|
+
attemptsSoFar:[NSMutableArray array]
|
|
107
|
+
firstAttemptStartTime:[NSDate date]
|
|
108
|
+
updatePackage:updatePackage
|
|
109
|
+
expectedBundleFileName:expectedBundleFileName
|
|
110
|
+
operationQueue:operationQueue
|
|
111
|
+
progressCallback:progressCallback
|
|
112
|
+
doneCallback:doneCallback
|
|
113
|
+
failCallback:failCallback];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* Tries the first archive of the queue, and decides what a failure of it means for the
|
|
118
|
+
* rest. A failure after the bundle was restored is on the asset side of the archive, so
|
|
119
|
+
* the next archive - which does not share it - is worth trying, and so is one the server
|
|
120
|
+
* never served, which is a verdict on one URL rather than on the archives at the others.
|
|
121
|
+
* A failure between those is in the bundle patch every archive carries byte for byte, or
|
|
122
|
+
* is something no verdict exists for, and either way the remaining archives are passed
|
|
123
|
+
* over: they could only fail the same way, and trying them would put more doomed downloads
|
|
124
|
+
* in front of the full one.
|
|
125
|
+
*
|
|
126
|
+
* Every verdict on an archive ends with the update installed - by an archive of the queue
|
|
127
|
+
* or by the full download behind it - so no verdict reaches the caller as an error, and the
|
|
128
|
+
* result retells the attempts one by one. A network that did not carry the archive is not a
|
|
129
|
+
* verdict on it: it is raised, because the full download is behind the same network.
|
|
130
|
+
*/
|
|
131
|
+
+ (void)tryNextArchive:(NSArray<NSDictionary *> *)archivesToTry
|
|
132
|
+
attemptsSoFar:(NSMutableArray<NSDictionary *> *)attempts
|
|
133
|
+
firstAttemptStartTime:(NSDate *)firstAttemptStartTime
|
|
134
|
+
updatePackage:(NSDictionary *)updatePackage
|
|
135
|
+
expectedBundleFileName:(NSString *)expectedBundleFileName
|
|
136
|
+
operationQueue:(dispatch_queue_t)operationQueue
|
|
137
|
+
progressCallback:(void (^)(long long, long long))progressCallback
|
|
138
|
+
doneCallback:(void (^)(NSDictionary *updateArchiveResult))doneCallback
|
|
139
|
+
failCallback:(void (^)(NSError *err))failCallback
|
|
140
|
+
{
|
|
141
|
+
NSString *archive = archivesToTry[0][UpdateArchiveResultArchiveKey];
|
|
142
|
+
NSArray<NSDictionary *> *remainingArchives = [archivesToTry subarrayWithRange:NSMakeRange(1, [archivesToTry count] - 1)];
|
|
143
|
+
NSDate *attemptStartTime = [NSDate date];
|
|
144
|
+
|
|
94
145
|
// Set once the applier has restored the bundle, which is also what tells a failure
|
|
95
146
|
// that follows apart from one that came before.
|
|
96
147
|
__block NSNumber *applyDurationMs = nil;
|
|
148
|
+
// Set when the server answered this archive's URL with a status instead of the archive,
|
|
149
|
+
// which is a verdict on that URL and not on the archives at the others.
|
|
150
|
+
__block BOOL archiveWasNotServed = NO;
|
|
151
|
+
|
|
152
|
+
void (^giveUpAttempt)(NSString *failureReason) = ^(NSString *failureReason) {
|
|
153
|
+
[self deleteBinaryPatchFolder];
|
|
154
|
+
[attempts addObject:[self archiveAttemptEntry:archive
|
|
155
|
+
failureReason:failureReason
|
|
156
|
+
applyDurationMs:applyDurationMs
|
|
157
|
+
attemptStartTime:attemptStartTime]];
|
|
158
|
+
|
|
159
|
+
if ((applyDurationMs != nil || archiveWasNotServed) && [remainingArchives count] > 0) {
|
|
160
|
+
[self tryNextArchive:remainingArchives
|
|
161
|
+
attemptsSoFar:attempts
|
|
162
|
+
firstAttemptStartTime:firstAttemptStartTime
|
|
163
|
+
updatePackage:updatePackage
|
|
164
|
+
expectedBundleFileName:expectedBundleFileName
|
|
165
|
+
operationQueue:operationQueue
|
|
166
|
+
progressCallback:progressCallback
|
|
167
|
+
doneCallback:doneCallback
|
|
168
|
+
failCallback:failCallback];
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Timed before the full download starts, because that is not time the patch
|
|
173
|
+
// path spent.
|
|
174
|
+
NSDictionary *fallbackResult = [self archiveFallbackResult:failureReason
|
|
175
|
+
archive:archive
|
|
176
|
+
attempts:attempts
|
|
177
|
+
firstAttemptStartTime:firstAttemptStartTime];
|
|
178
|
+
[self downloadAndInstallPackage:updatePackage
|
|
179
|
+
expectedBundleFileName:expectedBundleFileName
|
|
180
|
+
operationQueue:operationQueue
|
|
181
|
+
downloadUrl:updatePackage[DownloadUrlKey]
|
|
182
|
+
isBinaryPatchUpdate:NO
|
|
183
|
+
progressCallback:progressCallback
|
|
184
|
+
doneCallback:^{
|
|
185
|
+
doneCallback(fallbackResult);
|
|
186
|
+
}
|
|
187
|
+
failCallback:failCallback
|
|
188
|
+
patchAppliedCallback:nil
|
|
189
|
+
patchFallbackCallback:nil];
|
|
190
|
+
};
|
|
191
|
+
|
|
97
192
|
[self downloadAndInstallPackage:updatePackage
|
|
98
193
|
expectedBundleFileName:expectedBundleFileName
|
|
99
194
|
operationQueue:operationQueue
|
|
100
|
-
downloadUrl:
|
|
195
|
+
downloadUrl:archivesToTry[0][DownloadUrlKey]
|
|
101
196
|
isBinaryPatchUpdate:YES
|
|
102
197
|
progressCallback:progressCallback
|
|
103
198
|
doneCallback:^{
|
|
104
199
|
[self deleteBinaryPatchFolder];
|
|
105
|
-
|
|
200
|
+
[attempts addObject:[self archiveAttemptEntry:archive
|
|
201
|
+
failureReason:nil
|
|
202
|
+
applyDurationMs:applyDurationMs
|
|
203
|
+
attemptStartTime:attemptStartTime]];
|
|
204
|
+
doneCallback([self appliedArchiveResult:archive
|
|
205
|
+
attempts:attempts
|
|
206
|
+
firstAttemptStartTime:firstAttemptStartTime]);
|
|
106
207
|
}
|
|
107
208
|
failCallback:^(NSError *err) {
|
|
108
|
-
[
|
|
109
|
-
|
|
209
|
+
if ([CodePushErrorUtils isNetworkFailure:err]) {
|
|
210
|
+
// The network is what failed, not the archive, and the full
|
|
211
|
+
// archive is behind the same network - only larger, and
|
|
212
|
+
// started over from nothing. Falling back here would spend a
|
|
213
|
+
// second download to reach the failure already in hand.
|
|
214
|
+
CPLog(@"The %@ archive could not be downloaded (%@). Giving up on the download.",
|
|
215
|
+
archive, err.localizedDescription);
|
|
216
|
+
[self deleteBinaryPatchFolder];
|
|
217
|
+
failCallback(err);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
archiveWasNotServed = [CodePushErrorUtils isHttpStatusError:err];
|
|
222
|
+
CPLog(@"The %@ archive could not be applied (%@). Falling back.", archive, err.localizedDescription);
|
|
110
223
|
// An error raised after the bundle was restored is the restored
|
|
111
224
|
// update failing the checks every update passes before it is
|
|
112
225
|
// installed. Before that point the appliers have no word for what
|
|
113
|
-
// happened - the
|
|
226
|
+
// happened - the archive not being downloadable, say - and
|
|
114
227
|
// inventing one here would put a value on the wire that no
|
|
115
228
|
// platform reports, so the fallback is reported without a reason.
|
|
116
|
-
|
|
229
|
+
giveUpAttempt(applyDurationMs == nil
|
|
117
230
|
? nil
|
|
118
|
-
:
|
|
119
|
-
downloadFullPackage([self binaryPatchFallbackResult:failureReason
|
|
120
|
-
attemptStartTime:patchAttemptStartTime]);
|
|
231
|
+
: CodePushArchiveFallbackReasonPackageVerificationFailed);
|
|
121
232
|
}
|
|
122
233
|
patchAppliedCallback:^(double durationMs) {
|
|
123
234
|
applyDurationMs = @(durationMs);
|
|
124
235
|
}
|
|
125
236
|
patchFallbackCallback:^(NSString *failureReason) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
downloadFullPackage([self binaryPatchFallbackResult:failureReason
|
|
129
|
-
attemptStartTime:patchAttemptStartTime]);
|
|
237
|
+
CPLog(@"The %@ archive failed (%@). Falling back.", archive, failureReason);
|
|
238
|
+
giveUpAttempt(failureReason);
|
|
130
239
|
}];
|
|
131
240
|
}
|
|
132
241
|
|
|
133
242
|
/*
|
|
134
|
-
*
|
|
243
|
+
* One archive the ladder tried: which archive it was, why it was given up on when it was,
|
|
244
|
+
* how long the try ran whichever way it ended, and how long its apply took when it got that
|
|
245
|
+
* far.
|
|
135
246
|
*/
|
|
136
|
-
+ (NSDictionary *)
|
|
247
|
+
+ (NSDictionary *)archiveAttemptEntry:(NSString *)archive
|
|
248
|
+
failureReason:(NSString *)failureReason
|
|
249
|
+
applyDurationMs:(NSNumber *)applyDurationMs
|
|
250
|
+
attemptStartTime:(NSDate *)attemptStartTime
|
|
251
|
+
{
|
|
252
|
+
NSMutableDictionary *entry = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
253
|
+
UpdateArchiveResultArchiveKey: archive,
|
|
254
|
+
UpdateArchiveAttemptDurationMsKey: @(round([[NSDate date] timeIntervalSinceDate:attemptStartTime] * 1000)),
|
|
255
|
+
}];
|
|
256
|
+
if (failureReason) {
|
|
257
|
+
entry[UpdateArchiveResultFallbackReasonKey] = failureReason;
|
|
258
|
+
}
|
|
259
|
+
// An attempt that never restored the bundle has no apply to report.
|
|
260
|
+
if (applyDurationMs) {
|
|
261
|
+
entry[UpdateArchiveAttemptApplyDurationMsKey] = applyDurationMs;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return entry;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/*
|
|
268
|
+
* The result of an archive the update was installed from, timed over the whole path.
|
|
269
|
+
*/
|
|
270
|
+
+ (NSDictionary *)appliedArchiveResult:(NSString *)archive
|
|
271
|
+
attempts:(NSArray<NSDictionary *> *)attempts
|
|
272
|
+
firstAttemptStartTime:(NSDate *)firstAttemptStartTime
|
|
137
273
|
{
|
|
138
274
|
return @{
|
|
139
|
-
|
|
140
|
-
|
|
275
|
+
UpdateArchiveResultStatusKey: UpdateArchiveResultStatusApplied,
|
|
276
|
+
UpdateArchiveResultArchiveKey: archive,
|
|
277
|
+
UpdateArchiveResultTotalDurationMsKey: @(round([[NSDate date] timeIntervalSinceDate:firstAttemptStartTime] * 1000)),
|
|
278
|
+
UpdateArchiveResultAttemptsKey: [attempts copy],
|
|
141
279
|
};
|
|
142
280
|
}
|
|
143
281
|
|
|
144
282
|
/*
|
|
145
|
-
* The result of a patch
|
|
283
|
+
* The result of a patch path the update had to be downloaded in full after.
|
|
146
284
|
*
|
|
147
|
-
* There is no completed apply to time here, so the
|
|
148
|
-
*
|
|
149
|
-
* out when the attempt ended in an error none of the appliers has a word for.
|
|
285
|
+
* There is no completed apply to time here, so the path itself is what is timed: from the
|
|
286
|
+
* first archive being asked for to the moment the last one was given up on. The reason is
|
|
287
|
+
* left out when the last attempt ended in an error none of the appliers has a word for.
|
|
150
288
|
*/
|
|
151
|
-
+ (NSDictionary *)
|
|
152
|
-
|
|
289
|
+
+ (NSDictionary *)archiveFallbackResult:(NSString *)failureReason
|
|
290
|
+
archive:(NSString *)archive
|
|
291
|
+
attempts:(NSArray<NSDictionary *> *)attempts
|
|
292
|
+
firstAttemptStartTime:(NSDate *)firstAttemptStartTime
|
|
153
293
|
{
|
|
154
294
|
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
155
|
-
|
|
156
|
-
|
|
295
|
+
UpdateArchiveResultStatusKey: UpdateArchiveResultStatusFallback,
|
|
296
|
+
UpdateArchiveResultArchiveKey: archive,
|
|
297
|
+
UpdateArchiveResultTotalDurationMsKey: @(round([[NSDate date] timeIntervalSinceDate:firstAttemptStartTime] * 1000)),
|
|
298
|
+
UpdateArchiveResultAttemptsKey: [attempts copy],
|
|
157
299
|
}];
|
|
158
300
|
if (failureReason) {
|
|
159
|
-
result[
|
|
301
|
+
result[UpdateArchiveResultFallbackReasonKey] = failureReason;
|
|
160
302
|
}
|
|
161
303
|
|
|
162
304
|
return result;
|
|
@@ -166,17 +308,17 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
166
308
|
* Downloads an update from one of its archives and installs it.
|
|
167
309
|
*
|
|
168
310
|
* isBinaryPatchUpdate says whether the archive holds a binary patch of the JS bundle,
|
|
169
|
-
* which has to be applied before the contents are the update.
|
|
170
|
-
*
|
|
171
|
-
* can never end up on the patch path.
|
|
311
|
+
* which has to be applied before the contents are the update. Both the asset diff and the
|
|
312
|
+
* patch archive are downloaded that way; the full archive never is, so an update being
|
|
313
|
+
* downloaded in full can never end up on the patch path.
|
|
172
314
|
*
|
|
173
315
|
* patchAppliedCallback is called with how long the apply took the moment the bundle has
|
|
174
316
|
* been restored, which is before the update is known to install. It is nil for a full
|
|
175
317
|
* download, which has no patch to apply.
|
|
176
318
|
*
|
|
177
319
|
* patchFallbackCallback is called instead of doneCallback when the patch cannot be
|
|
178
|
-
* applied: the update was not installed, and the caller has to
|
|
179
|
-
* It is nil for a full download, which has nothing to fall back to.
|
|
320
|
+
* applied: the update was not installed, and the caller has to move on to the next
|
|
321
|
+
* archive. It is nil for a full download, which has nothing to fall back to.
|
|
180
322
|
*/
|
|
181
323
|
+ (void)downloadAndInstallPackage:(NSDictionary *)updatePackage
|
|
182
324
|
expectedBundleFileName:(NSString *)expectedBundleFileName
|
|
@@ -270,13 +412,23 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
270
412
|
BOOL isDiffUpdate = [[NSFileManager defaultManager] fileExistsAtPath:diffManifestFilePath];
|
|
271
413
|
|
|
272
414
|
if (isDiffUpdate) {
|
|
415
|
+
// A merge that cannot complete has a word of its own, because it says
|
|
416
|
+
// the diff went wrong on its asset side - the one failure the patch
|
|
417
|
+
// archive, which carries every asset, is not implicated in. A diff
|
|
418
|
+
// manifest served outside the patch path keeps failing as the error
|
|
419
|
+
// it always was.
|
|
420
|
+
void (^mergeFailCallback)(NSError *) = !isBinaryPatchUpdate ? failCallback : ^(NSError *mergeError) {
|
|
421
|
+
CPLog(@"The asset diff could not be merged (%@).", mergeError.localizedDescription);
|
|
422
|
+
patchFallbackCallback(CodePushArchiveFallbackReasonAssetMergeFailed);
|
|
423
|
+
};
|
|
424
|
+
|
|
273
425
|
// Copy the current package to the new package.
|
|
274
426
|
NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:&error];
|
|
275
427
|
if (error) {
|
|
276
|
-
|
|
428
|
+
mergeFailCallback(error);
|
|
277
429
|
return;
|
|
278
430
|
}
|
|
279
|
-
|
|
431
|
+
|
|
280
432
|
if (currentPackageFolderPath == nil) {
|
|
281
433
|
// Currently running the binary version, copy files from the bundled resources
|
|
282
434
|
NSString *newUpdateCodePushPath = [newUpdateFolderPath stringByAppendingPathComponent:[CodePushUpdateUtils manifestFolderPrefix]];
|
|
@@ -285,23 +437,23 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
285
437
|
attributes:nil
|
|
286
438
|
error:&error];
|
|
287
439
|
if (error) {
|
|
288
|
-
|
|
440
|
+
mergeFailCallback(error);
|
|
289
441
|
return;
|
|
290
442
|
}
|
|
291
|
-
|
|
443
|
+
|
|
292
444
|
[[NSFileManager defaultManager] copyItemAtPath:[CodePush bundleAssetsPath]
|
|
293
445
|
toPath:[newUpdateCodePushPath stringByAppendingPathComponent:[CodePushUpdateUtils assetsFolderName]]
|
|
294
446
|
error:&error];
|
|
295
447
|
if (error) {
|
|
296
|
-
|
|
448
|
+
mergeFailCallback(error);
|
|
297
449
|
return;
|
|
298
450
|
}
|
|
299
|
-
|
|
451
|
+
|
|
300
452
|
[[NSFileManager defaultManager] copyItemAtPath:[[CodePush binaryBundleURL] path]
|
|
301
453
|
toPath:[newUpdateCodePushPath stringByAppendingPathComponent:[[CodePush binaryBundleURL] lastPathComponent]]
|
|
302
454
|
error:&error];
|
|
303
455
|
if (error) {
|
|
304
|
-
|
|
456
|
+
mergeFailCallback(error);
|
|
305
457
|
return;
|
|
306
458
|
}
|
|
307
459
|
} else {
|
|
@@ -309,25 +461,44 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
309
461
|
toPath:newUpdateFolderPath
|
|
310
462
|
error:&error];
|
|
311
463
|
if (error) {
|
|
312
|
-
|
|
464
|
+
mergeFailCallback(error);
|
|
313
465
|
return;
|
|
314
466
|
}
|
|
315
467
|
}
|
|
316
|
-
|
|
468
|
+
|
|
317
469
|
// Delete files mentioned in the manifest.
|
|
318
470
|
NSString *manifestContent = [NSString stringWithContentsOfFile:diffManifestFilePath
|
|
319
471
|
encoding:NSUTF8StringEncoding
|
|
320
472
|
error:&error];
|
|
321
473
|
if (error) {
|
|
322
|
-
|
|
474
|
+
mergeFailCallback(error);
|
|
323
475
|
return;
|
|
324
476
|
}
|
|
325
|
-
|
|
477
|
+
|
|
326
478
|
NSData *data = [manifestContent dataUsingEncoding:NSUTF8StringEncoding];
|
|
327
479
|
NSDictionary *manifestJSON = [NSJSONSerialization JSONObjectWithData:data
|
|
328
480
|
options:kNilOptions
|
|
329
481
|
error:&error];
|
|
330
|
-
|
|
482
|
+
if (error) {
|
|
483
|
+
mergeFailCallback(error);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// A manifest that does not name the files to delete is not a manifest
|
|
488
|
+
// with nothing to delete - the CLI writes the key on every release, an
|
|
489
|
+
// empty list included. Iterating what is not there would skip every
|
|
490
|
+
// deletion and merge on, so it is refused here, the way the other
|
|
491
|
+
// platform's merge refuses it, for the two platforms to report one
|
|
492
|
+
// reason for one state.
|
|
493
|
+
NSArray *deletedFiles = [manifestJSON isKindOfClass:[NSDictionary class]]
|
|
494
|
+
? manifestJSON[@"deletedFiles"]
|
|
495
|
+
: nil;
|
|
496
|
+
if (![deletedFiles isKindOfClass:[NSArray class]]) {
|
|
497
|
+
error = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat:@"The asset diff manifest at %@ does not name the files to delete.", diffManifestFilePath]];
|
|
498
|
+
mergeFailCallback(error);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
331
502
|
for (NSString *deletedFileName in deletedFiles) {
|
|
332
503
|
// The manifest is untrusted input, so an entry that reaches outside the
|
|
333
504
|
// package folder is ignored rather than followed. Whether skipping it
|
|
@@ -338,16 +509,16 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
338
509
|
[[NSFileManager defaultManager] removeItemAtPath:absoluteDeletedFilePath
|
|
339
510
|
error:&error];
|
|
340
511
|
if (error) {
|
|
341
|
-
|
|
512
|
+
mergeFailCallback(error);
|
|
342
513
|
return;
|
|
343
514
|
}
|
|
344
515
|
}
|
|
345
516
|
}
|
|
346
|
-
|
|
517
|
+
|
|
347
518
|
[[NSFileManager defaultManager] removeItemAtPath:diffManifestFilePath
|
|
348
519
|
error:&error];
|
|
349
520
|
if (error) {
|
|
350
|
-
|
|
521
|
+
mergeFailCallback(error);
|
|
351
522
|
return;
|
|
352
523
|
}
|
|
353
524
|
}
|
|
@@ -414,11 +585,11 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
414
585
|
|
|
415
586
|
} else {
|
|
416
587
|
if (isBinaryPatchUpdate) {
|
|
417
|
-
// Whatever the
|
|
588
|
+
// Whatever the archive URL served, it is not an update archive - an error page
|
|
418
589
|
// answered with a 200 looks like this too. Moving it into place would
|
|
419
590
|
// install bytes no hash has ever been checked against, so the full archive
|
|
420
591
|
// is downloaded instead.
|
|
421
|
-
patchFallbackCallback(
|
|
592
|
+
patchFallbackCallback(CodePushArchiveFallbackReasonInvalidManifest);
|
|
422
593
|
return;
|
|
423
594
|
}
|
|
424
595
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bravemobile/react-native-code-push",
|
|
3
|
-
"version": "13.1.0-beta.
|
|
3
|
+
"version": "13.1.0-beta.5",
|
|
4
4
|
"description": "React Native plugin for the CodePush service",
|
|
5
5
|
"main": "src/CodePush.js",
|
|
6
6
|
"react-native": "src/CodePush.js",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"package.json",
|
|
57
57
|
"THIRD-PARTY-NOTICES",
|
|
58
58
|
"!ios/build",
|
|
59
|
+
"!ios/CodePushTests",
|
|
59
60
|
"!android/build",
|
|
60
61
|
"!android/app/build",
|
|
61
62
|
"!android/gradle",
|
package/src/CodePush.js
CHANGED
|
@@ -189,9 +189,16 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
|
|
|
189
189
|
download_url: latestReleaseInfo.downloadUrl,
|
|
190
190
|
/**
|
|
191
191
|
* Only released updates that were published with a binary patch carry this.
|
|
192
|
-
*
|
|
192
|
+
* A release offering neither this nor a diff is downloaded in full from
|
|
193
|
+
* `download_url`.
|
|
193
194
|
*/
|
|
194
|
-
binary_patch_download_url:
|
|
195
|
+
binary_patch_download_url: latestReleaseInfo.binaryPatchDownloadUrl,
|
|
196
|
+
/**
|
|
197
|
+
* The diff stands next to the patch url rather than in its place, so that the
|
|
198
|
+
* native side can try the diff first and still hold the patch archive to fall
|
|
199
|
+
* back on when the diff fails on its asset side.
|
|
200
|
+
*/
|
|
201
|
+
asset_diff_download_url: diffPackageDownloadUrl,
|
|
195
202
|
// (`enabled` will always be true in the release information obtained from the previous process.)
|
|
196
203
|
is_available: latestReleaseInfo.enabled,
|
|
197
204
|
package_hash: latestReleaseInfo.packageHash,
|
|
@@ -293,11 +300,14 @@ function mapToRemotePackageMetadata(updateInfo) {
|
|
|
293
300
|
packageHash: updateInfo.package_hash ?? '',
|
|
294
301
|
packageSize: updateInfo.package_size ?? 0,
|
|
295
302
|
downloadUrl: updateInfo.download_url ?? '',
|
|
296
|
-
// The
|
|
303
|
+
// The fields stay out of the package unless the update really has these archives,
|
|
297
304
|
// so that the native side sees exactly what it saw before patches existed.
|
|
298
305
|
...(updateInfo.binary_patch_download_url
|
|
299
306
|
? { binaryPatchDownloadUrl: updateInfo.binary_patch_download_url }
|
|
300
307
|
: {}),
|
|
308
|
+
...(updateInfo.asset_diff_download_url
|
|
309
|
+
? { assetDiffDownloadUrl: updateInfo.asset_diff_download_url }
|
|
310
|
+
: {}),
|
|
301
311
|
};
|
|
302
312
|
}
|
|
303
313
|
|
|
@@ -551,20 +561,20 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
|
|
|
551
561
|
updateDialog: null,
|
|
552
562
|
// The callback registered on the decorator is the default for every sync, including a
|
|
553
563
|
// manual one; a callback passed to this call overrides it for this call.
|
|
554
|
-
|
|
564
|
+
onUpdateArchiveResult: sharedCodePushOptions.onUpdateArchiveResult ?? null,
|
|
555
565
|
...options,
|
|
556
566
|
};
|
|
557
567
|
|
|
558
568
|
/*
|
|
559
|
-
* A callback an app registered to observe how
|
|
560
|
-
* cost it an update, so it is isolated the way the other sync callbacks are: whatever
|
|
561
|
-
* throws is logged and the install carries on. An app that registered none leaves the
|
|
569
|
+
* A callback an app registered to observe how the update archives went must not be able
|
|
570
|
+
* to cost it an update, so it is isolated the way the other sync callbacks are: whatever
|
|
571
|
+
* it throws is logged and the install carries on. An app that registered none leaves the
|
|
562
572
|
* download exactly as it was before this option existed.
|
|
563
573
|
*/
|
|
564
|
-
const
|
|
574
|
+
const onUpdateArchiveResult = typeof syncOptions.onUpdateArchiveResult === "function"
|
|
565
575
|
? (label, result) => {
|
|
566
576
|
try {
|
|
567
|
-
syncOptions.
|
|
577
|
+
syncOptions.onUpdateArchiveResult(label, result);
|
|
568
578
|
} catch (error) {
|
|
569
579
|
log(`An error has occurred : ${error.stack}`);
|
|
570
580
|
}
|
|
@@ -624,7 +634,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
|
|
|
624
634
|
|
|
625
635
|
const localPackage = await remotePackage.download(
|
|
626
636
|
downloadProgressCallback,
|
|
627
|
-
|
|
637
|
+
onUpdateArchiveResult && ((result) => onUpdateArchiveResult(remotePackageLabel, result)),
|
|
628
638
|
);
|
|
629
639
|
|
|
630
640
|
sharedCodePushOptions.onDownloadSuccess?.(remotePackageLabel);
|
|
@@ -760,8 +770,8 @@ let CodePush;
|
|
|
760
770
|
* onRolloutSkipped: (label: string, error: Error) => void | undefined,
|
|
761
771
|
* setOnRolloutSkipped(onRolloutSkippedFunction: (label: string, error: Error) => void | undefined): void,
|
|
762
772
|
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
773
|
+
* onUpdateArchiveResult: (label: string, result: object) => void | undefined,
|
|
774
|
+
* setOnUpdateArchiveResult(onUpdateArchiveResultFunction: (label: string, result: object) => void | undefined): void,
|
|
765
775
|
* }}
|
|
766
776
|
*/
|
|
767
777
|
const sharedCodePushOptions = {
|
|
@@ -812,11 +822,11 @@ const sharedCodePushOptions = {
|
|
|
812
822
|
if (typeof onRolloutSkippedFunction !== 'function') throw new Error('Please pass a function to onRolloutSkipped');
|
|
813
823
|
this.onRolloutSkipped = onRolloutSkippedFunction;
|
|
814
824
|
},
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (!
|
|
818
|
-
if (typeof
|
|
819
|
-
this.
|
|
825
|
+
onUpdateArchiveResult: undefined,
|
|
826
|
+
setOnUpdateArchiveResult(onUpdateArchiveResultFunction) {
|
|
827
|
+
if (!onUpdateArchiveResultFunction) return;
|
|
828
|
+
if (typeof onUpdateArchiveResultFunction !== 'function') throw new Error('Please pass a function to onUpdateArchiveResult');
|
|
829
|
+
this.onUpdateArchiveResult = onUpdateArchiveResultFunction;
|
|
820
830
|
},
|
|
821
831
|
}
|
|
822
832
|
|
|
@@ -854,7 +864,7 @@ function codePushify(options = {}) {
|
|
|
854
864
|
sharedCodePushOptions.setOnDownloadSuccess(options.onDownloadSuccess);
|
|
855
865
|
sharedCodePushOptions.setOnSyncError(options.onSyncError);
|
|
856
866
|
sharedCodePushOptions.setOnRolloutSkipped(options.onRolloutSkipped);
|
|
857
|
-
sharedCodePushOptions.
|
|
867
|
+
sharedCodePushOptions.setOnUpdateArchiveResult(options.onUpdateArchiveResult);
|
|
858
868
|
|
|
859
869
|
const decorator = (RootComponent) => {
|
|
860
870
|
class CodePushComponent extends React.Component {
|