update to master again

This commit is contained in:
Geoffrey Goh
2015-11-24 15:52:04 -08:00
4 changed files with 176 additions and 79 deletions

View File

@@ -1,7 +1,18 @@
#import "RCTBridgeModule.h"
@interface CodePush : NSObject<RCTBridgeModule>
@interface CodePush : NSObject <RCTBridgeModule>
/*
* This method is used to retrieve 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 that 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

@@ -5,24 +5,20 @@ var Sdk = require("code-push/script/acquisition-sdk").AcquisitionManager;
var { NativeCodePush, PackageMixins, Alert } = require("./CodePushNativePlatformAdapter");
function checkForUpdate(deploymentKey = null) {
var config;
var sdk;
var config, sdk;
return getConfiguration()
.then((configResult) => {
config = configResult;
.then((configResult) => {
// If a deployment key was explicitly provided,
// then let's override the one we retrieved
// from the native-side of the app.
if (deploymentKey) {
config.deploymentKey = deploymentKey;
config = Object.assign({}, configResult, { deploymentKey });
} else {
config = configResult;
}
return getSdk();
})
.then((sdkResult) => {
sdk = sdkResult;
sdk = getSDK(config);
// Allow dynamic overwrite of function. This is only to be used for tests.
return module.exports.getCurrentPackage();
})
@@ -74,23 +70,6 @@ var getConfiguration = (() => {
}
})();
var getSdk = (() => {
var sdk;
return function getSdk() {
if (sdk) {
return Promise.resolve(sdk);
} else if (testSdk) {
return Promise.resolve(testSdk);
} else {
return getConfiguration()
.then((configuration) => {
sdk = new Sdk(requestFetchAdapter, configuration);
return sdk;
});
}
}
})();
function getCurrentPackage() {
return new Promise((resolve, reject) => {
var localPackage;
@@ -112,6 +91,14 @@ function getCurrentPackage() {
});
}
function getSDK(config) {
if (testSdk) {
return testSdk;
} else {
return new Sdk(requestFetchAdapter, config);
}
}
/* Logs messages to console with the [CodePush] prefix */
function log(message) {
console.log(`[CodePush] ${message}`)

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 occurring.
*
* 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 update" has been applied
* (e.g. install was called with a non-immediate mode), but the app hasn't
* yet been restarted (either naturally or programmatically). 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), ^{
@@ -87,21 +103,26 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
NSString *pendingHash = pendingUpdate[PendingUpdateHashKey];
NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error];
// If the current hash is equivalent to the pending hash, then the app
// restart "picked up" the new update, but we need to kick off the
// rollback timer and ensure that the necessary state is setup.
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];
}
NSAssert([pendingHash isEqualToString:currentHash], @"There is a pending update but it's hash doesn't match that of the current package.");
// Kick off the rollback timer and ensure that the necessary state is setup for the pending update.
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart];
// 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 +133,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 +178,14 @@ static NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
return self;
}
/*
* This method performs the actual initialization work for an update
* to ensure that the necessary 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 +202,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 +213,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 +231,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 +252,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 +274,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 +293,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 +308,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 +373,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 +401,10 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
});
}
/*
* This method isn't publicly 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 +413,26 @@ RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash
resolve(@(isFailedHash));
}
/*
* This method isn't publicly 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 +440,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.
RCT_EXPORT_METHOD(restartImmedidateUpdate:(int)rollbackTimeout)
/*
* This method isn't publicly exposed via the "react-native-code-push"
* module, and is only used internally to support immediately installed updates.
*/
RCT_EXPORT_METHOD(restartImmediateUpdate:(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];

View File

@@ -49,7 +49,7 @@ module.exports = (NativeCodePush) => {
.then(function() {
updateInstalledCallback && updateInstalledCallback();
if (installMode == NativeCodePush.codePushInstallModeImmediate) {
NativeCodePush.restartImmedidateUpdate(rollbackTimeout);
NativeCodePush.restartImmediateUpdate(rollbackTimeout);
};
});
}