Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 92e0a60

Browse files
committed
fix rollback
1 parent 75fa7dc commit 92e0a60

File tree

2 files changed

+120
-138
lines changed

2 files changed

+120
-138
lines changed

CodePush.m

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
#import "CodePush.h"
88

9-
@implementation CodePush {
10-
BOOL _resumablePendingUpdateAvailable;
11-
}
9+
@implementation CodePush
1210

1311
RCT_EXPORT_MODULE()
1412

@@ -84,55 +82,6 @@ - (void)cancelRollbackTimer
8482
});
8583
}
8684

87-
/*
88-
* This method checks to see whether a "pending update" has been applied
89-
* (e.g. install was called with a non-immediate mode), but the app hasn't
90-
* yet been restarted (either naturally or programmatically). If there is one,
91-
* it will restart the app (if specified), and start the rollback timer.
92-
*
93-
* Note: This method is safe to call from any thread.
94-
*/
95-
- (void)checkForPendingUpdate:(BOOL)needsRestart
96-
{
97-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
98-
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
99-
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
100-
101-
if (pendingUpdate) {
102-
NSError *error;
103-
NSString *pendingHash = pendingUpdate[PendingUpdateHashKey];
104-
NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error];
105-
106-
NSAssert([pendingHash isEqualToString:currentHash], @"There is a pending update but it's hash doesn't match that of the current package.");
107-
108-
// Kick off the rollback timer and ensure that the necessary state is setup for the pending update.
109-
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
110-
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart];
111-
112-
// Clear the pending update and sync
113-
[preferences removeObjectForKey:PendingUpdateKey];
114-
[preferences synchronize];
115-
}
116-
});
117-
}
118-
119-
/*
120-
* This method is meant as a handler for the global app
121-
* resume notification, and therefore, should not be called
122-
* directly. It simply checks to see whether there is a pending
123-
* update that is meant to be installed on resume, and if so
124-
* it applies it and restarts the app.
125-
*/
126-
- (void)checkForPendingUpdateDuringResume
127-
{
128-
// In order to ensure that CodePush doesn't impact the app's
129-
// resume experience, we're using a simple boolean check to
130-
// check whether we need to restart, before reading the defaults store
131-
if (_resumablePendingUpdateAvailable) {
132-
[self checkForPendingUpdate:YES];
133-
}
134-
}
135-
13685
/*
13786
* This method is used by the React Native bridge to allow
13887
* our plugin to expose constants to the JS-side. In our case
@@ -165,19 +114,34 @@ - (instancetype)init
165114
// Do an async check to see whether
166115
// we need to start the rollback timer
167116
// due to a pending update being installed at start
168-
[self checkForPendingUpdate:NO];
169-
170-
// Register for app resume notifications so that we
171-
// can check for pending updates which support "restart on resume"
172-
[[NSNotificationCenter defaultCenter] addObserver:self
173-
selector:@selector(checkForPendingUpdateDuringResume)
174-
name:UIApplicationWillEnterForegroundNotification
175-
object:[UIApplication sharedApplication]];
117+
[self handleInitIfPendingUpdate];
176118
}
177119

178120
return self;
179121
}
180122

123+
124+
- (void)handleInitIfPendingUpdate
125+
{
126+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
127+
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
128+
129+
if (pendingUpdate) {
130+
didUpdate = true;
131+
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
132+
if (0 != rollbackTimeout) {
133+
dispatch_async(dispatch_get_main_queue(), ^{
134+
[self startRollbackTimer:rollbackTimeout];
135+
});
136+
}
137+
138+
// Clear the pending update and sync
139+
[preferences removeObjectForKey:PendingUpdateKey];
140+
[preferences synchronize];
141+
}
142+
}
143+
144+
181145
/*
182146
* This method performs the actual initialization work for an update
183147
* to ensure that the necessary state is setup, including:
@@ -390,10 +354,15 @@ - (void)startRollbackTimer:(int)rollbackTimeout
390354
if (error) {
391355
reject(error);
392356
} else {
393-
if (installMode != CodePushInstallModeImmediate) {
394-
_resumablePendingUpdateAvailable = (installMode == CodePushInstallModeOnNextResume);
395-
[self savePendingUpdate:updatePackage[@"packageHash"]
396-
rollbackTimeout:rollbackTimeout];
357+
if (installMode == CodePushInstallModeImmediate) {
358+
[self restartPendingUpdate];
359+
} else if (installMode == CodePushInstallModeOnNextResume) {
360+
// Register for app resume notifications so that we
361+
// can check for pending updates which support "restart on resume"
362+
[[NSNotificationCenter defaultCenter] addObserver:self
363+
selector:@selector(restartPendingUpdate)
364+
name:UIApplicationWillEnterForegroundNotification
365+
object:[UIApplication sharedApplication]];
397366
}
398367
// Signal to JS that the update has been applied.
399368
resolve(nil);
@@ -446,15 +415,28 @@ - (void)startRollbackTimer:(int)rollbackTimeout
446415
*/
447416
RCT_EXPORT_METHOD(restartImmediateUpdate:(int)rollbackTimeout)
448417
{
449-
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES];
418+
[self loadBundle];
450419
}
451420

