@@ -13,10 +13,17 @@ @implementation CodePush {
1313
1414RCT_EXPORT_MODULE ()
1515
16+ static BOOL needToReportRollback = NO;
17+ static BOOL isRunningBinaryVersion = NO ;
1618static BOOL testConfigurationFlag = NO ;
1719
20+ // These constants represent valid deployment statuses
21+ static NSString *const DeploymentFailed = @" DeploymentFailed" ;
22+ static NSString *const DeploymentSucceeded = @" DeploymentSucceeded" ;
23+
1824// These keys represent the names we use to store data in NSUserDefaults
1925static NSString *const FailedUpdatesKey = @" CODE_PUSH_FAILED_UPDATES" ;
26+ static NSString *const LastDeploymentReportKey = @" CODE_PUSH_LAST_DEPLOYMENT_REPORT" ;
2027static NSString *const PendingUpdateKey = @" CODE_PUSH_PENDING_UPDATE" ;
2128
2229// These keys are already "namespaced" by the PendingUpdateKey, so
@@ -26,6 +33,8 @@ @implementation CodePush {
2633
2734// These keys are used to inspect/augment the metadata
2835// that is associated with an update's package.
36+ static NSString *const DeploymentKeyKey = @" deploymentKey" ;
37+ static NSString *const LabelKey = @" label" ;
2938static NSString *const PackageHashKey = @" packageHash" ;
3039static NSString *const PackageIsPendingKey = @" isPending" ;
3140
@@ -54,6 +63,7 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
5463
5564 if (error || !packageFile) {
5665 NSLog (logMessageFormat, binaryJsBundleUrl);
66+ isRunningBinaryVersion = YES ;
5767 return binaryJsBundleUrl;
5868 }
5969
@@ -65,22 +75,25 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
6575 NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage: &error];
6676 if (error || !currentPackageMetadata) {
6777 NSLog (logMessageFormat, binaryJsBundleUrl);
78+ isRunningBinaryVersion = YES ;
6879 return binaryJsBundleUrl;
6980 }
7081
7182 NSString *packageAppVersion = [currentPackageMetadata objectForKey: @" appVersion" ];
7283
73- if ([binaryDate compare: packageDate] == NSOrderedAscending && [ binaryAppVersion isEqualToString: packageAppVersion]) {
84+ if ([binaryDate compare: packageDate] == NSOrderedAscending && ([CodePush isUsingTestConfiguration ] ||[ binaryAppVersion isEqualToString: packageAppVersion]) ) {
7485 // Return package file because it is newer than the app store binary's JS bundle
7586 NSURL *packageUrl = [[NSURL alloc ] initFileURLWithPath: packageFile];
7687 NSLog (logMessageFormat, packageUrl);
88+ isRunningBinaryVersion = NO ;
7789 return packageUrl;
7890 } else {
7991#ifndef DEBUG
8092 [CodePush clearUpdates ];
8193#endif
8294
8395 NSLog (logMessageFormat, binaryJsBundleUrl);
96+ isRunningBinaryVersion = YES ;
8497 return binaryJsBundleUrl;
8598 }
8699}
@@ -148,6 +161,19 @@ - (void)dealloc
148161 [[NSNotificationCenter defaultCenter ] removeObserver: self ];
149162}
150163
164+ - (NSString *)getPackageStatusReportIdentifier : (NSDictionary *)package
165+ {
166+ // Because deploymentKeys can be dynamically switched, we use a
167+ // combination of the deploymentKey and label as the packageIdentifier.
168+ NSString *deploymentKey = [package objectForKey: DeploymentKeyKey];
169+ NSString *label = [package objectForKey: LabelKey];
170+ if (deploymentKey && label) {
171+ return [[deploymentKey stringByAppendingString: @" :" ] stringByAppendingString: label];
172+ } else {
173+ return nil ;
174+ }
175+ }
176+
151177- (instancetype )init
152178{
153179 self = [super init ];
@@ -175,6 +201,7 @@ - (void)initializeUpdateAfterRestart
175201 // Pending update was initialized, but notifyApplicationReady was not called.
176202 // Therefore, deduce that it is a broken update and rollback.
177203 NSLog (@" Update did not finish loading the last time, rolling back to a previous version." );
204+ needToReportRollback = YES ;
178205 [self rollbackPackage ];
179206 } else {
180207 // Mark that we tried to initialize the new update, so that if it crashes,
@@ -185,6 +212,13 @@ - (void)initializeUpdateAfterRestart
185212 }
186213}
187214
215+ - (BOOL )isDeploymentStatusNotYetReported : (NSString *)appVersionOrPackageIdentifier
216+ {
217+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
218+ NSString *sentStatusReportIdentifier = [preferences objectForKey: LastDeploymentReportKey];
219+ return sentStatusReportIdentifier == nil || ![sentStatusReportIdentifier isEqualToString: appVersionOrPackageIdentifier];
220+ }
221+
188222/*
189223 * This method checks to see whether a specific package hash
190224 * has previously failed installation.
@@ -193,7 +227,25 @@ - (BOOL)isFailedHash:(NSString*)packageHash
193227{
194228 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
195229 NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
196- return (failedUpdates != nil && [failedUpdates containsObject: packageHash]);
230+ if (failedUpdates == nil || packageHash == nil ) {
231+ return NO ;
232+ } else {
233+ for (NSDictionary *failedPackage in failedUpdates)
234+ {
235+ // Type check is needed for backwards compatibility, where we used to just store
236+ // the failed package hash instead of the metadata. This only impacts "dev"
237+ // scenarios, since in production we clear out old information whenever a new
238+ // binary is applied.
239+ if ([failedPackage isKindOfClass: [NSDictionary class ]]) {
240+ NSString *failedPackageHash = [failedPackage objectForKey: PackageHashKey];
241+ if ([packageHash isEqualToString: failedPackageHash]) {
242+ return YES ;
243+ }
244+ }
245+ }
246+
247+ return NO ;
248+ }
197249}
198250
199251/*
@@ -237,6 +289,13 @@ - (void)loadBundle
237289 });
238290}
239291
292+ - (void )recordDeploymentStatusReported : (NSString *)appVersionOrPackageIdentifier
293+ {
294+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
295+ [preferences setValue: appVersionOrPackageIdentifier forKey: LastDeploymentReportKey];
296+ [preferences synchronize ];
297+ }
298+
240299/*
241300 * This method is used when an update has failed installation
242301 * and the app needs to be rolled back to the previous bundle.
@@ -247,10 +306,10 @@ - (void)loadBundle
247306- (void )rollbackPackage
248307{
249308 NSError *error;
250- NSString *packageHash = [CodePushPackage getCurrentPackageHash : &error];
309+ NSDictionary *failedPackage = [CodePushPackage getCurrentPackage : &error];
251310
252- // Write the current package's hash to the "failed list"
253- [self saveFailedUpdate: packageHash ];
311+ // Write the current package's metadata to the "failed list"
312+ [self saveFailedUpdate: failedPackage ];
254313
255314 // Rollback to the previous version and de-register the new update
256315 [CodePushPackage rollbackPackage ];
@@ -263,7 +322,7 @@ - (void)rollbackPackage
263322 * to store its hash so that it can be ignored on future
264323 * attempts to check the server for an update.
265324 */
266- - (void )saveFailedUpdate : (NSString *)packageHash
325+ - (void )saveFailedUpdate : (NSDictionary *)failedPackage
267326{
268327 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
269328 NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
@@ -275,7 +334,7 @@ - (void)saveFailedUpdate:(NSString *)packageHash
275334 failedUpdates = [failedUpdates mutableCopy ];
276335 }
277336
278- [failedUpdates addObject: packageHash ];
337+ [failedUpdates addObject: failedPackage ];
279338 [preferences setObject: failedUpdates forKey: FailedUpdatesKey];
280339 [preferences synchronize ];
281340}
@@ -467,6 +526,54 @@ - (void)savePendingUpdate:(NSString *)packageHash
467526 resolve ([NSNull null ]);
468527}
469528
529+ /*
530+ * This method is checks if a new status update exists (new version was installed,
531+ * or an update failed) and return its details (version label, status).
532+ */
533+ RCT_EXPORT_METHOD (getNewStatusReport:(RCTPromiseResolveBlock)resolve
534+ rejecter:(RCTPromiseRejectBlock)reject)
535+ {
536+ if (needToReportRollback) {
537+ // Check if there was a rollback that was not yet reported
538+ needToReportRollback = NO ;
539+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
540+ NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
541+ if (failedUpdates) {
542+ NSDictionary *lastFailedPackage = [failedUpdates lastObject ];
543+ if (lastFailedPackage) {
544+ NSString *lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier: lastFailedPackage];
545+ if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported: lastFailedPackageIdentifier]) {
546+ [self recordDeploymentStatusReported: lastFailedPackageIdentifier];
547+ resolve (@{ @" package" : lastFailedPackage, @" status" : DeploymentFailed });
548+ return ;
549+ }
550+ }
551+ }
552+ } else if (_isFirstRunAfterUpdate) {
553+ // Check if the current CodePush package has been reported
554+ NSError *error;
555+ NSDictionary *currentPackage = [CodePushPackage getCurrentPackage: &error];
556+ if (!error && currentPackage) {
557+ NSString *currentPackageIdentifier = [self getPackageStatusReportIdentifier: currentPackage];
558+ if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported: currentPackageIdentifier]) {
559+ [self recordDeploymentStatusReported: currentPackageIdentifier];
560+ resolve (@{ @" package" : currentPackage, @" status" : DeploymentSucceeded });
561+ return ;
562+ }
563+ }
564+ } else if (isRunningBinaryVersion || [_bridge.bundleURL.scheme hasPrefix: @" http" ]) {
565+ // Check if the current appVersion has been reported.
566+ NSString *appVersion = [[CodePushConfig current ] appVersion ];
567+ if ([self isDeploymentStatusNotYetReported: appVersion]) {
568+ [self recordDeploymentStatusReported: appVersion];
569+ resolve (@{ @" appVersion" : appVersion });
570+ return ;
571+ }
572+ }
573+
574+ resolve ([NSNull null ]);
575+ }
576+
470577/*
471578 * This method is the native side of the CodePush.restartApp() method.
472579 */
0 commit comments