Skip to content

Commit ff891db

Browse files
committed
Merge pull request microsoft#51 from Microsoft/restartOnResume
Restart on resume
2 parents 92112ab + ad551c2 commit ff891db

File tree

6 files changed

+119
-32
lines changed

6 files changed

+119
-32
lines changed

CodePush.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,10 @@ failCallback:(void (^)(NSError *err))failCallback;
7070

7171
+ (void)rollbackPackage;
7272

73-
@end
73+
@end
74+
75+
typedef NS_ENUM(NSInteger, CodePushRestartMode) {
76+
CodePushRestartModeNone,
77+
CodePushRestartModeImmediate,
78+
CodePushRestartModeOnNextResume
79+
};

CodePush.ios.js

+5
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ var CodePush = {
206206
notifyApplicationReady: NativeCodePush.notifyApplicationReady,
207207
setUpTestDependencies: setUpTestDependencies,
208208
sync: sync,
209+
RestartMode: {
210+
NONE: NativeCodePush.codePushRestartModeNone, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
211+
IMMEDIATE: NativeCodePush.codePushRestartModeImmediate, // Restart the app immediately
212+
ON_NEXT_RESUME: NativeCodePush.codePushRestartModeOnNextResume // Restart the app the next time it is resumed from the background
213+
},
209214
SyncStatus: {
210215
UP_TO_DATE: 0, // The running app is up-to-date
211216
UPDATE_IGNORED: 1, // The app had an optional update and the end-user chose to ignore it

CodePush.m

+86-28
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
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

912
RCT_EXPORT_MODULE()
1013

14+
BOOL didUpdate = NO;
1115
NSTimer *_timer;
1216
BOOL usingTestFolder = NO;
13-
BOOL didUpdate = NO;
1417

1518
NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
1619
NSString * 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
172228
RCT_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

CodePush.xcodeproj/project.pbxproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
/* Begin PBXBuildFile section */
1010
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
11-
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; };
11+
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */; };
12+
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; };
1213
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
1314
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
1415
/* End PBXBuildFile section */
@@ -29,6 +30,7 @@
2930
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
3031
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
3132
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
33+
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushRestartMode.m"; sourceTree = "<group>"; };
3234
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
3335
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
3436
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
@@ -56,6 +58,7 @@
5658
58B511D21A9E6C8500147676 = {
5759
isa = PBXGroup;
5860
children = (
61+
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */,
5962
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
6063
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
6164
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
@@ -121,6 +124,7 @@
121124
isa = PBXSourcesBuildPhase;
122125
buildActionMask = 2147483647;
123126
files = (
127+
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */,
124128
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
125129
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
126130
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,

RCTConvert+CodePushRestartMode.m

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#import "CodePush.h"
2+
#import "RCTConvert.h"
3+
4+
// Extending the RCTConvert class allows the React Native
5+
// bridge to handle args of type "CodePushRestartMode"
6+
@implementation RCTConvert (CodePushRestartMode)
7+
8+
RCT_ENUM_CONVERTER(CodePushRestartMode, (@{ @"codePushRestartModeNone": @(CodePushRestartModeNone),
9+
@"codePushRestartModeImmediate": @(CodePushRestartModeImmediate),
10+
@"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) }),
11+
CodePushRestartModeImmediate, // Default enum value
12+
integerValue)
13+
14+
@end

package-mixins.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ module.exports = (NativeCodePush) => {
3636
};
3737

3838
var local = {
39-
apply: function apply(rollbackTimeout = 0, restartImmediately = true) {
40-
return NativeCodePush.applyUpdate(this, rollbackTimeout, restartImmediately);
39+
apply: function apply(rollbackTimeout = 0, restartMode = NativeCodePush.codePushRestartModeImmediate) {
40+
return NativeCodePush.applyUpdate(this, rollbackTimeout, restartMode);
4141
}
4242
};
4343

0 commit comments

Comments
 (0)