Adding comments to CodePush class

This commit is contained in:
Jonathan Carter
2015-11-24 09:50:34 -08:00
parent 6aab2a59c5
commit a20e4a2602
2 changed files with 157 additions and 43 deletions

View File

@@ -1,7 +1,18 @@
#import "RCTBridgeModule.h"
@interface CodePush : NSObject<RCTBridgeModule>
@interface CodePush : NSObject <RCTBridgeModule>
/*
* This method is used to retreive the URL for the most recent
* version of the JavaScript bundle. This could be either the
* bundle that was packaged with the app binary, or the bundle
* that was downloaded as part of a CodePush update. The value returned
* should be used to "bootstrap" the React Native bridge.
*
* This method assumes that your JS bundle is named "main.jsbundle"
* and therefore, if it isn't, you should use either the bundleURLForResource:
* or bundleURLForResource:withExtension: methods to override thsat behavior.
*/
+ (NSURL *)bundleURL;
+ (NSURL *)bundleURLForResource:(NSString *)resourceName;
@@ -25,7 +36,7 @@
@end
@interface CodePushDownloadHandler : NSObject<NSURLConnectionDelegate>
@interface CodePushDownloadHandler : NSObject <NSURLConnectionDelegate>
@property (strong) NSOutputStream *outputFileStream;
@property long expectedContentLength;

View File

