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",