From c56a6f293534df5b88b864a0b3bbd80bc8d42a2e Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 12:46:47 -0800 Subject: [PATCH 1/8] Restart on resume --- CodePush.h | 8 +- CodePush.ios.js | 5 ++ CodePush.m | 113 +++++++++++++++++++++-------- CodePush.xcodeproj/project.pbxproj | 6 +- RCTConvert+CodePushRestartMode.m | 14 ++++ package-mixins.js | 5 +- 6 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 RCTConvert+CodePushRestartMode.m diff --git a/CodePush.h b/CodePush.h index 1e95da910..390373a8e 100644 --- a/CodePush.h +++ b/CodePush.h @@ -70,4 +70,10 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)rollbackPackage; -@end \ No newline at end of file +@end + +typedef NS_ENUM(NSInteger, CodePushRestartMode) { + CodePushRestartModeNone, + CodePushRestartModeImmediate, + CodePushRestartModeOnNextResume +}; \ No newline at end of file diff --git a/CodePush.ios.js b/CodePush.ios.js index f20ad569a..23d0c4e44 100644 --- a/CodePush.ios.js +++ b/CodePush.ios.js @@ -206,6 +206,11 @@ var CodePush = { notifyApplicationReady: NativeCodePush.notifyApplicationReady, setUpTestDependencies: setUpTestDependencies, sync: sync, + RestartMode: { + NONE: NativeCodePush.codePushRestartModeNone, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart + IMMEDIATE: NativeCodePush.codePushRestartModeImmediate, // Restart the app immediately + ON_NEXT_RESUME: NativeCodePush.codePushRestartModeOnNextResume // Restart the app the next time it is resumed from the background + }, SyncStatus: { UP_TO_DATE: 0, // The running app is up-to-date UPDATE_IGNORED: 1, // The app had an optional update and the end-user chose to ignore it diff --git a/CodePush.m b/CodePush.m index e0d4ec69d..3f3f6baf2 100644 --- a/CodePush.m +++ b/CodePush.m @@ -1,5 +1,6 @@ #import "RCTBridgeModule.h" #import "RCTEventDispatcher.h" +#import "RCTConvert.h" #import "RCTRootView.h" #import "RCTUtils.h" #import "CodePush.h" @@ -39,7 +40,7 @@ + (NSURL *)getBundleUrl NSDictionary *appFileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:packageFile error:nil]; NSDate *binaryDate = [binaryFileAttributes objectForKey:NSFileModificationDate]; NSDate *packageDate = [appFileAttribs objectForKey:NSFileModificationDate]; - + if ([binaryDate compare:packageDate] == NSOrderedAscending) { // Return package file because it is newer than the app store binary's JS bundle return [[NSURL alloc] initFileURLWithPath:packageFile]; @@ -56,26 +57,30 @@ - (void)cancelRollbackTimer }); } -- (CodePush *)init -{ - self = [super init]; +- (void)checkForPendingUpdate:(BOOL)isAppStart { + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - if (self) { - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; + if (pendingUpdate) + { + NSError *error; + NSString *pendingHash = pendingUpdate[@"hash"]; + NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; - if (pendingUpdate) - { - NSError *error; - NSString *pendingHash = pendingUpdate[@"hash"]; - NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; - - // If the current hash is equivalent to the pending hash, then the app - // restart "picked up" the new update, but we need to kick off the - // rollback timer and ensure that the necessary state is setup. - if ([pendingHash isEqualToString:currentHash]) { + // If the current hash is equivalent to the pending hash, then the app + // restart "picked up" the new update, but we need to kick off the + // rollback timer and ensure that the necessary state is setup. + if ([pendingHash isEqualToString:currentHash]) { + // We only want to initialize the rollback timer in two scenarios: + // 1) The app has been restarted, and therefore, the pending update is already applied + // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume + if (isAppStart || (!isAppStart && [pendingUpdate[@"allowRestartOnResume"] boolValue])) + { int rollbackTimeout = [pendingUpdate[@"rollbackTimeout"] intValue]; - [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:NO]; + + // If the app wasn't restarted "naturally", then we need to restart it manually + BOOL needsRestart = !isAppStart; + [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; // Clear the pending update and sync [preferences removeObjectForKey:PendingUpdateKey]; @@ -83,11 +88,47 @@ - (CodePush *)init } } } +} + +- (void)checkForPendingUpdateDuringResume { + [self checkForPendingUpdate:NO]; +} + +- (NSDictionary *)constantsToExport +{ + // Export the values of the CodePushRestartMode enum + // so that the script-side can easily stay in sync + return @{ @"codePushRestartModeNone": @(CodePushRestartModeNone), + @"codePushRestartModeImmediate": @(CodePushRestartModeImmediate), + @"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) + }; +}; + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (CodePush *)init +{ + self = [super init]; + + if (self) { + [self checkForPendingUpdate:YES]; + + // Register for app resume notifications so that we + // can check for pending updates which support "restart on resume" + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(checkForPendingUpdateDuringResume) + name:UIApplicationWillEnterForegroundNotification + object:[UIApplication sharedApplication]]; + } return self; } -- (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout needsRestart:(BOOL)needsRestart { +- (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout + needsRestart:(BOOL)needsRestart +{ didUpdate = YES; if (needsRestart) { @@ -101,7 +142,8 @@ - (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout needsRestart:(B } } -- (BOOL)isFailedHash:(NSString*)packageHash { +- (BOOL)isFailedHash:(NSString*)packageHash +{ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; return (failedUpdates != nil && [failedUpdates containsObject:packageHash]); @@ -146,13 +188,17 @@ - (void)saveFailedUpdate:(NSString *)packageHash { } - (void)savePendingUpdate:(NSString *)packageHash - rollbackTimeout:(int)rollbackTimeout { + rollbackTimeout:(int)rollbackTimeout + allowRestartOnResume:(BOOL)allowRestartOnResume +{ // Since we're not restarting, we need to store the fact that the update // was applied, but hasn't yet become "active". NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys: packageHash,@"hash", - rollbackTimeout,@"rollbackTimeout", nil]; + [NSNumber numberWithInt:rollbackTimeout],@"rollbackTimeout", + [NSNumber numberWithBool:allowRestartOnResume],@"allowRestartOnResume", + nil]; [preferences setObject:pendingUpdate forKey:PendingUpdateKey]; [preferences synchronize]; @@ -170,10 +216,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout // JavaScript-exported module methods RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage - rollbackTimeout:(int)rollbackTimeout - restartImmediately:(BOOL)restartImmediately - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rollbackTimeout:(int)rollbackTimeout + restartMode:(CodePushRestartMode)restartMode + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error; @@ -183,10 +229,13 @@ - (void)startRollbackTimer:(int)rollbackTimeout if (error) { reject(error); } else { - if (restartImmediately) { + if (restartMode == CodePushRestartModeImmediate) { [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES]; } else { - [self savePendingUpdate:updatePackage[@"packageHash"] rollbackTimeout:rollbackTimeout]; + BOOL allowsRestartOnResume = (restartMode == CodePushRestartModeOnNextResume); + [self savePendingUpdate:updatePackage[@"packageHash"] + rollbackTimeout:rollbackTimeout + allowRestartOnResume:allowsRestartOnResume]; } } }); @@ -256,10 +305,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout { NSError *error; BOOL isFirstRun = didUpdate - && nil != packageHash - && [packageHash length] > 0 - && [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]]; - + && nil != packageHash + && [packageHash length] > 0 + && [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]]; + resolve(@(isFirstRun)); } diff --git a/CodePush.xcodeproj/project.pbxproj b/CodePush.xcodeproj/project.pbxproj index 5e3c4f76a..9c87fecd9 100644 --- a/CodePush.xcodeproj/project.pbxproj +++ b/CodePush.xcodeproj/project.pbxproj @@ -8,7 +8,8 @@ /* Begin PBXBuildFile section */ 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; - 54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; }; + 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */; }; + 54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; }; 810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; }; 81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; }; /* End PBXBuildFile section */ @@ -29,6 +30,7 @@ 134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; }; 13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = ""; }; 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = ""; }; + 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushRestartMode.m"; sourceTree = ""; }; 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = ""; }; 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = ""; }; 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = ""; }; @@ -56,6 +58,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */, 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */, 810D4E6C1B96935000B397E9 /* CodePushPackage.m */, 81D51F391B6181C2000DA084 /* CodePushConfig.m */, @@ -121,6 +124,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */, 81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */, 54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */, 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */, diff --git a/RCTConvert+CodePushRestartMode.m b/RCTConvert+CodePushRestartMode.m new file mode 100644 index 000000000..f2d090fdd --- /dev/null +++ b/RCTConvert+CodePushRestartMode.m @@ -0,0 +1,14 @@ +#import "CodePush.h" +#import "RCTConvert.h" + +// Extending the RCTConvert class allows the React Native +// bridge to handle args of type "CodePushRestartMode" +@implementation RCTConvert (CodePushRestartMode) + +RCT_ENUM_CONVERTER(CodePushRestartMode, (@{ @"codePushRestartModeNone": @(CodePushRestartModeNone), + @"codePushRestartModeImmediate": @(CodePushRestartModeImmediate), + @"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) }), + CodePushRestartModeImmediate, // Default enum value + integerValue) + +@end \ No newline at end of file diff --git a/package-mixins.js b/package-mixins.js index 8c576b637..b7c090f0b 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -1,5 +1,6 @@ var extend = require("extend"); var { NativeAppEventEmitter } = require("react-native"); +var { RestartMode } = require("react-native-code-push"); module.exports = (NativeCodePush) => { var remote = { @@ -36,8 +37,8 @@ module.exports = (NativeCodePush) => { }; var local = { - apply: function apply(rollbackTimeout = 0, restartImmediately = true) { - return NativeCodePush.applyUpdate(this, rollbackTimeout, restartImmediately); + apply: function apply(rollbackTimeout = 0, restartMode = RestartMode.IMMEDIATE) { + return NativeCodePush.applyUpdate(this, rollbackTimeout, restartMode); } }; From 1a7fbf6bec20e5429343cbe6445778120cb13047 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 13:07:44 -0800 Subject: [PATCH 2/8] Fixing enum name --- package-mixins.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package-mixins.js b/package-mixins.js index b7c090f0b..d3225a638 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -1,6 +1,5 @@ var extend = require("extend"); var { NativeAppEventEmitter } = require("react-native"); -var { RestartMode } = require("react-native-code-push"); module.exports = (NativeCodePush) => { var remote = { @@ -37,7 +36,7 @@ module.exports = (NativeCodePush) => { }; var local = { - apply: function apply(rollbackTimeout = 0, restartMode = RestartMode.IMMEDIATE) { + apply: function apply(rollbackTimeout = 0, restartMode = NativeCodePush.codePushRestartModeImmediate) { return NativeCodePush.applyUpdate(this, rollbackTimeout, restartMode); } }; From 18469b593dcdcca5bb08b6b442f1a3e73d5032e7 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 13:36:25 -0800 Subject: [PATCH 3/8] Scheduling pending update check sync --- CodePush.m | 56 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/CodePush.m b/CodePush.m index 3f3f6baf2..e79bafa9f 100644 --- a/CodePush.m +++ b/CodePush.m @@ -58,36 +58,38 @@ - (void)cancelRollbackTimer } - (void)checkForPendingUpdate:(BOOL)isAppStart { - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - - if (pendingUpdate) - { - NSError *error; - NSString *pendingHash = pendingUpdate[@"hash"]; - NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - // If the current hash is equivalent to the pending hash, then the app - // restart "picked up" the new update, but we need to kick off the - // rollback timer and ensure that the necessary state is setup. - if ([pendingHash isEqualToString:currentHash]) { - // We only want to initialize the rollback timer in two scenarios: - // 1) The app has been restarted, and therefore, the pending update is already applied - // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume - if (isAppStart || (!isAppStart && [pendingUpdate[@"allowRestartOnResume"] boolValue])) - { - int rollbackTimeout = [pendingUpdate[@"rollbackTimeout"] intValue]; - - // If the app wasn't restarted "naturally", then we need to restart it manually - BOOL needsRestart = !isAppStart; - [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; - - // Clear the pending update and sync - [preferences removeObjectForKey:PendingUpdateKey]; - [preferences synchronize]; + if (pendingUpdate) + { + NSError *error; + NSString *pendingHash = pendingUpdate[@"hash"]; + NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; + + // If the current hash is equivalent to the pending hash, then the app + // restart "picked up" the new update, but we need to kick off the + // rollback timer and ensure that the necessary state is setup. + if ([pendingHash isEqualToString:currentHash]) { + // We only want to initialize the rollback timer in two scenarios: + // 1) The app has been restarted, and therefore, the pending update is already applied + // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume + if (isAppStart || (!isAppStart && [pendingUpdate[@"allowRestartOnResume"] boolValue])) + { + int rollbackTimeout = [pendingUpdate[@"rollbackTimeout"] intValue]; + + // If the app wasn't restarted "naturally", then we need to restart it manually + BOOL needsRestart = !isAppStart; + [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; + + // Clear the pending update and sync + [preferences removeObjectForKey:PendingUpdateKey]; + [preferences synchronize]; + } } } - } + }); } - (void)checkForPendingUpdateDuringResume { From 8cd36cd16919d8aebd220667dfd7881d478c1c1a Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 15:39:11 -0800 Subject: [PATCH 4/8] Converting pending update keys to consts --- CodePush.m | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/CodePush.m b/CodePush.m index e79bafa9f..16b89acad 100644 --- a/CodePush.m +++ b/CodePush.m @@ -16,6 +16,12 @@ @implementation CodePush NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; NSString * const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; +// These keys are already "namespaced" by the PendingUpdateKey, so +// their values don't need to be obfuscated to prevent collision with app data +NSString * const PendingUpdateAllowsRestartOnResumeKey = @"allowsRestartOnResume"; +NSString * const PendingUpdateHashKey = @"hash"; +NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout"; + @synthesize bridge = _bridge; // Public Obj-C API @@ -65,7 +71,7 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart { if (pendingUpdate) { NSError *error; - NSString *pendingHash = pendingUpdate[@"hash"]; + NSString *pendingHash = pendingUpdate[PendingUpdateHashKey]; NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; // If the current hash is equivalent to the pending hash, then the app @@ -75,9 +81,9 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart { // We only want to initialize the rollback timer in two scenarios: // 1) The app has been restarted, and therefore, the pending update is already applied // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume - if (isAppStart || (!isAppStart && [pendingUpdate[@"allowRestartOnResume"] boolValue])) + if (isAppStart || (!isAppStart && [pendingUpdate[PendingUpdateAllowsRestartOnResumeKey] boolValue])) { - int rollbackTimeout = [pendingUpdate[@"rollbackTimeout"] intValue]; + int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; // If the app wasn't restarted "naturally", then we need to restart it manually BOOL needsRestart = !isAppStart; @@ -197,9 +203,9 @@ - (void)savePendingUpdate:(NSString *)packageHash // was applied, but hasn't yet become "active". NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys: - packageHash,@"hash", - [NSNumber numberWithInt:rollbackTimeout],@"rollbackTimeout", - [NSNumber numberWithBool:allowRestartOnResume],@"allowRestartOnResume", + packageHash,PendingUpdateHashKey, + [NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, + [NSNumber numberWithBool:allowRestartOnResume],PendingUpdateAllowsRestartOnResumeKey, nil]; [preferences setObject:pendingUpdate forKey:PendingUpdateKey]; From b792ae07dd1064309a4e6c3fbaa0a6751a5d6666 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 15:46:30 -0800 Subject: [PATCH 5/8] Fixing some spacing --- CodePush.m | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/CodePush.m b/CodePush.m index 16b89acad..06ffc3aab 100644 --- a/CodePush.m +++ b/CodePush.m @@ -80,7 +80,7 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart { if ([pendingHash isEqualToString:currentHash]) { // We only want to initialize the rollback timer in two scenarios: // 1) The app has been restarted, and therefore, the pending update is already applied - // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume + // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume, so we need to restart the app and the kickoff the rollback timer if (isAppStart || (!isAppStart && [pendingUpdate[PendingUpdateAllowsRestartOnResumeKey] boolValue])) { int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; @@ -109,7 +109,7 @@ - (NSDictionary *)constantsToExport return @{ @"codePushRestartModeNone": @(CodePushRestartModeNone), @"codePushRestartModeImmediate": @(CodePushRestartModeImmediate), @"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) - }; + }; }; - (void)dealloc { @@ -179,7 +179,8 @@ - (void)rollbackPackage [self loadBundle]; } -- (void)saveFailedUpdate:(NSString *)packageHash { +- (void)saveFailedUpdate:(NSString *)packageHash +{ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; if (failedUpdates == nil) { @@ -224,10 +225,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout // JavaScript-exported module methods RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage - rollbackTimeout:(int)rollbackTimeout + rollbackTimeout:(int)rollbackTimeout restartMode:(CodePushRestartMode)restartMode - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error; @@ -250,8 +251,8 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { [CodePushPackage downloadPackage:updatePackage progressCallback:^(long expectedContentLength, long receivedContentLength) { @@ -280,13 +281,13 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { resolve([CodePushConfig getConfiguration]); } RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_main_queue(), ^{ NSError *error; @@ -300,16 +301,16 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { BOOL isFailedHash = [self isFailedHash:packageHash]; resolve(@(isFailedHash)); } RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash - resolve:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolve:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { NSError *error; BOOL isFirstRun = didUpdate @@ -321,7 +322,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { [self cancelRollbackTimer]; resolve([NSNull null]); From 0dd90e903953df829fca5cf25008b5099b0ae448 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 16:27:49 -0800 Subject: [PATCH 6/8] Fixing ordering --- CodePush.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CodePush.m b/CodePush.m index 06ffc3aab..389819b45 100644 --- a/CodePush.m +++ b/CodePush.m @@ -9,9 +9,9 @@ @implementation CodePush RCT_EXPORT_MODULE() +BOOL didUpdate = NO; NSTimer *_timer; BOOL usingTestFolder = NO; -BOOL didUpdate = NO; NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; NSString * const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; @@ -63,7 +63,8 @@ - (void)cancelRollbackTimer }); } -- (void)checkForPendingUpdate:(BOOL)isAppStart { +- (void)checkForPendingUpdate:(BOOL)isAppStart +{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; @@ -98,7 +99,8 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart { }); } -- (void)checkForPendingUpdateDuringResume { +- (void)checkForPendingUpdateDuringResume +{ [self checkForPendingUpdate:NO]; } @@ -112,7 +114,8 @@ - (NSDictionary *)constantsToExport }; }; -- (void)dealloc { +- (void)dealloc +{ [[NSNotificationCenter defaultCenter] removeObserver:self]; } From 54743b36e9f4d6edc2e707b51c6992359ca7d8ad Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 15 Nov 2015 19:51:43 -0800 Subject: [PATCH 7/8] Simplify the resume check for perf --- CodePush.m | 81 ++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/CodePush.m b/CodePush.m index 389819b45..b64ebc760 100644 --- a/CodePush.m +++ b/CodePush.m @@ -5,7 +5,9 @@ #import "RCTUtils.h" #import "CodePush.h" -@implementation CodePush +@implementation CodePush { + BOOL _resumablePendingUpdateAvailable; +} RCT_EXPORT_MODULE() @@ -18,7 +20,6 @@ @implementation CodePush // These keys are already "namespaced" by the PendingUpdateKey, so // their values don't need to be obfuscated to prevent collision with app data -NSString * const PendingUpdateAllowsRestartOnResumeKey = @"allowsRestartOnResume"; NSString * const PendingUpdateHashKey = @"hash"; NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout"; @@ -63,14 +64,13 @@ - (void)cancelRollbackTimer }); } -- (void)checkForPendingUpdate:(BOOL)isAppStart +- (void)checkForPendingUpdate:(BOOL)needsRestart { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - if (pendingUpdate) - { + if (pendingUpdate) { NSError *error; NSString *pendingHash = pendingUpdate[PendingUpdateHashKey]; NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; @@ -79,21 +79,12 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart // restart "picked up" the new update, but we need to kick off the // rollback timer and ensure that the necessary state is setup. if ([pendingHash isEqualToString:currentHash]) { - // We only want to initialize the rollback timer in two scenarios: - // 1) The app has been restarted, and therefore, the pending update is already applied - // 2) The app has been resumed, and the pending update indicates it supports being restarted on resume, so we need to restart the app and the kickoff the rollback timer - if (isAppStart || (!isAppStart && [pendingUpdate[PendingUpdateAllowsRestartOnResumeKey] boolValue])) - { - int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; - - // If the app wasn't restarted "naturally", then we need to restart it manually - BOOL needsRestart = !isAppStart; - [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; - - // Clear the pending update and sync - [preferences removeObjectForKey:PendingUpdateKey]; - [preferences synchronize]; - } + int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; + [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; + + // Clear the pending update and sync + [preferences removeObjectForKey:PendingUpdateKey]; + [preferences synchronize]; } } }); @@ -101,7 +92,12 @@ - (void)checkForPendingUpdate:(BOOL)isAppStart - (void)checkForPendingUpdateDuringResume { - [self checkForPendingUpdate:NO]; + // In order to ensure that CodePush doesn't impact the app's + // resume experience, we're using a simple boolean check to + // check whether we need to restart, before reading the defaults store + if (_resumablePendingUpdateAvailable) { + [self checkForPendingUpdate:YES]; + } } - (NSDictionary *)constantsToExport @@ -111,11 +107,13 @@ - (NSDictionary *)constantsToExport return @{ @"codePushRestartModeNone": @(CodePushRestartModeNone), @"codePushRestartModeImmediate": @(CodePushRestartModeImmediate), @"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) - }; + }; }; - (void)dealloc { + // Ensure the global resume handler is cleared, so that + // this object isn't kept alive unneccesarily [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -124,7 +122,10 @@ - (CodePush *)init self = [super init]; if (self) { - [self checkForPendingUpdate:YES]; + // Do an async check to see whether + // we need to start the rollback timer + // due to a pending update being applied at start + [self checkForPendingUpdate:NO]; // Register for app resume notifications so that we // can check for pending updates which support "restart on resume" @@ -201,16 +202,13 @@ - (void)saveFailedUpdate:(NSString *)packageHash - (void)savePendingUpdate:(NSString *)packageHash rollbackTimeout:(int)rollbackTimeout - allowRestartOnResume:(BOOL)allowRestartOnResume { // Since we're not restarting, we need to store the fact that the update // was applied, but hasn't yet become "active". NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys: packageHash,PendingUpdateHashKey, - [NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, - [NSNumber numberWithBool:allowRestartOnResume],PendingUpdateAllowsRestartOnResumeKey, - nil]; + [NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, nil]; [preferences setObject:pendingUpdate forKey:PendingUpdateKey]; [preferences synchronize]; @@ -228,10 +226,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout // JavaScript-exported module methods RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage - rollbackTimeout:(int)rollbackTimeout + rollbackTimeout:(int)rollbackTimeout restartMode:(CodePushRestartMode)restartMode - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error; @@ -244,18 +242,17 @@ - (void)startRollbackTimer:(int)rollbackTimeout if (restartMode == CodePushRestartModeImmediate) { [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES]; } else { - BOOL allowsRestartOnResume = (restartMode == CodePushRestartModeOnNextResume); + _resumablePendingUpdateAvailable = (restartMode == CodePushRestartModeOnNextResume); [self savePendingUpdate:updatePackage[@"packageHash"] - rollbackTimeout:rollbackTimeout - allowRestartOnResume:allowsRestartOnResume]; + rollbackTimeout:rollbackTimeout]; } } }); } RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { [CodePushPackage downloadPackage:updatePackage progressCallback:^(long expectedContentLength, long receivedContentLength) { @@ -284,13 +281,13 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { resolve([CodePushConfig getConfiguration]); } RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { dispatch_async(dispatch_get_main_queue(), ^{ NSError *error; @@ -304,16 +301,16 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { BOOL isFailedHash = [self isFailedHash:packageHash]; resolve(@(isFailedHash)); } RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash - resolve:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolve:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { NSError *error; BOOL isFirstRun = didUpdate @@ -325,7 +322,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout } RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { [self cancelRollbackTimer]; resolve([NSNull null]); From ad551c2327e94b3ea05f40b1eb322060d7d81337 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 16 Nov 2015 12:00:16 -0800 Subject: [PATCH 8/8] Fixing typo --- CodePush.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodePush.m b/CodePush.m index b64ebc760..7298685d0 100644 --- a/CodePush.m +++ b/CodePush.m @@ -113,7 +113,7 @@ - (NSDictionary *)constantsToExport - (void)dealloc { // Ensure the global resume handler is cleared, so that - // this object isn't kept alive unneccesarily + // this object isn't kept alive unnecessarily [[NSNotificationCenter defaultCenter] removeObserver:self]; }