diff --git a/CodePush.h b/CodePush.h index af37f22..437587f 100644 --- a/CodePush.h +++ b/CodePush.h @@ -95,6 +95,14 @@ failCallback:(void (^)(NSError *err))failCallback; @end +@interface CodePushTelemetryManager : NSObject + ++ (NSDictionary *)getBinaryUpdateReport:(NSString *)appVersion; ++ (NSDictionary *)getRollbackReport:(NSDictionary *)lastFailedPackage; ++ (NSDictionary *)getUpdateReport:(NSDictionary *)currentPackage; + +@end + typedef NS_ENUM(NSInteger, CodePushInstallMode) { CodePushInstallModeImmediate, CodePushInstallModeOnNextRestart, diff --git a/CodePush.js b/CodePush.js index 993a412..b95296a 100644 --- a/CodePush.js +++ b/CodePush.js @@ -103,9 +103,9 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }; - sdk.reportStatusDeploy = (deployedPackage, status) => { + sdk.reportStatusDeploy = (deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey) => { return new Promise((resolve, reject) => { - module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, (err) => { + module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey, (err) => { if (err) { reject(err); } else { @@ -140,13 +140,15 @@ async function notifyApplicationReady() { const statusReport = await NativeCodePush.getNewStatusReport(); if (statusReport) { const config = await getConfiguration(); + const previousLabelOrAppVersion = statusReport.previousLabelOrAppVersion; + const previousDeploymentKey = statusReport.previousDeploymentKey || config.deploymentKey; if (statusReport.appVersion) { const sdk = getPromisifiedSdk(requestFetchAdapter, config); - sdk.reportStatusDeploy(); + sdk.reportStatusDeploy(/* deployedPackage */ null, /* status */ null, previousLabelOrAppVersion, previousDeploymentKey); } else { config.deploymentKey = statusReport.package.deploymentKey; const sdk = getPromisifiedSdk(requestFetchAdapter, config); - sdk.reportStatusDeploy(statusReport.package, statusReport.status); + sdk.reportStatusDeploy(statusReport.package, statusReport.status, previousLabelOrAppVersion, previousDeploymentKey); } } } diff --git a/CodePush.m b/CodePush.m index d292676..89a41d4 100644 --- a/CodePush.m +++ b/CodePush.m @@ -25,7 +25,6 @@ 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 LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT"; static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; // These keys are already "namespaced" by the PendingUpdateKey, so @@ -35,8 +34,6 @@ 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"; @@ -168,19 +165,6 @@ 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]; @@ -219,13 +203,6 @@ static NSString *const PackageIsPendingKey = @"isPending"; } } -- (BOOL)isDeploymentStatusNotYetReported:(NSString *)appVersionOrPackageIdentifier -{ - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSString *sentStatusReportIdentifier = [preferences objectForKey:LastDeploymentReportKey]; - return sentStatusReportIdentifier == nil || ![sentStatusReportIdentifier isEqualToString:appVersionOrPackageIdentifier]; -} - /* * This method checks to see whether a specific package hash * has previously failed installation. @@ -296,13 +273,6 @@ static NSString *const PackageIsPendingKey = @"isPending"; }); } -- (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier -{ - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - [preferences setValue:appVersionOrPackageIdentifier forKey:LastDeploymentReportKey]; - [preferences synchronize]; -} - /* * This method is used when an update has failed installation * and the app needs to be rolled back to the previous bundle. @@ -530,7 +500,7 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { [CodePush removePendingUpdate]; - resolve([NSNull null]); + resolve(nil); } /* @@ -540,45 +510,35 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - 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) { - NSDictionary *lastFailedPackage = [failedUpdates lastObject]; - if (lastFailedPackage) { - NSString *lastFailedPackageIdentifier = [self getPackageStatusReportIdentifier:lastFailedPackage]; - if (lastFailedPackageIdentifier && [self isDeploymentStatusNotYetReported:lastFailedPackageIdentifier]) { - [self recordDeploymentStatusReported:lastFailedPackageIdentifier]; - resolve(@{ @"package": lastFailedPackage, @"status": DeploymentFailed }); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if (needToReportRollback) { + needToReportRollback = NO; + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; + if (failedUpdates) { + NSDictionary *lastFailedPackage = [failedUpdates lastObject]; + if (lastFailedPackage) { + resolve([CodePushTelemetryManager getRollbackReport:lastFailedPackage]); return; } } - } - } else if (_isFirstRunAfterUpdate) { - // Check if the current CodePush package has been reported - NSError *error; - 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 }); + } else if (_isFirstRunAfterUpdate) { + NSError *error; + NSDictionary *currentPackage = [CodePushPackage getCurrentPackage:&error]; + if (!error && currentPackage) { + resolve([CodePushTelemetryManager getUpdateReport:currentPackage]); 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 }); + } else if (isRunningBinaryVersion || [_bridge.bundleURL.scheme hasPrefix:@"http"]) { + // Check if the current appVersion has been reported. + NSString *appVersion = [[CodePushConfig current] appVersion]; + resolve([CodePushTelemetryManager getBinaryUpdateReport:appVersion]); return; } - } - - resolve([NSNull null]); + + resolve(nil); + }); } /* diff --git a/CodePush.xcodeproj/project.pbxproj b/CodePush.xcodeproj/project.pbxproj index 6961441..d3c086c 100644 --- a/CodePush.xcodeproj/project.pbxproj +++ b/CodePush.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; }; + 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */; }; 54A0026C1C0E2880004C3CEC /* aescrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024C1C0E2880004C3CEC /* aescrypt.c */; }; 54A0026D1C0E2880004C3CEC /* aeskey.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024D1C0E2880004C3CEC /* aeskey.c */; }; 54A0026E1C0E2880004C3CEC /* aestab.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024F1C0E2880004C3CEC /* aestab.c */; }; @@ -45,6 +46,7 @@ 13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = ""; }; 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = ""; }; 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushInstallMode.m"; sourceTree = ""; }; + 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushTelemetryManager.m; sourceTree = ""; }; 54A0024A1C0E2880004C3CEC /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = ""; }; 54A0024B1C0E2880004C3CEC /* aes_via_ace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes_via_ace.h; sourceTree = ""; }; 54A0024C1C0E2880004C3CEC /* aescrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aescrypt.c; sourceTree = ""; }; @@ -165,6 +167,7 @@ 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */, 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */, 810D4E6C1B96935000B397E9 /* CodePushPackage.m */, + 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */, 81D51F391B6181C2000DA084 /* CodePushConfig.m */, 13BE3DEC1AC21097009241FE /* CodePush.h */, 13BE3DED1AC21097009241FE /* CodePush.m */, @@ -239,6 +242,7 @@ 54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */, 54A002711C0E2880004C3CEC /* hmac.c in Sources */, 54A002721C0E2880004C3CEC /* prng.c in Sources */, + 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */, 54A002731C0E2880004C3CEC /* pwd2key.c in Sources */, 54A002751C0E2880004C3CEC /* ioapi.c in Sources */, 54A002771C0E2880004C3CEC /* unzip.c in Sources */, diff --git a/CodePushTelemetryManager.m b/CodePushTelemetryManager.m new file mode 100644 index 0000000..1feff55 --- /dev/null +++ b/CodePushTelemetryManager.m @@ -0,0 +1,127 @@ +#import "CodePush.h" + +static NSString *const DeploymentFailed = @"DeploymentFailed"; +static NSString *const DeploymentKeyKey = @"deploymentKey"; +static NSString *const DeploymentSucceeded = @"DeploymentSucceeded"; +static NSString *const LabelKey = @"label"; +static NSString *const LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT"; + +@implementation CodePushTelemetryManager + ++ (NSDictionary *)getBinaryUpdateReport:(NSString *)appVersion +{ + NSString *previousStatusReportIdentifier = [self getPreviousStatusReportIdentifier]; + if (previousStatusReportIdentifier == nil) { + [self recordDeploymentStatusReported:appVersion]; + return @{ @"appVersion": appVersion }; + } else if (![previousStatusReportIdentifier isEqualToString:appVersion]) { + [self recordDeploymentStatusReported:appVersion]; + if ([self isStatusReportIdentifierCodePushLabel:previousStatusReportIdentifier]) { + NSString *previousDeploymentKey = [self getDeploymentKeyFromStatusReportIdentifier:previousStatusReportIdentifier]; + NSString *previousLabel = [self getVersionLabelFromStatusReportIdentifier:previousStatusReportIdentifier]; + return @{ + @"appVersion": appVersion, + @"previousDeploymentKey": previousDeploymentKey, + @"previousLabelOrAppVersion": previousLabel + }; + } else { + // Previous status report was with a binary app version. + return @{ + @"appVersion": appVersion, + @"previousLabelOrAppVersion": previousStatusReportIdentifier + }; + } + } + + return nil; +} + ++ (NSDictionary *)getRollbackReport:(NSDictionary *)lastFailedPackage +{ + return @{ + @"package": lastFailedPackage, + @"status": DeploymentFailed + }; +} + ++ (NSDictionary *)getUpdateReport:(NSDictionary *)currentPackage +{ + NSString *currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage]; + NSString *previousStatusReportIdentifier = [self getPreviousStatusReportIdentifier]; + if (currentPackageIdentifier) { + if (previousStatusReportIdentifier == nil) { + [self recordDeploymentStatusReported:currentPackageIdentifier]; + return @{ + @"package": currentPackage, + @"status": DeploymentSucceeded + }; + } else if (![previousStatusReportIdentifier isEqualToString:currentPackageIdentifier]) { + [self recordDeploymentStatusReported:currentPackageIdentifier]; + if ([self isStatusReportIdentifierCodePushLabel:previousStatusReportIdentifier]) { + NSString *previousDeploymentKey = [self getDeploymentKeyFromStatusReportIdentifier:previousStatusReportIdentifier]; + NSString *previousLabel = [self getVersionLabelFromStatusReportIdentifier:previousStatusReportIdentifier]; + return @{ + @"package": currentPackage, + @"status": DeploymentSucceeded, + @"previousDeploymentKey": previousDeploymentKey, + @"previousLabelOrAppVersion": previousLabel + }; + } else { + // Previous status report was with a binary app version. + return @{ + @"package": currentPackage, + @"status": DeploymentSucceeded, + @"previousLabelOrAppVersion": previousStatusReportIdentifier + }; + } + } + } + + return nil; +} + +#pragma mark - private methods + ++ (NSString *)getDeploymentKeyFromStatusReportIdentifier:(NSString *)statusReportIdentifier +{ + return [[statusReportIdentifier componentsSeparatedByString:@":"] firstObject]; +} + ++ (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; + } +} + ++ (NSString *)getPreviousStatusReportIdentifier +{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSString *sentStatusReportIdentifier = [preferences objectForKey:LastDeploymentReportKey]; + return sentStatusReportIdentifier; +} + ++ (NSString *)getVersionLabelFromStatusReportIdentifier:(NSString *)statusReportIdentifier +{ + return [[statusReportIdentifier componentsSeparatedByString:@":"] lastObject]; +} + ++ (BOOL)isStatusReportIdentifierCodePushLabel:(NSString *)statusReportIdentifier +{ + return statusReportIdentifier != nil && [statusReportIdentifier containsString:@":"]; +} + ++ (void)recordDeploymentStatusReported:(NSString *)appVersionOrPackageIdentifier +{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + [preferences setValue:appVersionOrPackageIdentifier forKey:LastDeploymentReportKey]; + [preferences synchronize]; +} + +@end \ No newline at end of file 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 8d7d64a..937806f 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 @@ -51,25 +51,22 @@ public class CodePush { 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 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 PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE"; private final String RESOURCES_BUNDLE = "resources.arsc"; - 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"; + // Helper classes. + private CodePushNativeModule codePushNativeModule; private CodePushPackage codePushPackage; private CodePushReactPackage codePushReactPackage; - private CodePushNativeModule codePushNativeModule; + private CodePushTelemetryManager codePushTelemetryManager; // Config properties. private String deploymentKey; @@ -87,12 +84,12 @@ public class CodePush { public CodePush(String deploymentKey, Activity mainActivity, boolean isDebugMode) { SoLoader.init(mainActivity, false); - this.deploymentKey = deploymentKey; - this.codePushPackage = new CodePushPackage(mainActivity.getFilesDir().getAbsolutePath()); - this.mainActivity = mainActivity; this.applicationContext = mainActivity.getApplicationContext(); + this.codePushPackage = new CodePushPackage(mainActivity.getFilesDir().getAbsolutePath()); + this.codePushTelemetryManager = new CodePushTelemetryManager(this.applicationContext, CODE_PUSH_PREFERENCES); this.deploymentKey = deploymentKey; this.isDebugMode = isDebugMode; + this.mainActivity = mainActivity; PackageInfo pInfo = null; try { @@ -178,18 +175,6 @@ 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); @@ -262,16 +247,6 @@ public class CodePush { } } - private boolean isDeploymentStatusNotYetReported(String appVersionOrPackageIdentifier) { - SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - String lastDeploymentReportIdentifier = settings.getString(LAST_DEPLOYMENT_REPORT_KEY, null); - if (lastDeploymentReportIdentifier == null) { - return true; - } else { - return !lastDeploymentReportIdentifier.equals(appVersionOrPackageIdentifier); - } - } - private boolean isFailedHash(String packageHash) { JSONArray failedUpdates = getFailedUpdates(); if (packageHash != null) { @@ -306,11 +281,6 @@ public class CodePush { } } - private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier) { - SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - settings.edit().putString(LAST_DEPLOYMENT_REPORT_KEY, appVersionOrPackageIdentifier).commit(); - } - private void removeFailedUpdates() { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); settings.edit().remove(FAILED_UPDATES_KEY).commit(); @@ -457,20 +427,15 @@ public class CodePush { @ReactMethod public void getNewStatusReport(Promise promise) { 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 { JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1); WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON); - String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage); - if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) { - recordDeploymentStatusReported(lastFailedPackageIdentifier); - WritableNativeMap reportMap = new WritableNativeMap(); - reportMap.putMap("package", lastFailedPackage); - reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); - promise.resolve(reportMap); + WritableMap failedStatusReport = codePushTelemetryManager.getRollbackReport(lastFailedPackage); + if (failedStatusReport != null) { + promise.resolve(failedStatusReport); return; } } catch (JSONException e) { @@ -478,27 +443,18 @@ public class CodePush { } } } 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); + WritableMap newPackageStatusReport = codePushTelemetryManager.getUpdateReport(currentPackage); + if (newPackageStatusReport != null) { + promise.resolve(newPackageStatusReport); 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); + WritableMap newAppVersionStatusReport = codePushTelemetryManager.getBinaryUpdateReport(appVersion); + if (newAppVersionStatusReport != null) { + promise.resolve(newAppVersionStatusReport); return; } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java new file mode 100644 index 0000000..86ae472 --- /dev/null +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java @@ -0,0 +1,136 @@ +package com.microsoft.codepush.react; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; + +public class CodePushTelemetryManager { + + private Context applicationContext; + private final String CODE_PUSH_PREFERENCES; + private final String DEPLOYMENT_FAILED_STATUS = "DeploymentFailed"; + private final String DEPLOYMENT_KEY_KEY = "deploymentKey"; + private final String DEPLOYMENT_SUCCEEDED_STATUS = "DeploymentSucceeded"; + private final String LABEL_KEY = "label"; + private final String LAST_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_LAST_DEPLOYMENT_REPORT"; + + public CodePushTelemetryManager(Context applicationContext, String codePushPreferencesKey) { + this.applicationContext = applicationContext; + this.CODE_PUSH_PREFERENCES = codePushPreferencesKey; + } + + public WritableMap getBinaryUpdateReport(String appVersion) { + String previousStatusReportIdentifier = this.getPreviousStatusReportIdentifier(); + if (previousStatusReportIdentifier == null) { + this.recordDeploymentStatusReported(appVersion); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putString("appVersion", appVersion); + return reportMap; + } else if (!previousStatusReportIdentifier.equals(appVersion)) { + this.recordDeploymentStatusReported(appVersion); + WritableNativeMap reportMap = new WritableNativeMap(); + if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) { + String previousDeploymentKey = this.getDeploymentKeyFromStatusReportIdentifier(previousStatusReportIdentifier); + String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier); + reportMap.putString("appVersion", appVersion); + reportMap.putString("previousDeploymentKey", previousDeploymentKey); + reportMap.putString("previousLabelOrAppVersion", previousLabel); + } else { + // Previous status report was with a binary app version. + reportMap.putString("appVersion", appVersion); + reportMap.putString("previousLabelOrAppVersion", previousStatusReportIdentifier); + } + return reportMap; + } + + return null; + } + + public WritableMap getRollbackReport(WritableMap lastFailedPackage) { + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", lastFailedPackage); + reportMap.putString("status", DEPLOYMENT_FAILED_STATUS); + return reportMap; + } + + public WritableMap getUpdateReport(WritableMap currentPackage) { + String currentPackageIdentifier = this.getPackageStatusReportIdentifier(currentPackage); + String previousStatusReportIdentifier = this.getPreviousStatusReportIdentifier(); + if (currentPackageIdentifier != null) { + if (previousStatusReportIdentifier == null) { + this.recordDeploymentStatusReported(currentPackageIdentifier); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + return reportMap; + } else if (!previousStatusReportIdentifier.equals(currentPackageIdentifier)) { + this.recordDeploymentStatusReported(currentPackageIdentifier); + if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) { + String previousDeploymentKey = this.getDeploymentKeyFromStatusReportIdentifier(previousStatusReportIdentifier); + String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier); + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + reportMap.putString("previousDeploymentKey", previousDeploymentKey); + reportMap.putString("previousLabelOrAppVersion", previousLabel); + return reportMap; + } else { + // Previous status report was with a binary app version. + WritableNativeMap reportMap = new WritableNativeMap(); + reportMap.putMap("package", currentPackage); + reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS); + reportMap.putString("previousLabelOrAppVersion", previousStatusReportIdentifier); + return reportMap; + } + } + } + + return null; + } + + private String getDeploymentKeyFromStatusReportIdentifier(String statusReportIdentifier) { + String[] parsedIdentifier = statusReportIdentifier.split(":"); + if (parsedIdentifier.length > 0) { + return parsedIdentifier[0]; + } else { + return null; + } + } + + 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 String getPreviousStatusReportIdentifier() { + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + return settings.getString(LAST_DEPLOYMENT_REPORT_KEY, null); + } + + private String getVersionLabelFromStatusReportIdentifier(String statusReportIdentifier) { + String[] parsedIdentifier = statusReportIdentifier.split(":"); + if (parsedIdentifier.length > 1) { + return parsedIdentifier[1]; + } else { + return null; + } + } + + private boolean isStatusReportIdentifierCodePushLabel(String statusReportIdentifier) { + return statusReportIdentifier != null && statusReportIdentifier.contains(":"); + } + + private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier) { + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + settings.edit().putString(LAST_DEPLOYMENT_REPORT_KEY, appVersionOrPackageIdentifier).commit(); + } +} diff --git a/package.json b/package.json index 878f192..d626df2 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.1-beta", + "code-push": "1.5.2-beta", "semver": "^5.1.0" }, "devDependencies": { diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index eeeac81..eb61622 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -5,9 +5,10 @@ module.exports = { requestBody = null; } - var headers = { + const headers = { "Accept": "application/json", - "Content-Type": "application/json" + "Content-Type": "application/json", + "X-CodePush-SDK-Version": getSDKVersion() }; if (requestBody && typeof requestBody === "object") { @@ -30,6 +31,10 @@ module.exports = { } }; +function getSDKVersion() { + return require("./package.json").dependencies["code-push"]; +} + 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