452421
/*
453422
* This method is the native side of the CodePush.restartPendingUpdate() method.
454423
*/
455424
RCT_EXPORT_METHOD(restartPendingUpdate)
456425
{
457-
[self checkForPendingUpdate:YES];
426+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
427+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
428+
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
429+
430+
if (pendingUpdate) {
431+
NSError *error;
432+
NSString *pendingHash = pendingUpdate[PendingUpdateHashKey];
433+
NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error];
434+
435+
NSAssert([pendingHash isEqualToString:currentHash], @"There is a pending update but it's hash doesn't match that of the current package.");
436+
437+
[self loadBundle];
438+
}
439+
});
458440
}
459441

460442
RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL)shouldUseTestFolder)

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 72 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141

4242
public class CodePush {
4343

44-
private boolean resumablePendingUpdateAvailable = false;
4544
private boolean didUpdate = false;
4645
private Timer timer;
4746
private boolean usingTestFolder = false;
@@ -87,7 +86,7 @@ public CodePush(String deploymentKey, Activity mainActivity) {
8786
throw new CodePushUnknownException("Unable to get package info for " + applicationContext.getPackageName(), e);
8887
}
8988

90-
checkForPendingUpdate(/*needsRestart*/ false);
89+
handleInitIfPendingUpdate();
9190
}
9291

9392
public ReactPackage getReactPackage() {
@@ -144,52 +143,6 @@ private void cancelRollbackTimer() {
144143
}
145144
}
146145

147-
private void checkForPendingUpdate(boolean needsRestart) {
148-
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
149-
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
150-
151-
if (pendingUpdateString != null) {
152-
try {
153-
JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString);
154-
String pendingHash = pendingUpdateJSON.getString(PENDING_UPDATE_HASH_KEY);
155-
String currentHash = codePushPackage.getCurrentPackageHash();
156-
if (!pendingHash.equals(currentHash)) {
157-
throw new CodePushUnknownException("Pending hash " + pendingHash +
158-
" and current hash " + currentHash + " are different");
159-
}
160-
161-
int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY);
162-
initializeUpdateWithRollbackTimeout(rollbackTimeout, needsRestart);
163-
settings.edit().remove(PENDING_UPDATE_KEY).commit();
164-
} catch (JSONException e) {
165-
// Should not happen.
166-
throw new CodePushUnknownException("Unable to parse pending update metadata " +
167-
pendingUpdateString + " stored in SharedPreferences", e);
168-
} catch (IOException e) {
169-
// There is no current package hash.
170-
throw new CodePushUnknownException("Should not register a pending update without a saving a current package", e);
171-
}
172-
}
173-
}
174-
175-
private void checkForPendingUpdateDuringResume() {
176-
if (resumablePendingUpdateAvailable) {
177-
checkForPendingUpdate(/*needsRestart*/ true);
178-
}
179-
}
180-
181-
private void initializeUpdateWithRollbackTimeout(int rollbackTimeout, boolean needsRestart) {
182-
didUpdate = true;
183-
184-
if (needsRestart) {
185-
codePushNativeModule.loadBundle();
186-
}
187-
188-
if (0 != rollbackTimeout) {
189-
startRollbackTimer(rollbackTimeout);
190-
}
191-
}
192-
193146
private boolean isFailedHash(String packageHash) {
194147
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
195148
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
@@ -277,6 +230,28 @@ public void run() {
277230
}, timeout);
278231
}
279232

