@@ -15,9 +15,14 @@ @implementation CodePush {
15
15
16
16
static BOOL testConfigurationFlag = NO;
17
17
18
+ // These constants represent valid deployment statuses
19
+ static NSString *const DeploymentSucceeded = @" DeploymentSucceeded" ;
20
+ static NSString *const DeploymentFailed = @" DeploymentFailed" ;
21
+
18
22
// These keys represent the names we use to store data in NSUserDefaults
19
23
static NSString *const FailedUpdatesKey = @" CODE_PUSH_FAILED_UPDATES" ;
20
24
static NSString *const PendingUpdateKey = @" CODE_PUSH_PENDING_UPDATE" ;
25
+ static NSString *const StatusReportsKey = @" CODE_PUSH_STATUS_REPORTS" ;
21
26
22
27
// These keys are already "namespaced" by the PendingUpdateKey, so
23
28
// their values don't need to be obfuscated to prevent collision with app data
@@ -26,6 +31,8 @@ @implementation CodePush {
26
31
27
32
// These keys are used to inspect/augment the metadata
28
33
// that is associated with an update's package.
34
+ static NSString *const DeploymentKeyKey = @" deploymentKey" ;
35
+ static NSString *const LabelKey = @" label" ;
29
36
static NSString *const PackageHashKey = @" packageHash" ;
30
37
static NSString *const PackageIsPendingKey = @" isPending" ;
31
38
@@ -70,7 +77,7 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
70
77
71
78
NSString *packageAppVersion = [currentPackageMetadata objectForKey: @" appVersion" ];
72
79
73
- if ([binaryDate compare: packageDate] == NSOrderedAscending && [ binaryAppVersion isEqualToString: packageAppVersion]) {
80
+ if ([binaryDate compare: packageDate] == NSOrderedAscending && ([CodePush isUsingTestConfiguration ] ||[ binaryAppVersion isEqualToString: packageAppVersion]) ) {
74
81
// Return package file because it is newer than the app store binary's JS bundle
75
82
NSURL *packageUrl = [[NSURL alloc ] initFileURLWithPath: packageFile];
76
83
NSLog (logMessageFormat, packageUrl);
@@ -148,6 +155,19 @@ - (void)dealloc
148
155
[[NSNotificationCenter defaultCenter ] removeObserver: self ];
149
156
}
150
157
158
+ - (NSString *)getPackageStatusReportIdentifier : (NSDictionary *)package
159
+ {
160
+ // Because deploymentKeys can be dynamically switched, we use a
161
+ // combination of the deploymentKey and label as the packageIdentifier.
162
+ NSString *deploymentKey = [package objectForKey: DeploymentKeyKey];
163
+ NSString *label = [package objectForKey: LabelKey];
164
+ if (deploymentKey && label) {
165
+ return [[deploymentKey stringByAppendingString: @" :" ] stringByAppendingString: label];
166
+ } else {
167
+ return nil ;
168
+ }
169
+ }
170
+
151
171
- (instancetype )init
152
172
{
153
173
self = [super init ];
@@ -185,6 +205,13 @@ - (void)initializeUpdateAfterRestart
185
205
}
186
206
}
187
207
208
+ - (BOOL )isDeploymentStatusNotYetReported : (NSString *)appVersionOrPackageIdentifier
209
+ {
210
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
211
+ NSDictionary *sentStatusReports = [preferences objectForKey: StatusReportsKey];
212
+ return sentStatusReports == nil || [sentStatusReports objectForKey: appVersionOrPackageIdentifier] == nil ;
213
+ }
214
+
188
215
/*
189
216
* This method checks to see whether a specific package hash
190
217
* has previously failed installation.
@@ -193,7 +220,25 @@ - (BOOL)isFailedHash:(NSString*)packageHash
193
220
{
194
221
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
195
222
NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
196
- return (failedUpdates != nil && [failedUpdates containsObject: packageHash]);
223
+ if (failedUpdates == nil ) {
224
+ return NO ;
225
+ } else {
226
+ for (NSDictionary *failedPackage in failedUpdates)
227
+ {
228
+ // Type check is needed for backwards compatibility, where we used to just store
229
+ // the failed package hash instead of the metadata. This only impacts "dev"
230
+ // scenarios, since in production we clear out old information whenever a new
231
+ // binary is applied.
232
+ if ([failedPackage isKindOfClass: [NSDictionary class ]]) {
233
+ NSString *failedPackageHash = [failedPackage objectForKey: PackageHashKey];
234
+ if ([packageHash isEqualToString: failedPackageHash]) {
235
+ return YES ;
236
+ }
237
+ }
238
+ }
239
+
240
+ return NO ;
241
+ }
197
242
}
198
243
199
244
/*
@@ -237,6 +282,22 @@ - (void)loadBundle
237
282
});
238
283
}
239
284
285
+ - (void )recordDeploymentStatusReported : (NSString *)appVersionOrPackageIdentifier
286
+ status : (NSString *)status
287
+ {
288
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
289
+ NSMutableDictionary *sentStatusReports = [preferences objectForKey: StatusReportsKey];
290
+ if (sentStatusReports == nil ) {
291
+ sentStatusReports = [NSMutableDictionary dictionary ];
292
+ } else {
293
+ sentStatusReports = [sentStatusReports mutableCopy ];
294
+ }
295
+
296
+ [sentStatusReports setValue: status forKey: appVersionOrPackageIdentifier];
297
+ [preferences setValue: sentStatusReports forKey: StatusReportsKey];
298
+ [preferences synchronize ];
299
+ }
300
+
240
301
/*
241
302
* This method is used when an update has failed installation
242
303
* and the app needs to be rolled back to the previous bundle.
@@ -247,10 +308,10 @@ - (void)loadBundle
247
308
- (void )rollbackPackage
248
309
{
249
310
NSError *error;
250
- NSString *packageHash = [CodePushPackage getCurrentPackageHash : &error];
311
+ NSDictionary *failedPackage = [CodePushPackage getCurrentPackage : &error];
251
312
252
- // Write the current package's hash to the "failed list"
253
- [self saveFailedUpdate: packageHash ];
313
+ // Write the current package's metadata to the "failed list"
314
+ [self saveFailedUpdate: failedPackage ];
254
315
255
316
// Rollback to the previous version and de-register the new update
256
317
[CodePushPackage rollbackPackage ];
@@ -263,7 +324,7 @@ - (void)rollbackPackage
263
324
* to store its hash so that it can be ignored on future
264
325
* attempts to check the server for an update.
265
326
*/
266
- - (void )saveFailedUpdate : (NSString *)packageHash
327
+ - (void )saveFailedUpdate : (NSDictionary *)failedPackage
267
328
{
268
329
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
269
330
NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
@@ -275,7 +336,7 @@ - (void)saveFailedUpdate:(NSString *)packageHash
275
336
failedUpdates = [failedUpdates mutableCopy ];
276
337
}
277
338
278
- [failedUpdates addObject: packageHash ];
339
+ [failedUpdates addObject: failedPackage ];
279
340
[preferences setObject: failedUpdates forKey: FailedUpdatesKey];
280
341
[preferences synchronize ];
281
342
}
@@ -467,6 +528,55 @@ - (void)savePendingUpdate:(NSString *)packageHash
467
528
resolve ([NSNull null ]);
468
529
}
469
530
531
+ /*
532
+ * This method is checks if a new status update exists (new version was installed,
533
+ * or an update failed) and return its details (version label, status).
534
+ */
535
+ RCT_EXPORT_METHOD (getNewStatusReport:(RCTPromiseResolveBlock)resolve
536
+ rejecter:(RCTPromiseRejectBlock)reject)
537
+ {
538
+ // Check if the current appVersion has been reported.
539
+ NSString *appVersion = [[CodePushConfig current ] appVersion ];
540
+ if ([self isDeploymentStatusNotYetReported: appVersion]) {
541
+ [self recordDeploymentStatusReported: appVersion
542
+ status: DeploymentSucceeded];
543
+ resolve (@{ @" appVersion" : appVersion });
544
+ return ;
545
+ }
546
+
547
+ // Check if there was a rollback that was not yet reported
548
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
549
+ NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
550
+ if (failedUpdates) {
551
+ NSDictionary * lastFailedPackage = [failedUpdates lastObject ];
552
+ if (lastFailedPackage) {
553
+ NSString * lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier: lastFailedPackage];
554
+ if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported: lastFailedPackageIdentifier]) {
555
+ [self recordDeploymentStatusReported: lastFailedPackageIdentifier
556
+ status: DeploymentFailed];
557
+ resolve (@{ @" package" : lastFailedPackage, @" status" : DeploymentFailed });
558
+ return ;
559
+ }
560
+ }
561
+ }
562
+
563
+ // Check if the current CodePush package has been reported
564
+ NSError *error;
565
+ NSDictionary * currentPackage = [CodePushPackage getCurrentPackage: &error];
566
+ if (currentPackage) {
567
+ NSString * currentPackageIdentifier = [self getPackageStatusReportIdentifier: currentPackage];
568
+ if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported: currentPackageIdentifier]) {
569
+ [self recordDeploymentStatusReported: currentPackageIdentifier
570
+ status: DeploymentSucceeded];
571
+ resolve (@{ @" package" : currentPackage, @" status" : DeploymentSucceeded });
572
+ return ;
573
+ }
574
+ }
575
+
576
+ resolve ([NSNull null ]);
577
+ return ;
578
+ }
579
+
470
580
/*
471
581
* This method is the native side of the CodePush.restartApp() method.
472
582
*/
0 commit comments