@@ -13,10 +13,17 @@ @implementation CodePush {
13
13
14
14
RCT_EXPORT_MODULE ()
15
15
16
+ static BOOL needToReportRollback = NO;
17
+ static BOOL isRunningBinaryVersion = NO ;
16
18
static BOOL testConfigurationFlag = NO ;
17
19
20
+ // These constants represent valid deployment statuses
21
+ static NSString *const DeploymentFailed = @" DeploymentFailed" ;
22
+ static NSString *const DeploymentSucceeded = @" DeploymentSucceeded" ;
23
+
18
24
// These keys represent the names we use to store data in NSUserDefaults
19
25
static NSString *const FailedUpdatesKey = @" CODE_PUSH_FAILED_UPDATES" ;
26
+ static NSString *const LastDeploymentReportKey = @" CODE_PUSH_LAST_DEPLOYMENT_REPORT" ;
20
27
static NSString *const PendingUpdateKey = @" CODE_PUSH_PENDING_UPDATE" ;
21
28
22
29
// These keys are already "namespaced" by the PendingUpdateKey, so
@@ -26,6 +33,8 @@ @implementation CodePush {
26
33
27
34
// These keys are used to inspect/augment the metadata
28
35
// that is associated with an update's package.
36
+ static NSString *const DeploymentKeyKey = @" deploymentKey" ;
37
+ static NSString *const LabelKey = @" label" ;
29
38
static NSString *const PackageHashKey = @" packageHash" ;
30
39
static NSString *const PackageIsPendingKey = @" isPending" ;
31
40
@@ -54,6 +63,7 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
54
63
55
64
if (error || !packageFile) {
56
65
NSLog (logMessageFormat, binaryJsBundleUrl);
66
+ isRunningBinaryVersion = YES ;
57
67
return binaryJsBundleUrl;
58
68
}
59
69
@@ -65,22 +75,25 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
65
75
NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage: &error];
66
76
if (error || !currentPackageMetadata) {
67
77
NSLog (logMessageFormat, binaryJsBundleUrl);
78
+ isRunningBinaryVersion = YES ;
68
79
return binaryJsBundleUrl;
69
80
}
70
81
71
82
NSString *packageAppVersion = [currentPackageMetadata objectForKey: @" appVersion" ];
72
83
73
- if ([binaryDate compare: packageDate] == NSOrderedAscending && [ binaryAppVersion isEqualToString: packageAppVersion]) {
84
+ if ([binaryDate compare: packageDate] == NSOrderedAscending && ([CodePush isUsingTestConfiguration ] ||[ binaryAppVersion isEqualToString: packageAppVersion]) ) {
74
85
// Return package file because it is newer than the app store binary's JS bundle
75
86
NSURL *packageUrl = [[NSURL alloc ] initFileURLWithPath: packageFile];
76
87
NSLog (logMessageFormat, packageUrl);
88
+ isRunningBinaryVersion = NO ;
77
89
return packageUrl;
78
90
} else {
79
91
#ifndef DEBUG
80
92
[CodePush clearUpdates ];
81
93
#endif
82
94
83
95
NSLog (logMessageFormat, binaryJsBundleUrl);
96
+ isRunningBinaryVersion = YES ;
84
97
return binaryJsBundleUrl;
85
98
}
86
99
}
@@ -148,6 +161,19 @@ - (void)dealloc
148
161
[[NSNotificationCenter defaultCenter ] removeObserver: self ];
149
162
}
150
163
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
+
151
177
- (instancetype )init
152
178
{
153
179
self = [super init ];
@@ -175,6 +201,7 @@ - (void)initializeUpdateAfterRestart
175
201
// Pending update was initialized, but notifyApplicationReady was not called.
176
202
// Therefore, deduce that it is a broken update and rollback.
177
203
NSLog (@" Update did not finish loading the last time, rolling back to a previous version." );
204
+ needToReportRollback = YES ;
178
205
[self rollbackPackage ];
179
206
} else {
180
207
// Mark that we tried to initialize the new update, so that if it crashes,
@@ -185,6 +212,13 @@ - (void)initializeUpdateAfterRestart
185
212
}
186
213
}
187
214
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
+
188
222
/*
189
223
* This method checks to see whether a specific package hash
190
224
* has previously failed installation.
@@ -193,7 +227,25 @@ - (BOOL)isFailedHash:(NSString*)packageHash
193
227
{
194
228
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
195
229
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
+ }
197
249
}
198
250
199
251
/*
@@ -237,6 +289,13 @@ - (void)loadBundle
237
289
});
238
290
}
239
291
292
+ - (void )recordDeploymentStatusReported : (NSString *)appVersionOrPackageIdentifier
293
+ {
294
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
295
+ [preferences setValue: appVersionOrPackageIdentifier forKey: LastDeploymentReportKey];
296
+ [preferences synchronize ];
297
+ }
298
+
240
299
/*
241
300
* This method is used when an update has failed installation
242
301
* and the app needs to be rolled back to the previous bundle.
@@ -247,10 +306,10 @@ - (void)loadBundle
247
306
- (void )rollbackPackage
248
307
{
249
308
NSError *error;
250
- NSString *packageHash = [CodePushPackage getCurrentPackageHash : &error];
309
+ NSDictionary *failedPackage = [CodePushPackage getCurrentPackage : &error];
251
310
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 ];
254
313
255
314
// Rollback to the previous version and de-register the new update
256
315
[CodePushPackage rollbackPackage ];
@@ -263,7 +322,7 @@ - (void)rollbackPackage
263
322
* to store its hash so that it can be ignored on future
264
323
* attempts to check the server for an update.
265
324
*/
266
- - (void )saveFailedUpdate : (NSString *)packageHash
325
+ - (void )saveFailedUpdate : (NSDictionary *)failedPackage
267
326
{
268
327
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
269
328
NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
@@ -275,7 +334,7 @@ - (void)saveFailedUpdate:(NSString *)packageHash
275
334
failedUpdates = [failedUpdates mutableCopy ];
276
335
}
277
336
278
- [failedUpdates addObject: packageHash ];
337
+ [failedUpdates addObject: failedPackage ];
279
338
[preferences setObject: failedUpdates forKey: FailedUpdatesKey];
280
339
[preferences synchronize ];
281
340
}
@@ -467,6 +526,54 @@ - (void)savePendingUpdate:(NSString *)packageHash
467
526
resolve ([NSNull null ]);
468
527
}
469
528
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
+
470
577
/*
471
578
* This method is the native side of the CodePush.restartApp() method.
472
579
*/
0 commit comments