diff --git a/CodePush.js b/CodePush.js index 7baa35b..993a412 100644 --- a/CodePush.js +++ b/CodePush.js @@ -60,8 +60,9 @@ 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; } } @@ -101,7 +102,31 @@ function getPromisifiedSdk(requestFetchAdapter, config) { }); }); }; - + + sdk.reportStatusDeploy = (deployedPackage, status) => { + return new Promise((resolve, reject) => { + module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }; + + sdk.reportStatusDownload = (downloadedPackage) => { + return new Promise((resolve, reject) => { + module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }; + return sdk; } @@ -110,6 +135,22 @@ 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) { + const sdk = getPromisifiedSdk(requestFetchAdapter, config); + sdk.reportStatusDeploy(); + } else { + config.deploymentKey = statusReport.package.deploymentKey; + const sdk = getPromisifiedSdk(requestFetchAdapter, config); + sdk.reportStatusDeploy(statusReport.package, statusReport.status); + } + } +} + function restartApp(onlyIfUpdateIsPending = false) { NativeCodePush.restartApp(onlyIfUpdateIsPending); } @@ -269,7 +310,7 @@ const CodePush = { getConfiguration, getCurrentPackage, log, - notifyApplicationReady: NativeCodePush.notifyApplicationReady, + notifyApplicationReady, restartApp, setUpTestDependencies, sync, diff --git a/CodePush.m b/CodePush.m index c4946c7..3b69041 100644 --- a/CodePush.m +++ b/CodePush.m @@ -13,10 +13,17 @@ RCT_EXPORT_MODULE() +static BOOL needToReportRollback = NO; +static BOOL isRunningBinaryVersion = NO; static BOOL testConfigurationFlag = NO; +// These constants represent valid deployment statuses +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 LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT"; static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE"; // These keys are already "namespaced" by the PendingUpdateKey, so @@ -26,6 +33,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"; @@ -54,6 +63,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; if (error || !packageFile) { NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } @@ -65,15 +75,17 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; if (error || !currentPackageMetadata) { NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } 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); + isRunningBinaryVersion = NO; return packageUrl; } else { #ifndef DEBUG @@ -81,6 +93,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; #endif NSLog(logMessageFormat, binaryJsBundleUrl); + isRunningBinaryVersion = YES; return binaryJsBundleUrl; } } @@ -148,6 +161,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]; @@ -175,6 +201,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; // 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]; } else { // Mark that we tried to initialize the new update, so that if it crashes, @@ -185,6 +212,13 @@ 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. @@ -193,7 +227,25 @@ static NSString *const PackageIsPendingKey = @"isPending"; { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; - return (failedUpdates != nil && [failedUpdates containsObject:packageHash]); + if (failedUpdates == nil || packageHash == 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 +289,13 @@ 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. @@ -247,10 +306,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 +322,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 +334,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; failedUpdates = [failedUpdates mutableCopy]; } - [failedUpdates addObject:packageHash]; + [failedUpdates addObject:failedPackage]; [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; [preferences synchronize]; } @@ -467,6 +526,54 @@ 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) +{ + 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 }); + 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 }); + 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; + } + } + + resolve([NSNull null]); +} + /* * This method is the native side of the CodePush.restartApp() method. */ diff --git a/CodePushConfig.m b/CodePushConfig.m index 0de94de..9ea11ff 100644 --- a/CodePushConfig.m +++ b/CodePushConfig.m @@ -1,4 +1,5 @@ #import "CodePush.h" +#import @implementation CodePushConfig { NSMutableDictionary *_configDictionary; @@ -8,6 +9,7 @@ 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"; @@ -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/"; } @@ -39,6 +49,7 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; appVersion,AppVersionConfigKey, buildVersion,BuildVdersionConfigKey, serverURL,ServerURLConfigKey, + clientUniqueId,ClientUniqueIDConfigKey, deploymentKey,DeploymentKeyConfigKey, nil]; @@ -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/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 25e9568..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 }), "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/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/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 253d497..ffff99b 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js @@ -13,6 +13,16 @@ function createMockAcquisitionSdk(serverPackage, localPackage, expectedDeploymen callback(/*err:*/ null, serverPackage); }; + 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); + }; + return AcquisitionManager; } 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 a9743d8..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 @@ -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; @@ -38,22 +40,32 @@ import java.util.zip.ZipFile; public class CodePush { + private static boolean needToReportRollback = false; + private static boolean isRunningBinaryVersion = false; private static boolean testConfigurationFlag = false; + private boolean didUpdate = false; 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 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"; - private final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime"; private CodePushPackage codePushPackage; private CodePushReactPackage codePushReactPackage; @@ -132,6 +144,7 @@ public class CodePush { if (packageFilePath == null) { // There has not been any downloaded updates. CodePushUtils.logBundleUrl(binaryJsBundleUrl); + isRunningBinaryVersion = true; return binaryJsBundleUrl; } @@ -142,11 +155,12 @@ 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.appVersion.equals(pacakgeAppVersion)) { + (this.isUsingTestConfiguration() || this.appVersion.equals(packageAppVersion))) { CodePushUtils.logBundleUrl(packageFilePath); + isRunningBinaryVersion = false; return packageFilePath; } else { // The binary version is newer. @@ -156,15 +170,44 @@ public class CodePush { } CodePushUtils.logBundleUrl(binaryJsBundleUrl); + isRunningBinaryVersion = true; 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); } } + 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); @@ -200,6 +243,7 @@ public class CodePush { // 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(); } else { // Clear the React dev bundle cache so that new updates can be loaded. @@ -217,22 +261,35 @@ 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 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) { + 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 +306,11 @@ 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(); @@ -260,45 +322,31 @@ public class CodePush { } private void rollbackPackage() { - try { - String packageHash = codePushPackage.getCurrentPackageHash(); - saveFailedUpdate(packageHash); - } 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(); } - 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 +425,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); } @@ -385,30 +436,76 @@ 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; } }; asyncTask.execute(); } - + + @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); + return; + } + } catch (JSONException e) { + throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); + } + } + } 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 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; + } + } + + promise.resolve(""); + } + @ReactMethod public void installUpdate(final ReadableMap updatePackage, final int installMode, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @@ -464,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/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 = { diff --git a/package.json b/package.json index d90622c..38417f8 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.1-beta", "semver": "^5.1.0" }, "devDependencies": { diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index 6254def..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: verb, + method: getHttpMethodName(verb), headers: headers, - body: body + body: requestBody }); const statusCode = response.status; @@ -28,4 +28,20 @@ module.exports = { callback(err); } } -}; \ No newline at end of file +}; + +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", + "POST", + "PUT", + "DELETE", + "TRACE", + "OPTIONS", + "CONNECT", + "PATCH" + ][verb]; +} \ No newline at end of file