11#import " RCTBridgeModule.h"
22#import " RCTEventDispatcher.h"
3+ #import " RCTConvert.h"
34#import " RCTRootView.h"
45#import " RCTUtils.h"
56#import " CodePush.h"
67
7- @implementation CodePush
8+ @implementation CodePush {
9+ BOOL _resumablePendingUpdateAvailable;
10+ }
811
912RCT_EXPORT_MODULE ()
1013
14+ BOOL didUpdate = NO;
1115NSTimer *_timer;
1216BOOL usingTestFolder = NO ;
13- BOOL didUpdate = NO ;
1417
1518NSString * const FailedUpdatesKey = @" CODE_PUSH_FAILED_UPDATES" ;
1619NSString * const PendingUpdateKey = @" CODE_PUSH_PENDING_UPDATE" ;
1720
21+ // These keys are already "namespaced" by the PendingUpdateKey, so
22+ // their values don't need to be obfuscated to prevent collision with app data
23+ NSString * const PendingUpdateHashKey = @" hash" ;
24+ NSString * const PendingUpdateRollbackTimeoutKey = @" rollbackTimeout" ;
25+
1826@synthesize bridge = _bridge;
1927
2028// Public Obj-C API
@@ -39,7 +47,7 @@ + (NSURL *)getBundleUrl
3947 NSDictionary *appFileAttribs = [[NSFileManager defaultManager ] attributesOfItemAtPath: packageFile error: nil ];
4048 NSDate *binaryDate = [binaryFileAttributes objectForKey: NSFileModificationDate ];
4149 NSDate *packageDate = [appFileAttribs objectForKey: NSFileModificationDate ];
42-
50+
4351 if ([binaryDate compare: packageDate] == NSOrderedAscending) {
4452 // Return package file because it is newer than the app store binary's JS bundle
4553 return [[NSURL alloc ] initFileURLWithPath: packageFile];
@@ -56,38 +64,83 @@ - (void)cancelRollbackTimer
5664 });
5765}
5866
59- - (CodePush *) init
67+ - (void ) checkForPendingUpdate : ( BOOL ) needsRestart
6068{
61- self = [super init ];
62-
63- if (self) {
69+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
6470 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
6571 NSDictionary *pendingUpdate = [preferences objectForKey: PendingUpdateKey];
6672
67- if (pendingUpdate)
68- {
73+ if (pendingUpdate) {
6974 NSError *error;
70- NSString *pendingHash = pendingUpdate[@" hash " ];
75+ NSString *pendingHash = pendingUpdate[PendingUpdateHashKey ];
7176 NSString *currentHash = [CodePushPackage getCurrentPackageHash: &error];
7277
7378 // If the current hash is equivalent to the pending hash, then the app
7479 // restart "picked up" the new update, but we need to kick off the
7580 // rollback timer and ensure that the necessary state is setup.
7681 if ([pendingHash isEqualToString: currentHash]) {
77- int rollbackTimeout = [pendingUpdate[@" rollbackTimeout " ] intValue ];
78- [self initializeUpdateWithRollbackTimeout: rollbackTimeout needsRestart: NO ];
82+ int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey ] intValue ];
83+ [self initializeUpdateWithRollbackTimeout: rollbackTimeout needsRestart: needsRestart ];
7984
8085 // Clear the pending update and sync
8186 [preferences removeObjectForKey: PendingUpdateKey];
8287 [preferences synchronize ];
8388 }
8489 }
90+ });
91+ }
92+
93+ - (void )checkForPendingUpdateDuringResume
94+ {
95+ // In order to ensure that CodePush doesn't impact the app's
96+ // resume experience, we're using a simple boolean check to
97+ // check whether we need to restart, before reading the defaults store
98+ if (_resumablePendingUpdateAvailable) {
99+ [self checkForPendingUpdate: YES ];
100+ }
101+ }
102+
103+ - (NSDictionary *)constantsToExport
104+ {
105+ // Export the values of the CodePushRestartMode enum
106+ // so that the script-side can easily stay in sync
107+ return @{ @" codePushRestartModeNone" : @(CodePushRestartModeNone),
108+ @" codePushRestartModeImmediate" : @(CodePushRestartModeImmediate),
109+ @" codePushRestartModeOnNextResume" : @(CodePushRestartModeOnNextResume)
110+ };
111+ };
112+
113+ - (void )dealloc
114+ {
115+ // Ensure the global resume handler is cleared, so that
116+ // this object isn't kept alive unnecessarily
117+ [[NSNotificationCenter defaultCenter ] removeObserver: self ];
118+ }
119+
120+ - (CodePush *)init
121+ {
122+ self = [super init ];
123+
124+ if (self) {
125+ // Do an async check to see whether
126+ // we need to start the rollback timer
127+ // due to a pending update being applied at start
128+ [self checkForPendingUpdate: NO ];
129+
130+ // Register for app resume notifications so that we
131+ // can check for pending updates which support "restart on resume"
132+ [[NSNotificationCenter defaultCenter ] addObserver: self
133+ selector: @selector (checkForPendingUpdateDuringResume )
134+ name: UIApplicationWillEnterForegroundNotification
135+ object: [UIApplication sharedApplication ]];
85136 }
86137
87138 return self;
88139}
89140
90- - (void )initializeUpdateWithRollbackTimeout : (int )rollbackTimeout needsRestart : (BOOL )needsRestart {
141+ - (void )initializeUpdateWithRollbackTimeout : (int )rollbackTimeout
142+ needsRestart : (BOOL )needsRestart
143+ {
91144 didUpdate = YES ;
92145
93146 if (needsRestart) {
@@ -101,7 +154,8 @@ - (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout needsRestart:(B
101154 }
102155}
103156
104- - (BOOL )isFailedHash : (NSString *)packageHash {
157+ - (BOOL )isFailedHash : (NSString *)packageHash
158+ {
105159 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
106160 NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
107161 return (failedUpdates != nil && [failedUpdates containsObject: packageHash]);
@@ -129,7 +183,8 @@ - (void)rollbackPackage
129183 [self loadBundle ];
130184}
131185
132- - (void )saveFailedUpdate : (NSString *)packageHash {
186+ - (void )saveFailedUpdate : (NSString *)packageHash
187+ {
133188 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
134189 NSMutableArray *failedUpdates = [preferences objectForKey: FailedUpdatesKey];
135190 if (failedUpdates == nil ) {
@@ -146,13 +201,14 @@ - (void)saveFailedUpdate:(NSString *)packageHash {
146201}
147202
148203- (void )savePendingUpdate : (NSString *)packageHash
149- rollbackTimeout : (int )rollbackTimeout {
204+ rollbackTimeout : (int )rollbackTimeout
205+ {
150206 // Since we're not restarting, we need to store the fact that the update
151207 // was applied, but hasn't yet become "active".
152208 NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
153209 NSDictionary *pendingUpdate = [[NSDictionary alloc ] initWithObjectsAndKeys:
154- packageHash,@" hash " ,
155- rollbackTimeout, @" rollbackTimeout " , nil ];
210+ packageHash,PendingUpdateHashKey ,
211+ [ NSNumber numberWithInt: rollbackTimeout],PendingUpdateRollbackTimeoutKey , nil ];
156212
157213 [preferences setObject: pendingUpdate forKey: PendingUpdateKey];
158214 [preferences synchronize ];
@@ -170,10 +226,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout
170226
171227// JavaScript-exported module methods
172228RCT_EXPORT_METHOD (applyUpdate:(NSDictionary *)updatePackage
173- rollbackTimeout:(int )rollbackTimeout
174- restartImmediately:( BOOL )restartImmediately
175- resolver:(RCTPromiseResolveBlock)resolve
176- rejecter:(RCTPromiseRejectBlock)reject)
229+ rollbackTimeout:(int )rollbackTimeout
230+ restartMode:(CodePushRestartMode)restartMode
231+ resolver:(RCTPromiseResolveBlock)resolve
232+ rejecter:(RCTPromiseRejectBlock)reject)
177233{
178234 dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
179235 NSError *error;
@@ -183,10 +239,12 @@ - (void)startRollbackTimer:(int)rollbackTimeout
183239 if (error) {
184240 reject (error);
185241 } else {
186- if (restartImmediately ) {
242+ if (restartMode == CodePushRestartModeImmediate ) {
187243 [self initializeUpdateWithRollbackTimeout: rollbackTimeout needsRestart: YES ];
188244 } else {
189- [self savePendingUpdate: updatePackage[@" packageHash" ] rollbackTimeout: rollbackTimeout];
245+ _resumablePendingUpdateAvailable = (restartMode == CodePushRestartModeOnNextResume);
246+ [self savePendingUpdate: updatePackage[@" packageHash" ]
247+ rollbackTimeout: rollbackTimeout];
190248 }
191249 }
192250 });
@@ -256,10 +314,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout
256314{
257315 NSError *error;
258316 BOOL isFirstRun = didUpdate
259- && nil != packageHash
260- && [packageHash length ] > 0
261- && [packageHash isEqualToString: [CodePushPackage getCurrentPackageHash: &error]];
262-
317+ && nil != packageHash
318+ && [packageHash length ] > 0
319+ && [packageHash isEqualToString: [CodePushPackage getCurrentPackageHash: &error]];
320+
263321 resolve (@(isFirstRun));
264322}
265323
0 commit comments