From 51c40c9652786a7d99dda71125365dc01f4323b2 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 15 Jan 2016 15:15:41 -0800 Subject: [PATCH 1/8] report acquisition status --- CodePush.js | 32 ++- CodePush.m | 124 ++++++++++- CodePushConfig.m | 16 ++ .../testcases/SwitchDeploymentKeyTest.js | 2 +- .../resources/remotePackage.js | 1 + .../utils/mockAcquisitionSdk.js | 5 + .../microsoft/codepush/react/CodePush.java | 194 +++++++++++++++--- request-fetch-adapter.js | 18 +- 8 files changed, 347 insertions(+), 45 deletions(-) diff --git a/CodePush.js b/CodePush.js index 7baa35b..8d8244a 100644 --- a/CodePush.js +++ b/CodePush.js @@ -62,6 +62,7 @@ async function checkForUpdate(deploymentKey = null) { } else { const remotePackage = { ...update, ...PackageMixins.remote }; remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash); + remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey; return remotePackage; } } @@ -102,6 +103,18 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }; + sdk.reportStatus = (package, status) => { + return new Promise((resolve, reject) => { + module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, package, status, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }; + return sdk; } @@ -110,6 +123,23 @@ function log(message) { console.log(`[CodePush] ${message}`) } +async function notifyApplicationReady() { + await NativeCodePush.notifyApplicationReady(); + const statusReport = await NativeCodePush.getNewStatusReport(); + if (statusReport) { + const config = await getConfiguration(); + if (statusReport.appVersion) { + config.appVersion = statusReport.appVersion; + const sdk = getPromisifiedSdk(requestFetchAdapter, config); + sdk.reportStatus(); + } else { + config.deploymentKey = statusReport.package.deploymentKey; + const sdk = getPromisifiedSdk(requestFetchAdapter, config); + sdk.reportStatus(statusReport.package, statusReport.status); + } + } +} + function restartApp(onlyIfUpdateIsPending = false) { NativeCodePush.restartApp(onlyIfUpdateIsPending); } @@ -269,7 +299,7 @@ const CodePush = { getConfiguration, getCurrentPackage, log, - notifyApplicationReady: NativeCodePush.notifyApplicationReady, + notifyApplicationReady, restartApp, setUpTestDependencies, sync, diff --git a/CodePush.m b/CodePush.m index bba73ed..b886eea 100644 --- a/CodePush.m +++ b/CodePush.m @@ -15,9 +15,14 @@ RCT_EXPORT_MODULE() static BOOL testConfigurationFlag = NO; +// These constants represent valid deployment statuses +static NSString *const DeploymentSucceeded = @"DeploymentSucceeded"; +static NSString *const DeploymentFailed = @"DeploymentFailed"; + // These keys represent the names we use to store data in NSUserDefaults static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; +static NSString *const StatusReportsKey = @"CODE_PUSH_STATUS_REPORTS"; // These keys are already "namespaced" by the PendingUpdateKey, so // their values don't need to be obfuscated to prevent collision with app data @@ -26,6 +31,8 @@ static NSString *const PendingUpdateIsLoadingKey = @"isLoading"; // These keys are used to inspect/augment the metadata // that is associated with an update's package. +static NSString *const DeploymentKeyKey = @"deploymentKey"; +static NSString *const LabelKey = @"label"; static NSString *const PackageHashKey = @"packageHash"; static NSString *const PackageIsPendingKey = @"isPending"; @@ -70,7 +77,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSString *packageAppVersion = [currentPackageMetadata objectForKey:@"appVersion"]; - if ([binaryDate compare:packageDate] == NSOrderedAscending && [binaryAppVersion isEqualToString:packageAppVersion]) { + if ([binaryDate compare:packageDate] == NSOrderedAscending && ([CodePush isUsingTestConfiguration] ||[binaryAppVersion isEqualToString:packageAppVersion])) { // Return package file because it is newer than the app store binary's JS bundle NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile]; NSLog(logMessageFormat, packageUrl); @@ -148,6 +155,19 @@ static NSString *const PackageIsPendingKey = @"isPending"; [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (NSString *)getPackageStatusReportIdentifier:(NSDictionary *)package +{ + // Because deploymentKeys can be dynamically switched, we use a + // combination of the deploymentKey and label as the packageIdentifier. + NSString *deploymentKey = [package objectForKey:DeploymentKeyKey]; + NSString *label = [package objectForKey:LabelKey]; + if (deploymentKey && label) { + return [[deploymentKey stringByAppendingString:@":"] stringByAppendingString:label]; + } else { + return nil; + } +} + - (instancetype)init { self = [super init]; @@ -185,6 +205,13 @@ static NSString *const PackageIsPendingKey = @"isPending"; } } +- (BOOL)isDeploymentStatusNotYetReported:(NSString *)appVersionOrPackageIdentifier +{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSDictionary *sentStatusReports = [preferences objectForKey:StatusReportsKey]; + return sentStatusReports == nil || [sentStatusReports objectForKey:appVersionOrPackageIdentifier] == nil; +} + /* * This method checks to see whether a specific package hash * has previously failed installation. @@ -193,7 +220,25 @@ static NSString *const PackageIsPendingKey = @"isPending"; { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; - return (failedUpdates != nil && [failedUpdates containsObject:packageHash]); + if (failedUpdates == nil) { + return NO; + } else { + for (NSDictionary *failedPackage in failedUpdates) + { + // Type check is needed for backwards compatibility, where we used to just store + // the failed package hash instead of the metadata. This only impacts "dev" + // scenarios, since in production we clear out old information whenever a new + // binary is applied. + if ([failedPackage isKindOfClass:[NSDictionary class]]) { + NSString *failedPackageHash = [failedPackage objectForKey:PackageHashKey]; + if ([packageHash isEqualToString:failedPackageHash]) { + return YES; + } + } + } + + return NO; + } } /* @@ -237,6 +282,22 @@ static NSString *const PackageIsPendingKey = @"isPending"; }); } +- (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier + status:(NSString *)status +{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableDictionary *sentStatusReports = [preferences objectForKey:StatusReportsKey]; + if (sentStatusReports == nil) { + sentStatusReports = [NSMutableDictionary dictionary]; + } else { + sentStatusReports = [sentStatusReports mutableCopy]; + } + + [sentStatusReports setValue:status forKey:appVersionOrPackageIdentifier]; + [preferences setValue:sentStatusReports forKey:StatusReportsKey]; + [preferences synchronize]; +} + /* * This method is used when an update has failed installation * and the app needs to be rolled back to the previous bundle. @@ -247,10 +308,10 @@ static NSString *const PackageIsPendingKey = @"isPending"; - (void)rollbackPackage { NSError *error; - NSString *packageHash = [CodePushPackage getCurrentPackageHash:&error]; + NSDictionary *failedPackage = [CodePushPackage getCurrentPackage:&error]; - // Write the current package's hash to the "failed list" - [self saveFailedUpdate:packageHash]; + // Write the current package's metadata to the "failed list" + [self saveFailedUpdate:failedPackage]; // Rollback to the previous version and de-register the new update [CodePushPackage rollbackPackage]; @@ -263,7 +324,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; * to store its hash so that it can be ignored on future * attempts to check the server for an update. */ -- (void)saveFailedUpdate:(NSString *)packageHash +- (void)saveFailedUpdate:(NSDictionary *)failedPackage { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; @@ -275,7 +336,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; failedUpdates = [failedUpdates mutableCopy]; } - [failedUpdates addObject:packageHash]; + [failedUpdates addObject:failedPackage]; [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; [preferences synchronize]; } @@ -467,6 +528,55 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve resolve([NSNull null]); } +/* + * This method is checks if a new status update exists (new version was installed, + * or an update failed) and return its details (version label, status). + */ +RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + // Check if the current appVersion has been reported. + NSString *appVersion = [[CodePushConfig current] appVersion]; + if ([self isDeploymentStatusNotYetReported:appVersion]) { + [self recordDeploymentStatusReported:appVersion + status:DeploymentSucceeded]; + resolve(@{ @"appVersion": appVersion }); + return; + } + + // Check if there was a rollback that was not yet reported + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; + if (failedUpdates) { + NSDictionary* lastFailedPackage = [failedUpdates lastObject]; + if (lastFailedPackage) { + NSString* lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; + if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) { + [self recordDeploymentStatusReported:lastFailedPackageIdentifier + status:DeploymentFailed]; + resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed }); + return; + } + } + } + + // Check if the current CodePush package has been reported + NSError *error; + NSDictionary* currentPackage = [CodePushPackage getCurrentPackage:&error]; + if (currentPackage) { + NSString* currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; + if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) { + [self recordDeploymentStatusReported:currentPackageIdentifier + status:DeploymentSucceeded]; + resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded }); + return; + } + } + + resolve([NSNull null]); + return; +} + /* * This method is the native side of the CodePush.restartApp() method. */ diff --git a/CodePushConfig.m b/CodePushConfig.m index 0de94de..31884fc 100644 --- a/CodePushConfig.m +++ b/CodePushConfig.m @@ -1,4 +1,5 @@ #import "CodePush.h" +#import @implementation CodePushConfig { NSMutableDictionary *_configDictionary; @@ -10,6 +11,7 @@ static NSString * const AppVersionConfigKey = @"appVersion"; static NSString * const BuildVdersionConfigKey = @"buildVersion"; static NSString * const DeploymentKeyConfigKey = @"deploymentKey"; static NSString * const ServerURLConfigKey = @"serverUrl"; +static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId"; + (instancetype)current { @@ -31,6 +33,14 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; NSString *deploymentKey = [infoDictionary objectForKey:@"CodePushDeploymentKey"]; NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"]; + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + NSString* clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; + if (clientUniqueId == nil) { + clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + [userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey]; + [userDefaults synchronize]; + } + if (!serverURL) { serverURL = @"https://codepush.azurewebsites.net/"; } @@ -40,6 +50,7 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; buildVersion,BuildVdersionConfigKey, serverURL,ServerURLConfigKey, deploymentKey,DeploymentKeyConfigKey, + clientUniqueId,ClientUniqueIDConfigKey, nil]; return self; @@ -70,6 +81,11 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; return [_configDictionary objectForKey:ServerURLConfigKey]; } +- (NSString *)clientUniqueId +{ + return [_configDictionary objectForKey:ClientUniqueIDConfigKey]; +} + - (void)setDeploymentKey:(NSString *)deploymentKey { [_configDictionary setValue:deploymentKey forKey:DeploymentKeyConfigKey]; diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js index 25e9568..7427d72 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js @@ -25,7 +25,7 @@ let SwitchDeploymentKeyTest = createTestCaseComponent( }, async () => { let update = await CodePush.checkForUpdate(deploymentKey); - assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server"); + assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false, deploymentKey }), "checkForUpdate did not return the update from the server"); } ); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js index 5d3e57a..d974260 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js @@ -1,4 +1,5 @@ export default { + deploymentKey: "myKey123", description: "Angry flappy birds", appVersion: "1.5.0", label: "2.4.0", diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js index 253d497..aebc467 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js @@ -13,6 +13,11 @@ function createMockAcquisitionSdk(serverPackage, localPackage, expectedDeploymen callback(/*err:*/ null, serverPackage); }; + AcquisitionManager.prototype.reportStatus = (status, message, callback) => { + // No-op and return success. + callback(null, null); + }; + return AcquisitionManager; } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index a9743d8..ae74487 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -23,7 +23,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; +import android.provider.Settings; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -43,17 +45,24 @@ public class CodePush { private String assetsBundleFileName; + private final String ASSETS_BUNDLE_PREFIX = "assets://"; + private final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime"; + private final String CODE_PUSH_PREFERENCES = "CodePush"; + private final String DEPLOYMENT_FAILED_STATUS = "DeploymentFailed"; + private final String DEPLOYMENT_KEY_KEY = "deploymentKey"; + private final String DEPLOYMENT_SUCCEEDED_STATUS = "DeploymentSucceeded"; + private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress"; private final String FAILED_UPDATES_KEY = "CODE_PUSH_FAILED_UPDATES"; - private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE"; + private final String LABEL_KEY = "label"; + private final String PACKAGE_HASH_KEY = "packageHash"; private final String PENDING_UPDATE_HASH_KEY = "hash"; private final String PENDING_UPDATE_IS_LOADING_KEY = "isLoading"; - private final String ASSETS_BUNDLE_PREFIX = "assets://"; - private final String CODE_PUSH_PREFERENCES = "CodePush"; - private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress"; + private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE"; private final String RESOURCES_BUNDLE = "resources.arsc"; + private final String STATUS_REPORTS_KEY = "CODE_PUSH_STATUS_REPORTS"; + // This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78 private final String REACT_DEV_BUNDLE_CACHE_FILE_NAME = "ReactNativeDevBundle.js"; - private final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime"; private CodePushPackage codePushPackage; private CodePushReactPackage codePushReactPackage; @@ -145,7 +154,7 @@ public class CodePush { String pacakgeAppVersion = CodePushUtils.tryGetString(packageMetadata, "appVersion"); if (binaryModifiedDateDuringPackageInstall != null && binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime && - this.appVersion.equals(pacakgeAppVersion)) { + (this.isUsingTestConfiguration() || this.appVersion.equals(pacakgeAppVersion))) { CodePushUtils.logBundleUrl(packageFilePath); return packageFilePath; } else { @@ -165,6 +174,36 @@ public class CodePush { } } + private String getPackageStatusReportIdentifier(WritableMap updatePackage) { + // Because deploymentKeys can be dynamically switched, we use a + // combination of the deploymentKey and label as the packageIdentifier. + String deploymentKey = CodePushUtils.tryGetString(updatePackage, DEPLOYMENT_KEY_KEY); + String label = CodePushUtils.tryGetString(updatePackage, LABEL_KEY); + if (deploymentKey != null && label != null) { + return deploymentKey + ":" + label; + } else { + return null; + } + } + + private JSONArray getFailedUpdates() { + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null); + if (failedUpdatesString == null) { + return new JSONArray(); + } + + try { + JSONArray failedUpdates = new JSONArray(failedUpdatesString); + return failedUpdates; + } catch (JSONException e) { + // Unrecognized data format, clear and replace with expected format. + JSONArray emptyArray = new JSONArray(); + settings.edit().putString(FAILED_UPDATES_KEY, emptyArray.toString()).commit(); + return emptyArray; + } + } + private JSONObject getPendingUpdate() { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null); @@ -217,22 +256,39 @@ public class CodePush { } } } - - private boolean isFailedHash(String packageHash) { + + private boolean isDeploymentStatusNotYetReported(String appVersionOrPackageIdentifier) { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null); - if (failedUpdatesString == null) { - return false; + String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null); + if (sentStatusReportsString == null) { + return true; + } else { + try { + JSONObject sentStatusReports = new JSONObject(sentStatusReportsString); + return !sentStatusReports.has(appVersionOrPackageIdentifier); + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to parse sent status reports information " + + sentStatusReportsString + " stored in SharedPreferences.", e); + } + } + } + + private boolean isFailedHash(String packageHash) { + JSONArray failedUpdates = getFailedUpdates(); + for (int i = 0; i < failedUpdates.length(); i++) { + JSONObject failedPackage = null; + try { + failedPackage = failedUpdates.getJSONObject(i); + String failedPackageHash = failedPackage.getString(PACKAGE_HASH_KEY); + if (packageHash.equals(failedPackageHash)) { + return true; + } + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e); + } } - try { - JSONObject failedUpdates = new JSONObject(failedUpdatesString); - return failedUpdates.has(packageHash); - } catch (JSONException e) { - // Should not happen. - throw new CodePushUnknownException("Unable to parse failed updates information " + - failedUpdatesString + " stored in SharedPreferences", e); - } + return false; } private boolean isPendingUpdate(String packageHash) { @@ -249,6 +305,24 @@ public class CodePush { } } + private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier, String status) { + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null); + JSONObject sentStatusReports; + try { + if (sentStatusReportsString == null) { + sentStatusReports = new JSONObject(); + } else { + sentStatusReports = new JSONObject(sentStatusReportsString); + } + + sentStatusReports.put(appVersionOrPackageIdentifier, status); + settings.edit().putString(STATUS_REPORTS_KEY, sentStatusReports.toString()).commit(); + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to save new entry in SharedPreferences under " + STATUS_REPORTS_KEY + ".", e); + } + } + private void removeFailedUpdates() { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); settings.edit().remove(FAILED_UPDATES_KEY).commit(); @@ -261,8 +335,8 @@ public class CodePush { private void rollbackPackage() { try { - String packageHash = codePushPackage.getCurrentPackageHash(); - saveFailedUpdate(packageHash); + WritableMap failedPackage = codePushPackage.getCurrentPackage(); + saveFailedUpdate(failedPackage); } catch (IOException e) { throw new CodePushUnknownException("Attempted a rollback without having a current downloaded package", e); } @@ -276,29 +350,25 @@ public class CodePush { removePendingUpdate(); } - private void saveFailedUpdate(String packageHash) { + private void saveFailedUpdate(WritableMap failedPackage) { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null); - JSONObject failedUpdates; + JSONArray failedUpdates; if (failedUpdatesString == null) { - failedUpdates = new JSONObject(); + failedUpdates = new JSONArray(); } else { try { - failedUpdates = new JSONObject(failedUpdatesString); + failedUpdates = new JSONArray(failedUpdatesString); } catch (JSONException e) { // Should not happen. throw new CodePushMalformedDataException("Unable to parse failed updates information " + failedUpdatesString + " stored in SharedPreferences", e); } } - try { - failedUpdates.put(packageHash, true); - settings.edit().putString(FAILED_UPDATES_KEY, failedUpdates.toString()).commit(); - } catch (JSONException e) { - // Should not happen unless the packageHash is null. - throw new CodePushUnknownException("Unable to save package hash " + - packageHash + " as a failed update", e); - } + + JSONObject failedPackageJSON = CodePushUtils.convertReadableToJsonObject(failedPackage); + failedUpdates.put(failedPackageJSON); + settings.edit().putString(FAILED_UPDATES_KEY, failedUpdates.toString()).commit(); } private void savePendingUpdate(String packageHash, boolean isLoading) { @@ -377,6 +447,9 @@ public class CodePush { configMap.putInt("buildVersion", buildVersion); configMap.putString("deploymentKey", deploymentKey); configMap.putString("serverUrl", serverUrl); + configMap.putString("clientUniqueId", + Settings.Secure.getString(mainActivity.getContentResolver(), + android.provider.Settings.Secure.ANDROID_ID)); promise.resolve(configMap); } @@ -408,7 +481,60 @@ public class CodePush { asyncTask.execute(); } - + + @ReactMethod + public void getNewStatusReport(Promise promise) { + // Check if the current appVersion has been reported. + if (isDeploymentStatusNotYetReported(appVersion)) { + recordDeploymentStatusReported(appVersion, DEPLOYMENT_SUCCEEDED_STATUS); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putString("appVersion", appVersion); + promise.resolve(reportMap); + return; + } + + // Check if there was a rollback that was not yet reported + JSONArray failedUpdates = getFailedUpdates(); + if (failedUpdates != null && failedUpdates.length() > 0) { + try { + JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1); + WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON); + String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage); + if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) { + recordDeploymentStatusReported(lastFailedPackageIdentifier, DEPLOYMENT_FAILED_STATUS); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", lastFailedPackage); + reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); + promise.resolve(reportMap); + return; + } + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); + } + } + + // Check if the current CodePush package has been reported + try { + WritableMap currentPackage = codePushPackage.getCurrentPackage(); + if (currentPackage != null) { + String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage); + if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) { + recordDeploymentStatusReported(currentPackageIdentifier, DEPLOYMENT_SUCCEEDED_STATUS); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + promise.resolve(reportMap); + return; + } + } + } catch (IOException e) { + // No current package, resolve no report. + promise.resolve(""); + } + + promise.resolve(""); + } + @ReactMethod public void installUpdate(final ReadableMap updatePackage, final int installMode, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index 6254def..b18a8cf 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -16,7 +16,7 @@ module.exports = { try { const response = await fetch(url, { - method: verb, + method: getHttpMethodName(verb), headers: headers, body: body }); @@ -28,4 +28,18 @@ module.exports = { callback(err); } } -}; \ No newline at end of file +}; + +function getHttpMethodName(verb) { + return [ + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "TRACE", + "OPTIONS", + "CONNECT", + "PATCH" + ][verb]; +} \ No newline at end of file From a3e3936d06ac01d08b7a0fd181f592da9be32d75 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 15 Jan 2016 15:18:51 -0800 Subject: [PATCH 2/8] bump acquisition sdk version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a937e37..7639f47 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/Microsoft/react-native-code-push" }, "dependencies": { - "code-push": "^1.1.1-beta", + "code-push": "1.5.0-beta", "semver": "^5.1.0" }, "devDependencies": { From a44c7923cabe6f896ffa436625a641c61904c352 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Mon, 18 Jan 2016 16:56:37 -0800 Subject: [PATCH 3/8] feedback --- CodePush.js | 25 +++-- CodePush.m | 67 ++++++----- CodePushConfig.m | 11 +- .../testcases/FirstUpdateTest.js | 2 +- .../testcases/NewUpdateTest.js | 2 +- .../testcases/SwitchDeploymentKeyTest.js | 2 +- .../testcases/DownloadProgressTest.js | 2 +- .../resources/RollbackTestBundleV1.js | 2 +- .../testcases/InstallModeImmediateTest.js | 2 +- .../testcases/InstallModeOnNextRestartTest.js | 2 +- .../testcases/InstallModeOnNextResumeTest.js | 2 +- .../testcases/IsFailedUpdateTest.js | 2 +- .../testcases/IsFirstRunTest.js | 2 +- .../testcases/IsPendingTest.js | 2 +- .../testcases/NotifyApplicationReadyTest.js | 2 +- .../testcases/RollbackTest.js | 2 +- .../utils/mockAcquisitionSdk.js | 7 +- .../microsoft/codepush/react/CodePush.java | 106 ++++++++++-------- package-mixins.js | 50 +++++---- 19 files changed, 162 insertions(+), 130 deletions(-) diff --git a/CodePush.js b/CodePush.js index 8d8244a..8a2f103 100644 --- a/CodePush.js +++ b/CodePush.js @@ -60,7 +60,7 @@ async function checkForUpdate(deploymentKey = null) { if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) { return null; } else { - const remotePackage = { ...update, ...PackageMixins.remote }; + const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) }; remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash); remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey; return remotePackage; @@ -102,8 +102,8 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }); }; - - sdk.reportStatus = (package, status) => { + + sdk.reportStatusDeploy = (package, status) => { return new Promise((resolve, reject) => { module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, package, status, (err) => { if (err) { @@ -114,7 +114,19 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }); }; - + + sdk.reportStatusDownload = (package, status) => { + return new Promise((resolve, reject) => { + module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, package, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }; + return sdk; } @@ -129,13 +141,12 @@ async function notifyApplicationReady() { if (statusReport) { const config = await getConfiguration(); if (statusReport.appVersion) { - config.appVersion = statusReport.appVersion; const sdk = getPromisifiedSdk(requestFetchAdapter, config); - sdk.reportStatus(); + sdk.reportStatusDeploy(); } else { config.deploymentKey = statusReport.package.deploymentKey; const sdk = getPromisifiedSdk(requestFetchAdapter, config); - sdk.reportStatus(statusReport.package, statusReport.status); + sdk.reportStatusDeploy(statusReport.package, statusReport.status); } } } diff --git a/CodePush.m b/CodePush.m index b886eea..7d42b0a 100644 --- a/CodePush.m +++ b/CodePush.m @@ -13,6 +13,7 @@ RCT_EXPORT_MODULE() +static BOOL didRollback = NO; static BOOL testConfigurationFlag = NO; // These constants represent valid deployment statuses @@ -189,6 +190,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { + didRollback = NO; _isFirstRunAfterUpdate = YES; BOOL updateIsLoading = [pendingUpdate[PendingUpdateIsLoadingKey] boolValue]; if (updateIsLoading) { @@ -196,6 +198,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; // Therefore, deduce that it is a broken update and rollback. NSLog(@"Update did not finish loading the last time, rolling back to a previous version."); [self rollbackPackage]; + didRollback = YES; } else { // Mark that we tried to initialize the new update, so that if it crashes, // we will know that we need to rollback when the app next starts. @@ -220,7 +223,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; - if (failedUpdates == nil) { + if (failedUpdates == nil || packageHash == nil) { return NO; } else { for (NSDictionary *failedPackage in failedUpdates) @@ -535,6 +538,39 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + if (didRollback) { + // Check if there was a rollback that was not yet reported + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; + if (failedUpdates) { + NSDictionary* lastFailedPackage = [failedUpdates lastObject]; + if (lastFailedPackage) { + NSString* lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; + if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) { + [self recordDeploymentStatusReported:lastFailedPackageIdentifier + status:DeploymentFailed]; + resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed }); + return; + } + } + } + } + + if (_isFirstRunAfterUpdate) { + // Check if the current CodePush package has been reported + NSError *error; + NSDictionary* currentPackage = [CodePushPackage getCurrentPackage:&error]; + if (currentPackage) { + NSString* currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; + if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) { + [self recordDeploymentStatusReported:currentPackageIdentifier + status:DeploymentSucceeded]; + resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded }); + return; + } + } + } + // Check if the current appVersion has been reported. NSString *appVersion = [[CodePushConfig current] appVersion]; if ([self isDeploymentStatusNotYetReported:appVersion]) { @@ -544,35 +580,6 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve return; } - // Check if there was a rollback that was not yet reported - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; - if (failedUpdates) { - NSDictionary* lastFailedPackage = [failedUpdates lastObject]; - if (lastFailedPackage) { - NSString* lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; - if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) { - [self recordDeploymentStatusReported:lastFailedPackageIdentifier - status:DeploymentFailed]; - resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed }); - return; - } - } - } - - // Check if the current CodePush package has been reported - NSError *error; - NSDictionary* currentPackage = [CodePushPackage getCurrentPackage:&error]; - if (currentPackage) { - NSString* currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; - if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) { - [self recordDeploymentStatusReported:currentPackageIdentifier - status:DeploymentSucceeded]; - resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded }); - return; - } - } - resolve([NSNull null]); return; } diff --git a/CodePushConfig.m b/CodePushConfig.m index 31884fc..341ba94 100644 --- a/CodePushConfig.m +++ b/CodePushConfig.m @@ -9,9 +9,9 @@ static CodePushConfig *_currentConfig; static NSString * const AppVersionConfigKey = @"appVersion"; static NSString * const BuildVdersionConfigKey = @"buildVersion"; +static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId"; static NSString * const DeploymentKeyConfigKey = @"deploymentKey"; static NSString * const ServerURLConfigKey = @"serverUrl"; -static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId"; + (instancetype)current { @@ -34,12 +34,7 @@ static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId"; NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"]; NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; - NSString* clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; - if (clientUniqueId == nil) { - clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; - [userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey]; - [userDefaults synchronize]; - } + NSString* clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; if (!serverURL) { serverURL = @"https://codepush.azurewebsites.net/"; @@ -49,8 +44,8 @@ static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId"; appVersion,AppVersionConfigKey, buildVersion,BuildVdersionConfigKey, serverURL,ServerURLConfigKey, - deploymentKey,DeploymentKeyConfigKey, clientUniqueId,ClientUniqueIDConfigKey, + deploymentKey,DeploymentKeyConfigKey, nil]; return self; diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js index 21fbd0e..1b80c04 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js @@ -24,7 +24,7 @@ let FirstUpdateTest = createTestCaseComponent( }, async () => { let update = await CodePush.checkForUpdate(); - assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server"); + assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false }), "checkForUpdate did not return the update from the server"); } ); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js index c097c16..8f196c7 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js @@ -23,7 +23,7 @@ let NewUpdateTest = createTestCaseComponent( }, async () => { let update = await CodePush.checkForUpdate(); - assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false }), "checkForUpdate did not return the update from the server"); + assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false }), "checkForUpdate did not return the update from the server"); } ); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js index 7427d72..74628ba 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js @@ -25,7 +25,7 @@ let SwitchDeploymentKeyTest = createTestCaseComponent( }, async () => { let update = await CodePush.checkForUpdate(deploymentKey); - assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote, failedInstall: false, deploymentKey }), "checkForUpdate did not return the update from the server"); + assert.equal(JSON.stringify(update), JSON.stringify({ ...serverPackage, ...PackageMixins.remote(), failedInstall: false, deploymentKey }), "checkForUpdate did not return the update from the server"); } ); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js index ee7bd98..ac419e3 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js @@ -28,7 +28,7 @@ let DownloadProgressTest = createTestCaseComponent( "should successfully download all the bytes contained in the test packages", () => { testPackages.forEach((aPackage, index) => { - testPackages[index] = Object.assign(aPackage, PackageMixins.remote); + testPackages[index] = Object.assign(aPackage, PackageMixins.remote()); }); return Promise.resolve(); }, diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js index 565ab06..2cc5faa 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js @@ -27,7 +27,7 @@ let RollbackTest = React.createClass({ await NativeCodePush.downloadAndReplaceCurrentBundle("http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.includeRequire.runModule.bundle?platform=ios&dev=true"); } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); let localPackage = await remotePackage.download(); return await localPackage.install(NativeCodePush.codePushInstallModeImmediate); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js index 7c74f3d..bc74375 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js @@ -21,7 +21,7 @@ let InstallModeImmediateTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download(); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js index 9381951..1106bc1 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js @@ -22,7 +22,7 @@ let InstallModeOnNextRestartTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download(); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js index ed695e7..48e88a8 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js @@ -21,7 +21,7 @@ let InstallModeOnNextResumeTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download() diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js index 5669de1..2fb11b9 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js @@ -21,7 +21,7 @@ let IsFailedUpdateTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download(); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js index f8c28cd..02644b3 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js @@ -20,7 +20,7 @@ let IsFirstRunTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download(); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js index 3e61f71..355a633 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js @@ -21,7 +21,7 @@ let IsPendingTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download(); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js index d77e125..15b7477 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js @@ -21,7 +21,7 @@ let NotifyApplicationReadyTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download() diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js index d002965..bf6e433 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js @@ -21,7 +21,7 @@ let RollbackTest = createTestCaseComponent( remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.includeRequire.runModule.bundle?platform=ios&dev=true" } - remotePackage = Object.assign(remotePackage, PackageMixins.remote); + remotePackage = Object.assign(remotePackage, PackageMixins.remote()); }, async () => { let localPackage = await remotePackage.download() diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js index aebc467..ffff99b 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js @@ -13,7 +13,12 @@ function createMockAcquisitionSdk(serverPackage, localPackage, expectedDeploymen callback(/*err:*/ null, serverPackage); }; - AcquisitionManager.prototype.reportStatus = (status, message, callback) => { + AcquisitionManager.prototype.reportStatusDeploy = (package, status, callback) => { + // No-op and return success. + callback(null, null); + }; + + AcquisitionManager.prototype.reportStatusDownload = (package, callback) => { // No-op and return success. callback(null, null); }; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index ae74487..1b45069 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -41,6 +41,8 @@ import java.util.zip.ZipFile; public class CodePush { private static boolean testConfigurationFlag = false; + private static boolean didRollback = false; + private boolean didUpdate = false; private String assetsBundleFileName; @@ -233,6 +235,7 @@ public class CodePush { JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { didUpdate = true; + didRollback = false; try { boolean updateIsLoading = pendingUpdate.getBoolean(PENDING_UPDATE_IS_LOADING_KEY); if (updateIsLoading) { @@ -240,6 +243,7 @@ public class CodePush { // Therefore, deduce that it is a broken update and rollback. CodePushUtils.log("Update did not finish loading the last time, rolling back to a previous version."); rollbackPackage(); + didRollback = true; } else { // Clear the React dev bundle cache so that new updates can be loaded. if (this.isDebugMode) { @@ -275,16 +279,18 @@ public class CodePush { private boolean isFailedHash(String packageHash) { JSONArray failedUpdates = getFailedUpdates(); - for (int i = 0; i < failedUpdates.length(); i++) { - JSONObject failedPackage = null; - try { - failedPackage = failedUpdates.getJSONObject(i); - String failedPackageHash = failedPackage.getString(PACKAGE_HASH_KEY); - if (packageHash.equals(failedPackageHash)) { - return true; + if (packageHash != null) { + for (int i = 0; i < failedUpdates.length(); i++) { + JSONObject failedPackage = null; + try { + failedPackage = failedUpdates.getJSONObject(i); + String failedPackageHash = failedPackage.getString(PACKAGE_HASH_KEY); + if (packageHash.equals(failedPackageHash)) { + return true; + } + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e); } - } catch (JSONException e) { - throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e); } } @@ -484,6 +490,49 @@ public class CodePush { @ReactMethod public void getNewStatusReport(Promise promise) { + // Check if there was a rollback that was not yet reported + if (didRollback) { + JSONArray failedUpdates = getFailedUpdates(); + if (failedUpdates != null && failedUpdates.length() > 0) { + try { + JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1); + WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON); + String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage); + if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) { + recordDeploymentStatusReported(lastFailedPackageIdentifier, DEPLOYMENT_FAILED_STATUS); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", lastFailedPackage); + reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); + promise.resolve(reportMap); + return; + } + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); + } + } + } + + // Check if the current CodePush package has been reported + if (didUpdate) { + try { + WritableMap currentPackage = codePushPackage.getCurrentPackage(); + if (currentPackage != null) { + String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage); + if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) { + recordDeploymentStatusReported(currentPackageIdentifier, DEPLOYMENT_SUCCEEDED_STATUS); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + promise.resolve(reportMap); + return; + } + } + } catch (IOException e) { + // If didUpdate is true, there should be a current package, so this should not happen. + throw new CodePushUnknownException("Error getting current package after an update.", e); + } + } + // Check if the current appVersion has been reported. if (isDeploymentStatusNotYetReported(appVersion)) { recordDeploymentStatusReported(appVersion, DEPLOYMENT_SUCCEEDED_STATUS); @@ -493,45 +542,6 @@ public class CodePush { return; } - // Check if there was a rollback that was not yet reported - JSONArray failedUpdates = getFailedUpdates(); - if (failedUpdates != null && failedUpdates.length() > 0) { - try { - JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1); - WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON); - String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage); - if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) { - recordDeploymentStatusReported(lastFailedPackageIdentifier, DEPLOYMENT_FAILED_STATUS); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putMap("package", lastFailedPackage); - reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); - promise.resolve(reportMap); - return; - } - } catch (JSONException e) { - throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); - } - } - - // Check if the current CodePush package has been reported - try { - WritableMap currentPackage = codePushPackage.getCurrentPackage(); - if (currentPackage != null) { - String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage); - if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) { - recordDeploymentStatusReported(currentPackageIdentifier, DEPLOYMENT_SUCCEEDED_STATUS); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putMap("package", currentPackage); - reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); - promise.resolve(reportMap); - return; - } - } - } catch (IOException e) { - // No current package, resolve no report. - promise.resolve(""); - } - promise.resolve(""); } diff --git a/package-mixins.js b/package-mixins.js index 6b2f75e..6078bee 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -1,35 +1,39 @@ +import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk"; import { DeviceEventEmitter } from "react-native"; // This function is used to augment remote and local // package objects with additional functionality/properties // beyond what is included in the metadata sent by the server. module.exports = (NativeCodePush) => { - const remote = { - async download(downloadProgressCallback) { - if (!this.downloadUrl) { - throw new Error("Cannot download an update without a download url"); - } + const remote = (reportStatusDownload) => { + return { + async download(downloadProgressCallback) { + if (!this.downloadUrl) { + throw new Error("Cannot download an update without a download url"); + } - let downloadProgressSubscription; - if (downloadProgressCallback) { - // Use event subscription to obtain download progress. - downloadProgressSubscription = DeviceEventEmitter.addListener( - "CodePushDownloadProgress", - downloadProgressCallback - ); - } + let downloadProgressSubscription; + if (downloadProgressCallback) { + // Use event subscription to obtain download progress. + downloadProgressSubscription = DeviceEventEmitter.addListener( + "CodePushDownloadProgress", + downloadProgressCallback + ); + } - // Use the downloaded package info. Native code will save the package info - // so that the client knows what the current package version is. - try { - const downloadedPackage = await NativeCodePush.downloadUpdate(this); - return { ...downloadedPackage, ...local }; - } finally { - downloadProgressSubscription && downloadProgressSubscription.remove(); - } - }, + // Use the downloaded package info. Native code will save the package info + // so that the client knows what the current package version is. + try { + const downloadedPackage = await NativeCodePush.downloadUpdate(this); + reportStatusDownload && reportStatusDownload(this); + return { ...downloadedPackage, ...local }; + } finally { + downloadProgressSubscription && downloadProgressSubscription.remove(); + } + }, - isPending: false // A remote package could never be in a pending state + isPending: false // A remote package could never be in a pending state + }; }; const local = { From d20e9ccc1e4023bf66a78214a95cfe77d3cfd4e1 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Jan 2016 16:57:37 -0800 Subject: [PATCH 4/8] feedback --- CodePush.m | 58 +++---- .../microsoft/codepush/react/CodePush.java | 148 ++++++------------ .../codepush/react/CodePushPackage.java | 30 ++-- request-fetch-adapter.js | 2 + 4 files changed, 99 insertions(+), 139 deletions(-) diff --git a/CodePush.m b/CodePush.m index 7d42b0a..d521d75 100644 --- a/CodePush.m +++ b/CodePush.m @@ -23,7 +23,7 @@ static NSString *const DeploymentFailed = @"DeploymentFailed"; // These keys represent the names we use to store data in NSUserDefaults static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; -static NSString *const StatusReportsKey = @"CODE_PUSH_STATUS_REPORTS"; +static NSString *const LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT"; // These keys are already "namespaced" by the PendingUpdateKey, so // their values don't need to be obfuscated to prevent collision with app data @@ -211,8 +211,8 @@ static NSString *const PackageIsPendingKey = @"isPending"; - (BOOL)isDeploymentStatusNotYetReported:(NSString *)appVersionOrPackageIdentifier { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSDictionary *sentStatusReports = [preferences objectForKey:StatusReportsKey]; - return sentStatusReports == nil || [sentStatusReports objectForKey:appVersionOrPackageIdentifier] == nil; + NSString *sentStatusReportIdentifier = [preferences objectForKey:LastDeploymentReportKey]; + return sentStatusReportIdentifier == nil || ![sentStatusReportIdentifier isEqualToString:appVersionOrPackageIdentifier]; } /* @@ -286,18 +286,9 @@ static NSString *const PackageIsPendingKey = @"isPending"; } - (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier - status:(NSString *)status { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSMutableDictionary *sentStatusReports = [preferences objectForKey:StatusReportsKey]; - if (sentStatusReports == nil) { - sentStatusReports = [NSMutableDictionary dictionary]; - } else { - sentStatusReports = [sentStatusReports mutableCopy]; - } - - [sentStatusReports setValue:status forKey:appVersionOrPackageIdentifier]; - [preferences setValue:sentStatusReports forKey:StatusReportsKey]; + [preferences setValue:LastDeploymentReportKey forKey:appVersionOrPackageIdentifier]; [preferences synchronize]; } @@ -543,45 +534,48 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; if (failedUpdates) { - NSDictionary* lastFailedPackage = [failedUpdates lastObject]; + NSDictionary *lastFailedPackage = [failedUpdates lastObject]; if (lastFailedPackage) { - NSString* lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; + NSString *lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) { - [self recordDeploymentStatusReported:lastFailedPackageIdentifier - status:DeploymentFailed]; + [self recordDeploymentStatusReported:lastFailedPackageIdentifier]; resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed }); return; } } } - } - - if (_isFirstRunAfterUpdate) { + } else if (_isFirstRunAfterUpdate) { // Check if the current CodePush package has been reported NSError *error; NSDictionary* currentPackage = [CodePushPackage getCurrentPackage:&error]; if (currentPackage) { NSString* currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) { - [self recordDeploymentStatusReported:currentPackageIdentifier - status:DeploymentSucceeded]; + [self recordDeploymentStatusReported:currentPackageIdentifier]; resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded }); return; } } + } else { + NSError *error; + NSString *currentPackageHash = [CodePushPackage getCurrentPackageHash:&error]; + if (error || currentPackageHash == nil) { + // Check if the current appVersion has been reported. Use date as the binary identifier to + // handle binary releases that do not modify the appVersion. + NSURL *binaryJsBundleUrl = [CodePush bundleURL]; + NSDictionary *binaryFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[binaryJsBundleUrl path] error:nil]; + NSTimeInterval binaryDate = [[binaryFileAttributes objectForKey:NSFileModificationDate] timeIntervalSince1970]; + NSString* binaryIdentifier = [NSString stringWithFormat:@"%f", binaryDate]; + + if ([self isDeploymentStatusNotYetReported:binaryIdentifier]) { + [self recordDeploymentStatusReported:binaryIdentifier]; + resolve(@{ @"appVersion": [[CodePushConfig current] appVersion] }); + return; + } + } } - // Check if the current appVersion has been reported. - NSString *appVersion = [[CodePushConfig current] appVersion]; - if ([self isDeploymentStatusNotYetReported:appVersion]) { - [self recordDeploymentStatusReported:appVersion - status:DeploymentSucceeded]; - resolve(@{ @"appVersion": appVersion }); - return; - } - resolve([NSNull null]); - return; } /* diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 1b45069..a58c817 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -61,7 +61,7 @@ public class CodePush { private final String PENDING_UPDATE_IS_LOADING_KEY = "isLoading"; private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE"; private final String RESOURCES_BUNDLE = "resources.arsc"; - private final String STATUS_REPORTS_KEY = "CODE_PUSH_STATUS_REPORTS"; + private final String LAST_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_LAST_DEPLOYMENT_REPORT"; // This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78 private final String REACT_DEV_BUNDLE_CACHE_FILE_NAME = "ReactNativeDevBundle.js"; @@ -153,10 +153,10 @@ public class CodePush { binaryModifiedDateDuringPackageInstall = Long.parseLong(binaryModifiedDateDuringPackageInstallString); } - String pacakgeAppVersion = CodePushUtils.tryGetString(packageMetadata, "appVersion"); + String packageAppVersion = CodePushUtils.tryGetString(packageMetadata, "appVersion"); if (binaryModifiedDateDuringPackageInstall != null && binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime && - (this.isUsingTestConfiguration() || this.appVersion.equals(pacakgeAppVersion))) { + (this.isUsingTestConfiguration() || this.appVersion.equals(packageAppVersion))) { CodePushUtils.logBundleUrl(packageFilePath); return packageFilePath; } else { @@ -169,8 +169,6 @@ public class CodePush { CodePushUtils.logBundleUrl(binaryJsBundleUrl); return binaryJsBundleUrl; } - } catch (IOException e) { - throw new CodePushUnknownException("Error in getting current package bundle path", e); } catch (NumberFormatException e) { throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e); } @@ -263,17 +261,11 @@ public class CodePush { private boolean isDeploymentStatusNotYetReported(String appVersionOrPackageIdentifier) { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null); - if (sentStatusReportsString == null) { + String lastDeploymentReportIdentifier = settings.getString(LAST_DEPLOYMENT_REPORT_KEY, null); + if (lastDeploymentReportIdentifier == null) { return true; } else { - try { - JSONObject sentStatusReports = new JSONObject(sentStatusReportsString); - return !sentStatusReports.has(appVersionOrPackageIdentifier); - } catch (JSONException e) { - throw new CodePushUnknownException("Unable to parse sent status reports information " + - sentStatusReportsString + " stored in SharedPreferences.", e); - } + return !lastDeploymentReportIdentifier.equals(appVersionOrPackageIdentifier); } } @@ -311,22 +303,9 @@ public class CodePush { } } - private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier, String status) { + private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier) { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null); - JSONObject sentStatusReports; - try { - if (sentStatusReportsString == null) { - sentStatusReports = new JSONObject(); - } else { - sentStatusReports = new JSONObject(sentStatusReportsString); - } - - sentStatusReports.put(appVersionOrPackageIdentifier, status); - settings.edit().putString(STATUS_REPORTS_KEY, sentStatusReports.toString()).commit(); - } catch (JSONException e) { - throw new CodePushUnknownException("Unable to save new entry in SharedPreferences under " + STATUS_REPORTS_KEY + ".", e); - } + settings.edit().putString(LAST_DEPLOYMENT_REPORT_KEY, appVersionOrPackageIdentifier).commit(); } private void removeFailedUpdates() { @@ -340,19 +319,9 @@ public class CodePush { } private void rollbackPackage() { - try { - WritableMap failedPackage = codePushPackage.getCurrentPackage(); - saveFailedUpdate(failedPackage); - } catch (IOException e) { - throw new CodePushUnknownException("Attempted a rollback without having a current downloaded package", e); - } - - try { - codePushPackage.rollbackPackage(); - } catch (IOException e) { - throw new CodePushUnknownException("Error in rolling back package", e); - } - + WritableMap failedPackage = codePushPackage.getCurrentPackage(); + saveFailedUpdate(failedPackage); + codePushPackage.rollbackPackage(); removePendingUpdate(); } @@ -464,23 +433,17 @@ public class CodePush { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Object... params) { - try { - WritableMap currentPackage = codePushPackage.getCurrentPackage(); + WritableMap currentPackage = codePushPackage.getCurrentPackage(); - Boolean isPendingUpdate = false; + Boolean isPendingUpdate = false; - if (currentPackage.hasKey(codePushPackage.PACKAGE_HASH_KEY)) { - String currentHash = currentPackage.getString(codePushPackage.PACKAGE_HASH_KEY); - isPendingUpdate = CodePush.this.isPendingUpdate(currentHash); - } - - currentPackage.putBoolean("isPending", isPendingUpdate); - promise.resolve(currentPackage); - } catch (IOException e) { - e.printStackTrace(); - promise.reject(e.getMessage()); + if (currentPackage.hasKey(codePushPackage.PACKAGE_HASH_KEY)) { + String currentHash = currentPackage.getString(codePushPackage.PACKAGE_HASH_KEY); + isPendingUpdate = CodePush.this.isPendingUpdate(currentHash); } - + + currentPackage.putBoolean("isPending", isPendingUpdate); + promise.resolve(currentPackage); return null; } }; @@ -490,8 +453,8 @@ public class CodePush { @ReactMethod public void getNewStatusReport(Promise promise) { - // Check if there was a rollback that was not yet reported if (didRollback) { + // Check if there was a rollback that was not yet reported JSONArray failedUpdates = getFailedUpdates(); if (failedUpdates != null && failedUpdates.length() > 0) { try { @@ -499,7 +462,7 @@ public class CodePush { WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON); String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage); if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) { - recordDeploymentStatusReported(lastFailedPackageIdentifier, DEPLOYMENT_FAILED_STATUS); + recordDeploymentStatusReported(lastFailedPackageIdentifier); WritableNativeMap reportMap = new WritableNativeMap(); reportMap.putMap("package", lastFailedPackage); reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); @@ -510,36 +473,34 @@ public class CodePush { throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); } } - } - - // Check if the current CodePush package has been reported - if (didUpdate) { - try { - WritableMap currentPackage = codePushPackage.getCurrentPackage(); - if (currentPackage != null) { - String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage); - if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) { - recordDeploymentStatusReported(currentPackageIdentifier, DEPLOYMENT_SUCCEEDED_STATUS); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putMap("package", currentPackage); - reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); - promise.resolve(reportMap); - return; - } + } else if (didUpdate) { + // Check if the current CodePush package has been reported + WritableMap currentPackage = codePushPackage.getCurrentPackage(); + if (currentPackage != null) { + String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage); + if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) { + recordDeploymentStatusReported(currentPackageIdentifier); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + promise.resolve(reportMap); + return; + } + } + } else { + String currentPackageHash = null; + currentPackageHash = codePushPackage.getCurrentPackageHash(); + if (currentPackageHash == null) { + // Check if the current appVersion has been reported. + String binaryIdentifier = "" + getBinaryResourcesModifiedTime(); + if (isDeploymentStatusNotYetReported(binaryIdentifier)) { + recordDeploymentStatusReported(binaryIdentifier); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putString("appVersion", appVersion); + promise.resolve(reportMap); + return; } - } catch (IOException e) { - // If didUpdate is true, there should be a current package, so this should not happen. - throw new CodePushUnknownException("Error getting current package after an update.", e); } - } - - // Check if the current appVersion has been reported. - if (isDeploymentStatusNotYetReported(appVersion)) { - recordDeploymentStatusReported(appVersion, DEPLOYMENT_SUCCEEDED_STATUS); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putString("appVersion", appVersion); - promise.resolve(reportMap); - return; } promise.resolve(""); @@ -600,16 +561,11 @@ public class CodePush { @ReactMethod public void isFirstRun(String packageHash, Promise promise) { - try { - boolean isFirstRun = didUpdate - && packageHash != null - && packageHash.length() > 0 - && packageHash.equals(codePushPackage.getCurrentPackageHash()); - promise.resolve(isFirstRun); - } catch (IOException e) { - e.printStackTrace(); - promise.reject(e.getMessage()); - } + boolean isFirstRun = didUpdate + && packageHash != null + && packageHash.length() > 0 + && packageHash.equals(codePushPackage.getCurrentPackageHash()); + promise.resolve(isFirstRun); } @ReactMethod diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java index e464f03..d91364a 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java @@ -50,20 +50,28 @@ public class CodePushPackage { return CodePushUtils.appendPathComponent(getCodePushPath(), STATUS_FILE); } - public WritableMap getCurrentPackageInfo() throws IOException { + public WritableMap getCurrentPackageInfo() { String statusFilePath = getStatusFilePath(); if (!CodePushUtils.fileAtPathExists(statusFilePath)) { return new WritableNativeMap(); } - return CodePushUtils.getWritableMapFromFile(statusFilePath); + try { + return CodePushUtils.getWritableMapFromFile(statusFilePath); + } catch (IOException e) { + throw new CodePushUnknownException("Error getting current package info" , e); + } } - public void updateCurrentPackageInfo(ReadableMap packageInfo) throws IOException { - CodePushUtils.writeReadableMapToFile(packageInfo, getStatusFilePath()); + public void updateCurrentPackageInfo(ReadableMap packageInfo) { + try { + CodePushUtils.writeReadableMapToFile(packageInfo, getStatusFilePath()); + } catch (IOException e) { + throw new CodePushUnknownException("Error updating current package info" , e); + } } - public String getCurrentPackageFolderPath() throws IOException { + public String getCurrentPackageFolderPath() { WritableMap info = getCurrentPackageInfo(); String packageHash = CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY); if (packageHash == null) { @@ -73,7 +81,7 @@ public class CodePushPackage { return getPackageFolderPath(packageHash); } - public String getCurrentPackageBundlePath() throws IOException { + public String getCurrentPackageBundlePath() { String packageFolder = getCurrentPackageFolderPath(); if (packageFolder == null) { return null; @@ -86,17 +94,17 @@ public class CodePushPackage { return CodePushUtils.appendPathComponent(getCodePushPath(), packageHash); } - public String getCurrentPackageHash() throws IOException { + public String getCurrentPackageHash() { WritableMap info = getCurrentPackageInfo(); return CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY); } - public String getPreviousPackageHash() throws IOException { + public String getPreviousPackageHash() { WritableMap info = getCurrentPackageInfo(); return CodePushUtils.tryGetString(info, PREVIOUS_PACKAGE_KEY); } - public WritableMap getCurrentPackage() throws IOException { + public WritableMap getCurrentPackage() { String folderPath = getCurrentPackageFolderPath(); if (folderPath == null) { return new WritableNativeMap(); @@ -111,7 +119,7 @@ public class CodePushPackage { } } - public WritableMap getPackage(String packageHash) throws IOException { + public WritableMap getPackage(String packageHash) { String folderPath = getPackageFolderPath(packageHash); String packageFilePath = CodePushUtils.appendPathComponent(folderPath, PACKAGE_FILE_NAME); try { @@ -185,7 +193,7 @@ public class CodePushPackage { updateCurrentPackageInfo(info); } - public void rollbackPackage() throws IOException { + public void rollbackPackage() { WritableMap info = getCurrentPackageInfo(); String currentPackageFolderPath = getCurrentPackageFolderPath(); CodePushUtils.deleteDirectoryAtPath(currentPackageFolderPath); diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index b18a8cf..0646918 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -31,6 +31,8 @@ module.exports = { }; function getHttpMethodName(verb) { + // Note: This should stay in sync with the enum definition in + // https://github.com/Microsoft/code-push/blob/master/sdk/script/acquisition-sdk.ts#L6 return [ "GET", "HEAD", From 88b7fb3af835800a856864872a2a27c0e95eb386 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Jan 2016 17:45:54 -0800 Subject: [PATCH 5/8] isRunningBinary --- CodePush.m | 9 ++++++--- .../java/com/microsoft/codepush/react/CodePush.java | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CodePush.m b/CodePush.m index d521d75..e9b4474 100644 --- a/CodePush.m +++ b/CodePush.m @@ -14,6 +14,7 @@ RCT_EXPORT_MODULE() static BOOL didRollback = NO; +static BOOL isRunningBinaryVersion = NO; static BOOL testConfigurationFlag = NO; // These constants represent valid deployment statuses @@ -62,6 +63,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; if (error || !packageFile) { NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } @@ -73,6 +75,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; if (error || !currentPackageMetadata) { NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } @@ -82,6 +85,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; // Return package file because it is newer than the app store binary's JS bundle NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile]; NSLog(logMessageFormat, packageUrl); + isRunningBinaryVersion = NO; return packageUrl; } else { #ifndef DEBUG @@ -89,6 +93,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; #endif NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } } @@ -557,9 +562,7 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve } } } else { - NSError *error; - NSString *currentPackageHash = [CodePushPackage getCurrentPackageHash:&error]; - if (error || currentPackageHash == nil) { + if (isRunningBinaryVersion) { // Check if the current appVersion has been reported. Use date as the binary identifier to // handle binary releases that do not modify the appVersion. NSURL *binaryJsBundleUrl = [CodePush bundleURL]; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index a58c817..7919a45 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -40,8 +40,9 @@ import java.util.zip.ZipFile; public class CodePush { - private static boolean testConfigurationFlag = false; private static boolean didRollback = false; + private static boolean isRunningBinaryVersion = false; + private static boolean testConfigurationFlag = false; private boolean didUpdate = false; @@ -143,6 +144,7 @@ public class CodePush { if (packageFilePath == null) { // There has not been any downloaded updates. CodePushUtils.logBundleUrl(binaryJsBundleUrl); + isRunningBinaryVersion = true; return binaryJsBundleUrl; } @@ -158,6 +160,7 @@ public class CodePush { binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime && (this.isUsingTestConfiguration() || this.appVersion.equals(packageAppVersion))) { CodePushUtils.logBundleUrl(packageFilePath); + isRunningBinaryVersion = false; return packageFilePath; } else { // The binary version is newer. @@ -167,6 +170,7 @@ public class CodePush { } CodePushUtils.logBundleUrl(binaryJsBundleUrl); + isRunningBinaryVersion = true; return binaryJsBundleUrl; } } catch (NumberFormatException e) { @@ -488,9 +492,7 @@ public class CodePush { } } } else { - String currentPackageHash = null; - currentPackageHash = codePushPackage.getCurrentPackageHash(); - if (currentPackageHash == null) { + if (isRunningBinaryVersion) { // Check if the current appVersion has been reported. String binaryIdentifier = "" + getBinaryResourcesModifiedTime(); if (isDeploymentStatusNotYetReported(binaryIdentifier)) { From aec64cc0a2fce8d0c04b379d3f96c6ef4f165106 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Jan 2016 22:41:56 -0800 Subject: [PATCH 6/8] remove package reserved word, bump version, persist device identifier --- CodePush.js | 8 ++++---- CodePushConfig.m | 7 ++++++- package.json | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CodePush.js b/CodePush.js index 8a2f103..f295c5a 100644 --- a/CodePush.js +++ b/CodePush.js @@ -103,9 +103,9 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }; - sdk.reportStatusDeploy = (package, status) => { + sdk.reportStatusDeploy = (deployedPackage, status) => { return new Promise((resolve, reject) => { - module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, package, status, (err) => { + module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, (err) => { if (err) { reject(err); } else { @@ -115,9 +115,9 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }; - sdk.reportStatusDownload = (package, status) => { + sdk.reportStatusDownload = (downloadedPackage, status) => { return new Promise((resolve, reject) => { - module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, package, (err) => { + module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => { if (err) { reject(err); } else { diff --git a/CodePushConfig.m b/CodePushConfig.m index 341ba94..8a7afa7 100644 --- a/CodePushConfig.m +++ b/CodePushConfig.m @@ -34,7 +34,12 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"]; NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; - NSString* clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + NSString* clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; + if (clientUniqueId == nil) { + clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + [userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey]; + [userDefaults synchronize]; + } if (!serverURL) { serverURL = @"https://codepush.azurewebsites.net/"; diff --git a/package.json b/package.json index 7639f47..73ffe0d 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/Microsoft/react-native-code-push" }, "dependencies": { - "code-push": "1.5.0-beta", + "code-push": "1.5.1-beta", "semver": "^5.1.0" }, "devDependencies": { From 2d5d80f6065771a754195c4d63d801a0372899d2 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 21 Jan 2016 01:33:01 -0800 Subject: [PATCH 7/8] fix issue with packager --- CodePush.m | 31 +++++++------------ CodePushConfig.m | 4 +-- .../microsoft/codepush/react/CodePush.java | 20 ++++++------ request-fetch-adapter.js | 14 ++++----- 4 files changed, 30 insertions(+), 39 deletions(-) diff --git a/CodePush.m b/CodePush.m index e9b4474..cab26cf 100644 --- a/CodePush.m +++ b/CodePush.m @@ -18,13 +18,13 @@ static BOOL isRunningBinaryVersion = NO; static BOOL testConfigurationFlag = NO; // These constants represent valid deployment statuses -static NSString *const DeploymentSucceeded = @"DeploymentSucceeded"; static NSString *const DeploymentFailed = @"DeploymentFailed"; +static NSString *const DeploymentSucceeded = @"DeploymentSucceeded"; // These keys represent the names we use to store data in NSUserDefaults static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; -static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; static NSString *const LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT"; +static 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 @@ -552,29 +552,22 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve } else if (_isFirstRunAfterUpdate) { // Check if the current CodePush package has been reported NSError *error; - NSDictionary* currentPackage = [CodePushPackage getCurrentPackage:&error]; - if (currentPackage) { - NSString* currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; + NSDictionary *currentPackage = [CodePushPackage getCurrentPackage:&error]; + if (!error && currentPackage) { + NSString *currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; if (currentPackageIdentifier && [self isDeploymentStatusNotYetReported:currentPackageIdentifier]) { [self recordDeploymentStatusReported:currentPackageIdentifier]; resolve(@{ @"package": currentPackage, @"status": DeploymentSucceeded }); return; } } - } else { - if (isRunningBinaryVersion) { - // Check if the current appVersion has been reported. Use date as the binary identifier to - // handle binary releases that do not modify the appVersion. - NSURL *binaryJsBundleUrl = [CodePush bundleURL]; - NSDictionary *binaryFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[binaryJsBundleUrl path] error:nil]; - NSTimeInterval binaryDate = [[binaryFileAttributes objectForKey:NSFileModificationDate] timeIntervalSince1970]; - NSString* binaryIdentifier = [NSString stringWithFormat:@"%f", binaryDate]; - - if ([self isDeploymentStatusNotYetReported:binaryIdentifier]) { - [self recordDeploymentStatusReported:binaryIdentifier]; - resolve(@{ @"appVersion": [[CodePushConfig current] appVersion] }); - return; - } + } else if (isRunningBinaryVersion || [_bridge.bundleURL.scheme hasPrefix:@"http"]) { + // Check if the current appVersion has been reported. + NSString *appVersion = [[CodePushConfig current] appVersion]; + if ([self isDeploymentStatusNotYetReported:appVersion]) { + [self recordDeploymentStatusReported:appVersion]; + resolve(@{ @"appVersion": appVersion }); + return; } } diff --git a/CodePushConfig.m b/CodePushConfig.m index 8a7afa7..9ea11ff 100644 --- a/CodePushConfig.m +++ b/CodePushConfig.m @@ -33,8 +33,8 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; NSString *deploymentKey = [infoDictionary objectForKey:@"CodePushDeploymentKey"]; NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"]; - NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; - NSString* clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSString *clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; if (clientUniqueId == nil) { clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; [userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey]; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 7919a45..4474074 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -491,17 +491,15 @@ public class CodePush { return; } } - } else { - if (isRunningBinaryVersion) { - // Check if the current appVersion has been reported. - String binaryIdentifier = "" + getBinaryResourcesModifiedTime(); - if (isDeploymentStatusNotYetReported(binaryIdentifier)) { - recordDeploymentStatusReported(binaryIdentifier); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putString("appVersion", appVersion); - promise.resolve(reportMap); - return; - } + } else if (isRunningBinaryVersion) { + // Check if the current appVersion has been reported. + String binaryIdentifier = "" + getBinaryResourcesModifiedTime(); + if (isDeploymentStatusNotYetReported(binaryIdentifier)) { + recordDeploymentStatusReported(binaryIdentifier); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putString("appVersion", appVersion); + promise.resolve(reportMap); + return; } } diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index 0646918..eeeac81 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -1,8 +1,8 @@ module.exports = { - async request(verb, url, body, callback) { - if (typeof body === "function") { - callback = body; - body = null; + async request(verb, url, requestBody, callback) { + if (typeof requestBody === "function") { + callback = requestBody; + requestBody = null; } var headers = { @@ -10,15 +10,15 @@ module.exports = { "Content-Type": "application/json" }; - if (body && typeof body === "object") { - body = JSON.stringify(body); + if (requestBody && typeof requestBody === "object") { + requestBody = JSON.stringify(requestBody); } try { const response = await fetch(url, { method: getHttpMethodName(verb), headers: headers, - body: body + body: requestBody }); const statusCode = response.status; From 3e4ac610fe2a9826756005f0ee0bf44266c56d70 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 22 Jan 2016 01:44:38 -0800 Subject: [PATCH 8/8] fix rollback bug --- CodePush.js | 2 +- CodePush.m | 10 +++++----- Examples/CodePushDemoApp/crossplatformdemo.js | 4 ++++ .../java/com/microsoft/codepush/react/CodePush.java | 8 ++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CodePush.js b/CodePush.js index f295c5a..993a412 100644 --- a/CodePush.js +++ b/CodePush.js @@ -115,7 +115,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }; - sdk.reportStatusDownload = (downloadedPackage, status) => { + sdk.reportStatusDownload = (downloadedPackage) => { return new Promise((resolve, reject) => { module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => { if (err) { diff --git a/CodePush.m b/CodePush.m index f431550..3b69041 100644 --- a/CodePush.m +++ b/CodePush.m @@ -13,7 +13,7 @@ RCT_EXPORT_MODULE() -static BOOL didRollback = NO; +static BOOL needToReportRollback = NO; static BOOL isRunningBinaryVersion = NO; static BOOL testConfigurationFlag = NO; @@ -195,15 +195,14 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { - didRollback = NO; _isFirstRunAfterUpdate = YES; BOOL updateIsLoading = [pendingUpdate[PendingUpdateIsLoadingKey] boolValue]; if (updateIsLoading) { // Pending update was initialized, but notifyApplicationReady was not called. // Therefore, deduce that it is a broken update and rollback. NSLog(@"Update did not finish loading the last time, rolling back to a previous version."); + needToReportRollback = YES; [self rollbackPackage]; - didRollback = YES; } else { // Mark that we tried to initialize the new update, so that if it crashes, // we will know that we need to rollback when the app next starts. @@ -293,7 +292,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; - (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - [preferences setValue:LastDeploymentReportKey forKey:appVersionOrPackageIdentifier]; + [preferences setValue:appVersionOrPackageIdentifier forKey:LastDeploymentReportKey]; [preferences synchronize]; } @@ -534,8 +533,9 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if (didRollback) { + if (needToReportRollback) { // Check if there was a rollback that was not yet reported + needToReportRollback = NO; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; if (failedUpdates) { diff --git a/Examples/CodePushDemoApp/crossplatformdemo.js b/Examples/CodePushDemoApp/crossplatformdemo.js index b340b3f..d941af0 100644 --- a/Examples/CodePushDemoApp/crossplatformdemo.js +++ b/Examples/CodePushDemoApp/crossplatformdemo.js @@ -81,6 +81,10 @@ let CodePushDemoApp = React.createClass({ } }, + componentDidMount() { + CodePush.notifyApplicationReady(); + }, + getInitialState() { return { }; }, diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 4474074..8d7d64a 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -40,7 +40,7 @@ import java.util.zip.ZipFile; public class CodePush { - private static boolean didRollback = false; + private static boolean needToReportRollback = false; private static boolean isRunningBinaryVersion = false; private static boolean testConfigurationFlag = false; @@ -237,15 +237,14 @@ public class CodePush { JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { didUpdate = true; - didRollback = false; try { boolean updateIsLoading = pendingUpdate.getBoolean(PENDING_UPDATE_IS_LOADING_KEY); if (updateIsLoading) { // Pending update was initialized, but notifyApplicationReady was not called. // Therefore, deduce that it is a broken update and rollback. CodePushUtils.log("Update did not finish loading the last time, rolling back to a previous version."); + needToReportRollback = true; rollbackPackage(); - didRollback = true; } else { // Clear the React dev bundle cache so that new updates can be loaded. if (this.isDebugMode) { @@ -457,8 +456,9 @@ public class CodePush { @ReactMethod public void getNewStatusReport(Promise promise) { - if (didRollback) { + if (needToReportRollback) { // Check if there was a rollback that was not yet reported + needToReportRollback = false; JSONArray failedUpdates = getFailedUpdates(); if (failedUpdates != null && failedUpdates.length() > 0) { try {