@@ -1,8 +1,9 @@
#import "RCTBridgeModule.h"
#import "RCTEventDispatcher.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTRootView.h"
#import "RCTUtils.h"
#import "CodePush.h"
@implementation CodePush {
@@ -15,16 +16,17 @@ static BOOL didUpdate = NO;
static NSTimer *_timer;
static BOOL usingTestFolder = NO;
static NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
static NSString * const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
// These keys are already "namespaced" by the PendingUpdateKey, so
// their values don't need to be obfuscated to prevent collision with app data
static NSString * const PendingUpdateHashKey = @"hash";
static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
static NSString *const PendingUpdateHashKey = @"hash";
static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
@synthesize bridge = _bridge;
// Public Obj-C API (see header for method comments)
+ (NSURL *)bundleURL
{
return [self bundleURLForResource:@"main"];
@@ -43,8 +45,7 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error];
NSURL *binaryJsBundleUrl = [[NSBundle mainBundle] URLForResource:resourceName withExtension:resourceExtension];
if (error || !packageFile)
{
if (error || !packageFile) {
return binaryJsBundleUrl;
}
@@ -61,14 +62,21 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
}
// Public Obj-C API
+ (NSString *)getDocumentsDirectory
{
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return documentsDirectory;
}
// Internal API methods
// Private API methods
/*
* This method cancels the currently running rollback
* timer, which has the effect of stopping an automatic
* rollback from occuring.
*
* Note: This method is safe to call from any thread.
*/
- (void)cancelRollbackTimer
{
dispatch_async(dispatch_get_main_queue(), ^{
@@ -76,6 +84,14 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
});
}
/*
* This method checks to see whether a "pending udpate" has been applied
* (e.g. install was called with a non-immediate mode), but the app hasn't
* yet been restarted (either naturally or synthentically). If there is one,
* it will restart the app (if specified), and start the rollback timer.
*
* Note: This method is safe to call from any thread.
*/
- (void)checkForPendingUpdate:(BOOL)needsRestart
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -93,15 +109,24 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
if ([pendingHash isEqualToString:currentHash]) {
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart];
// Clear the pending update and sync
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
} else {
// NOTE: We shouldn't ever reach here
}
// Clear the pending update and sync
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
}
});
}
/*
* This method is meant as a handler for the global app
* resume notification, and therefore, should not be called
* directly. It simply checks to see whether there is a pending
* update that is meant to be installed on resume, and if so
* it applies it and restarts the app.
*/
- (void)checkForPendingUpdateDuringResume
{
// In order to ensure that CodePush doesn't impact the app's
@@ -112,6 +137,12 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
}
/*
* This method is used by the React Native bridge to allow
* our plugin to expose constants to the JS-side. In our case
* we're simply exporting enum values so that the JS and Native
* sides of the plugin can be in sync.
*/
- (NSDictionary *)constantsToExport
{
// Export the values of the CodePushInstallMode enum
@@ -151,6 +182,14 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
return self;
}
/*
* This method performs the actual initialization work for a update
* to ensure that the neccessary state is setup, including:
* --------------------------------------------------------
* 1. Updating the current bundle URL to point at the latest update on disk
* 2. Optionally restarting the app to load the new bundle
* 3. Optionally starting the rollback protection timer
*/
- (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout
needsRestart:(BOOL)needsRestart
{
@@ -167,6 +206,10 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
}
/*
* This method checks to see whether a specific package hash
* has previously failed installation.
*/
- (BOOL)isFailedHash:(NSString*)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
@@ -174,6 +217,11 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
return (failedUpdates != nil && [failedUpdates containsObject:packageHash]);
}
/*
* This method updates the React Native bridge's bundle URL
* to point at the latest CodePush update, and then restarts
* the bridge. This isn't meant to be called directly.
*/
- (void)loadBundle
{
// If the current bundle URL is using http(s), then assume the dev
@@ -187,6 +235,13 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
[_bridge reload];
}
/*
* This method is used when an update has failed installation
* and the app needs to be rolled back to the previous bundle.
* This method is automatically called when the rollback timer
* expires without the app indicating whether the update succeeded,
* and therefore, it shouldn't be called directly.
*/
- (void)rollbackPackage
{
NSError *error;
@@ -201,6 +256,11 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
[self loadBundle];
}
/*
* When an update failed to apply, this method can be called
* to store its hash so that it can be ignored on future
* attempts to check the server for an update.
*/
- (void)saveFailedUpdate:(NSString *)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
@@ -218,6 +278,11 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
[preferences synchronize];
}
/*
* When an update is installed whose mode isn't IMMEDIATE, this method
* can be called to store the pending update's metadata (e.g. rollbackTimeout)
* so that it can be used when the actual update application occurs at a later point.
*/
- (void)savePendingUpdate:(NSString *)packageHash
rollbackTimeout:(int)rollbackTimeout
{
@@ -232,6 +297,10 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
[preferences synchronize];
}
/*
* This method handles starting the actual rollback timer
* after an update has been installed.
*/
- (void)startRollbackTimer:(int)rollbackTimeout
{
double timeoutInSeconds = rollbackTimeout / 1000;
@@ -243,42 +312,57 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
// JavaScript-exported module methods
/*
* This is native-side of the RemotePackage.download method
*/
RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[CodePushPackage downloadPackage:updatePackage
progressCallback:^(long expectedContentLength, long receivedContentLength) {
[self.bridge.eventDispatcher
sendAppEventWithName:@"CodePushDownloadProgress"
body:@{
@"totalBytes":[NSNumber numberWithLong:expectedContentLength],
@"receivedBytes":[NSNumber numberWithLong:receivedContentLength]
}];
}
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage
getPackage:updatePackage[@"packageHash"]
error:&err];
if (err) {
return reject(err);
}
resolve(newPackage);
}
failCallback:^(NSError *err) {
reject(err);
}];
// The download is progressing forward
progressCallback:^(long expectedContentLength, long receivedContentLength) {
// Notify the script-side about the progress
[self.bridge.eventDispatcher
sendAppEventWithName:@"CodePushDownloadProgress"
body:@{
@"totalBytes":[NSNumber numberWithLong:expectedContentLength],
@"receivedBytes":[NSNumber numberWithLong:receivedContentLength]
}];
}
// The download completed
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[@"packageHash"] error:&err];
if (err) {
return reject(err);
}
resolve(newPackage);
}
// The download failed
failCallback:^(NSError *err) {
reject(err);
}];
}
/*
* This is the native side of the CodePush.getConfiguration method. It isn't
* currently exposed via the "react-native-code-push" module, and is used
* internally only by the CodePush.checkForUpdate method in order to get the
* app version, as well as the deployment key that was configured in the Info.plist file.
*/
RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
resolve([[CodePushConfig current] configuration]);
}
/*
* This method is the native side of the CodePush.getCurrentPackage method.
*/
RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
@@ -293,6 +377,9 @@ RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve
});
}
/*
* This method is the native side of the LocalPackage.install method.
*/
RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
rollbackTimeout:(int)rollbackTimeout
installMode:(CodePushInstallMode)installMode
@@ -318,6 +405,10 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
});
}
/*
* This method isn't publically exposed via the "react-native-code-push"
* module, and is only used internally to populate the RemotePackage.failedApply property.
*/
RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
@@ -326,19 +417,26 @@ RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash
resolve(@(isFailedHash));
}
/*
* This method isn't publically exposed via the "react-native-code-push"
* module, and is only used internally to populate the LocalPackage.isFirstRun property.
*/
RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
BOOL isFirstRun = didUpdate
&& nil != packageHash
&& [packageHash length] > 0
&& [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]];
&& nil != packageHash
&& [packageHash length] > 0
&& [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]];
resolve(@(isFirstRun));
}
/*
* This method is the native side of the CodePush.notifyApplicationReady() method.
*/
RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
@@ -346,13 +444,18 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
resolve([NSNull null]);
}
// This function is exposed solely for immediately installed
// update support, and shouldn't be consumed directly by user code.
/*
* This method isn't publically exposed via the "react-native-code-push"
* module, and is only used internally to support immediately installed updates.
*/
RCT_EXPORT_METHOD(restartImmedidateUpdate:(int)rollbackTimeout)
{
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES];
}
/*
* This method is the native side of the CodePush.restartPendingUpdate() method.
*/
RCT_EXPORT_METHOD(restartPendingUpdate)
{
[self checkForPendingUpdate:YES];