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

Commit 05697c4

Browse files
committed
remove rollback timeout
1 parent b059fd7 commit 05697c4

File tree

4 files changed

+84
-99
lines changed

4 files changed

+84
-99
lines changed

CodePush.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback)
167167
deploymentKey: null,
168168
ignoreFailedUpdates: true,
169169
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
170-
rollbackTimeout: 0,
171170
updateDialog: null,
172171

173172
...options
@@ -230,7 +229,7 @@ function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback)
230229
remotePackage.download(downloadProgressCallback)
231230
.then((localPackage) => {
232231
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
233-
return localPackage.install(syncOptions.rollbackTimeout, syncOptions.installMode, () => {
232+
return localPackage.install(syncOptions.installMode, () => {
234233
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
235234
resolve(CodePush.SyncStatus.UPDATE_INSTALLED);
236235
});

CodePush.m

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ @implementation CodePush {
2222
// These keys are already "namespaced" by the PendingUpdateKey, so
2323
// their values don't need to be obfuscated to prevent collision with app data
2424
static NSString *const PendingUpdateHashKey = @"hash";
25-
static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
25+
static NSString *const PendingUpdateWasInitializedKey = @"wasInitialized";
2626

2727
@synthesize bridge = _bridge;
2828

@@ -70,20 +70,6 @@ + (NSString *)getApplicationSupportDirectory
7070

7171
// Private API methods
7272

73-
/*
74-
* This method cancels the currently running rollback
75-
* timer, which has the effect of stopping an automatic
76-
* rollback from occurring.
77-
*
78-
* Note: This method is safe to call from any thread.
79-
*/
80-
- (void)cancelRollbackTimer
81-
{
82-
dispatch_async(dispatch_get_main_queue(), ^{
83-
[_timer invalidate];
84-
});
85-
}
86-
8773
/*
8874
* This method is used by the React Native bridge to allow
8975
* our plugin to expose constants to the JS-side. In our case
@@ -130,16 +116,17 @@ - (void)initializeUpdateAfterRestart
130116

131117
if (pendingUpdate) {
132118
_isFirstRunAfterUpdate = YES;
133-
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
134-
if (0 != rollbackTimeout) {
135-
dispatch_async(dispatch_get_main_queue(), ^{
136-
[self startRollbackTimer:rollbackTimeout];
137-
});
119+
BOOL wasInitialized = [pendingUpdate[PendingUpdateWasInitializedKey] boolValue];
120+
if (wasInitialized) {
121+
// Pending update was initialized, but notifiyApplicationReady was not called.
122+
// Therefore, deduce that it is a broken update and rollback.
123+
[self rollbackPackage];
124+
} else {
125+
// Mark that we tried to initiazlie the new update, so that if it crashes,
126+
// we will know that we need to rollback when the app next starts.
127+
[self savePendingUpdate:pendingUpdate[PendingUpdateHashKey]
128+
wasInitialized:YES];
138129
}
139-
140-
// Clear the pending update and sync
141-
[preferences removeObjectForKey:PendingUpdateKey];
142-
[preferences synchronize];
143130
}
144131
}
145132

@@ -189,7 +176,7 @@ - (void)rollbackPackage
189176

190177
// Do the actual rollback and then
191178
// refresh the app with the previous package
192-
[CodePushPackage rollbackPackage];
179+
[self removePendingUpdate];
193180
[self loadBundle];
194181
}
195182

@@ -215,39 +202,36 @@ - (void)saveFailedUpdate:(NSString *)packageHash
215202
[preferences synchronize];
216203
}
217204

205+
/*
206+
* This method is called in notifyApplicationReady to register the fact that
207+
* the pending update succeeded and therefore can be removed.
208+
*/
209+
- (void)removePendingUpdate
210+
{
211+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
212+
[preferences removeObjectForKey:PendingUpdateKey];
213+
[preferences synchronize];
214+
}
215+
218216
/*
219217
* When an update is installed whose mode isn't IMMEDIATE, this method
220-
* can be called to store the pending update's metadata (e.g. rollbackTimeout)
218+
* can be called to store the pending update's metadata (e.g. packageHash)
221219
* so that it can be used when the actual update application occurs at a later point.
222220
*/
223221
- (void)savePendingUpdate:(NSString *)packageHash
224-
rollbackTimeout:(int)rollbackTimeout
222+
wasInitialized:(BOOL)wasInitialized
225223
{
226224
// Since we're not restarting, we need to store the fact that the update
227225
// was installed, but hasn't yet become "active".
228226
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
229227
NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys:
230228
packageHash,PendingUpdateHashKey,
231-
[NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, nil];
229+
[NSNumber numberWithBool:wasInitialized],PendingUpdateWasInitializedKey, nil];
232230

233231
[preferences setObject:pendingUpdate forKey:PendingUpdateKey];
234232
[preferences synchronize];
235233
}
236234

237-
/*
238-
* This method handles starting the actual rollback timer
239-
* after an update has been installed.
240-
*/
241-
- (void)startRollbackTimer:(int)rollbackTimeout
242-
{
243-
double timeoutInSeconds = rollbackTimeout / 1000;
244-
_timer = [NSTimer scheduledTimerWithTimeInterval:timeoutInSeconds
245-
target:self
246-
selector:@selector(rollbackPackage)
247-
userInfo:nil
248-
repeats:NO];
249-
}
250-
251235
// JavaScript-exported module methods
252236

253237
/*
@@ -318,7 +302,6 @@ - (void)startRollbackTimer:(int)rollbackTimeout
318302
* This method is the native side of the LocalPackage.install method.
319303
*/
320304
RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
321-
rollbackTimeout:(int)rollbackTimeout
322305
installMode:(CodePushInstallMode)installMode
323306
resolver:(RCTPromiseResolveBlock)resolve
324307
rejecter:(RCTPromiseRejectBlock)reject)
@@ -332,7 +315,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout
332315
reject(error);
333316
} else {
334317
[self savePendingUpdate:updatePackage[@"packageHash"]
335-
rollbackTimeout:rollbackTimeout];
318+
wasInitialized:NO];
336319

337320
if (installMode == CodePushInstallModeImmediate) {
338321
[self loadBundle];
@@ -389,7 +372,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout
389372
RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
390373
rejecter:(RCTPromiseRejectBlock)reject)
391374
{
392-
[self cancelRollbackTimer];
375+
[self removePendingUpdate];
393376
resolve([NSNull null]);
394377
}
395378

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

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.microsoft.codepush.react;
22

33
import com.facebook.react.ReactPackage;
4-
import com.facebook.react.bridge.ActivityEventListener;
54
import com.facebook.react.bridge.JavaScriptModule;
65
import com.facebook.react.bridge.LifecycleEventListener;
76
import com.facebook.react.bridge.NativeModule;
@@ -31,28 +30,23 @@
3130
import java.io.File;
3231
import java.io.IOException;
3332
import java.util.ArrayList;
34-
import java.util.Calendar;
35-
import java.util.Date;
3633
import java.util.HashMap;
3734
import java.util.List;
3835
import java.util.Map;
39-
import java.util.Timer;
40-
import java.util.TimerTask;
4136
import java.util.zip.ZipEntry;
4237
import java.util.zip.ZipFile;
4338

4439
public class CodePush {
4540

4641
private boolean didUpdate = false;
47-
private Timer timer;
4842
private boolean usingTestFolder = false;
4943

5044
private String assetsBundleFileName;
5145

5246
private final String FAILED_UPDATES_KEY = "CODE_PUSH_FAILED_UPDATES";
5347
private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE";
5448
private final String PENDING_UPDATE_HASH_KEY = "hash";
55-
private final String PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY = "rollbackTimeout";
49+
private final String PENDING_UPDATE_WAS_INITIALIZED_KEY = "wasInitialized";
5650
private final String ASSETS_BUNDLE_PREFIX = "assets://";
5751
private final String CODE_PUSH_PREFERENCES = "CodePush";
5852
private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress";
@@ -98,10 +92,6 @@ public ReactPackage getReactPackage() {
9892
return codePushReactPackage;
9993
}
10094

101-
public String getDocumentsDirectory() {
102-
return codePushPackage.getDocumentsDirectory();
103-
}
104-
10595
public String getBundleUrl(String assetsBundleFileName) {
10696
this.assetsBundleFileName = assetsBundleFileName;
10797
String binaryJsBundleUrl = ASSETS_BUNDLE_PREFIX + assetsBundleFileName;
@@ -138,13 +128,6 @@ public String getBundleUrl(String assetsBundleFileName) {
138128
}
139129
}
140130

141-
private void cancelRollbackTimer() {
142-
if(timer != null) {
143-
timer.cancel();
144-
timer = null;
145-
}
146-
}
147-
148131
private boolean isFailedHash(String packageHash) {
149132
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
150133
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
@@ -176,7 +159,10 @@ private void rollbackPackage() {
176159
throw new CodePushUnknownException("Error in rolling back package", e);
177160
}
178161

179-
codePushNativeModule.loadBundle();
162+
removePendingUpdate();
163+
if (codePushNativeModule != null) {
164+
codePushNativeModule.loadBundle();
165+
}
180166
}
181167

182168
private void saveFailedUpdate(String packageHash) {
@@ -204,68 +190,85 @@ private void saveFailedUpdate(String packageHash) {
204190
}
205191
}
206192

207-
private void savePendingUpdate(String packageHash, int rollbackTimeout) {
193+
private void removePendingUpdate() {
194+
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
195+
settings.edit().remove(PENDING_UPDATE_KEY).commit();
196+
}
197+
198+
private void savePendingUpdate(String packageHash, boolean wasInitialized) {
208199
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
209200
JSONObject pendingUpdate = new JSONObject();
210201
try {
211202
pendingUpdate.put(PENDING_UPDATE_HASH_KEY, packageHash);
212-
pendingUpdate.put(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY, rollbackTimeout);
203+
pendingUpdate.put(PENDING_UPDATE_WAS_INITIALIZED_KEY, wasInitialized);
213204
settings.edit().putString(PENDING_UPDATE_KEY, pendingUpdate.toString()).commit();
214205
} catch (JSONException e) {
215206
// Should not happen.
216207
throw new CodePushUnknownException("Unable to save pending update.", e);
217208
}
218-
219-
}
220-
221-
private void startRollbackTimer(int rollbackTimeout) {
222-
timer = new Timer();
223-
Calendar c = Calendar.getInstance();
224-
c.setTime(new Date());
225-
c.add(Calendar.MILLISECOND, rollbackTimeout);
226-
Date timeout = c.getTime();
227-
timer.schedule(new TimerTask() {
228-
@Override
229-
public void run() {
230-
rollbackPackage();
231-
}
232-
}, timeout);
233209
}
234210

235-
private void initializeUpdateAfterRestart() {
211+
private JSONObject getPendingUpdate() {
236212
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
237213
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
214+
if (pendingUpdateString == null) {
215+
return null;
216+
}
238217

239-
if (pendingUpdateString != null) {
218+
try {
219+
JSONObject pendingUpdate = new JSONObject(pendingUpdateString);
220+
return pendingUpdate;
221+
} catch (JSONException e) {
222+
// Should not happen.
223+
throw new CodePushUnknownException("Unable to parse pending update metadata " +
224+
pendingUpdateString + " stored in SharedPreferences", e);
225+
}
226+
}
227+
228+
private void initializeUpdateAfterRestart() {
229+
JSONObject pendingUpdate = getPendingUpdate();
230+
if (pendingUpdate != null) {
231+
didUpdate = true;
240232
try {
241-
didUpdate = true;
242-
JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString);
243-
int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY);
244-
if (0 != rollbackTimeout) {
245-
startRollbackTimer(rollbackTimeout);
233+
boolean wasInitialized = pendingUpdate.getBoolean(PENDING_UPDATE_WAS_INITIALIZED_KEY);
234+
if (wasInitialized) {
235+
// Pending update was initialized, but notifiyApplicationReady was not called.
236+
// Therefore, deduce that it is a broken update and rollback.
237+
rollbackPackage();
238+
} else {
239+
// Mark that we tried to initiazlie the new update, so that if it crashes,
240+
// we will know that we need to rollback when the app next starts.
241+
savePendingUpdate(pendingUpdate.getString(PENDING_UPDATE_HASH_KEY),
242+
/* wasInitialized */true);
246243
}
247-
248-
settings.edit().remove(PENDING_UPDATE_KEY).commit();
249244
} catch (JSONException e) {
250245
// Should not happen.
251-
throw new CodePushUnknownException("Unable to parse pending update metadata " +
252-
pendingUpdateString + " stored in SharedPreferences", e);
246+
throw new CodePushUnknownException("Unable to read pending update metadata stored in SharedPreferences", e);
253247
}
254248
}
255249
}
256250

257251
private class CodePushNativeModule extends ReactContextBaseJavaModule {
258252

259253
private LifecycleEventListener lifecycleEventListener = null;
254+
private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js";
255+
256+
private void clearReactDevBundleCache() {
257+
File cachedDevBundle = new File(getReactApplicationContext().getFilesDir(), JS_BUNDLE_FILE_NAME);
258+
if (cachedDevBundle.exists()) {
259+
cachedDevBundle.delete();
260+
}
261+
}
260262

261263
private void loadBundle() {
264+
clearReactDevBundleCache();
262265
Intent intent = mainActivity.getIntent();
263266
mainActivity.finish();
264267
mainActivity.startActivity(intent);
265268
}
266269

267270
@ReactMethod
268-
public void installUpdate(final ReadableMap updatePackage, final int rollbackTimeout, final int installMode, final Promise promise) {
271+
public void installUpdate(final ReadableMap updatePackage, final int installMode, final Promise promise) {
269272
AsyncTask asyncTask = new AsyncTask() {
270273
@Override
271274
protected Void doInBackground(Object[] params) {
@@ -276,7 +279,7 @@ protected Void doInBackground(Object[] params) {
276279
if (pendingHash == null) {
277280
throw new CodePushUnknownException("Update package to be installed has no hash.");
278281
} else {
279-
savePendingUpdate(pendingHash, rollbackTimeout);
282+
savePendingUpdate(pendingHash, /* wasInitialized */false);
280283
}
281284

282285
if (installMode == CodePushInstallMode.IMMEDIATE.getValue()) {
@@ -394,7 +397,7 @@ public void isFirstRun(String packageHash, Promise promise) {
394397

395398
@ReactMethod
396399
public void notifyApplicationReady(Promise promise) {
397-
cancelRollbackTimer();
400+
removePendingUpdate();
398401
promise.resolve("");
399402
}
400403

package-mixins.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ module.exports = (NativeCodePush) => {
3535
};
3636

3737
var local = {
38-
install: function install(rollbackTimeout = 0, installMode = NativeCodePush.codePushInstallModeOnNextRestart, updateInstalledCallback) {
39-
return NativeCodePush.installUpdate(this, rollbackTimeout, installMode)
38+
install: function install(installMode = NativeCodePush.codePushInstallModeOnNextRestart, updateInstalledCallback) {
39+
return NativeCodePush.installUpdate(this, installMode)
4040
.then(function() {
4141
updateInstalledCallback && updateInstalledCallback();
4242
if (installMode == NativeCodePush.codePushInstallModeImmediate) {

0 commit comments

Comments
 (0)