From c64b76c018825dce3503e397565dddebd7e5f668 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 2 Nov 2015 09:49:30 -0800 Subject: [PATCH] Adding support for failedApply on the package objects --- CodePush.h | 8 +- CodePush.ios.js | 92 +++++++++++++--------- CodePush.m | 190 +++++++++++++++++++++++----------------------- CodePushPackage.m | 2 +- package-mixins.js | 8 +- 5 files changed, 159 insertions(+), 141 deletions(-) diff --git a/CodePush.h b/CodePush.h index c84b64b..9f3697f 100644 --- a/CodePush.h +++ b/CodePush.h @@ -1,10 +1,9 @@ #import "RCTBridgeModule.h" -@interface CodePush : NSObject +@interface CodePush : NSObject -+ (NSString *) getDocumentsDirectory; - -+ (NSURL *) getBundleUrl; ++ (NSString *)getDocumentsDirectory; ++ (NSURL *)getBundleUrl; @end @@ -48,4 +47,5 @@ error:(NSError **)error; + (void)rollbackPackage; + @end \ No newline at end of file diff --git a/CodePush.ios.js b/CodePush.ios.js index c0c39cf..5160c8f 100644 --- a/CodePush.ios.js +++ b/CodePush.ios.js @@ -1,10 +1,11 @@ 'use strict'; var extend = require("extend"); -var NativeCodePush = require('react-native').NativeModules.CodePush; +var NativeCodePush = require("react-native").NativeModules.CodePush; var requestFetchAdapter = require("./request-fetch-adapter.js"); var Sdk = require("code-push/script/acquisition-sdk").AcquisitionManager; var packageMixins = require("./package-mixins")(NativeCodePush); + var { AlertIOS } = require("react-native"); // This function is only used for tests. Replaces the default SDK, configuration and native bridge @@ -50,45 +51,62 @@ var getSdk = (() => { } })(); +function getCurrentPackage() { + return new Promise((resolve, reject) => { + var localPackage; + NativeCodePush.getCurrentPackage() + .then((currentPackage) => { + localPackage = currentPackage; + return NativeCodePush.isFailedUpdate(currentPackage.packageHash) + }) + .then((failedUpdate) => { + localPackage.failedApply = failedUpdate; + resolve(localPackage); + }) + .catch(reject) + .done(); + }); +} + function checkForUpdate() { var config; var sdk; + return getConfiguration() - .then((configResult) => { - config = configResult; - return getSdk(); - }) - .then((sdkResult) => { - sdk = sdkResult; - return getCurrentPackage(); - }) - .then((localPackage) => { - var queryPackage = {appVersion: config.appVersion}; - if (localPackage && localPackage.appVersion === config.appVersion) { - queryPackage = localPackage; - } + .then((configResult) => { + config = configResult; + return getSdk(); + }) + .then((sdkResult) => { + sdk = sdkResult; + return getCurrentPackage(); + }) + .then((localPackage) => { + var queryPackage = { appVersion: config.appVersion }; + if (localPackage && localPackage.appVersion === config.appVersion) { + queryPackage = localPackage; + } - return new Promise((resolve, reject) => { - sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => { - if (err) return reject(err); - if (update) { - // There is an update available for a different native app version. In the current version of this plugin, we treat that as no update. - if (update.updateAppVersion) resolve(false); - else resolve(extend({}, update, packageMixins.remote)); - } else { - resolve(update); - } - }); - }); - }); -} - -function getCurrentPackage() { - return NativeCodePush.getCurrentPackage(); -} - -function notifyApplicationReady() { - return NativeCodePush.notifyApplicationReady(); + return new Promise((resolve, reject) => { + sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => { + if (err) { + reject(err); + } + + if (update) { + update = extend(update, packageMixins.remote); + + NativeCodePush.isFailedUpdate(update.packageHash) + .then((isFailedHash) => { + update.failedApply = isFailedHash; + resolve(update); + }); + } else { + resolve(update); + } + }); + }); + }); } /** @@ -174,10 +192,10 @@ function sync(options = {}) { }; var CodePush = { - getConfiguration: getConfiguration, checkForUpdate: checkForUpdate, + getConfiguration: getConfiguration, getCurrentPackage: getCurrentPackage, - notifyApplicationReady: notifyApplicationReady, + notifyApplicationReady: NativeCodePush.notifyApplicationReady, setUpTestDependencies: setUpTestDependencies, sync: sync, SyncStatus: { diff --git a/CodePush.m b/CodePush.m index 82331c7..b329912 100644 --- a/CodePush.m +++ b/CodePush.m @@ -1,82 +1,57 @@ -#import "CodePush.h" - #import "RCTBridgeModule.h" #import "RCTRootView.h" #import "RCTUtils.h" - +#import "CodePush.h" @implementation CodePush RCT_EXPORT_MODULE() -RCTBridge * _bridge; +RCTBridge *_bridge; NSTimer *_timer; BOOL usingTestFolder = NO; +NSString * const FailedUpdatesKey = @"FAILED_UPDATES"; +NSString * const UpdateBundleFileName = @"app.jsbundle"; + @synthesize bridge = _bridge; +// Public Obj-C API + (NSString *)getDocumentsDirectory { - NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0]; + NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; return documentsDirectory; } -+ (NSString *) getBundlePath -{ - NSString * bundleFolderPath = [self getPackageFolderPath]; - NSString* appBundleName = @"main.jsbundle"; - return [bundleFolderPath stringByAppendingPathComponent:appBundleName]; -} - -+ (NSString *) getPackageFolderPath -{ - NSString* documentsDirectory = [self getDocumentsDirectory]; - NSString* pathExtension = [[@"CodePush/" stringByAppendingString: (usingTestFolder ? @"test/" : @"")] stringByAppendingString: @"currentPackage"]; - NSString* packageFolder = [documentsDirectory stringByAppendingPathComponent:pathExtension]; - return packageFolder; -} - -+ (NSString *) getPreviousPackageFolderPath -{ - NSString* documentsDirectory = [self getDocumentsDirectory]; - NSString* pathExtension = [[@"CodePush/" stringByAppendingString: (usingTestFolder ? @"test/" : @"")] stringByAppendingString: @"previous"]; - NSString* packageFolder = [documentsDirectory stringByAppendingPathComponent:pathExtension]; - return packageFolder; -} - -+ (NSString *) getPackagePath -{ - NSString *packageFolderPath = [self getPackageFolderPath]; - NSString* appPackageName = @"localpackage.json"; - return [packageFolderPath stringByAppendingPathComponent:appPackageName]; -} - -+ (NSString *) getPreviousPackagePath -{ - NSString * packageFolderPath = [self getPreviousPackageFolderPath]; - NSString* appPackageName = @"localpackage.json"; - return [packageFolderPath stringByAppendingPathComponent:appPackageName]; -} - -+ (NSURL *) getNativeBundleURL -{ - return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -} - -+ (NSURL *) getBundleUrl ++ (NSURL *)getBundleUrl { NSError *error; NSString *packageFolder = [CodePushPackage getCurrentPackageFolderPath:&error]; - if (error || !packageFolder) { - return [self getNativeBundleURL]; + if (error || !packageFolder) + { + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; } - NSString *packageFile = [packageFolder stringByAppendingPathComponent:@"app.jsbundle"]; + NSString *packageFile = [packageFolder stringByAppendingPathComponent:UpdateBundleFileName]; return [[NSURL alloc] initFileURLWithPath:packageFile]; } -+ (void) loadBundle +// Internal API methods ++ (void)cancelRollbackTimer +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [_timer invalidate]; + }); +} + ++ (BOOL)isFailedHash:(NSString*)packageHash { + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; + return (failedUpdates != nil && [failedUpdates containsObject:packageHash]); +} + ++ (void)loadBundle { dispatch_async(dispatch_get_main_queue(), ^{ RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:[self getBundleUrl] @@ -90,37 +65,71 @@ BOOL usingTestFolder = NO; }); } -+ (void) rollbackPackage:(NSTimer *)timer { ++ (void)rollbackPackage +{ + NSError *error; + NSString *packageHash = [CodePushPackage getCurrentPackageHash:&error]; + + // Write the current package's hash to the "failed list" + [self saveFailedUpdate:packageHash]; + + // Do the actual rollback and then + // refresh the app with the previous package [CodePushPackage rollbackPackage]; [self loadBundle]; } -+ (void) startRollbackTimer:(int)rollbackTimeout ++ (void)saveFailedUpdate:(NSString *)packageHash { + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; + if (failedUpdates == nil) { + failedUpdates = [[NSMutableArray alloc] init]; + } else { + // The NSUserDefaults sytem always returns immutable + // objects, regardless if you stored something mutable. + failedUpdates = [failedUpdates mutableCopy]; + } + + [failedUpdates addObject:packageHash]; + [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; + [preferences synchronize]; +} + ++ (void)startRollbackTimer:(int)rollbackTimeout { double timeoutInSeconds = rollbackTimeout / 1000; _timer = [NSTimer scheduledTimerWithTimeInterval:timeoutInSeconds target:self - selector:@selector(rollbackPackage:) + selector:@selector(rollbackPackage) userInfo:nil repeats:NO]; } -+ (void) cancelRollbackTimer -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [_timer invalidate]; - }); -} - -RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL) shouldUseTestFolder) -{ - usingTestFolder = shouldUseTestFolder; -} - -RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve +// JavaScript-exported module methods +RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage + rollbackTimeout:(int)rollbackTimeout + resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - resolve([CodePushConfig getConfiguration]); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError *error; + [CodePushPackage applyPackage:updatePackage + error:&error]; + + if (error) { + reject(error); + } + + [CodePush loadBundle]; + + if (0 != rollbackTimeout) { + dispatch_async(dispatch_get_main_queue(), ^{ + [CodePush startRollbackTimer:rollbackTimeout]; + }); + + } + }); } RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage @@ -147,31 +156,10 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage }); } -RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage - rollbackTimeout:(int)rollbackTimeout - resolver:(RCTPromiseResolveBlock)resolve +RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *error; - [CodePushPackage applyPackage:updatePackage - error:&error]; - - if (error) { - reject(error); - } - - [CodePush loadBundle]; - - if (0 != rollbackTimeout) { - dispatch_async(dispatch_get_main_queue(), ^{ - [CodePush startRollbackTimer:rollbackTimeout]; - }); - - } - }); - + resolve([CodePushConfig getConfiguration]); } RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve @@ -188,12 +176,24 @@ RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve }); } +RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + BOOL isFailedHash = [CodePush isFailedHash:packageHash]; + resolve(@(isFailedHash)); +} + RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject) { [CodePush cancelRollbackTimer]; - resolve([NSNull null]); } -@end +RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL)shouldUseTestFolder) +{ + usingTestFolder = shouldUseTestFolder; +} + +@end \ No newline at end of file diff --git a/CodePushPackage.m b/CodePushPackage.m index afd7062..71d8394 100644 --- a/CodePushPackage.m +++ b/CodePushPackage.m @@ -6,7 +6,7 @@ NSString * const StatusFile = @"codepush.json"; + (NSString *)getCodePushPath { - return [[CodePush getDocumentsDirectory] stringByAppendingPathComponent:@"CodePush"]; + return [NSHomeDirectory() stringByAppendingPathComponent:@"CodePush"]; } + (NSString *)getStatusFilePath diff --git a/package-mixins.js b/package-mixins.js index 291fd29..ef0cffe 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -2,6 +2,9 @@ var extend = require("extend"); module.exports = (NativeCodePush) => { var remote = { + abortDownload: function abortDownload() { + return NativeCodePush.abortDownload(this); + }, download: function download() { if (!this.downloadUrl) { return Promise.reject(new Error("Cannot download an update without a download url")); @@ -13,9 +16,6 @@ module.exports = (NativeCodePush) => { .then((downloadedPackage) => { return extend({}, downloadedPackage, local); }); - }, - abortDownload: function abortDownload() { - return NativeCodePush.abortDownload(this); } }; @@ -29,4 +29,4 @@ module.exports = (NativeCodePush) => { remote: remote, local: local }; -}; +}; \ No newline at end of file