233+
private void handleInitIfPendingUpdate() {
234+
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
235+
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
236+
237+
if (pendingUpdateString != null) {
238+
try {
239+
didUpdate = true;
240+
JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString);
241+
int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY);
242+
if (0 != rollbackTimeout) {
243+
startRollbackTimer(rollbackTimeout);
244+
}
245+
246+
settings.edit().remove(PENDING_UPDATE_KEY).commit();
247+
} catch (JSONException e) {
248+
// Should not happen.
249+
throw new CodePushUnknownException("Unable to parse pending update metadata " +
250+
pendingUpdateString + " stored in SharedPreferences", e);
251+
}
252+
}
253+
}
254+
280255
private class CodePushNativeModule extends ReactContextBaseJavaModule {
281256

282257
private void loadBundle() {
@@ -289,15 +264,33 @@ private void loadBundle() {
289264
public void installUpdate(ReadableMap updatePackage, int rollbackTimeout, int installMode, Promise promise) {
290265
try {
291266
codePushPackage.installPackage(updatePackage);
267+
268+
String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY);
269+
if (pendingHash == null) {
270+
throw new CodePushUnknownException("Update package to be installed has no hash.");
271+
} else {
272+
savePendingUpdate(pendingHash, rollbackTimeout);
273+
}
274+
292275
if (installMode != CodePushInstallMode.IMMEDIATE.getValue()) {
293-
resumablePendingUpdateAvailable = installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue();
294-
String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY);
295-
if (pendingHash == null) {
296-
throw new CodePushUnknownException("Update package to be installed has no hash.");
297-
} else {
298-
savePendingUpdate(pendingHash, rollbackTimeout);
299-
}
276+
restartPendingUpdate();
277+
} else if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()) {
278+
getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() {
279+
@Override
280+
public void onHostResume() {
281+
restartPendingUpdate();
282+
}
283+
284+
@Override
285+
public void onHostPause() {
286+
}
287+
288+
@Override
289+
public void onHostDestroy() {
290+
}
291+
});
300292
}
293+
301294
promise.resolve("");
302295
} catch (IOException e) {
303296
e.printStackTrace();
@@ -377,12 +370,34 @@ public void setUsingTestFolder(boolean shouldUseTestFolder) {
377370

378371
@ReactMethod
379372
public void restartImmediateUpdate(int rollbackTimeout) {
380-
initializeUpdateWithRollbackTimeout(rollbackTimeout, /*needsRestart*/ true);
373+
loadBundle();
381374
}
382375

383376
@ReactMethod
384377
public void restartPendingUpdate() {
385-
checkForPendingUpdate(/*needsRestart*/ true);
378+
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
379+
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
380+
381+
if (pendingUpdateString != null) {
382+
try {
383+
JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString);
384+
String pendingHash = pendingUpdateJSON.getString(PENDING_UPDATE_HASH_KEY);
385+
String currentHash = codePushPackage.getCurrentPackageHash();
386+
if (!pendingHash.equals(currentHash)) {
387+
throw new CodePushUnknownException("Pending hash " + pendingHash +
388+
" and current hash " + currentHash + " are different");
389+
}
390+
391+
loadBundle();
392+
} catch (JSONException e) {
393+
// Should not happen.
394+
throw new CodePushUnknownException("Unable to parse pending update metadata " +
395+
pendingUpdateString + " stored in SharedPreferences", e);
396+
} catch (IOException e) {
397+
// There is no current package hash.
398+
throw new CodePushUnknownException("Should not register a pending update without a saving a current package", e);
399+
}
400+
}
386401
}
387402

388403
@Override
@@ -414,21 +429,6 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactAppli
414429
nativeModules.add(CodePush.this.codePushNativeModule);
415430
nativeModules.add(dialogModule);
416431

417-
reactApplicationContext.addLifecycleEventListener(new LifecycleEventListener() {
418-
@Override
419-
public void onHostResume() {
420-
CodePush.this.checkForPendingUpdateDuringResume();
421-
}
422-
423-
@Override
424-
public void onHostPause() {
425-
}
426-
427-
@Override
428-
public void onHostDestroy() {
429-
}
430-
});
431-
432432
return nativeModules;
433433
}
434434

0 commit comments

Comments
 (0)